mirror of
https://github.com/denoland/std.git
synced 2024-11-22 04:59:05 +00:00
fix(node): improve util.isDeepStrictEqual (#1765)
This commit is contained in:
parent
5a405e5a46
commit
823852a4ff
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user