summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/api/assert.md2
-rw-r--r--doc/api/util.md16
-rw-r--r--lib/assert.js509
-rw-r--r--lib/internal/util/comparisons.js516
-rw-r--r--lib/util.js5
-rw-r--r--node.gyp1
-rw-r--r--test/parallel/test-util-isDeepStrictEqual.js483
7 files changed, 1028 insertions, 504 deletions
diff --git a/doc/api/assert.md b/doc/api/assert.md
index 6179caa27f..4da3e15125 100644
--- a/doc/api/assert.md
+++ b/doc/api/assert.md
@@ -134,7 +134,7 @@ changes:
* `expected` {any}
* `message` {any}
-Similar to `assert.deepEqual()` with the following exceptions:
+Identical to [`assert.deepEqual()`][] with the following exceptions:
1. Primitive values besides `NaN` are compared using the [Strict Equality
Comparison][] ( `===` ). Set and Map values, Map keys and `NaN` are compared
diff --git a/doc/api/util.md b/doc/api/util.md
index 6619ee2ad6..b13d02a8c0 100644
--- a/doc/api/util.md
+++ b/doc/api/util.md
@@ -455,6 +455,21 @@ util.inspect.defaultOptions.maxArrayLength = null;
console.log(arr); // logs the full array
```
+## util.isDeepStrictEqual(val1, val2)
+<!-- YAML
+added: REPLACEME
+-->
+
+* `val1` {any}
+* `val2` {any}
+* Returns: {string}
+
+Returns `true` if there is deep strict equality between `val` and `val2`.
+Otherwise, returns `false`.
+
+See [`assert.deepStrictEqual()`][] for more information about deep strict
+equality.
+
## util.promisify(original)
<!-- YAML
added: v8.0.0
@@ -1187,6 +1202,7 @@ Deprecated predecessor of `console.log`.
[`Buffer.isBuffer()`]: buffer.html#buffer_class_method_buffer_isbuffer_obj
[`Error`]: errors.html#errors_class_error
[`Object.assign()`]: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
+[`assert.deepStrictEqual()`]: assert.html#assert_assert_deepstrictequal_actual_expected_message
[`console.error()`]: console.html#console_console_error_data_args
[`console.log()`]: console.html#console_console_log_data_args
[`util.inspect()`]: #util_util_inspect_object_options
diff --git a/lib/assert.js b/lib/assert.js
index a29408f47e..9c7260a1a8 100644
--- a/lib/assert.js
+++ b/lib/assert.js
@@ -20,12 +20,9 @@
'use strict';
-const { compare } = process.binding('buffer');
-const { isSet, isMap, isDate, isRegExp } = process.binding('util');
-const { objectToString } = require('internal/util');
-const { isArrayBufferView } = require('internal/util/types');
+const { isDeepEqual, isDeepStrictEqual } =
+ require('internal/util/comparisons');
const errors = require('internal/errors');
-const { propertyIsEnumerable } = Object.prototype;
// The assert module provides functions that throw
// AssertionError's when particular conditions are not met. The
@@ -100,522 +97,28 @@ assert.notEqual = function notEqual(actual, expected, message) {
// The equivalence assertion tests a deep equality relation.
assert.deepEqual = function deepEqual(actual, expected, message) {
- if (!innerDeepEqual(actual, expected, false)) {
+ if (!isDeepEqual(actual, expected)) {
innerFail(actual, expected, message, 'deepEqual', deepEqual);
}
};
/* eslint-enable */
assert.deepStrictEqual = function deepStrictEqual(actual, expected, message) {
- if (!innerDeepEqual(actual, expected, true)) {
+ if (!isDeepStrictEqual(actual, expected)) {
innerFail(actual, expected, message, 'deepStrictEqual', deepStrictEqual);
}
};
-// Check if they have the same source and flags
-function areSimilarRegExps(a, b) {
- return a.source === b.source && a.flags === b.flags;
-}
-
-// For small buffers it's faster to compare the buffer in a loop. The c++
-// barrier including the Uint8Array operation takes the advantage of the faster
-// binary compare otherwise. The break even point was at about 300 characters.
-function areSimilarTypedArrays(a, b, max) {
- const len = a.byteLength;
- if (len !== b.byteLength) {
- return false;
- }
- if (len < max) {
- for (var offset = 0; offset < len; offset++) {
- if (a[offset] !== b[offset]) {
- return false;
- }
- }
- return true;
- }
- return compare(new Uint8Array(a.buffer, a.byteOffset, len),
- new Uint8Array(b.buffer, b.byteOffset, b.byteLength)) === 0;
-}
-
-function isFloatTypedArrayTag(tag) {
- return tag === '[object Float32Array]' || tag === '[object Float64Array]';
-}
-
-function isArguments(tag) {
- return tag === '[object Arguments]';
-}
-
-function isObjectOrArrayTag(tag) {
- return tag === '[object Array]' || tag === '[object Object]';
-}
-
-// Notes: Type tags are historical [[Class]] properties that can be set by
-// FunctionTemplate::SetClassName() in C++ or Symbol.toStringTag in JS
-// and retrieved using Object.prototype.toString.call(obj) in JS
-// See https://tc39.github.io/ecma262/#sec-object.prototype.tostring
-// for a list of tags pre-defined in the spec.
-// There are some unspecified tags in the wild too (e.g. typed array tags).
-// Since tags can be altered, they only serve fast failures
-//
-// Typed arrays and buffers are checked by comparing the content in their
-// underlying ArrayBuffer. This optimization requires that it's
-// reasonable to interpret their underlying memory in the same way,
-// which is checked by comparing their type tags.
-// (e.g. a Uint8Array and a Uint16Array with the same memory content
-// could still be different because they will be interpreted differently).
-//
-// For strict comparison, objects should have
-// a) The same built-in type tags
-// b) The same prototypes.
-function strictDeepEqual(actual, expected, memos) {
- if (typeof actual !== 'object') {
- return typeof actual === 'number' && Number.isNaN(actual) &&
- Number.isNaN(expected);
- }
- if (typeof expected !== 'object' || actual === null || expected === null) {
- return false;
- }
- const actualTag = objectToString(actual);
- const expectedTag = objectToString(expected);
-
- if (actualTag !== expectedTag) {
- return false;
- }
- if (Object.getPrototypeOf(actual) !== Object.getPrototypeOf(expected)) {
- return false;
- }
- if (actualTag === '[object Array]') {
- // Check for sparse arrays and general fast path
- if (actual.length !== expected.length)
- return false;
- // Skip testing the part below and continue with the keyCheck.
- return keyCheck(actual, expected, true, memos);
- }
- if (actualTag === '[object Object]') {
- // Skip testing the part below and continue with the keyCheck.
- return keyCheck(actual, expected, true, memos);
- }
- if (isDate(actual)) {
- if (actual.getTime() !== expected.getTime()) {
- return false;
- }
- } else if (isRegExp(actual)) {
- if (!areSimilarRegExps(actual, expected)) {
- return false;
- }
- } else if (actualTag === '[object Error]') {
- // Do not compare the stack as it might differ even though the error itself
- // is otherwise identical. The non-enumerable name should be identical as
- // the prototype is also identical. Otherwise this is caught later on.
- if (actual.message !== expected.message) {
- return false;
- }
- } else if (isArrayBufferView(actual)) {
- if (!areSimilarTypedArrays(actual, expected,
- isFloatTypedArrayTag(actualTag) ? 0 : 300)) {
- return false;
- }
- // Buffer.compare returns true, so actual.length === expected.length
- // if they both only contain numeric keys, we don't need to exam further
- return keyCheck(actual, expected, true, memos, actual.length,
- expected.length);
- } else if (typeof actual.valueOf === 'function') {
- const actualValue = actual.valueOf();
- // Note: Boxed string keys are going to be compared again by Object.keys
- if (actualValue !== actual) {
- if (!innerDeepEqual(actualValue, expected.valueOf(), true))
- return false;
- // Fast path for boxed primitives
- var lengthActual = 0;
- var lengthExpected = 0;
- if (typeof actualValue === 'string') {
- lengthActual = actual.length;
- lengthExpected = expected.length;
- }
- return keyCheck(actual, expected, true, memos, lengthActual,
- lengthExpected);
- }
- }
- return keyCheck(actual, expected, true, memos);
-}
-
-function looseDeepEqual(actual, expected, memos) {
- if (actual === null || typeof actual !== 'object') {
- if (expected === null || typeof expected !== 'object') {
- // eslint-disable-next-line eqeqeq
- return actual == expected;
- }
- return false;
- }
- if (expected === null || typeof expected !== 'object') {
- return false;
- }
- if (isDate(actual) && isDate(expected)) {
- return actual.getTime() === expected.getTime();
- }
- if (isRegExp(actual) && isRegExp(expected)) {
- return areSimilarRegExps(actual, expected);
- }
- if (actual instanceof Error && expected instanceof Error) {
- if (actual.message !== expected.message || actual.name !== expected.name)
- return false;
- }
- const actualTag = objectToString(actual);
- const expectedTag = objectToString(expected);
- if (actualTag === expectedTag) {
- if (!isObjectOrArrayTag(actualTag) && isArrayBufferView(actual)) {
- return areSimilarTypedArrays(actual, expected,
- isFloatTypedArrayTag(actualTag) ?
- Infinity : 300);
- }
- // Ensure reflexivity of deepEqual with `arguments` objects.
- // See https://github.com/nodejs/node-v0.x-archive/pull/7178
- } else if (isArguments(actualTag) || isArguments(expectedTag)) {
- return false;
- }
- return keyCheck(actual, expected, false, memos);
-}
-
-function keyCheck(actual, expected, strict, memos, lengthA, lengthB) {
- // For all remaining Object pairs, including Array, objects and Maps,
- // equivalence is determined by having:
- // a) The same number of owned enumerable properties
- // b) The same set of keys/indexes (although not necessarily the same order)
- // c) Equivalent values for every corresponding key/index
- // d) For Sets and Maps, equal contents
- // Note: this accounts for both named and indexed properties on Arrays.
- var aKeys = Object.keys(actual);
- var bKeys = Object.keys(expected);
- var i;
-
- // The pair must have the same number of owned properties.
- if (aKeys.length !== bKeys.length)
- return false;
-
- if (strict) {
- var symbolKeysA = Object.getOwnPropertySymbols(actual);
- var symbolKeysB = Object.getOwnPropertySymbols(expected);
- if (symbolKeysA.length !== 0) {
- symbolKeysA = symbolKeysA.filter((k) =>
- propertyIsEnumerable.call(actual, k));
- symbolKeysB = symbolKeysB.filter((k) =>
- propertyIsEnumerable.call(expected, k));
- if (symbolKeysA.length !== symbolKeysB.length)
- return false;
- } else if (symbolKeysB.length !== 0 && symbolKeysB.filter((k) =>
- propertyIsEnumerable.call(expected, k)).length !== 0) {
- return false;
- }
- if (lengthA !== undefined) {
- if (aKeys.length !== lengthA || bKeys.length !== lengthB)
- return false;
- if (symbolKeysA.length === 0)
- return true;
- aKeys = [];
- bKeys = [];
- }
- if (symbolKeysA.length !== 0) {
- aKeys.push(...symbolKeysA);
- bKeys.push(...symbolKeysB);
- }
- }
-
- // Cheap key test:
- const keys = {};
- for (i = 0; i < aKeys.length; i++) {
- keys[aKeys[i]] = true;
- }
- for (i = 0; i < aKeys.length; i++) {
- if (keys[bKeys[i]] === undefined)
- return false;
- }
-
- // Use memos to handle cycles.
- if (memos === undefined) {
- memos = {
- actual: new Map(),
- expected: new Map(),
- position: 0
- };
- } else {
- // We prevent up to two map.has(x) calls by directly retrieving the value
- // and checking for undefined. The map can only contain numbers, so it is
- // safe to check for undefined only.
- const expectedMemoA = memos.actual.get(actual);
- if (expectedMemoA !== undefined) {
- const expectedMemoB = memos.expected.get(expected);
- if (expectedMemoB !== undefined) {
- return expectedMemoA === expectedMemoB;
- }
- }
- memos.position++;
- }
-
- memos.actual.set(actual, memos.position);
- memos.expected.set(expected, memos.position);
-
- const areEq = objEquiv(actual, expected, strict, aKeys, memos);
-
- memos.actual.delete(actual);
- memos.expected.delete(expected);
-
- return areEq;
-}
-
-function innerDeepEqual(actual, expected, strict, memos) {
- // All identical values are equivalent, as determined by ===.
- if (actual === expected) {
- if (actual !== 0)
- return true;
- return strict ? Object.is(actual, expected) : true;
- }
-
- // Check more closely if actual and expected are equal.
- if (strict === true)
- return strictDeepEqual(actual, expected, memos);
-
- return looseDeepEqual(actual, expected, memos);
-}
-
-function setHasEqualElement(set, val1, strict, memo) {
- // Go looking.
- for (const val2 of set) {
- if (innerDeepEqual(val1, val2, strict, memo)) {
- // Remove the matching element to make sure we do not check that again.
- set.delete(val2);
- return true;
- }
- }
-
- return false;
-}
-
-// Note: we actually run this multiple times for each loose key!
-// This is done to prevent slowing down the average case.
-function setHasLoosePrim(a, b, val) {
- const altValues = findLooseMatchingPrimitives(val);
- if (altValues === undefined)
- return false;
-
- var matches = 1;
- for (var i = 0; i < altValues.length; i++) {
- if (b.has(altValues[i])) {
- matches--;
- }
- if (a.has(altValues[i])) {
- matches++;
- }
- }
- return matches === 0;
-}
-
-function setEquiv(a, b, strict, memo) {
- // This code currently returns false for this pair of sets:
- // assert.deepEqual(new Set(['1', 1]), new Set([1]))
- //
- // In theory, all the items in the first set have a corresponding == value in
- // the second set, but the sets have different sizes. Its a silly case,
- // and more evidence that deepStrictEqual should always be preferred over
- // deepEqual.
- if (a.size !== b.size)
- return false;
-
- // This is a lazily initiated Set of entries which have to be compared
- // pairwise.
- var set = null;
- for (const val of a) {
- // Note: Checking for the objects first improves the performance for object
- // heavy sets but it is a minor slow down for primitives. As they are fast
- // to check this improves the worst case scenario instead.
- if (typeof val === 'object' && val !== null) {
- if (set === null) {
- set = new Set();
- }
- // If the specified value doesn't exist in the second set its an not null
- // object (or non strict only: a not matching primitive) we'll need to go
- // hunting for something thats deep-(strict-)equal to it. To make this
- // O(n log n) complexity we have to copy these values in a new set first.
- set.add(val);
- } else if (!b.has(val) && (strict || !setHasLoosePrim(a, b, val))) {
- return false;
- }
- }
-
- if (set !== null) {
- for (const val of b) {
- // We have to check if a primitive value is already
- // matching and only if it's not, go hunting for it.
- if (typeof val === 'object' && val !== null) {
- if (!setHasEqualElement(set, val, strict, memo))
- return false;
- } else if (!a.has(val) && (strict || !setHasLoosePrim(b, a, val))) {
- return false;
- }
- }
- }
-
- return true;
-}
-
-function findLooseMatchingPrimitives(prim) {
- var values, number;
- switch (typeof prim) {
- case 'number':
- values = ['' + prim];
- if (prim === 1 || prim === 0)
- values.push(Boolean(prim));
- return values;
- case 'string':
- number = +prim;
- if ('' + number === prim) {
- values = [number];
- if (number === 1 || number === 0)
- values.push(Boolean(number));
- }
- return values;
- case 'undefined':
- return [null];
- case 'object': // Only pass in null as object!
- return [undefined];
- case 'boolean':
- number = +prim;
- return [number, '' + number];
- }
-}
-
-// This is a ugly but relatively fast way to determine if a loose equal entry
-// actually has a correspondent matching entry. Otherwise checking for such
-// values would be way more expensive (O(n^2)).
-// Note: we actually run this multiple times for each loose key!
-// This is done to prevent slowing down the average case.
-function mapHasLoosePrim(a, b, key1, memo, item1, item2) {
- const altKeys = findLooseMatchingPrimitives(key1);
- if (altKeys === undefined)
- return false;
-
- const setA = new Set();
- const setB = new Set();
-
- var keyCount = 1;
-
- setA.add(item1);
- if (b.has(key1)) {
- keyCount--;
- setB.add(item2);
- }
-
- for (var i = 0; i < altKeys.length; i++) {
- const key2 = altKeys[i];
- if (a.has(key2)) {
- keyCount++;
- setA.add(a.get(key2));
- }
- if (b.has(key2)) {
- keyCount--;
- setB.add(b.get(key2));
- }
- }
- if (keyCount !== 0 || setA.size !== setB.size)
- return false;
-
- for (const val of setA) {
- if (typeof val === 'object' && val !== null) {
- if (!setHasEqualElement(setB, val, false, memo))
- return false;
- } else if (!setB.has(val) && !setHasLoosePrim(setA, setB, val)) {
- return false;
- }
- }
- return true;
-}
-
-function mapHasEqualEntry(set, map, key1, item1, strict, memo) {
- // To be able to handle cases like:
- // Map([[{}, 'a'], [{}, 'b']]) vs Map([[{}, 'b'], [{}, 'a']])
- // ... we need to consider *all* matching keys, not just the first we find.
- for (const key2 of set) {
- if (innerDeepEqual(key1, key2, strict, memo) &&
- innerDeepEqual(item1, map.get(key2), strict, memo)) {
- set.delete(key2);
- return true;
- }
- }
-
- return false;
-}
-
-function mapEquiv(a, b, strict, memo) {
- if (a.size !== b.size)
- return false;
-
- var set = null;
-
- for (const [key, item1] of a) {
- if (typeof key === 'object' && key !== null) {
- if (set === null) {
- set = new Set();
- }
- set.add(key);
- } else {
- // By directly retrieving the value we prevent another b.has(key) check in
- // almost all possible cases.
- const item2 = b.get(key);
- if ((item2 === undefined && !b.has(key) ||
- !innerDeepEqual(item1, item2, strict, memo)) &&
- (strict || !mapHasLoosePrim(a, b, key, memo, item1, item2))) {
- return false;
- }
- }
- }
-
- if (set !== null) {
- for (const [key, item] of b) {
- if (typeof key === 'object' && key !== null) {
- if (!mapHasEqualEntry(set, a, key, item, strict, memo))
- return false;
- } else if (!a.has(key) &&
- (strict || !mapHasLoosePrim(b, a, key, memo, item))) {
- return false;
- }
- }
- }
-
- return true;
-}
-
-function objEquiv(a, b, strict, keys, memos) {
- // Sets and maps don't have their entries accessible via normal object
- // properties.
- if (isSet(a)) {
- if (!isSet(b) || !setEquiv(a, b, strict, memos))
- return false;
- } else if (isMap(a)) {
- if (!isMap(b) || !mapEquiv(a, b, strict, memos))
- return false;
- } else if (isSet(b) || isMap(b)) {
- return false;
- }
-
- // The pair must have equivalent values for every corresponding key.
- // Possibly expensive deep test:
- for (var i = 0; i < keys.length; i++) {
- const key = keys[i];
- if (!innerDeepEqual(a[key], b[key], strict, memos))
- return false;
- }
- return true;
-}
-
// The non-equivalence assertion tests for any deep inequality.
assert.notDeepEqual = function notDeepEqual(actual, expected, message) {
- if (innerDeepEqual(actual, expected, false)) {
+ if (isDeepEqual(actual, expected)) {
innerFail(actual, expected, message, 'notDeepEqual', notDeepEqual);
}
};
assert.notDeepStrictEqual = notDeepStrictEqual;
function notDeepStrictEqual(actual, expected, message) {
- if (innerDeepEqual(actual, expected, true)) {
+ if (isDeepStrictEqual(actual, expected)) {
innerFail(actual, expected, message, 'notDeepStrictEqual',
notDeepStrictEqual);
}
diff --git a/lib/internal/util/comparisons.js b/lib/internal/util/comparisons.js
new file mode 100644
index 0000000000..1145bc7d99
--- /dev/null
+++ b/lib/internal/util/comparisons.js
@@ -0,0 +1,516 @@
+'use strict';
+
+const { compare } = process.binding('buffer');
+const { isArrayBufferView } = require('internal/util/types');
+const { isDate, isMap, isRegExp, isSet } = process.binding('util');
+
+function objectToString(o) {
+ return Object.prototype.toString.call(o);
+}
+
+// Check if they have the same source and flags
+function areSimilarRegExps(a, b) {
+ return a.source === b.source && a.flags === b.flags;
+}
+
+// For small buffers it's faster to compare the buffer in a loop. The c++
+// barrier including the Uint8Array operation takes the advantage of the faster
+// binary compare otherwise. The break even point was at about 300 characters.
+function areSimilarTypedArrays(a, b, max) {
+ const len = a.byteLength;
+ if (len !== b.byteLength) {
+ return false;
+ }
+ if (len < max) {
+ for (var offset = 0; offset < len; offset++) {
+ if (a[offset] !== b[offset]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return compare(new Uint8Array(a.buffer, a.byteOffset, len),
+ new Uint8Array(b.buffer, b.byteOffset, b.byteLength)) === 0;
+}
+
+function isFloatTypedArrayTag(tag) {
+ return tag === '[object Float32Array]' || tag === '[object Float64Array]';
+}
+
+function isArguments(tag) {
+ return tag === '[object Arguments]';
+}
+
+function isObjectOrArrayTag(tag) {
+ return tag === '[object Array]' || tag === '[object Object]';
+}
+
+// Notes: Type tags are historical [[Class]] properties that can be set by
+// FunctionTemplate::SetClassName() in C++ or Symbol.toStringTag in JS
+// and retrieved using Object.prototype.toString.call(obj) in JS
+// See https://tc39.github.io/ecma262/#sec-object.prototype.tostring
+// for a list of tags pre-defined in the spec.
+// There are some unspecified tags in the wild too (e.g. typed array tags).
+// Since tags can be altered, they only serve fast failures
+//
+// Typed arrays and buffers are checked by comparing the content in their
+// underlying ArrayBuffer. This optimization requires that it's
+// reasonable to interpret their underlying memory in the same way,
+// which is checked by comparing their type tags.
+// (e.g. a Uint8Array and a Uint16Array with the same memory content
+// could still be different because they will be interpreted differently).
+//
+// For strict comparison, objects should have
+// a) The same built-in type tags
+// b) The same prototypes.
+function strictDeepEqual(val1, val2, memos) {
+ if (typeof val1 !== 'object') {
+ return typeof val1 === 'number' && Number.isNaN(val1) &&
+ Number.isNaN(val2);
+ }
+ if (typeof val2 !== 'object' || val1 === null || val2 === null) {
+ return false;
+ }
+ const val1Tag = objectToString(val1);
+ const val2Tag = objectToString(val2);
+
+ if (val1Tag !== val2Tag) {
+ return false;
+ }
+ if (Object.getPrototypeOf(val1) !== Object.getPrototypeOf(val2)) {
+ return false;
+ }
+ if (val1Tag === '[object Array]') {
+ // Check for sparse arrays and general fast path
+ if (val1.length !== val2.length)
+ return false;
+ // Skip testing the part below and continue with the keyCheck.
+ return keyCheck(val1, val2, true, memos);
+ }
+ if (val1Tag === '[object Object]') {
+ // Skip testing the part below and continue with the keyCheck.
+ return keyCheck(val1, val2, true, memos);
+ }
+ if (isDate(val1)) {
+ if (val1.getTime() !== val2.getTime()) {
+ return false;
+ }
+ } else if (isRegExp(val1)) {
+ if (!areSimilarRegExps(val1, val2)) {
+ return false;
+ }
+ } else if (val1Tag === '[object Error]') {
+ // Do not compare the stack as it might differ even though the error itself
+ // is otherwise identical. The non-enumerable name should be identical as
+ // the prototype is also identical. Otherwise this is caught later on.
+ if (val1.message !== val2.message) {
+ return false;
+ }
+ } else if (isArrayBufferView(val1)) {
+ if (!areSimilarTypedArrays(val1, val2,
+ isFloatTypedArrayTag(val1Tag) ? 0 : 300)) {
+ return false;
+ }
+ // Buffer.compare returns true, so val1.length === val2.length
+ // if they both only contain numeric keys, we don't need to exam further
+ return keyCheck(val1, val2, true, memos, val1.length,
+ val2.length);
+ } else if (typeof val1.valueOf === 'function') {
+ const val1Value = val1.valueOf();
+ // Note: Boxed string keys are going to be compared again by Object.keys
+ if (val1Value !== val1) {
+ if (!innerDeepEqual(val1Value, val2.valueOf(), true))
+ return false;
+ // Fast path for boxed primitives
+ var lengthval1 = 0;
+ var lengthval2 = 0;
+ if (typeof val1Value === 'string') {
+ lengthval1 = val1.length;
+ lengthval2 = val2.length;
+ }
+ return keyCheck(val1, val2, true, memos, lengthval1,
+ lengthval2);
+ }
+ }
+ return keyCheck(val1, val2, true, memos);
+}
+
+function looseDeepEqual(val1, val2, memos) {
+ if (val1 === null || typeof val1 !== 'object') {
+ if (val2 === null || typeof val2 !== 'object') {
+ // eslint-disable-next-line eqeqeq
+ return val1 == val2;
+ }
+ return false;
+ }
+ if (val2 === null || typeof val2 !== 'object') {
+ return false;
+ }
+ if (isDate(val1) && isDate(val2)) {
+ return val1.getTime() === val2.getTime();
+ }
+ if (isRegExp(val1) && isRegExp(val2)) {
+ return areSimilarRegExps(val1, val2);
+ }
+ if (val1 instanceof Error && val2 instanceof Error) {
+ if (val1.message !== val2.message || val1.name !== val2.name)
+ return false;
+ }
+ const val1Tag = objectToString(val1);
+ const val2Tag = objectToString(val2);
+ if (val1Tag === val2Tag) {
+ if (!isObjectOrArrayTag(val1Tag) && isArrayBufferView(val1)) {
+ return areSimilarTypedArrays(val1, val2,
+ isFloatTypedArrayTag(val1Tag) ?
+ Infinity : 300);
+ }
+ // Ensure reflexivity of deepEqual with `arguments` objects.
+ // See https://github.com/nodejs/node-v0.x-archive/pull/7178
+ } else if (isArguments(val1Tag) || isArguments(val2Tag)) {
+ return false;
+ }
+ return keyCheck(val1, val2, false, memos);
+}
+
+function keyCheck(val1, val2, strict, memos, lengthA, lengthB) {
+ // For all remaining Object pairs, including Array, objects and Maps,
+ // equivalence is determined by having:
+ // a) The same number of owned enumerable properties
+ // b) The same set of keys/indexes (although not necessarily the same order)
+ // c) Equivalent values for every corresponding key/index
+ // d) For Sets and Maps, equal contents
+ // Note: this accounts for both named and indexed properties on Arrays.
+ var aKeys = Object.keys(val1);
+ var bKeys = Object.keys(val2);
+ var i;
+
+ // The pair must have the same number of owned properties.
+ if (aKeys.length !== bKeys.length)
+ return false;
+
+ if (strict) {
+ var symbolKeysA = Object.getOwnPropertySymbols(val1);
+ var symbolKeysB = Object.getOwnPropertySymbols(val2);
+ if (symbolKeysA.length !== 0) {
+ symbolKeysA = symbolKeysA.filter((k) =>
+ propertyIsEnumerable.call(val1, k));
+ symbolKeysB = symbolKeysB.filter((k) =>
+ propertyIsEnumerable.call(val2, k));
+ if (symbolKeysA.length !== symbolKeysB.length)
+ return false;
+ } else if (symbolKeysB.length !== 0 && symbolKeysB.filter((k) =>
+ propertyIsEnumerable.call(val2, k)).length !== 0) {
+ return false;
+ }
+ if (lengthA !== undefined) {
+ if (aKeys.length !== lengthA || bKeys.length !== lengthB)
+ return false;
+ if (symbolKeysA.length === 0)
+ return true;
+ aKeys = [];
+ bKeys = [];
+ }
+ if (symbolKeysA.length !== 0) {
+ aKeys.push(...symbolKeysA);
+ bKeys.push(...symbolKeysB);
+ }
+ }
+
+ // Cheap key test:
+ const keys = {};
+ for (i = 0; i < aKeys.length; i++) {
+ keys[aKeys[i]] = true;
+ }
+ for (i = 0; i < aKeys.length; i++) {
+ if (keys[bKeys[i]] === undefined)
+ return false;
+ }
+
+ // Use memos to handle cycles.
+ if (memos === undefined) {
+ memos = {
+ val1: new Map(),
+ val2: new Map(),
+ position: 0
+ };
+ } else {
+ // We prevent up to two map.has(x) calls by directly retrieving the value
+ // and checking for undefined. The map can only contain numbers, so it is
+ // safe to check for undefined only.
+ 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);
+
+ memos.val1.delete(val1);
+ memos.val2.delete(val2);
+
+ return areEq;
+}
+
+function innerDeepEqual(val1, val2, strict, memos) {
+ // All identical values are equivalent, as determined by ===.
+ if (val1 === val2) {
+ if (val1 !== 0)
+ return true;
+ return strict ? Object.is(val1, val2) : true;
+ }
+
+ // Check more closely if val1 and val2 are equal.
+ if (strict === true)
+ return strictDeepEqual(val1, val2, memos);
+
+ return looseDeepEqual(val1, val2, memos);
+}
+
+function setHasEqualElement(set, val1, strict, memo) {
+ // Go looking.
+ for (const val2 of set) {
+ if (innerDeepEqual(val1, val2, strict, memo)) {
+ // Remove the matching element to make sure we do not check that again.
+ set.delete(val2);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Note: we val1ly run this multiple times for each loose key!
+// This is done to prevent slowing down the average case.
+function setHasLoosePrim(a, b, val) {
+ const altValues = findLooseMatchingPrimitives(val);
+ if (altValues === undefined)
+ return false;
+
+ var matches = 1;
+ for (var i = 0; i < altValues.length; i++) {
+ if (b.has(altValues[i])) {
+ matches--;
+ }
+ if (a.has(altValues[i])) {
+ matches++;
+ }
+ }
+ return matches === 0;
+}
+
+function setEquiv(a, b, strict, memo) {
+ // This code currently returns false for this pair of sets:
+ // assert.deepEqual(new Set(['1', 1]), new Set([1]))
+ //
+ // In theory, all the items in the first set have a corresponding == value in
+ // the second set, but the sets have different sizes. Its a silly case,
+ // and more evidence that deepStrictEqual should always be preferred over
+ // deepEqual.
+ if (a.size !== b.size)
+ return false;
+
+ // This is a lazily initiated Set of entries which have to be compared
+ // pairwise.
+ var set = null;
+ for (const val of a) {
+ // Note: Checking for the objects first improves the performance for object
+ // heavy sets but it is a minor slow down for primitives. As they are fast
+ // to check this improves the worst case scenario instead.
+ if (typeof val === 'object' && val !== null) {
+ if (set === null) {
+ set = new Set();
+ }
+ // If the specified value doesn't exist in the second set its an not null
+ // object (or non strict only: a not matching primitive) we'll need to go
+ // hunting for something thats deep-(strict-)equal to it. To make this
+ // O(n log n) complexity we have to copy these values in a new set first.
+ set.add(val);
+ } else if (!b.has(val) && (strict || !setHasLoosePrim(a, b, val))) {
+ return false;
+ }
+ }
+
+ if (set !== null) {
+ for (const val of b) {
+ // We have to check if a primitive value is already
+ // matching and only if it's not, go hunting for it.
+ if (typeof val === 'object' && val !== null) {
+ if (!setHasEqualElement(set, val, strict, memo))
+ return false;
+ } else if (!a.has(val) && (strict || !setHasLoosePrim(b, a, val))) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+function findLooseMatchingPrimitives(prim) {
+ var values, number;
+ switch (typeof prim) {
+ case 'number':
+ values = ['' + prim];
+ if (prim === 1 || prim === 0)
+ values.push(Boolean(prim));
+ return values;
+ case 'string':
+ number = +prim;
+ if ('' + number === prim) {
+ values = [number];
+ if (number === 1 || number === 0)
+ values.push(Boolean(number));
+ }
+ return values;
+ case 'undefined':
+ return [null];
+ case 'object': // Only pass in null as object!
+ return [undefined];
+ case 'boolean':
+ number = +prim;
+ return [number, '' + number];
+ }
+}
+
+// This is a ugly but relatively fast way to determine if a loose equal entry
+// val1ly has a correspondent matching entry. Otherwise checking for such
+// values would be way more expensive (O(n^2)).
+// Note: we val1ly run this multiple times for each loose key!
+// This is done to prevent slowing down the average case.
+function mapHasLoosePrim(a, b, key1, memo, item1, item2) {
+ const altKeys = findLooseMatchingPrimitives(key1);
+ if (altKeys === undefined)
+ return false;
+
+ const setA = new Set();
+ const setB = new Set();
+
+ var keyCount = 1;
+
+ setA.add(item1);
+ if (b.has(key1)) {
+ keyCount--;
+ setB.add(item2);
+ }
+
+ for (var i = 0; i < altKeys.length; i++) {
+ const key2 = altKeys[i];
+ if (a.has(key2)) {
+ keyCount++;
+ setA.add(a.get(key2));
+ }
+ if (b.has(key2)) {
+ keyCount--;
+ setB.add(b.get(key2));
+ }
+ }
+ if (keyCount !== 0 || setA.size !== setB.size)
+ return false;
+
+ for (const val of setA) {
+ if (typeof val === 'object' && val !== null) {
+ if (!setHasEqualElement(setB, val, false, memo))
+ return false;
+ } else if (!setB.has(val) && !setHasLoosePrim(setA, setB, val)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+function mapHasEqualEntry(set, map, key1, item1, strict, memo) {
+ // To be able to handle cases like:
+ // Map([[{}, 'a'], [{}, 'b']]) vs Map([[{}, 'b'], [{}, 'a']])
+ // ... we need to consider *all* matching keys, not just the first we find.
+ for (const key2 of set) {
+ if (innerDeepEqual(key1, key2, strict, memo) &&
+ innerDeepEqual(item1, map.get(key2), strict, memo)) {
+ set.delete(key2);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+function mapEquiv(a, b, strict, memo) {
+ if (a.size !== b.size)
+ return false;
+
+ var set = null;
+
+ for (const [key, item1] of a) {
+ if (typeof key === 'object' && key !== null) {
+ if (set === null) {
+ set = new Set();
+ }
+ set.add(key);
+ } else {
+ // By directly retrieving the value we prevent another b.has(key) check in
+ // almost all possible cases.
+ const item2 = b.get(key);
+ if ((item2 === undefined && !b.has(key) ||
+ !innerDeepEqual(item1, item2, strict, memo)) &&
+ (strict || !mapHasLoosePrim(a, b, key, memo, item1, item2))) {
+ return false;
+ }
+ }
+ }
+
+ if (set !== null) {
+ for (const [key, item] of b) {
+ if (typeof key === 'object' && key !== null) {
+ if (!mapHasEqualEntry(set, a, key, item, strict, memo))
+ return false;
+ } else if (!a.has(key) &&
+ (strict || !mapHasLoosePrim(b, a, key, memo, item))) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+function objEquiv(a, b, strict, keys, memos) {
+ // Sets and maps don't have their entries accessible via normal object
+ // properties.
+ if (isSet(a)) {
+ if (!isSet(b) || !setEquiv(a, b, strict, memos))
+ return false;
+ } else if (isMap(a)) {
+ if (!isMap(b) || !mapEquiv(a, b, strict, memos))
+ return false;
+ } else if (isSet(b) || isMap(b)) {
+ return false;
+ }
+
+ // The pair must have equivalent values for every corresponding key.
+ // Possibly expensive deep test:
+ for (var i = 0; i < keys.length; i++) {
+ const key = keys[i];
+ if (!innerDeepEqual(a[key], b[key], strict, memos))
+ return false;
+ }
+ return true;
+}
+
+function isDeepEqual(val1, val2) {
+ return innerDeepEqual(val1, val2, false);
+}
+
+function isDeepStrictEqual(val1, val2) {
+ return innerDeepEqual(val1, val2, true);
+}
+
+module.exports = {
+ isDeepEqual,
+ isDeepStrictEqual
+};
diff --git a/lib/util.js b/lib/util.js
index b97b1ede0c..00da489b88 100644
--- a/lib/util.js
+++ b/lib/util.js
@@ -49,6 +49,10 @@ const {
} = require('internal/util/types');
const {
+ isDeepStrictEqual
+} = require('internal/util/comparisons');
+
+const {
customInspectSymbol,
deprecate,
getConstructorOf,
@@ -1118,6 +1122,7 @@ module.exports = exports = {
isArray: Array.isArray,
isBoolean,
isBuffer,
+ isDeepStrictEqual,
isNull,
isNullOrUndefined,
isNumber,
diff --git a/node.gyp b/node.gyp
index 4509c4c742..39ec90fa3d 100644
--- a/node.gyp
+++ b/node.gyp
@@ -123,6 +123,7 @@
'lib/internal/tls.js',
'lib/internal/url.js',
'lib/internal/util.js',
+ 'lib/internal/util/comparisons.js',
'lib/internal/util/types.js',
'lib/internal/http2/core.js',
'lib/internal/http2/compat.js',
diff --git a/test/parallel/test-util-isDeepStrictEqual.js b/test/parallel/test-util-isDeepStrictEqual.js
new file mode 100644
index 0000000000..356a9a7132
--- /dev/null
+++ b/test/parallel/test-util-isDeepStrictEqual.js
@@ -0,0 +1,483 @@
+'use strict';
+
+// Confirm functionality of `util.isDeepStrictEqual()`.
+
+require('../common');
+
+const assert = require('assert');
+const util = require('util');
+
+class MyDate extends Date {
+ constructor(...args) {
+ super(...args);
+ this[0] = '1';
+ }
+}
+
+class MyRegExp extends RegExp {
+ constructor(...args) {
+ super(...args);
+ this[0] = '1';
+ }
+}
+
+{
+ const arr = new Uint8Array([120, 121, 122, 10]);
+ const buf = Buffer.from(arr);
+ // They have different [[Prototype]]
+ assert.strictEqual(util.isDeepStrictEqual(arr, buf), false);
+
+ const buf2 = Buffer.from(arr);
+ buf2.prop = 1;
+
+ assert.strictEqual(util.isDeepStrictEqual(buf2, buf), false);
+
+ const arr2 = new Uint8Array([120, 121, 122, 10]);
+ arr2.prop = 5;
+ assert.strictEqual(util.isDeepStrictEqual(arr, arr2), false);
+}
+
+{
+ const date = new Date('2016');
+
+ const date2 = new MyDate('2016');
+
+ // deepStrictEqual checks own properties
+ assert.strictEqual(util.isDeepStrictEqual(date, date2), false);
+ assert.strictEqual(util.isDeepStrictEqual(date2, date), false);
+}
+
+{
+ const re1 = new RegExp('test');
+ const re2 = new MyRegExp('test');
+
+ // deepStrictEqual checks all properties
+ assert.strictEqual(util.isDeepStrictEqual(re1, re2), false);
+}
+
+{
+ // For these cases, deepStrictEqual should throw.
+ const similar = new Set([
+ { 0: '1' }, // Object
+ { 0: 1 }, // Object
+ new String('1'), // Object
+ ['1'], // Array
+ [1], // Array
+ new MyDate('2016'), // Date with this[0] = '1'
+ new MyRegExp('test'), // RegExp with this[0] = '1'
+ new Int8Array([1]), // Int8Array
+ new Uint8Array([1]), // Uint8Array
+ new Int16Array([1]), // Int16Array
+ new Uint16Array([1]), // Uint16Array
+ new Int32Array([1]), // Int32Array
+ new Uint32Array([1]), // Uint32Array
+ Buffer.from([1]), // Buffer
+ ]);
+
+ for (const a of similar) {
+ for (const b of similar) {
+ if (a !== b) {
+ assert.strictEqual(util.isDeepStrictEqual(a, b), false);
+ }
+ }
+ }
+}
+
+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);
+}
+
+// es6 Maps and Sets
+utilIsDeepStrict(new Set(), new Set());
+utilIsDeepStrict(new Map(), new Map());
+
+utilIsDeepStrict(new Set([1, 2, 3]), new Set([1, 2, 3]));
+notUtilIsDeepStrict(new Set([1, 2, 3]), new Set([1, 2, 3, 4]));
+notUtilIsDeepStrict(new Set([1, 2, 3, 4]), new Set([1, 2, 3]));
+utilIsDeepStrict(new Set(['1', '2', '3']), new Set(['1', '2', '3']));
+utilIsDeepStrict(new Set([[1, 2], [3, 4]]), new Set([[3, 4], [1, 2]]));
+
+{
+ const a = [ 1, 2 ];
+ const b = [ 3, 4 ];
+ const c = [ 1, 2 ];
+ const d = [ 3, 4 ];
+
+ utilIsDeepStrict(
+ { a: a, b: b, s: new Set([a, b]) },
+ { a: c, b: d, s: new Set([d, c]) }
+ );
+}
+
+utilIsDeepStrict(new Map([[1, 1], [2, 2]]), new Map([[1, 1], [2, 2]]));
+utilIsDeepStrict(new Map([[1, 1], [2, 2]]), new Map([[2, 2], [1, 1]]));
+notUtilIsDeepStrict(new Map([[1, 1], [2, 2]]), new Map([[1, 2], [2, 1]]));
+notUtilIsDeepStrict(
+ new Map([[[1], 1], [{}, 2]]),
+ new Map([[[1], 2], [{}, 1]])
+);
+
+notUtilIsDeepStrict(new Set([1]), [1]);
+notUtilIsDeepStrict(new Set(), []);
+notUtilIsDeepStrict(new Set(), {});
+
+notUtilIsDeepStrict(new Map([['a', 1]]), { a: 1 });
+notUtilIsDeepStrict(new Map(), []);
+notUtilIsDeepStrict(new Map(), {});
+
+notUtilIsDeepStrict(new Set(['1']), new Set([1]));
+
+notUtilIsDeepStrict(new Map([['1', 'a']]), new Map([[1, 'a']]));
+notUtilIsDeepStrict(new Map([['a', '1']]), new Map([['a', 1]]));
+notUtilIsDeepStrict(new Map([['a', '1']]), new Map([['a', 2]]));
+
+utilIsDeepStrict(new Set([{}]), new Set([{}]));
+
+// Ref: https://github.com/nodejs/node/issues/13347
+notUtilIsDeepStrict(
+ new Set([{ a: 1 }, { a: 1 }]),
+ new Set([{ a: 1 }, { a: 2 }])
+);
+notUtilIsDeepStrict(
+ new Set([{ a: 1 }, { a: 1 }, { a: 2 }]),
+ new Set([{ a: 1 }, { a: 2 }, { a: 2 }])
+);
+notUtilIsDeepStrict(
+ new Map([[{ x: 1 }, 5], [{ x: 1 }, 5]]),
+ new Map([[{ x: 1 }, 5], [{ x: 2 }, 5]])
+);
+
+notUtilIsDeepStrict(new Set([3, '3']), new Set([3, 4]));
+notUtilIsDeepStrict(new Map([[3, 0], ['3', 0]]), new Map([[3, 0], [4, 0]]));
+
+notUtilIsDeepStrict(
+ new Set([{ a: 1 }, { a: 1 }, { a: 2 }]),
+ new Set([{ a: 1 }, { a: 2 }, { a: 2 }])
+);
+
+// Mixed primitive and object keys
+utilIsDeepStrict(
+ new Map([[1, 'a'], [{}, 'a']]),
+ new Map([[1, 'a'], [{}, 'a']])
+);
+utilIsDeepStrict(
+ new Set([1, 'a', [{}, 'a']]),
+ new Set([1, 'a', [{}, 'a']])
+);
+
+// This is an awful case, where a map contains multiple equivalent keys:
+notUtilIsDeepStrict(
+ new Map([[1, 'a'], ['1', 'b']]),
+ new Map([['1', 'a'], [true, 'b']])
+);
+notUtilIsDeepStrict(
+ new Set(['a']),
+ new Set(['b'])
+);
+utilIsDeepStrict(
+ new Map([[{}, 'a'], [{}, 'b']]),
+ new Map([[{}, 'b'], [{}, 'a']])
+);
+notUtilIsDeepStrict(
+ new Map([[true, 'a'], ['1', 'b'], [1, 'a']]),
+ new Map([['1', 'a'], [1, 'b'], [true, 'a']])
+);
+notUtilIsDeepStrict(
+ new Map([[true, 'a'], ['1', 'b'], [1, 'c']]),
+ new Map([['1', 'a'], [1, 'b'], [true, 'a']])
+);
+
+// Similar object keys
+notUtilIsDeepStrict(
+ new Set([{}, {}]),
+ new Set([{}, 1])
+);
+notUtilIsDeepStrict(
+ new Set([[{}, 1], [{}, 1]]),
+ new Set([[{}, 1], [1, 1]])
+);
+notUtilIsDeepStrict(
+ new Map([[{}, 1], [{}, 1]]),
+ new Map([[{}, 1], [1, 1]])
+);
+notUtilIsDeepStrict(
+ new Map([[{}, 1], [true, 1]]),
+ new Map([[{}, 1], [1, 1]])
+);
+
+// Similar primitive key / values
+notUtilIsDeepStrict(
+ new Set([1, true, false]),
+ new Set(['1', 0, '0'])
+);
+notUtilIsDeepStrict(
+ new Map([[1, 5], [true, 5], [false, 5]]),
+ new Map([['1', 5], [0, 5], ['0', 5]])
+);
+
+// undefined value in Map
+utilIsDeepStrict(
+ new Map([[1, undefined]]),
+ new Map([[1, undefined]])
+);
+notUtilIsDeepStrict(
+ new Map([[1, null]]),
+ new Map([['1', undefined]])
+);
+notUtilIsDeepStrict(
+ new Map([[1, undefined]]),
+ new Map([[2, undefined]])
+);
+
+// null as key
+utilIsDeepStrict(
+ new Map([[null, 3]]),
+ new Map([[null, 3]])
+);
+notUtilIsDeepStrict(
+ new Map([[null, undefined]]),
+ new Map([[undefined, null]])
+);
+notUtilIsDeepStrict(
+ new Set([null]),
+ new Set([undefined])
+);
+
+// GH-6416. Make sure circular refs don't throw.
+{
+ const b = {};
+ b.b = b;
+ const c = {};
+ c.b = c;
+
+ utilIsDeepStrict(b, c);
+
+ const d = {};
+ d.a = 1;
+ d.b = d;
+ const e = {};
+ e.a = 1;
+ e.b = {};
+
+ notUtilIsDeepStrict(d, e);
+}
+
+// GH-14441. Circular structures should be consistent
+{
+ const a = {};
+ const b = {};
+ a.a = a;
+ b.a = {};
+ b.a.a = a;
+ utilIsDeepStrict(a, b);
+}
+
+{
+ const a = new Set();
+ const b = new Set();
+ const c = new Set();
+ a.add(a);
+ b.add(b);
+ c.add(a);
+ utilIsDeepStrict(b, c);
+}
+
+// GH-7178. Ensure reflexivity of deepEqual with `arguments` objects.
+{
+ const args = (function() { return arguments; })();
+ notUtilIsDeepStrict([], args);
+}
+
+// More checking that arguments objects are handled correctly
+{
+ // eslint-disable-next-line func-style
+ const returnArguments = function() { return arguments; };
+
+ const someArgs = returnArguments('a');
+ const sameArgs = returnArguments('a');
+ const diffArgs = returnArguments('b');
+
+ notUtilIsDeepStrict(someArgs, ['a']);
+ notUtilIsDeepStrict(someArgs, { '0': 'a' });
+ notUtilIsDeepStrict(someArgs, diffArgs);
+ utilIsDeepStrict(someArgs, sameArgs);
+}
+
+{
+ const values = [
+ 123,
+ Infinity,
+ 0,
+ null,
+ undefined,
+ false,
+ true,
+ {},
+ [],
+ () => {},
+ ];
+ utilIsDeepStrict(new Set(values), new Set(values));
+ utilIsDeepStrict(new Set(values), new Set(values.reverse()));
+
+ const mapValues = values.map((v) => [v, { a: 5 }]);
+ utilIsDeepStrict(new Map(mapValues), new Map(mapValues));
+ utilIsDeepStrict(new Map(mapValues), new Map(mapValues.reverse()));
+}
+
+{
+ const s1 = new Set();
+ const s2 = new Set();
+ s1.add(1);
+ s1.add(2);
+ s2.add(2);
+ s2.add(1);
+ utilIsDeepStrict(s1, s2);
+}
+
+{
+ const m1 = new Map();
+ const m2 = new Map();
+ const obj = { a: 5, b: 6 };
+ m1.set(1, obj);
+ m1.set(2, 'hi');
+ m1.set(3, [1, 2, 3]);
+
+ m2.set(2, 'hi'); // different order
+ m2.set(1, obj);
+ m2.set(3, [1, 2, 3]); // deep equal, but not reference equal.
+
+ utilIsDeepStrict(m1, m2);
+}
+
+{
+ const m1 = new Map();
+ const m2 = new Map();
+
+ // m1 contains itself.
+ m1.set(1, m1);
+ m2.set(1, new Map());
+
+ notUtilIsDeepStrict(m1, m2);
+}
+
+{
+ const map1 = new Map([[1, 1]]);
+ const map2 = new Map([[1, '1']]);
+ assert.strictEqual(util.isDeepStrictEqual(map1, map2), false);
+}
+
+{
+ // Two equivalent sets / maps with different key/values applied shouldn't be
+ // the same. This is a terrible idea to do in practice, but deepEqual should
+ // still check for it.
+ const s1 = new Set();
+ const s2 = new Set();
+ s1.x = 5;
+ notUtilIsDeepStrict(s1, s2);
+
+ const m1 = new Map();
+ const m2 = new Map();
+ m1.x = 5;
+ notUtilIsDeepStrict(m1, m2);
+}
+
+{
+ // Circular references.
+ const s1 = new Set();
+ s1.add(s1);
+ const s2 = new Set();
+ s2.add(s2);
+ utilIsDeepStrict(s1, s2);
+
+ const m1 = new Map();
+ m1.set(2, m1);
+ const m2 = new Map();
+ m2.set(2, m2);
+ utilIsDeepStrict(m1, m2);
+
+ const m3 = new Map();
+ m3.set(m3, 2);
+ const m4 = new Map();
+ m4.set(m4, 2);
+ utilIsDeepStrict(m3, m4);
+}
+
+// Handle sparse arrays
+utilIsDeepStrict([1, , , 3], [1, , , 3]);
+notUtilIsDeepStrict([1, , , 3], [1, , , 3, , , ]);
+
+// Handle different error messages
+{
+ const err1 = new Error('foo1');
+ const err2 = new Error('foo2');
+ const err3 = new TypeError('foo1');
+ notUtilIsDeepStrict(err1, err2, assert.AssertionError);
+ notUtilIsDeepStrict(err1, err3, assert.AssertionError);
+ // TODO: evaluate if this should throw or not. The same applies for RegExp
+ // Date and any object that has the same keys but not the same prototype.
+ notUtilIsDeepStrict(err1, {}, assert.AssertionError);
+}
+
+// Handle NaN
+assert.strictEqual(util.isDeepStrictEqual(NaN, NaN), true);
+assert.strictEqual(util.isDeepStrictEqual({ a: NaN }, { a: NaN }), true);
+assert.strictEqual(
+ util.isDeepStrictEqual([ 1, 2, NaN, 4 ], [ 1, 2, NaN, 4 ]),
+ true
+);
+
+// Handle boxed primitives
+{
+ const boxedString = new String('test');
+ const boxedSymbol = Object(Symbol());
+ notUtilIsDeepStrict(new Boolean(true), Object(false));
+ notUtilIsDeepStrict(Object(true), new Number(1));
+ notUtilIsDeepStrict(new Number(2), new Number(1));
+ notUtilIsDeepStrict(boxedSymbol, Object(Symbol()));
+ notUtilIsDeepStrict(boxedSymbol, {});
+ utilIsDeepStrict(boxedSymbol, boxedSymbol);
+ utilIsDeepStrict(Object(true), Object(true));
+ utilIsDeepStrict(Object(2), Object(2));
+ utilIsDeepStrict(boxedString, Object('test'));
+ boxedString.slow = true;
+ notUtilIsDeepStrict(boxedString, Object('test'));
+ boxedSymbol.slow = true;
+ notUtilIsDeepStrict(boxedSymbol, {});
+}
+
+// Minus zero
+notUtilIsDeepStrict(0, -0);
+utilIsDeepStrict(-0, -0);
+
+// Handle symbols (enumerable only)
+{
+ const symbol1 = Symbol();
+ const obj1 = { [symbol1]: 1 };
+ const obj2 = { [symbol1]: 1 };
+ const obj3 = { [Symbol()]: 1 };
+ // Add a non enumerable symbol as well. It is going to be ignored!
+ Object.defineProperty(obj2, Symbol(), { value: 1 });
+ notUtilIsDeepStrict(obj1, obj3);
+ utilIsDeepStrict(obj1, obj2);
+ // TypedArrays have a fast path. Test for this as well.
+ const a = new Uint8Array(4);
+ const b = new Uint8Array(4);
+ a[symbol1] = true;
+ b[symbol1] = false;
+ notUtilIsDeepStrict(a, b);
+ b[symbol1] = true;
+ utilIsDeepStrict(a, b);
+ // The same as TypedArrays is valid for boxed primitives
+ const boxedStringA = new String('test');
+ const boxedStringB = new String('test');
+ boxedStringA[symbol1] = true;
+ notUtilIsDeepStrict(boxedStringA, boxedStringB);
+ boxedStringA[symbol1] = true;
+ utilIsDeepStrict(a, b);
+}