data_mimic-fn.js

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;
}