index.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. const copyProperty = (to, from, property, ignoreNonConfigurable) => {
  2. // `Function#length` should reflect the parameters of `to` not `from` since we keep its body.
  3. // `Function#prototype` is non-writable and non-configurable so can never be modified.
  4. if (property === 'length' || property === 'prototype') {
  5. return;
  6. }
  7. // `Function#arguments` and `Function#caller` should not be copied. They were reported to be present in `Reflect.ownKeys` for some devices in React Native (#41), so we explicitly ignore them here.
  8. if (property === 'arguments' || property === 'caller') {
  9. return;
  10. }
  11. const toDescriptor = Object.getOwnPropertyDescriptor(to, property);
  12. const fromDescriptor = Object.getOwnPropertyDescriptor(from, property);
  13. if (!canCopyProperty(toDescriptor, fromDescriptor) && ignoreNonConfigurable) {
  14. return;
  15. }
  16. Object.defineProperty(to, property, fromDescriptor);
  17. };
  18. // `Object.defineProperty()` throws if the property exists, is not configurable and either:
  19. // - one its descriptors is changed
  20. // - it is non-writable and its value is changed
  21. const canCopyProperty = function (toDescriptor, fromDescriptor) {
  22. return toDescriptor === undefined || toDescriptor.configurable || (
  23. toDescriptor.writable === fromDescriptor.writable
  24. && toDescriptor.enumerable === fromDescriptor.enumerable
  25. && toDescriptor.configurable === fromDescriptor.configurable
  26. && (toDescriptor.writable || toDescriptor.value === fromDescriptor.value)
  27. );
  28. };
  29. const changePrototype = (to, from) => {
  30. const fromPrototype = Object.getPrototypeOf(from);
  31. if (fromPrototype === Object.getPrototypeOf(to)) {
  32. return;
  33. }
  34. Object.setPrototypeOf(to, fromPrototype);
  35. };
  36. const wrappedToString = (withName, fromBody) => `/* Wrapped ${withName}*/\n${fromBody}`;
  37. const toStringDescriptor = Object.getOwnPropertyDescriptor(Function.prototype, 'toString');
  38. const toStringName = Object.getOwnPropertyDescriptor(Function.prototype.toString, 'name');
  39. // We call `from.toString()` early (not lazily) to ensure `from` can be garbage collected.
  40. // We use `bind()` instead of a closure for the same reason.
  41. // Calling `from.toString()` early also allows caching it in case `to.toString()` is called several times.
  42. const changeToString = (to, from, name) => {
  43. const withName = name === '' ? '' : `with ${name.trim()}() `;
  44. const newToString = wrappedToString.bind(null, withName, from.toString());
  45. // Ensure `to.toString.toString` is non-enumerable and has the same `same`
  46. Object.defineProperty(newToString, 'name', toStringName);
  47. const {writable, enumerable, configurable} = toStringDescriptor; // We destructue to avoid a potential `get` descriptor.
  48. Object.defineProperty(to, 'toString', {value: newToString, writable, enumerable, configurable});
  49. };
  50. export default function mimicFunction(to, from, {ignoreNonConfigurable = false} = {}) {
  51. const {name} = to;
  52. for (const property of Reflect.ownKeys(from)) {
  53. copyProperty(to, from, property, ignoreNonConfigurable);
  54. }
  55. changePrototype(to, from);
  56. changeToString(to, from, name);
  57. return to;
  58. }