import { 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 { if (val1 === val2) { if (val1 !== 0) return true; return strict ? Object.is(val1, val2) : true; } if (strict) { if (typeof val1 !== "object") { return ( typeof val1 === "number" && Number.isNaN(val1) && Number.isNaN(val2) ); } if (typeof val2 !== "object" || val1 === null || val2 === null) { return false; } if (Object.getPrototypeOf(val1) !== Object.getPrototypeOf(val2)) { return false; } } else { 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);
if ( val1Tag !== val2Tag ) { return false; }
if (Array.isArray(val1)) { 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) { if ( (!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);
if (aKeys.length !== bKeys.length) { return false; } }
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; } 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;}
function 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;}
function 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 );}function areEqualArrayBuffers(buf1: any, buf2: any): boolean { return ( buf1.byteLength === buf2.byteLength && Buffer.compare(new Uint8Array(buf1), new Uint8Array(buf2)) === 0 );}
function 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)) ); } 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; } } }
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) { 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;}
function 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;}