diff --git a/__tests__/index.ts b/__tests__/index.ts index 76ec43d..ed9771a 100644 --- a/__tests__/index.ts +++ b/__tests__/index.ts @@ -14,7 +14,7 @@ hash.update('foo bar'); const SIMPLE_TYPES: PlainObject = { boolean: true, - error: new Error('boom'), + error: new TypeError('boom'), fn() { return 'foo'; }, @@ -53,7 +53,7 @@ const COMPLEX_TYPES: PlainObject = { })('foo', 'bar', 'baz'), array: ['foo', { bar: 'baz' }], arrayBuffer: new ArrayBuffer(8), - buffer: new Buffer('this is a test buffer'), + buffer: Buffer.from('this is a test buffer'), customPrototype: Object.create({ method() { return 'foo'; @@ -62,22 +62,22 @@ const COMPLEX_TYPES: PlainObject = { }), dataView: new DataView(new ArrayBuffer(16)), date: new Date(), - float32Array: new Float32Array([12, 15]), - float64Array: new Float64Array([12, 15]), + float32Array: new Float32Array([1, 2]), + float64Array: new Float64Array([3, 4]), hash, - int8Array: new Int8Array([12, 15]), - int16Array: new Int16Array([12, 15]), - int32Array: new Int32Array([12, 15]), + int8Array: new Int8Array([5, 6]), + int16Array: new Int16Array([7, 8]), + int32Array: new Int32Array([9, 10]), map: new Map().set('foo', { bar: { baz: 'quz' } }), object: { foo: { bar: 'baz' } }, regexp: /foo/, set: new Set().add('foo').add({ bar: { baz: 'quz' } }), // Disabling, as jest fails intermittently with blob construction. // blob: new Blob(['hey!'], {type : 'text/html'}), - uint8Array: new Uint8Array([12, 15]), - uint8ClampedArray: new Uint8ClampedArray([12, 15]), - uint16Array: new Uint16Array([12, 15]), - uint32Array: new Uint32Array([12, 15]), + uint8Array: new Uint8Array([11, 12]), + uint8ClampedArray: new Uint8ClampedArray([13, 14]), + uint16Array: new Uint16Array([15, 16]), + uint32Array: new Uint32Array([17, 18]), }; Object.defineProperties(COMPLEX_TYPES, { @@ -168,8 +168,8 @@ describe('copy', () => { const properties = [].concat( Object.keys(SIMPLE_TYPES), Object.getOwnPropertySymbols(SIMPLE_TYPES).filter((symbol) => - Object.prototype.propertyIsEnumerable.call(SIMPLE_TYPES, symbol), - ), + Object.prototype.propertyIsEnumerable.call(SIMPLE_TYPES, symbol) + ) ); properties.forEach((property: string | symbol) => { @@ -192,7 +192,7 @@ describe('copy', () => { const properties = [ ...Object.keys(COMPLEX_TYPES), ...Object.getOwnPropertySymbols(COMPLEX_TYPES).filter((symbol) => - Object.prototype.propertyIsEnumerable.call(COMPLEX_TYPES, symbol), + Object.prototype.propertyIsEnumerable.call(COMPLEX_TYPES, symbol) ), ]; @@ -202,7 +202,7 @@ describe('copy', () => { expect({ ...result[property] }).toEqual({ ...COMPLEX_TYPES[property] }); } else if (property === 'customPrototype') { expect(Object.getPrototypeOf(result[property])).toBe( - Object.getPrototypeOf(COMPLEX_TYPES[property]), + Object.getPrototypeOf(COMPLEX_TYPES[property]) ); expect(result[property]).toEqual(COMPLEX_TYPES[property]); } else { @@ -226,42 +226,6 @@ describe('copy', () => { expect(result).toEqual(SPECIAL_TYPES); }); - it('will handle when buffers are not supported', () => { - const cleanComplexTypes = Object.keys(COMPLEX_TYPES).reduce( - (types: PlainObject, key) => { - if ( - key !== 'arguments' && - key !== 'arrayBuffer' && - key !== 'buffer' && - key !== 'dataView' && - !/float(.*)Array/.test(key) && - !/int(.*)Array/.test(key) - ) { - types[key] = COMPLEX_TYPES[key]; - } - - return types; - }, - {}, - ); - - const realm = { - Date: global.Date, - Error: global.Error, - Map: global.Map, - RegExp: global.RegExp, - Set: global.Set, - Blob: global.Blob, - WeakMap: global.WeakMap, - WeakSet: global.WeakSet, - } as typeof globalThis; - - const result = copy(cleanComplexTypes, { realm }); - - expect(result).not.toBe(cleanComplexTypes); - expect(result).toEqual(cleanComplexTypes); - }); - it('will copy referenced objects', () => { const reusedObject = { name: 'I like trains!', @@ -347,7 +311,7 @@ describe('copyStrict', () => { const properties = [].concat( Object.getOwnPropertyNames(SIMPLE_TYPES), - Object.getOwnPropertySymbols(SIMPLE_TYPES), + Object.getOwnPropertySymbols(SIMPLE_TYPES) ); properties.forEach((property: string | symbol) => { @@ -374,7 +338,7 @@ describe('copyStrict', () => { const properties = [].concat( Object.getOwnPropertyNames(complexTypes), - Object.getOwnPropertySymbols(complexTypes), + Object.getOwnPropertySymbols(complexTypes) ); properties.forEach((property: string | symbol) => { @@ -388,10 +352,10 @@ describe('copyStrict', () => { }); } else if (property === 'customPrototype') { expect(Object.getPrototypeOf(result[property])).toBe( - Object.getPrototypeOf(COMPLEX_TYPES[property]), + Object.getPrototypeOf(COMPLEX_TYPES[property]) ); expect(Object.getPrototypeOf(result2[property])).toBe( - Object.getPrototypeOf(COMPLEX_TYPES[property]), + Object.getPrototypeOf(COMPLEX_TYPES[property]) ); expect(result[property]).toEqual(COMPLEX_TYPES[property]); @@ -427,45 +391,6 @@ describe('copyStrict', () => { expect(result2).toEqual(SPECIAL_TYPES); }); - it('will handle when buffers are not supported', () => { - const cleanComplexTypes = Object.keys(COMPLEX_TYPES).reduce( - (types: PlainObject, key) => { - if ( - key !== 'arguments' && - key !== 'arrayBuffer' && - key !== 'buffer' && - key !== 'dataView' && - !/float(.*)Array/.test(key) && - !/int(.*)Array/.test(key) - ) { - types[key] = COMPLEX_TYPES[key]; - } - - return types; - }, - {}, - ); - - const realm = { - Date: global.Date, - Error: global.Error, - Map: global.Map, - RegExp: global.RegExp, - Set: global.Set, - WeakMap: global.WeakMap, - WeakSet: global.WeakSet, - } as typeof globalThis; - - const result = copy(cleanComplexTypes, { isStrict: true, realm }); - const result2 = copyStrict(cleanComplexTypes, { realm }); - - expect(result).not.toBe(cleanComplexTypes); - expect(result2).not.toBe(cleanComplexTypes); - - expect(result).toEqual(cleanComplexTypes); - expect(result2).toEqual(cleanComplexTypes); - }); - it('will copy referenced objects', () => { const reusedObject = { name: 'I like trains!', diff --git a/__tests__/utils.ts b/__tests__/utils.ts index cb072c7..2b7be8c 100644 --- a/__tests__/utils.ts +++ b/__tests__/utils.ts @@ -56,9 +56,8 @@ describe('createCache', () => { describe('getCleanClone', () => { it('will return a pure object when there is no constructor', () => { const object = Object.create(null); - const realm = global; - const result = utils.getCleanClone(object, realm); + const result = utils.getCleanClone(object); expect(result).not.toBe(object); expect(result).toEqual(object); @@ -71,9 +70,7 @@ describe('getCleanClone', () => { object.__proto__ = null; - const realm = global; - - const result = utils.getCleanClone(object, realm); + const result = utils.getCleanClone(object); expect(result).not.toBe(object); expect(result).toEqual(object); @@ -83,9 +80,8 @@ describe('getCleanClone', () => { it('will return an empty POJO when the object passed is a POJO', () => { const object = { foo: 'bar' }; - const realm = global; - const result = utils.getCleanClone(object, realm); + const result = utils.getCleanClone(object); expect(result).not.toBe(object); expect(result).toEqual({}); @@ -100,9 +96,7 @@ describe('getCleanClone', () => { object.foo = 'bar'; - const realm = global; - - const result = utils.getCleanClone(object, realm); + const result = utils.getCleanClone(object); expect(result).not.toBe(object); expect(result).toEqual({}); @@ -112,9 +106,8 @@ describe('getCleanClone', () => { it('will return an empty object with the given constructor when it is a global constructor', () => { const object = new Map(); - const realm = global; - const result = utils.getCleanClone(object, realm); + const result = utils.getCleanClone(object); expect(result).not.toBe(object); expect(result).toEqual(new Map()); @@ -134,9 +127,8 @@ describe('getCleanClone', () => { } const object = new Foo('bar'); - const realm = global; - const result = utils.getCleanClone(object, realm); + const result = utils.getCleanClone(object); expect(result).not.toBe(object); expect(result).toEqual(Object.create(Foo.prototype)); @@ -160,11 +152,10 @@ describe('getObjectCloneLoose', () => { foo: 'bar', [symbol]: 'blah', }; - const realm = global; const handleCopy = jest.fn().mockImplementation((arg) => arg); const cache = utils.createCache(); - const result = utils.getObjectCloneLoose(object, realm, handleCopy, cache); + const result = utils.getObjectCloneLoose(object, handleCopy, cache); Object.getOwnPropertySymbols = original; @@ -174,7 +165,7 @@ describe('getObjectCloneLoose', () => { clone[key] = object[key as keyof typeof object]; return clone; - }, {}), + }, {}) ); expect(handleCopy).toHaveBeenCalledTimes(Object.keys(object).length); @@ -185,17 +176,16 @@ describe('getObjectCloneLoose', () => { bar: { baz: 'quz' }, [Symbol('quz')]: 'blah', }; - const realm = global; const handleCopy = jest.fn().mockImplementation((arg) => arg); const cache = utils.createCache(); - const result = utils.getObjectCloneLoose(object, realm, handleCopy, cache); + const result = utils.getObjectCloneLoose(object, handleCopy, cache); expect(result).not.toBe(object); expect(result).toEqual(object); expect(handleCopy).toHaveBeenCalledTimes( - Object.keys(object).length + Object.getOwnPropertySymbols(object).length, + Object.keys(object).length + Object.getOwnPropertySymbols(object).length ); }); }); @@ -222,11 +212,10 @@ describe('getObjectCloneStrict', () => { value: 'blah', }); - const realm = global; const handleCopy = jest.fn().mockImplementation((arg) => arg); const cache = utils.createCache(); - const result = utils.getObjectCloneStrict(object, realm, handleCopy, cache); + const result = utils.getObjectCloneStrict(object, handleCopy, cache); Object.getOwnPropertySymbols = original; @@ -238,12 +227,12 @@ describe('getObjectCloneStrict', () => { return clone; }, - {}, - ), + {} + ) ); expect(handleCopy).toHaveBeenCalledTimes( - Object.getOwnPropertyNames(object).length, + Object.getOwnPropertyNames(object).length ); }); @@ -261,18 +250,17 @@ describe('getObjectCloneStrict', () => { value: 'blah', }); - const realm = global; const handleCopy = jest.fn().mockImplementation((arg) => arg); const cache = utils.createCache(); - const result = utils.getObjectCloneStrict(object, realm, handleCopy, cache); + const result = utils.getObjectCloneStrict(object, handleCopy, cache); expect(result).not.toBe(object); expect(result).toEqual(object); expect(handleCopy).toHaveBeenCalledTimes( Object.getOwnPropertyNames(object).length + - Object.getOwnPropertySymbols(object).length, + Object.getOwnPropertySymbols(object).length ); }); }); diff --git a/src/index.ts b/src/index.ts index 5f8dff1..503b416 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,37 +9,13 @@ import type { Cache, InternalCopier, Realm } from './utils'; export interface Options { isStrict?: boolean; - realm?: Realm; } export interface StrictOptions extends Omit {} const { isArray } = Array; const { assign, getPrototypeOf } = Object; - -const GLOBAL_THIS: Realm = (function () { - if (typeof globalThis !== 'undefined') { - return globalThis; - } - - if (typeof self !== 'undefined') { - return self; - } - - if (typeof window !== 'undefined') { - return window; - } - - if (typeof global !== 'undefined') { - return global; - } - - if (console && console.error) { - console.error('Unable to locate global object, returning "this".'); - } - - return this; -})(); +const { toString } = Object.prototype; /** * Copy an value deeply as much as possible. @@ -54,7 +30,6 @@ const GLOBAL_THIS: Realm = (function () { export function copy(value: Value, options?: Options): Value { // manually coalesced instead of default parameters for performance const isStrict = !!(options && options.isStrict); - const realm = (options && options.realm) || GLOBAL_THIS; const getObjectClone = isStrict ? getObjectCloneStrict : getObjectCloneLoose; /** @@ -79,15 +54,15 @@ export function copy(value: Value, options?: Options): Value { const Constructor = prototype && prototype.constructor; // plain objects - if (!Constructor || Constructor === realm.Object) { - return getObjectClone(value, realm, handleCopy, cache); + if (!Constructor || Constructor === Object) { + return getObjectClone(value, handleCopy, cache); } // arrays if (isArray(value)) { // if strict, include non-standard properties if (isStrict) { - return getObjectCloneStrict(value, realm, handleCopy, cache); + return getObjectCloneStrict(value, handleCopy, cache); } const clone = new Constructor(); @@ -105,13 +80,15 @@ export function copy(value: Value, options?: Options): Value { return clone; } + const objectClass = toString.call(value); + // dates - if (value instanceof realm.Date) { + if (objectClass === '[object Date]') { return new Constructor(value.getTime()); } // regexps - if (value instanceof realm.RegExp) { + if (objectClass === '[object RegExp]') { const clone = new Constructor( value.source, value.flags || getRegExpFlags(value) @@ -123,7 +100,7 @@ export function copy(value: Value, options?: Options): Value { } // maps - if (realm.Map && value instanceof realm.Map) { + if (objectClass === '[object Map]') { const clone = new Constructor(); cache.set(value, clone); @@ -136,7 +113,7 @@ export function copy(value: Value, options?: Options): Value { } // sets - if (realm.Set && value instanceof realm.Set) { + if (objectClass === '[object Set]') { const clone = new Constructor(); cache.set(value, clone); @@ -149,41 +126,38 @@ export function copy(value: Value, options?: Options): Value { } // blobs - if (realm.Blob && value instanceof realm.Blob) { + if (objectClass === '[object Blob]') { return value.slice(0, value.size, value.type); } - // buffers (node-only) - if (realm.Buffer && realm.Buffer.isBuffer(value)) { - const clone = realm.Buffer.allocUnsafe - ? realm.Buffer.allocUnsafe(value.length) - : new Constructor(value.length); + // dataviews + if (objectClass === '[object DataView]') { + const clone = new Constructor(value.buffer.slice(0)); cache.set(value, clone); - value.copy(clone); return clone; } - // arraybuffers / dataviews - if (realm.ArrayBuffer) { - // dataviews - if (realm.ArrayBuffer.isView(value)) { - const clone = new Constructor(value.buffer.slice(0)); - - cache.set(value, clone); - - return clone; - } - - // arraybuffers - if (value instanceof realm.ArrayBuffer) { - const clone = value.slice(0); + // array buffers + if ( + objectClass === '[object Float32Array]' || + objectClass === '[object Float64Array]' || + objectClass === '[object Int8Array]' || + objectClass === '[object Int16Array]' || + objectClass === '[object Int32Array]' || + objectClass === '[object Uint8Array]' || + objectClass === '[object Uint8ClampedArray]' || + objectClass === '[object Uint16Array]' || + objectClass === '[object Uint32Array]' || + objectClass === '[object Uint64Array]' || + objectClass === '[object ArrayBuffer]' + ) { + const clone = value.slice(0); - cache.set(value, clone); + cache.set(value, clone); - return clone; - } + return clone; } // if the value cannot / should not be cloned, don't @@ -191,17 +165,17 @@ export function copy(value: Value, options?: Options): Value { // promise-like typeof value.then === 'function' || // errors - value instanceof Error || + objectClass === '[object Error]' || // weakmaps - (realm.WeakMap && value instanceof realm.WeakMap) || + objectClass === '[object WeakMap]' || // weaksets - (realm.WeakSet && value instanceof realm.WeakSet) + objectClass === '[object WeakSet]' ) { return value; } // assume anything left is a custom constructor - return getObjectClone(value, realm, handleCopy, cache); + return getObjectClone(value, handleCopy, cache); }; return handleCopy(value, createCache()); diff --git a/src/utils.ts b/src/utils.ts index 960412e..dcb0168 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -10,7 +10,6 @@ export type InternalCopier = (value: Value, cache: Cache) => Value; export type ObjectCloner = ( object: Value, - realm: Realm, handleCopy: InternalCopier, cache: Cache ) => Value; @@ -65,7 +64,7 @@ export const createCache = /** * Get an empty version of the object with the same prototype it has. */ -export function getCleanClone(object: any, realm: Realm): any { +export function getCleanClone(object: any): any { const prototype = object.__proto__ || getPrototypeOf(object); if (!prototype) { @@ -74,8 +73,8 @@ export function getCleanClone(object: any, realm: Realm): any { const Constructor = prototype.constructor; - if (Constructor === realm.Object) { - return prototype === realm.Object.prototype ? {} : create(prototype); + if (Constructor === Object) { + return prototype === Object.prototype ? {} : create(prototype); } if (~toStringFunction.call(Constructor).indexOf('[native code]')) { @@ -93,11 +92,10 @@ export function getCleanClone(object: any, realm: Realm): any { */ export function getObjectCloneLoose( object: any, - realm: Realm, handleCopy: InternalCopier, cache: Cache ): any { - const clone: any = getCleanClone(object, realm); + const clone: any = getCleanClone(object); // set in the cache immediately to be able to reuse the object recursively cache.set(object, clone); @@ -143,11 +141,10 @@ const getStrictProperties = SYMBOL_PROPERTIES */ export function getObjectCloneStrict( object: any, - realm: Realm, handleCopy: InternalCopier, cache: Cache ): any { - const clone: any = getCleanClone(object, realm); + const clone: any = getCleanClone(object); // set in the cache immediately to be able to reuse the object recursively cache.set(object, clone);