2023-10-03 11:14:36 +08:00
'use strict' ;
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 } ) ;
} ;
const mimicFn = ( 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 ;
} ;
module . exports = mimicFn ;