UNPKG

@gravityforms/utils

Version:
84 lines (69 loc) 3.68 kB
const copyProperty = ( to, from, property, ignoreNonConfigurable ) => { // `Function#length` should reflect the parameters of `to` not `from` since we keep its body. // `Function#prototype` is non-writable and non-configurable so can never be modified. if ( property === 'length' || property === 'prototype' ) { return; } // `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. if ( property === 'arguments' || property === 'caller' ) { return; } const toDescriptor = Object.getOwnPropertyDescriptor( to, property ); const fromDescriptor = Object.getOwnPropertyDescriptor( from, property ); if ( ! canCopyProperty( toDescriptor, fromDescriptor ) && ignoreNonConfigurable ) { return; } Object.defineProperty( to, property, fromDescriptor ); }; // `Object.defineProperty()` throws if the property exists, is not configurable and either: // - one its descriptors is changed // - it is non-writable and its value is changed const canCopyProperty = function( toDescriptor, fromDescriptor ) { return toDescriptor === undefined || toDescriptor.configurable || ( toDescriptor.writable === fromDescriptor.writable && toDescriptor.enumerable === fromDescriptor.enumerable && toDescriptor.configurable === fromDescriptor.configurable && ( toDescriptor.writable || toDescriptor.value === fromDescriptor.value ) ); }; const changePrototype = ( to, from ) => { const fromPrototype = Object.getPrototypeOf( from ); if ( fromPrototype === Object.getPrototypeOf( to ) ) { return; } Object.setPrototypeOf( to, fromPrototype ); }; const wrappedToString = ( withName, fromBody ) => `/* Wrapped ${ withName }*/\n${ fromBody }`; const toStringDescriptor = Object.getOwnPropertyDescriptor( Function.prototype, 'toString' ); const toStringName = Object.getOwnPropertyDescriptor( Function.prototype.toString, 'name' ); // We call `from.toString()` early (not lazily) to ensure `from` can be garbage collected. // We use `bind()` instead of a closure for the same reason. // Calling `from.toString()` early also allows caching it in case `to.toString()` is called several times. const changeToString = ( to, from, name ) => { const withName = name === '' ? '' : `with ${ name.trim() }() `; const newToString = wrappedToString.bind( null, withName, from.toString() ); // Ensure `to.toString.toString` is non-enumerable and has the same `same` Object.defineProperty( newToString, 'name', toStringName ); Object.defineProperty( to, 'toString', { ...toStringDescriptor, value: newToString } ); }; /** * @module mimicFunction * @description Modifies the `to` function to mimic the `from` function and returns the `to` function. * Prototype, class, and inherited properties are copied to the `to` function. `to.toString()` will return * the same as `from.toString()` but prepended with a `with to()` comment. * * @param {Function} to The function to be modified. * @param {Function} from The function to be mimicked. * @param {boolean} options.ignoreNonConfigurable Whether to Skip modifying non-configurable properties instead of throwing an error. * * @return {Function} The `to` function with the properties from the `from` function. */ export default function mimicFunction( to, from, { ignoreNonConfigurable = false } = {} ) { const { name } = to; for ( const property of Reflect.ownKeys( from ) ) { copyProperty( to, from, property, ignoreNonConfigurable ); } changePrototype( to, from ); changeToString( to, from, name ); return to; }