fix(node): improve util.isDeepStrictEqual (#1765)

This commit is contained in:
standvpmnt 2021-12-27 13:04:51 +05:30 committed by GitHub
parent 5a405e5a46
commit 823852a4ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 156 additions and 93 deletions

View File

@ -94,7 +94,6 @@ function utilIsDeepStrict(a, b) {
assert.strictEqual(util.isDeepStrictEqual(a, b), true);
assert.strictEqual(util.isDeepStrictEqual(b, a), true);
}
function notUtilIsDeepStrict(a, b) {
assert.strictEqual(util.isDeepStrictEqual(a, b), false);
assert.strictEqual(util.isDeepStrictEqual(b, a), false);
@ -548,32 +547,30 @@ assert.strictEqual(
utilIsDeepStrict(Object(BigInt(1)), Object(BigInt(1)));
notUtilIsDeepStrict(Object(BigInt(1)), Object(BigInt(2)));
// TODO Decide if Deno implementation will require these cases wherein
// prototype of the object is artifically/superficially modified
// const booleanish = new Boolean(true);
// Object.defineProperty(booleanish, Symbol.toStringTag, { value: "String" });
// Object.setPrototypeOf(booleanish, String.prototype);
// notUtilIsDeepStrict(booleanish, new String("true"));
const booleanish = new Boolean(true);
Object.defineProperty(booleanish, Symbol.toStringTag, { value: "String" });
Object.setPrototypeOf(booleanish, String.prototype);
notUtilIsDeepStrict(booleanish, new String("true"));
// const numberish = new Number(42);
// Object.defineProperty(numberish, Symbol.toStringTag, { value: "String" });
// Object.setPrototypeOf(numberish, String.prototype);
// notUtilIsDeepStrict(numberish, new String("42"));
const numberish = new Number(42);
Object.defineProperty(numberish, Symbol.toStringTag, { value: "String" });
Object.setPrototypeOf(numberish, String.prototype);
notUtilIsDeepStrict(numberish, new String("42"));
// const stringish = new String("0");
// Object.defineProperty(stringish, Symbol.toStringTag, { value: "Number" });
// Object.setPrototypeOf(stringish, Number.prototype);
// notUtilIsDeepStrict(stringish, new Number(0));
const stringish = new String("0");
Object.defineProperty(stringish, Symbol.toStringTag, { value: "Number" });
Object.setPrototypeOf(stringish, Number.prototype);
notUtilIsDeepStrict(stringish, new Number(0));
// const bigintish = new Object(BigInt(42));
// Object.defineProperty(bigintish, Symbol.toStringTag, { value: "String" });
// Object.setPrototypeOf(bigintish, String.prototype);
// notUtilIsDeepStrict(bigintish, new String("42"));
const bigintish = new Object(BigInt(42));
Object.defineProperty(bigintish, Symbol.toStringTag, { value: "String" });
Object.setPrototypeOf(bigintish, String.prototype);
notUtilIsDeepStrict(bigintish, new String("42"));
// const symbolish = new Object(Symbol("fhqwhgads"));
// Object.defineProperty(symbolish, Symbol.toStringTag, { value: "String" });
// Object.setPrototypeOf(symbolish, String.prototype);
// notUtilIsDeepStrict(symbolish, new String("fhqwhgads"));
const symbolish = new Object(Symbol("fhqwhgads"));
Object.defineProperty(symbolish, Symbol.toStringTag, { value: "String" });
Object.setPrototypeOf(symbolish, String.prototype);
notUtilIsDeepStrict(symbolish, new String("fhqwhgads"));
}
// Minus zero

View File

@ -42,10 +42,13 @@ interface Memo {
let memo: Memo;
export function isDeepStrictEqual(val1: unknown, val2: unknown): boolean {
return isDeepEqual(val1, val2, true);
return innerDeepEqual(val1, val2, true);
}
function isDeepEqual(val1: unknown, val2: unknown): boolean {
return innerDeepEqual(val1, val2, false);
}
function isDeepEqual(
function innerDeepEqual(
val1: unknown,
val2: unknown,
strict: boolean,
@ -85,17 +88,13 @@ function isDeepEqual(
}
}
let val1Tag;
let val2Tag;
if (typeof val1 === "object" && val1 !== null) {
val1Tag = (val1 as object).toString();
}
if (typeof val2 === "object" && val2 !== null) {
val2Tag = (val2 as object).toString();
}
const val1Tag = Object.prototype.toString.call(val1);
const val2Tag = Object.prototype.toString.call(val2);
// prototype must be Strictly Equal
if (val1Tag !== val2Tag) {
if (
val1Tag !== val2Tag
) {
return false;
}
@ -330,7 +329,8 @@ function keyCheck(
}
function areSimilarRegExps(a: RegExp, b: RegExp) {
return a.source === b.source && a.flags === b.flags;
return a.source === b.source && a.flags === b.flags &&
a.lastIndex === b.lastIndex;
}
// TODO(standvpmnt): add type for arguments
@ -366,41 +366,60 @@ function areEqualArrayBuffers(buf1: any, buf2: any): boolean {
);
}
// 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 shortcoming
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(
a.valueOf(),
b.valueOf(),
Number.prototype.valueOf.call(a),
Number.prototype.valueOf.call(b),
)
);
}
if (isStringObject(a)) {
return (
isStringObject(b) &&
(a.valueOf() === b.valueOf())
(String.prototype.valueOf.call(a) === String.prototype.valueOf.call(b))
);
}
if (isBooleanObject(a)) {
return (
isBooleanObject(b) &&
(a.valueOf() === b.valueOf())
(Boolean.prototype.valueOf.call(a) === Boolean.prototype.valueOf.call(b))
);
}
if (isBigIntObject(a)) {
return (
isBigIntObject(b) &&
(a.valueOf() === b.valueOf())
(BigInt.prototype.valueOf.call(a) === BigInt.prototype.valueOf.call(b))
);
}
if (isSymbolObject(a)) {
return (
isSymbolObject(b) &&
(a.valueOf() === b.valueOf())
(Symbol.prototype.valueOf.call(a) ===
Symbol.prototype.valueOf.call(b))
);
}
return false;
// assert.fail(`Unknown boxed type ${val1}`);
// return false;
throw Error(`Unknown boxed type`);
}
function getEnumerables(val: any, keys: any) {
@ -430,7 +449,7 @@ function objEquiv(
if (obj1.hasOwnProperty(i)) {
if (
!obj2.hasOwnProperty(i) ||
!isDeepEqual(obj1[i], obj2[i], strict, memos)
!innerDeepEqual(obj1[i], obj2[i], strict, memos)
) {
return false;
}
@ -442,7 +461,7 @@ function objEquiv(
const key = keys1[i];
if (
!obj2.hasOwnProperty(key) ||
!isDeepEqual(obj1[key], obj2[key], strict, memos)
!innerDeepEqual(obj1[key], obj2[key], strict, memos)
) {
return false;
}
@ -461,13 +480,60 @@ function objEquiv(
// Expensive test
for (i = 0; i < keys.length; i++) {
const key = keys[i];
if (!isDeepEqual(obj1[key], obj2[key], strict, memos)) {
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) {
@ -481,16 +547,14 @@ function setEquiv(set1: any, set2: any, strict: boolean, memos: Memo): boolean {
} else if (!set2.has(item)) {
if (strict) return false;
// TODO(standvpmnt): handling of non-strict is pending
// Since we do not need to handle non-strict case
// if (!setMightHaveLoosePrim(set1, set2, item)) {
// return false;
// }
if (!setMightHaveLoosePrim(set1, set2, item)) {
return false;
}
// if (set === null) {
// set = new Set();
// }
// set.add(item);
if (set === null) {
set = new Set();
}
set.add(item);
}
}
@ -512,7 +576,28 @@ function setEquiv(set1: any, set2: any, strict: boolean, memos: Memo): boolean {
return true;
}
// TODO(standvpmnt): Implementation of non-strict cases is pending
// TODO(standvpmnt): add types for argument
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;
@ -525,11 +610,19 @@ function mapEquiv(map1: any, map2: any, strict: boolean, memos: Memo): boolean {
} else {
const item2 = map2.get(key);
if (
(item2 === undefined && !map2.has(key)) ||
!isDeepEqual(item1, item2, strict, memos)
(
(item2 === undefined && !map2.has(key)) ||
!innerDeepEqual(item1, item2, strict, memos)
)
) {
if (strict) return false;
// TODO(standvpmnt): Implementation of non-strict cases is pending
if (!mapMightHaveLoosePrimitive(map1, map2, key, item1, memos)) {
return false;
}
if (set === null) {
set = new Set();
}
set.add(key);
}
}
}
@ -540,15 +633,13 @@ function mapEquiv(map1: any, map2: any, strict: boolean, memos: Memo): boolean {
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;
}
// TODO(standvpmnt): Implement handling of case with non-strict equal
// else if (
// !strict &&
// // (!map1.has(key) || !isDeepEqual(map1.get(key), item, false, memos)) &&
// // !mapHasEqualEntry(set, map1, key, item, false, memos)
// ) {
// return false;
// }
}
return set.size === 0;
}
@ -556,30 +647,6 @@ function mapEquiv(map1: any, map2: any, strict: boolean, memos: Memo): boolean {
return true;
}
// TODO(standvpmnt): Implement handling of case with non-strict equal
// function setMightHaveLoosePrim(set1, set2, primitive) {
// 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 (isDeepEqual(val1, val2, strict, memos)) {
set.delete(val2);
return true;
}
}
return false;
}
function mapHasEqualEntry(
set: any,
map: any,
@ -590,13 +657,12 @@ function mapHasEqualEntry(
): boolean {
for (const key2 of set) {
if (
isDeepEqual(key1, key2, strict, memos) &&
isDeepEqual(item1, map.get(key2), strict, memos)
innerDeepEqual(key1, key2, strict, memos) &&
innerDeepEqual(item1, map.get(key2), strict, memos)
) {
set.delete(key2);
return true;
}
}
return false;
}