Skip to main content
Go to Latest
File
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.// Copyright Joyent and Node contributors. All rights reserved. MIT license.
// deno-lint-ignore-fileimport { isAnyArrayBuffer, isArrayBufferView, isBigIntObject, isBooleanObject, isBoxedPrimitive, isDate, isFloat32Array, isFloat64Array, isMap, isNativeError, isNumberObject, isRegExp, isSet, isStringObject, isSymbolObject, isTypedArray,} from "./types.ts";
import { Buffer } from "../../buffer.ts";import { getOwnNonIndexProperties, ONLY_ENUMERABLE, SKIP_SYMBOLS,} from "../../internal_binding/util.ts";
enum valueType { noIterator, isArray, isSet, isMap,}
interface Memo { val1: Map<unknown, unknown>; val2: Map<unknown, unknown>; position: number;}let memo: Memo;
export function isDeepStrictEqual(val1: unknown, val2: unknown): boolean { return innerDeepEqual(val1, val2, true);}export function isDeepEqual(val1: unknown, val2: unknown): boolean { return innerDeepEqual(val1, val2, false);}
function innerDeepEqual( val1: unknown, val2: unknown, strict: boolean, memos = memo,): boolean { // Basic case covered by Strict Equality Comparison if (val1 === val2) { if (val1 !== 0) return true; return strict ? Object.is(val1, val2) : true; } if (strict) { // Cases where the values are not objects // If both values are Not a Number NaN if (typeof val1 !== "object") { return ( typeof val1 === "number" && Number.isNaN(val1) && Number.isNaN(val2) ); } // If either value is null if (typeof val2 !== "object" || val1 === null || val2 === null) { return false; } // If the prototype are not the same if (Object.getPrototypeOf(val1) !== Object.getPrototypeOf(val2)) { return false; } } else { // Non strict case where values are either null or NaN if (val1 === null || typeof val1 !== "object") { if (val2 === null || typeof val2 !== "object") { return val1 == val2 || (Number.isNaN(val1) && Number.isNaN(val2)); } return false; } if (val2 === null || typeof val2 !== "object") { return false; } }
const val1Tag = Object.prototype.toString.call(val1); const val2Tag = Object.prototype.toString.call(val2);
// prototype must be Strictly Equal if ( val1Tag !== val2Tag ) { return false; }
// handling when values are array if (Array.isArray(val1)) { // quick rejection cases if (!Array.isArray(val2) || val1.length !== val2.length) { return false; } const filter = strict ? ONLY_ENUMERABLE : ONLY_ENUMERABLE | SKIP_SYMBOLS; const keys1 = getOwnNonIndexProperties(val1, filter); const keys2 = getOwnNonIndexProperties(val2, filter); if (keys1.length !== keys2.length) { return false; } return keyCheck(val1, val2, strict, memos, valueType.isArray, keys1); } else if (val1Tag === "[object Object]") { return keyCheck( val1 as object, val2 as object, strict, memos, valueType.noIterator, ); } else if (val1 instanceof Date) { if (!(val2 instanceof Date) || val1.getTime() !== val2.getTime()) { return false; } } else if (val1 instanceof RegExp) { if (!(val2 instanceof RegExp) || !areSimilarRegExps(val1, val2)) { return false; } } else if (isNativeError(val1) || val1 instanceof Error) { // stack may or may not be same, hence it shouldn't be compared if ( // How to handle the type errors here (!isNativeError(val2) && !(val2 instanceof Error)) || (val1 as Error).message !== (val2 as Error).message || (val1 as Error).name !== (val2 as Error).name ) { return false; } } else if (isArrayBufferView(val1)) { const TypedArrayPrototypeGetSymbolToStringTag = (val: []) => Object.getOwnPropertySymbols(val) .map((item) => item.toString()) .toString(); if ( isTypedArray(val1) && isTypedArray(val2) && (TypedArrayPrototypeGetSymbolToStringTag(val1 as []) !== TypedArrayPrototypeGetSymbolToStringTag(val2 as [])) ) { return false; }
if (!strict && (isFloat32Array(val1) || isFloat64Array(val1))) { if (!areSimilarFloatArrays(val1, val2)) { return false; } } else if (!areSimilarTypedArrays(val1, val2)) { return false; } const filter = strict ? ONLY_ENUMERABLE : ONLY_ENUMERABLE | SKIP_SYMBOLS; const keysVal1 = getOwnNonIndexProperties(val1 as object, filter); const keysVal2 = getOwnNonIndexProperties(val2 as object, filter); if (keysVal1.length !== keysVal2.length) { return false; } return keyCheck( val1 as object, val2 as object, strict, memos, valueType.noIterator, keysVal1, ); } else if (isSet(val1)) { if ( !isSet(val2) || (val1 as Set<unknown>).size !== (val2 as Set<unknown>).size ) { return false; } return keyCheck( val1 as object, val2 as object, strict, memos, valueType.isSet, ); } else if (isMap(val1)) { if ( !isMap(val2) || (val1 as Set<unknown>).size !== (val2 as Set<unknown>).size ) { return false; } return keyCheck( val1 as object, val2 as object, strict, memos, valueType.isMap, ); } else if (isAnyArrayBuffer(val1)) { if (!isAnyArrayBuffer(val2) || !areEqualArrayBuffers(val1, val2)) { return false; } } else if (isBoxedPrimitive(val1)) { if (!isEqualBoxedPrimitive(val1, val2)) { return false; } } else if ( Array.isArray(val2) || isArrayBufferView(val2) || isSet(val2) || isMap(val2) || isDate(val2) || isRegExp(val2) || isAnyArrayBuffer(val2) || isBoxedPrimitive(val2) || isNativeError(val2) || val2 instanceof Error ) { return false; } return keyCheck( val1 as object, val2 as object, strict, memos, valueType.noIterator, );}
function keyCheck( val1: object, val2: object, strict: boolean, memos: Memo, iterationType: valueType, aKeys: (string | symbol)[] = [],) { if (arguments.length === 5) { aKeys = Object.keys(val1); const bKeys = Object.keys(val2);
// The pair must have the same number of owned properties. if (aKeys.length !== bKeys.length) { return false; } }
// Cheap key test let i = 0; for (; i < aKeys.length; i++) { if (!val2.propertyIsEnumerable(aKeys[i])) { return false; } }
if (strict && arguments.length === 5) { const symbolKeysA = Object.getOwnPropertySymbols(val1); if (symbolKeysA.length !== 0) { let count = 0; for (i = 0; i < symbolKeysA.length; i++) { const key = symbolKeysA[i]; if (val1.propertyIsEnumerable(key)) { if (!val2.propertyIsEnumerable(key)) { return false; } // added toString here aKeys.push(key.toString()); count++; } else if (val2.propertyIsEnumerable(key)) { return false; } } const symbolKeysB = Object.getOwnPropertySymbols(val2); if ( symbolKeysA.length !== symbolKeysB.length && getEnumerables(val2, symbolKeysB).length !== count ) { return false; } } else { const symbolKeysB = Object.getOwnPropertySymbols(val2); if ( symbolKeysB.length !== 0 && getEnumerables(val2, symbolKeysB).length !== 0 ) { return false; } } } if ( aKeys.length === 0 && (iterationType === valueType.noIterator || (iterationType === valueType.isArray && (val1 as []).length === 0) || (val1 as Set<unknown>).size === 0) ) { return true; }
if (memos === undefined) { memos = { val1: new Map(), val2: new Map(), position: 0, }; } else { const val2MemoA = memos.val1.get(val1); if (val2MemoA !== undefined) { const val2MemoB = memos.val2.get(val2); if (val2MemoB !== undefined) { return val2MemoA === val2MemoB; } } memos.position++; }
memos.val1.set(val1, memos.position); memos.val2.set(val2, memos.position);
const areEq = objEquiv(val1, val2, strict, aKeys, memos, iterationType);
memos.val1.delete(val1); memos.val2.delete(val2);
return areEq;}
function areSimilarRegExps(a: RegExp, b: RegExp) { return a.source === b.source && a.flags === b.flags && a.lastIndex === b.lastIndex;}
// TODO(standvpmnt): add type for argumentsfunction areSimilarFloatArrays(arr1: any, arr2: any): boolean { if (arr1.byteLength !== arr2.byteLength) { return false; } for (let i = 0; i < arr1.byteLength; i++) { if (arr1[i] !== arr2[i]) { return false; } } return true;}
// TODO(standvpmnt): add type for argumentsfunction areSimilarTypedArrays(arr1: any, arr2: any): boolean { if (arr1.byteLength !== arr2.byteLength) { return false; } return ( Buffer.compare( new Uint8Array(arr1.buffer, arr1.byteOffset, arr1.byteLength), new Uint8Array(arr2.buffer, arr2.byteOffset, arr2.byteLength), ) === 0 );}// TODO(standvpmnt): add type for argumentsfunction areEqualArrayBuffers(buf1: any, buf2: any): boolean { return ( buf1.byteLength === buf2.byteLength && Buffer.compare(new Uint8Array(buf1), new Uint8Array(buf2)) === 0 );}
// TODO(standvpmnt): this check of getOwnPropertySymbols and getOwnPropertyNames// length is sufficient to handle the current test case, however this will fail// to catch a scenario wherein the getOwnPropertySymbols and getOwnPropertyNames// length is the same(will be very contrived but a possible shortcomingfunction isEqualBoxedPrimitive(a: any, b: any): boolean { if ( Object.getOwnPropertyNames(a).length !== Object.getOwnPropertyNames(b).length ) { return false; } if ( Object.getOwnPropertySymbols(a).length !== Object.getOwnPropertySymbols(b).length ) { return false; } if (isNumberObject(a)) { return ( isNumberObject(b) && Object.is( Number.prototype.valueOf.call(a), Number.prototype.valueOf.call(b), ) ); } if (isStringObject(a)) { return ( isStringObject(b) && (String.prototype.valueOf.call(a) === String.prototype.valueOf.call(b)) ); } if (isBooleanObject(a)) { return ( isBooleanObject(b) && (Boolean.prototype.valueOf.call(a) === Boolean.prototype.valueOf.call(b)) ); } if (isBigIntObject(a)) { return ( isBigIntObject(b) && (BigInt.prototype.valueOf.call(a) === BigInt.prototype.valueOf.call(b)) ); } if (isSymbolObject(a)) { return ( isSymbolObject(b) && (Symbol.prototype.valueOf.call(a) === Symbol.prototype.valueOf.call(b)) ); } // assert.fail(`Unknown boxed type ${val1}`); // return false; throw Error(`Unknown boxed type`);}
function getEnumerables(val: any, keys: any) { return keys.filter((key: string) => val.propertyIsEnumerable(key));}
function objEquiv( obj1: any, obj2: any, strict: boolean, keys: any, memos: Memo, iterationType: valueType,): boolean { let i = 0;
if (iterationType === valueType.isSet) { if (!setEquiv(obj1, obj2, strict, memos)) { return false; } } else if (iterationType === valueType.isMap) { if (!mapEquiv(obj1, obj2, strict, memos)) { return false; } } else if (iterationType === valueType.isArray) { for (; i < obj1.length; i++) { if (obj1.hasOwnProperty(i)) { if ( !obj2.hasOwnProperty(i) || !innerDeepEqual(obj1[i], obj2[i], strict, memos) ) { return false; } } else if (obj2.hasOwnProperty(i)) { return false; } else { const keys1 = Object.keys(obj1); for (; i < keys1.length; i++) { const key = keys1[i]; if ( !obj2.hasOwnProperty(key) || !innerDeepEqual(obj1[key], obj2[key], strict, memos) ) { return false; } } if (keys1.length !== Object.keys(obj2).length) { return false; } if (keys1.length !== Object.keys(obj2).length) { return false; } return true; } } }
// Expensive test for (i = 0; i < keys.length; i++) { const key = keys[i]; if (!innerDeepEqual(obj1[key], obj2[key], strict, memos)) { return false; } } return true;}
function findLooseMatchingPrimitives( primitive: unknown,): boolean | null | undefined { switch (typeof primitive) { case "undefined": return null; case "object": return undefined; case "symbol": return false; case "string": primitive = +primitive; case "number": if (Number.isNaN(primitive)) { return false; } } return true;}
function setMightHaveLoosePrim( set1: Set<unknown>, set2: Set<unknown>, primitive: any,) { const altValue = findLooseMatchingPrimitives(primitive); if (altValue != null) return altValue;
return set2.has(altValue) && !set1.has(altValue);}
function setHasEqualElement( set: any, val1: any, strict: boolean, memos: Memo,): boolean { for (const val2 of set) { if (innerDeepEqual(val1, val2, strict, memos)) { set.delete(val2); return true; } }
return false;}
function setEquiv(set1: any, set2: any, strict: boolean, memos: Memo): boolean { let set = null; for (const item of set1) { if (typeof item === "object" && item !== null) { if (set === null) { // What is SafeSet from primordials? // set = new SafeSet(); set = new Set(); } set.add(item); } else if (!set2.has(item)) { if (strict) return false;
if (!setMightHaveLoosePrim(set1, set2, item)) { return false; }
if (set === null) { set = new Set(); } set.add(item); } }
if (set !== null) { for (const item of set2) { if (typeof item === "object" && item !== null) { if (!setHasEqualElement(set, item, strict, memos)) return false; } else if ( !strict && !set1.has(item) && !setHasEqualElement(set, item, strict, memos) ) { return false; } } return set.size === 0; }
return true;}
// TODO(standvpmnt): add types for argumentfunction mapMightHaveLoosePrimitive( map1: Map<unknown, unknown>, map2: Map<unknown, unknown>, primitive: any, item: any, memos: Memo,): boolean { const altValue = findLooseMatchingPrimitives(primitive); if (altValue != null) { return altValue; } const curB = map2.get(altValue); if ( (curB === undefined && !map2.has(altValue)) || !innerDeepEqual(item, curB, false, memo) ) { return false; } return !map1.has(altValue) && innerDeepEqual(item, curB, false, memos);}
function mapEquiv(map1: any, map2: any, strict: boolean, memos: Memo): boolean { let set = null;
for (const { 0: key, 1: item1 } of map1) { if (typeof key === "object" && key !== null) { if (set === null) { set = new Set(); } set.add(key); } else { const item2 = map2.get(key); if ( ( (item2 === undefined && !map2.has(key)) || !innerDeepEqual(item1, item2, strict, memos) ) ) { if (strict) return false; if (!mapMightHaveLoosePrimitive(map1, map2, key, item1, memos)) { return false; } if (set === null) { set = new Set(); } set.add(key); } } }
if (set !== null) { for (const { 0: key, 1: item } of map2) { if (typeof key === "object" && key !== null) { if (!mapHasEqualEntry(set, map1, key, item, strict, memos)) { return false; } } else if ( !strict && (!map1.has(key) || !innerDeepEqual(map1.get(key), item, false, memos)) && !mapHasEqualEntry(set, map1, key, item, false, memos) ) { return false; } } return set.size === 0; }
return true;}
function mapHasEqualEntry( set: any, map: any, key1: any, item1: any, strict: boolean, memos: Memo,): boolean { for (const key2 of set) { if ( innerDeepEqual(key1, key2, strict, memos) && innerDeepEqual(item1, map.get(key2), strict, memos) ) { set.delete(key2); return true; } } return false;}