index.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. 'use strict';
  2. const copyProperty = (to, from, property, ignoreNonConfigurable) => {
  3. // `Function#length` should reflect the parameters of `to` not `from` since we keep its body.
  4. // `Function#prototype` is non-writable and non-configurable so can never be modified.
  5. if (property === 'length' || property === 'prototype') {
  6. return;
  7. }
  8. // `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.
  9. if (property === 'arguments' || property === 'caller') {
  10. return;
  11. }
  12. const toDescriptor = Object.getOwnPropertyDescriptor(to, property);
  13. const fromDescriptor = Object.getOwnPropertyDescriptor(from, property);
  14. if (!canCopyProperty(toDescriptor, fromDescriptor) && ignoreNonConfigurable) {
  15. return;
  16. }
  17. Object.defineProperty(to, property, fromDescriptor);
  18. };
  19. // `Object.defineProperty()` throws if the property exists, is not configurable and either:
  20. // - one its descriptors is changed
  21. // - it is non-writable and its value is changed
  22. const canCopyProperty = function (toDescriptor, fromDescriptor) {
  23. return toDescriptor === undefined || toDescriptor.configurable || (
  24. toDescriptor.writable === fromDescriptor.writable &&
  25. toDescriptor.enumerable === fromDescriptor.enumerable &&
  26. toDescriptor.configurable === fromDescriptor.configurable &&
  27. (toDescriptor.writable || toDescriptor.value === fromDescriptor.value)
  28. );
  29. };
  30. const changePrototype = (to, from) => {
  31. const fromPrototype = Object.getPrototypeOf(from);
  32. if (fromPrototype === Object.getPrototypeOf(to)) {
  33. return;
  34. }
  35. Object.setPrototypeOf(to, fromPrototype);
  36. };
  37. const wrappedToString = (withName, fromBody) => `/* Wrapped ${withName}*/\n${fromBody}`;
  38. const toStringDescriptor = Object.getOwnPropertyDescriptor(Function.prototype, 'toString');
  39. const toStringName = Object.getOwnPropertyDescriptor(Function.prototype.toString, 'name');
  40. // We call `from.toString()` early (not lazily) to ensure `from` can be garbage collected.
  41. // We use `bind()` instead of a closure for the same reason.
  42. // Calling `from.toString()` early also allows caching it in case `to.toString()` is called several times.
  43. const changeToString = (to, from, name) => {
  44. const withName = name === '' ? '' : `with ${name.trim()}() `;
  45. const newToString = wrappedToString.bind(null, withName, from.toString());
  46. // Ensure `to.toString.toString` is non-enumerable and has the same `same`
  47. Object.defineProperty(newToString, 'name', toStringName);
  48. Object.defineProperty(to, 'toString', {...toStringDescriptor, value: newToString});
  49. };
  50. const mimicFn = (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. };
  59. module.exports = mimicFn;