Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions lib/internal/assert/assertion_error.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const {
ObjectDefineProperty,
ObjectGetPrototypeOf,
ObjectPrototypeHasOwnProperty,
SafeSet,
String,
StringPrototypeRepeat,
StringPrototypeSlice,
Expand Down Expand Up @@ -42,7 +43,10 @@ const kReadableOperator = {
const kMaxShortStringLength = 12;
const kMaxLongStringLength = 512;

const kMethodsWithCustomMessageDiff = ['deepStrictEqual', 'strictEqual', 'partialDeepStrictEqual'];
const kMethodsWithCustomMessageDiff = new SafeSet()
.add('deepStrictEqual')
.add('strictEqual')
.add('partialDeepStrictEqual');

function copyError(source) {
const target = ObjectAssign(
Expand Down Expand Up @@ -263,7 +267,7 @@ class AssertionError extends Error {
if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0;

if (message != null) {
if (kMethodsWithCustomMessageDiff.includes(operator)) {
if (kMethodsWithCustomMessageDiff.has(operator)) {
super(createErrDiff(actual, expected, operator, message, diff));
} else {
super(String(message));
Expand All @@ -283,7 +287,7 @@ class AssertionError extends Error {
expected = copyError(expected);
}

if (kMethodsWithCustomMessageDiff.includes(operator)) {
if (kMethodsWithCustomMessageDiff.has(operator)) {
super(createErrDiff(actual, expected, operator, message, diff));
} else if (operator === 'notDeepStrictEqual' ||
operator === 'notStrictEqual') {
Expand Down
120 changes: 51 additions & 69 deletions lib/internal/util/comparisons.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ const {
Array,
ArrayBuffer,
ArrayIsArray,
ArrayPrototypeFilter,
ArrayPrototypePush,
BigInt,
BigInt64Array,
Expand Down Expand Up @@ -91,7 +90,7 @@ const wellKnownConstructors = new SafeSet()
.add(WeakMap)
.add(WeakSet);

if (Float16Array) { // TODO(BridgeAR): Remove when regularly supported
if (Float16Array) { // TODO(BridgeAR): Remove when Flag got removed from V8
wellKnownConstructors.add(Float16Array);
}

Expand Down Expand Up @@ -127,6 +126,11 @@ const {
getOwnNonIndexProperties,
} = internalBinding('util');

let kKeyObject;
let kExtractable;
let kAlgorithm;
let kKeyUsages;

const kStrict = 2;
const kStrictWithoutPrototypes = 3;
const kLoose = 0;
Expand All @@ -152,7 +156,7 @@ function isPartialUint8Array(a, b) {
}
let offsetA = 0;
for (let offsetB = 0; offsetB < lenB; offsetB++) {
while (!ObjectIs(a[offsetA], b[offsetB])) {
while (a[offsetA] !== b[offsetB]) {
offsetA++;
if (offsetA > lenA - lenB + offsetB) {
return false;
Expand Down Expand Up @@ -187,11 +191,7 @@ function areSimilarFloatArrays(a, b) {
}

function areSimilarTypedArrays(a, b) {
if (a.byteLength !== b.byteLength) {
return false;
}
return compare(new Uint8Array(a.buffer, a.byteOffset, a.byteLength),
new Uint8Array(b.buffer, b.byteOffset, b.byteLength)) === 0;
return a.byteLength === b.byteLength && compare(a, b) === 0;
}

function areEqualArrayBuffers(buf1, buf2) {
Expand Down Expand Up @@ -225,7 +225,7 @@ function isEqualBoxedPrimitive(val1, val2) {
assert.fail(`Unknown boxed type ${val1}`);
}

function isEnumerableOrIdentical(val1, val2, prop, mode, memos, method) {
function isEnumerableOrIdentical(val1, val2, prop, mode, memos) {
return hasEnumerable(val2, prop) || // This is handled by Object.keys()
(mode === kPartial && (val2[prop] === undefined || (prop === 'message' && val2[prop] === ''))) ||
innerDeepEqual(val1[prop], val2[prop], mode, memos);
Expand Down Expand Up @@ -304,9 +304,9 @@ function objectComparisonStart(val1, val2, mode, memos) {
}
const time1 = DatePrototypeGetTime(val1);
const time2 = DatePrototypeGetTime(val2);
if (time1 !== time2) {
// eslint-disable-next-line no-self-compare
return time1 !== time1 && time2 !== time2;
// eslint-disable-next-line no-self-compare
if (time1 !== time2 && (time1 === time1 || time2 === time2)) {
return false;
}
} else if (isRegExp(val1)) {
if (!isRegExp(val2) || !areSimilarRegExps(val1, val2)) {
Expand Down Expand Up @@ -400,8 +400,10 @@ function objectComparisonStart(val1, val2, mode, memos) {
return false;
}
} else if (isCryptoKey(val1)) {
const { kKeyObject } = require('internal/crypto/util');
const { kExtractable, kAlgorithm, kKeyUsages } = require('internal/crypto/keys');
if (kKeyObject === undefined) {
kKeyObject = require('internal/crypto/util').kKeyObject;
({ kExtractable, kAlgorithm, kKeyUsages } = require('internal/crypto/keys'));
}
if (!isCryptoKey(val2) ||
val1[kExtractable] !== val2[kExtractable] ||
!innerDeepEqual(val1[kAlgorithm], val2[kAlgorithm], mode, memos) ||
Expand All @@ -417,18 +419,11 @@ function objectComparisonStart(val1, val2, mode, memos) {
return keyCheck(val1, val2, mode, memos, kNoIterator);
}

function getEnumerables(val, keys) {
return ArrayPrototypeFilter(keys, (key) => hasEnumerable(val, key));
}

function partialSymbolEquiv(val1, val2, keys2) {
const symbolKeys = getOwnSymbols(val2);
if (symbolKeys.length !== 0) {
for (const key of symbolKeys) {
if (hasEnumerable(val2, key)) {
if (!hasEnumerable(val1, key)) {
return false;
}
ArrayPrototypePush(keys2, key);
}
}
Expand Down Expand Up @@ -460,32 +455,19 @@ function keyCheck(val1, val2, mode, memos, iterationType, keys2) {
} else if (keys2.length !== (keys1 = ObjectKeys(val1)).length) {
return false;
} else if (mode === kStrict || mode === kStrictWithoutPrototypes) {
const symbolKeysA = getOwnSymbols(val1);
if (symbolKeysA.length !== 0) {
let count = 0;
for (const key of symbolKeysA) {
if (hasEnumerable(val1, key)) {
if (!hasEnumerable(val2, key)) {
return false;
}
ArrayPrototypePush(keys2, key);
count++;
} else if (hasEnumerable(val2, key)) {
return false;
}
for (const key of getOwnSymbols(val1)) {
if (hasEnumerable(val1, key)) {
ArrayPrototypePush(keys1, key);
}
const symbolKeysB = getOwnSymbols(val2);
if (symbolKeysA.length !== symbolKeysB.length &&
getEnumerables(val2, symbolKeysB).length !== count) {
return false;
}
} else {
const symbolKeysB = getOwnSymbols(val2);
if (symbolKeysB.length !== 0 &&
getEnumerables(val2, symbolKeysB).length !== 0) {
return false;
}
for (const key of getOwnSymbols(val2)) {
if (hasEnumerable(val2, key)) {
ArrayPrototypePush(keys2, key);
}
}
if (keys1.length !== keys2.length) {
return false;
}
}
}

Expand Down Expand Up @@ -647,16 +629,14 @@ function partialObjectSetEquiv(array, a, b, mode, memo) {
}

function arrayHasEqualElement(array, val1, mode, memo, comparator, start, end) {
let matched = false;
for (let i = end - 1; i >= start; i--) {
if (comparator(val1, array[i], mode, memo)) {
// Remove the matching element to make sure we do not check that again.
array.splice(i, 1);
matched = true;
break;
// Move the matching element to make sure we do not check that again.
array[i] = array[end];
return true;
}
}
return matched;
return false;
}

function setObjectEquiv(array, a, b, mode, memo) {
Expand Down Expand Up @@ -798,18 +778,16 @@ function partialObjectMapEquiv(array, a, b, mode, memo) {
}

function arrayHasEqualMapElement(array, key1, item1, b, mode, memo, comparator, start, end) {
let matched = false;
for (let i = end - 1; i >= start; i--) {
const key2 = array[i];
if (comparator(key1, key2, mode, memo) &&
innerDeepEqual(item1, b.get(key2), mode, memo)) {
// Remove the matching element to make sure we do not check that again.
array.splice(i, 1);
matched = true;
break;
// Move the matching element to make sure we do not check that again.
array[i] = array[end];
return true;
}
}
return matched;
return false;
}

function mapObjectEquiv(array, a, b, mode, memo) {
Expand Down Expand Up @@ -898,17 +876,21 @@ function mapEquiv(a, b, mode, memo) {
}

function partialSparseArrayEquiv(a, b, mode, memos, startA, startB) {
let aPos = 0;
const keysA = ObjectKeys(a).slice(startA);
const keysB = ObjectKeys(b).slice(startB);
if (keysA.length < keysB.length) {
let aPos = startA;
const keysA = ObjectKeys(a);
const keysB = ObjectKeys(b);
const keysBLength = keysB.length;
const keysALength = keysA.length;
const lenA = keysALength - startA;
const lenB = keysBLength - startB;
if (lenA < lenB) {
return false;
}
for (let i = 0; i < keysB.length; i++) {
const keyB = keysB[i];
for (let i = 0; i < lenB; i++) {
const keyB = keysB[startB + i];
while (!innerDeepEqual(a[keysA[aPos]], b[keyB], mode, memos)) {
aPos++;
if (aPos > keysA.length - keysB.length + i) {
if (aPos > keysALength - lenB + i) {
return false;
}
}
Expand Down Expand Up @@ -979,8 +961,11 @@ function objEquiv(a, b, mode, keys1, keys2, memos, iterationType) {
// property in V8 13.0 compared to calling Object.propertyIsEnumerable()
// and accessing the property regularly.
const descriptor = ObjectGetOwnPropertyDescriptor(a, key);
if (!descriptor?.enumerable ||
!innerDeepEqual(descriptor.value !== undefined ? descriptor.value : a[key], b[key], mode, memos)) {
if (descriptor === undefined || descriptor.enumerable !== true) {
return false;
}
const value = descriptor.writable !== undefined ? descriptor.value : a[key];
if (!innerDeepEqual(value, b[key], mode, memos)) {
return false;
}
}
Expand Down Expand Up @@ -1029,10 +1014,7 @@ module.exports = {
return detectCycles(val1, val2, kLoose);
},
isDeepStrictEqual(val1, val2, skipPrototype) {
if (skipPrototype) {
return detectCycles(val1, val2, kStrictWithoutPrototypes);
}
return detectCycles(val1, val2, kStrict);
return detectCycles(val1, val2, skipPrototype ? kStrictWithoutPrototypes : kStrict);
},
isPartialStrictEqual(val1, val2) {
return detectCycles(val1, val2, kPartial);
Expand Down
13 changes: 12 additions & 1 deletion test/parallel/test-assert-deep.js
Original file line number Diff line number Diff line change
Expand Up @@ -752,7 +752,18 @@ test('Additional tests', () => {

assertNotDeepOrStrict(new Date(), new Date(2000, 3, 14));

assertDeepAndStrictEqual(new Date('foo'), new Date('bar'));
{
// Invalid dates deep comparison.
const date1 = new Date('foo');
const date2 = new Date('bar');
date1.foo = true;
date2.foo = true;
assertDeepAndStrictEqual(date1, date2);

date1.bar = false;
date2.bar = true;
assertNotDeepOrStrict(date1, date2);
}

assertDeepAndStrictEqual(/a/, /a/);
assertDeepAndStrictEqual(/a/g, /a/g);
Expand Down
Loading
Loading