mirror of
https://github.com/tc39/test262.git
synced 2025-07-26 07:25:15 +02:00
Cleanup compareArray and deepEqual assertions, remove assert._formatValue
Fixes #2372
This commit is contained in:
parent
4182972a49
commit
6b66b82d88
@ -94,58 +94,6 @@ assert.throws = function (expectedErrorConstructor, func, message) {
|
|||||||
$ERROR(message);
|
$ERROR(message);
|
||||||
};
|
};
|
||||||
|
|
||||||
assert._formatValue = (value, seen) => {
|
|
||||||
switch (typeof value) {
|
|
||||||
case 'string':
|
|
||||||
return typeof JSON !== "undefined" ? JSON.stringify(value) : `"${value}"`;
|
|
||||||
case 'number':
|
|
||||||
case 'boolean':
|
|
||||||
case 'symbol':
|
|
||||||
case 'bigint':
|
|
||||||
return value.toString();
|
|
||||||
case 'undefined':
|
|
||||||
return 'undefined';
|
|
||||||
case 'function':
|
|
||||||
return `[Function${value.name ? `: ${value.name}` : ''}]`;
|
|
||||||
case 'object':
|
|
||||||
if (value === null) return 'null';
|
|
||||||
if (value instanceof Date) return `Date "${value.toISOString()}"`;
|
|
||||||
if (value instanceof RegExp) return value.toString();
|
|
||||||
if (!seen) {
|
|
||||||
seen = {
|
|
||||||
counter: 0,
|
|
||||||
map: new Map()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let usage = seen.map.get(value);
|
|
||||||
if (usage) {
|
|
||||||
usage.used = true;
|
|
||||||
return `[Ref: #${usage.id}]`;
|
|
||||||
}
|
|
||||||
|
|
||||||
usage = { id: ++seen.counter, used: false };
|
|
||||||
seen.map.set(value, usage);
|
|
||||||
|
|
||||||
if (typeof Set !== "undefined" && value instanceof Set) {
|
|
||||||
return `Set {${Array.from(value).map(value => assert._formatValue(value, seen)).join(', ')}}${usage.used ? ` as #${usage.id}` : ''}`;
|
|
||||||
}
|
|
||||||
if (typeof Map !== "undefined" && value instanceof Map) {
|
|
||||||
return `Map {${Array.from(value).map(pair => `${assert._formatValue(pair[0], seen)} => ${assert._formatValue(pair[1], seen)}}`).join(', ')}}${usage.used ? ` as #${usage.id}` : ''}`;
|
|
||||||
}
|
|
||||||
if (Array.isArray ? Array.isArray(value) : value instanceof Array) {
|
|
||||||
return `[${value.map(value => assert._formatValue(value, seen)).join(', ')}]${usage.used ? ` as #${usage.id}` : ''}`;
|
|
||||||
}
|
|
||||||
let tag = Symbol.toStringTag in value ? value[Symbol.toStringTag] : 'Object';
|
|
||||||
if (tag === 'Object' && Object.getPrototypeOf(value) === null) {
|
|
||||||
tag = '[Object: null prototype]';
|
|
||||||
}
|
|
||||||
return `${tag ? `${tag} ` : ''}{ ${Object.keys(value).map(key => `${key.toString()}: ${assert._formatValue(value[key], seen)}`).join(', ')} }${usage.used ? ` as #${usage.id}` : ''}`;
|
|
||||||
default:
|
|
||||||
return typeof value;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
assert._toString = function (value) {
|
assert._toString = function (value) {
|
||||||
try {
|
try {
|
||||||
return String(value);
|
return String(value);
|
||||||
|
@ -6,40 +6,34 @@ description: |
|
|||||||
defines: [compareArray]
|
defines: [compareArray]
|
||||||
---*/
|
---*/
|
||||||
|
|
||||||
// @ts-check
|
|
||||||
|
|
||||||
function isSameValue(a, b) {
|
|
||||||
if (a === 0 && b === 0) return 1 / a === 1 / b;
|
|
||||||
if (a !== a && b !== b) return true;
|
|
||||||
|
|
||||||
return a === b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @param {T[]} a
|
|
||||||
* @param {T[]} b
|
|
||||||
*/
|
|
||||||
function compareArray(a, b) {
|
function compareArray(a, b) {
|
||||||
if (b.length !== a.length) {
|
if (b.length !== a.length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < a.length; i++) {
|
for (var i = 0; i < a.length; i++) {
|
||||||
if (!isSameValue(b[i], a[i])) {
|
if (!compareArray.isSameValue(b[i], a[i])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
compareArray.isSameValue = function(a, b) {
|
||||||
* @template T
|
if (a === 0 && b === 0) return 1 / a === 1 / b;
|
||||||
* @param {T[]} actual
|
if (a !== a && b !== b) return true;
|
||||||
* @param {T[]} expected
|
|
||||||
* @param {string} [message]
|
return a === b;
|
||||||
*/
|
};
|
||||||
assert.compareArray = function(actual, expected, message) {
|
|
||||||
assert(compareArray(actual, expected),
|
compareArray.format = function(array) {
|
||||||
'Expected ' + assert._formatValue(actual) + ' and ' + assert._formatValue(expected) + ' to have the same contents. ' + message);
|
return `[${array.map(String).join(', ')}]`;
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.compareArray = function(actual, expected, message) {
|
||||||
|
var format = compareArray.format;
|
||||||
|
assert(
|
||||||
|
compareArray(actual, expected),
|
||||||
|
`Expected ${format(actual)} and ${format(expected)} to have the same contents. ${(message || '')}`
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@ -6,268 +6,322 @@ description: >
|
|||||||
defines: [assert.deepEqual]
|
defines: [assert.deepEqual]
|
||||||
---*/
|
---*/
|
||||||
|
|
||||||
var deepEqual = (function () {
|
assert.deepEqual = function(actual, expected, message) {
|
||||||
var EQUAL = 1;
|
var format = assert.deepEqual.format;
|
||||||
var NOT_EQUAL = -1;
|
assert(
|
||||||
var UNKNOWN = 0;
|
assert.deepEqual._compare(actual, expected),
|
||||||
|
`Expected ${format(actual)} to be structurally equal to ${format(expected)}. ${(message || '')}`
|
||||||
function deepEqual(a, b) {
|
);
|
||||||
return compareEquality(a, b) === EQUAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
function compareEquality(a, b, cache) {
|
|
||||||
return compareIf(a, b, isOptional, compareOptionality)
|
|
||||||
|| compareIf(a, b, isPrimitiveEquatable, comparePrimitiveEquality)
|
|
||||||
|| compareIf(a, b, isObjectEquatable, compareObjectEquality, cache)
|
|
||||||
|| NOT_EQUAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
function compareIf(a, b, test, compare, cache) {
|
|
||||||
return !test(a)
|
|
||||||
? !test(b) ? UNKNOWN : NOT_EQUAL
|
|
||||||
: !test(b) ? NOT_EQUAL : cacheComparison(a, b, compare, cache);
|
|
||||||
}
|
|
||||||
|
|
||||||
function tryCompareStrictEquality(a, b) {
|
|
||||||
return a === b ? EQUAL : UNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
function tryCompareTypeOfEquality(a, b) {
|
|
||||||
return typeof a !== typeof b ? NOT_EQUAL : UNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
function tryCompareToStringTagEquality(a, b) {
|
|
||||||
var aTag = Symbol.toStringTag in a ? a[Symbol.toStringTag] : undefined;
|
|
||||||
var bTag = Symbol.toStringTag in b ? b[Symbol.toStringTag] : undefined;
|
|
||||||
return aTag !== bTag ? NOT_EQUAL : UNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isOptional(value) {
|
|
||||||
return value === undefined
|
|
||||||
|| value === null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function compareOptionality(a, b) {
|
|
||||||
return tryCompareStrictEquality(a, b)
|
|
||||||
|| NOT_EQUAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPrimitiveEquatable(value) {
|
|
||||||
switch (typeof value) {
|
|
||||||
case 'string':
|
|
||||||
case 'number':
|
|
||||||
case 'bigint':
|
|
||||||
case 'boolean':
|
|
||||||
case 'symbol':
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return isBoxed(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function comparePrimitiveEquality(a, b) {
|
|
||||||
if (isBoxed(a)) a = a.valueOf();
|
|
||||||
if (isBoxed(b)) b = b.valueOf();
|
|
||||||
return tryCompareStrictEquality(a, b)
|
|
||||||
|| tryCompareTypeOfEquality(a, b)
|
|
||||||
|| compareIf(a, b, isNaNEquatable, compareNaNEquality)
|
|
||||||
|| NOT_EQUAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isNaNEquatable(value) {
|
|
||||||
return typeof value === 'number';
|
|
||||||
}
|
|
||||||
|
|
||||||
function compareNaNEquality(a, b) {
|
|
||||||
return isNaN(a) && isNaN(b) ? EQUAL : NOT_EQUAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isObjectEquatable(value) {
|
|
||||||
return typeof value === 'object';
|
|
||||||
}
|
|
||||||
|
|
||||||
function compareObjectEquality(a, b, cache) {
|
|
||||||
if (!cache) cache = new Map();
|
|
||||||
return getCache(cache, a, b)
|
|
||||||
|| setCache(cache, a, b, EQUAL) // consider equal for now
|
|
||||||
|| cacheComparison(a, b, tryCompareStrictEquality, cache)
|
|
||||||
|| cacheComparison(a, b, tryCompareToStringTagEquality, cache)
|
|
||||||
|| compareIf(a, b, isValueOfEquatable, compareValueOfEquality)
|
|
||||||
|| compareIf(a, b, isToStringEquatable, compareToStringEquality)
|
|
||||||
|| compareIf(a, b, isArrayLikeEquatable, compareArrayLikeEquality, cache)
|
|
||||||
|| compareIf(a, b, isStructurallyEquatable, compareStructuralEquality, cache)
|
|
||||||
|| compareIf(a, b, isIterableEquatable, compareIterableEquality, cache)
|
|
||||||
|| cacheComparison(a, b, fail, cache);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isBoxed(value) {
|
|
||||||
return value instanceof String
|
|
||||||
|| value instanceof Number
|
|
||||||
|| value instanceof Boolean
|
|
||||||
|| typeof Symbol === 'function' && value instanceof Symbol
|
|
||||||
|| typeof BigInt === 'function' && value instanceof BigInt;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isValueOfEquatable(value) {
|
|
||||||
return value instanceof Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
function compareValueOfEquality(a, b) {
|
|
||||||
return compareIf(a.valueOf(), b.valueOf(), isPrimitiveEquatable, comparePrimitiveEquality)
|
|
||||||
|| NOT_EQUAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isToStringEquatable(value) {
|
|
||||||
return value instanceof RegExp;
|
|
||||||
}
|
|
||||||
|
|
||||||
function compareToStringEquality(a, b) {
|
|
||||||
return compareIf(a.toString(), b.toString(), isPrimitiveEquatable, comparePrimitiveEquality)
|
|
||||||
|| NOT_EQUAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isArrayLikeEquatable(value) {
|
|
||||||
return (Array.isArray ? Array.isArray(value) : value instanceof Array)
|
|
||||||
|| (typeof Uint8Array === 'function' && value instanceof Uint8Array)
|
|
||||||
|| (typeof Uint8ClampedArray === 'function' && value instanceof Uint8ClampedArray)
|
|
||||||
|| (typeof Uint16Array === 'function' && value instanceof Uint16Array)
|
|
||||||
|| (typeof Uint32Array === 'function' && value instanceof Uint32Array)
|
|
||||||
|| (typeof Int8Array === 'function' && value instanceof Int8Array)
|
|
||||||
|| (typeof Int16Array === 'function' && value instanceof Int16Array)
|
|
||||||
|| (typeof Int32Array === 'function' && value instanceof Int32Array)
|
|
||||||
|| (typeof Float32Array === 'function' && value instanceof Float32Array)
|
|
||||||
|| (typeof Float64Array === 'function' && value instanceof Float64Array)
|
|
||||||
|| (typeof BigUint64Array === 'function' && value instanceof BigUint64Array)
|
|
||||||
|| (typeof BigInt64Array === 'function' && value instanceof BigInt64Array);
|
|
||||||
}
|
|
||||||
|
|
||||||
function compareArrayLikeEquality(a, b, cache) {
|
|
||||||
if (a.length !== b.length) return NOT_EQUAL;
|
|
||||||
for (var i = 0; i < a.length; i++) {
|
|
||||||
if (compareEquality(a[i], b[i], cache) === NOT_EQUAL) {
|
|
||||||
return NOT_EQUAL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return EQUAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isStructurallyEquatable(value) {
|
|
||||||
return !(typeof Promise === 'function' && value instanceof Promise // only comparable by reference
|
|
||||||
|| typeof WeakMap === 'function' && value instanceof WeakMap // only comparable by reference
|
|
||||||
|| typeof WeakSet === 'function' && value instanceof WeakSet // only comparable by reference
|
|
||||||
|| typeof Map === 'function' && value instanceof Map // comparable via @@iterator
|
|
||||||
|| typeof Set === 'function' && value instanceof Set); // comparable via @@iterator
|
|
||||||
}
|
|
||||||
|
|
||||||
function compareStructuralEquality(a, b, cache) {
|
|
||||||
var aKeys = [];
|
|
||||||
for (var key in a) aKeys.push(key);
|
|
||||||
|
|
||||||
var bKeys = [];
|
|
||||||
for (var key in b) bKeys.push(key);
|
|
||||||
|
|
||||||
if (aKeys.length !== bKeys.length) {
|
|
||||||
return NOT_EQUAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
aKeys.sort();
|
|
||||||
bKeys.sort();
|
|
||||||
|
|
||||||
for (var i = 0; i < aKeys.length; i++) {
|
|
||||||
var aKey = aKeys[i];
|
|
||||||
var bKey = bKeys[i];
|
|
||||||
if (compareEquality(aKey, bKey, cache) === NOT_EQUAL) {
|
|
||||||
return NOT_EQUAL;
|
|
||||||
}
|
|
||||||
if (compareEquality(a[aKey], b[bKey], cache) === NOT_EQUAL) {
|
|
||||||
return NOT_EQUAL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return compareIf(a, b, isIterableEquatable, compareIterableEquality, cache)
|
|
||||||
|| EQUAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isIterableEquatable(value) {
|
|
||||||
return typeof Symbol === 'function'
|
|
||||||
&& typeof value[Symbol.iterator] === 'function';
|
|
||||||
}
|
|
||||||
|
|
||||||
function compareIteratorEquality(a, b, cache) {
|
|
||||||
if (typeof Map === 'function' && a instanceof Map && b instanceof Map ||
|
|
||||||
typeof Set === 'function' && a instanceof Set && b instanceof Set) {
|
|
||||||
if (a.size !== b.size) return NOT_EQUAL; // exit early if we detect a difference in size
|
|
||||||
}
|
|
||||||
|
|
||||||
var ar, br;
|
|
||||||
while (true) {
|
|
||||||
ar = a.next();
|
|
||||||
br = b.next();
|
|
||||||
if (ar.done) {
|
|
||||||
if (br.done) return EQUAL;
|
|
||||||
if (b.return) b.return();
|
|
||||||
return NOT_EQUAL;
|
|
||||||
}
|
|
||||||
if (br.done) {
|
|
||||||
if (a.return) a.return();
|
|
||||||
return NOT_EQUAL;
|
|
||||||
}
|
|
||||||
if (compareEquality(ar.value, br.value, cache) === NOT_EQUAL) {
|
|
||||||
if (a.return) a.return();
|
|
||||||
if (b.return) b.return();
|
|
||||||
return NOT_EQUAL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function compareIterableEquality(a, b, cache) {
|
|
||||||
return compareIteratorEquality(a[Symbol.iterator](), b[Symbol.iterator](), cache);
|
|
||||||
}
|
|
||||||
|
|
||||||
function cacheComparison(a, b, compare, cache) {
|
|
||||||
var result = compare(a, b, cache);
|
|
||||||
if (cache && (result === EQUAL || result === NOT_EQUAL)) {
|
|
||||||
setCache(cache, a, b, /** @type {EQUAL | NOT_EQUAL} */(result));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function fail() {
|
|
||||||
return NOT_EQUAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setCache(cache, left, right, result) {
|
|
||||||
var otherCache;
|
|
||||||
|
|
||||||
otherCache = cache.get(left);
|
|
||||||
if (!otherCache) cache.set(left, otherCache = new Map());
|
|
||||||
otherCache.set(right, result);
|
|
||||||
|
|
||||||
otherCache = cache.get(right);
|
|
||||||
if (!otherCache) cache.set(right, otherCache = new Map());
|
|
||||||
otherCache.set(left, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCache(cache, left, right) {
|
|
||||||
var otherCache;
|
|
||||||
/** @type {EQUAL | NOT_EQUAL | UNKNOWN | undefined} */
|
|
||||||
var result;
|
|
||||||
|
|
||||||
otherCache = cache.get(left);
|
|
||||||
result = otherCache && otherCache.get(right);
|
|
||||||
if (result) return result;
|
|
||||||
|
|
||||||
otherCache = cache.get(right);
|
|
||||||
result = otherCache && otherCache.get(left);
|
|
||||||
if (result) return result;
|
|
||||||
|
|
||||||
return UNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
return deepEqual;
|
|
||||||
})();
|
|
||||||
|
|
||||||
assert.deepEqual = function (actual, expected, message) {
|
|
||||||
assert(deepEqual(actual, expected),
|
|
||||||
'Expected ' + assert._formatValue(actual) + ' to be structurally equal to ' + assert._formatValue(expected) + '. ' + (message || ''));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
assert.deepEqual.format = function(value, seen) {
|
||||||
|
switch (typeof value) {
|
||||||
|
case 'string':
|
||||||
|
return typeof JSON !== "undefined" ? JSON.stringify(value) : `"${value}"`;
|
||||||
|
case 'number':
|
||||||
|
case 'boolean':
|
||||||
|
case 'symbol':
|
||||||
|
case 'bigint':
|
||||||
|
return value.toString();
|
||||||
|
case 'undefined':
|
||||||
|
return 'undefined';
|
||||||
|
case 'function':
|
||||||
|
return `[Function${value.name ? `: ${value.name}` : ''}]`;
|
||||||
|
case 'object':
|
||||||
|
if (value === null) return 'null';
|
||||||
|
if (value instanceof Date) return `Date "${value.toISOString()}"`;
|
||||||
|
if (value instanceof RegExp) return value.toString();
|
||||||
|
if (!seen) {
|
||||||
|
seen = {
|
||||||
|
counter: 0,
|
||||||
|
map: new Map()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let usage = seen.map.get(value);
|
||||||
|
if (usage) {
|
||||||
|
usage.used = true;
|
||||||
|
return `[Ref: #${usage.id}]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
usage = { id: ++seen.counter, used: false };
|
||||||
|
seen.map.set(value, usage);
|
||||||
|
|
||||||
|
if (typeof Set !== "undefined" && value instanceof Set) {
|
||||||
|
return `Set {${Array.from(value).map(value => assert.deepEqual.format(value, seen)).join(', ')}}${usage.used ? ` as #${usage.id}` : ''}`;
|
||||||
|
}
|
||||||
|
if (typeof Map !== "undefined" && value instanceof Map) {
|
||||||
|
return `Map {${Array.from(value).map(pair => `${assert.deepEqual.format(pair[0], seen)} => ${assert.deepEqual.format(pair[1], seen)}}`).join(', ')}}${usage.used ? ` as #${usage.id}` : ''}`;
|
||||||
|
}
|
||||||
|
if (Array.isArray ? Array.isArray(value) : value instanceof Array) {
|
||||||
|
return `[${value.map(value => assert.deepEqual.format(value, seen)).join(', ')}]${usage.used ? ` as #${usage.id}` : ''}`;
|
||||||
|
}
|
||||||
|
let tag = Symbol.toStringTag in value ? value[Symbol.toStringTag] : 'Object';
|
||||||
|
if (tag === 'Object' && Object.getPrototypeOf(value) === null) {
|
||||||
|
tag = '[Object: null prototype]';
|
||||||
|
}
|
||||||
|
return `${tag ? `${tag} ` : ''}{ ${Object.keys(value).map(key => `${key.toString()}: ${assert.deepEqual.format(value[key], seen)}`).join(', ')} }${usage.used ? ` as #${usage.id}` : ''}`;
|
||||||
|
default:
|
||||||
|
return typeof value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.deepEqual._compare = (function () {
|
||||||
|
var EQUAL = 1;
|
||||||
|
var NOT_EQUAL = -1;
|
||||||
|
var UNKNOWN = 0;
|
||||||
|
|
||||||
|
function deepEqual(a, b) {
|
||||||
|
return compareEquality(a, b) === EQUAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareEquality(a, b, cache) {
|
||||||
|
return compareIf(a, b, isOptional, compareOptionality)
|
||||||
|
|| compareIf(a, b, isPrimitiveEquatable, comparePrimitiveEquality)
|
||||||
|
|| compareIf(a, b, isObjectEquatable, compareObjectEquality, cache)
|
||||||
|
|| NOT_EQUAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareIf(a, b, test, compare, cache) {
|
||||||
|
return !test(a)
|
||||||
|
? !test(b) ? UNKNOWN : NOT_EQUAL
|
||||||
|
: !test(b) ? NOT_EQUAL : cacheComparison(a, b, compare, cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
function tryCompareStrictEquality(a, b) {
|
||||||
|
return a === b ? EQUAL : UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
function tryCompareTypeOfEquality(a, b) {
|
||||||
|
return typeof a !== typeof b ? NOT_EQUAL : UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
function tryCompareToStringTagEquality(a, b) {
|
||||||
|
var aTag = Symbol.toStringTag in a ? a[Symbol.toStringTag] : undefined;
|
||||||
|
var bTag = Symbol.toStringTag in b ? b[Symbol.toStringTag] : undefined;
|
||||||
|
return aTag !== bTag ? NOT_EQUAL : UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isOptional(value) {
|
||||||
|
return value === undefined
|
||||||
|
|| value === null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareOptionality(a, b) {
|
||||||
|
return tryCompareStrictEquality(a, b)
|
||||||
|
|| NOT_EQUAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPrimitiveEquatable(value) {
|
||||||
|
switch (typeof value) {
|
||||||
|
case 'string':
|
||||||
|
case 'number':
|
||||||
|
case 'bigint':
|
||||||
|
case 'boolean':
|
||||||
|
case 'symbol':
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return isBoxed(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function comparePrimitiveEquality(a, b) {
|
||||||
|
if (isBoxed(a)) a = a.valueOf();
|
||||||
|
if (isBoxed(b)) b = b.valueOf();
|
||||||
|
return tryCompareStrictEquality(a, b)
|
||||||
|
|| tryCompareTypeOfEquality(a, b)
|
||||||
|
|| compareIf(a, b, isNaNEquatable, compareNaNEquality)
|
||||||
|
|| NOT_EQUAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNaNEquatable(value) {
|
||||||
|
return typeof value === 'number';
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareNaNEquality(a, b) {
|
||||||
|
return isNaN(a) && isNaN(b) ? EQUAL : NOT_EQUAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isObjectEquatable(value) {
|
||||||
|
return typeof value === 'object';
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareObjectEquality(a, b, cache) {
|
||||||
|
if (!cache) cache = new Map();
|
||||||
|
return getCache(cache, a, b)
|
||||||
|
|| setCache(cache, a, b, EQUAL) // consider equal for now
|
||||||
|
|| cacheComparison(a, b, tryCompareStrictEquality, cache)
|
||||||
|
|| cacheComparison(a, b, tryCompareToStringTagEquality, cache)
|
||||||
|
|| compareIf(a, b, isValueOfEquatable, compareValueOfEquality)
|
||||||
|
|| compareIf(a, b, isToStringEquatable, compareToStringEquality)
|
||||||
|
|| compareIf(a, b, isArrayLikeEquatable, compareArrayLikeEquality, cache)
|
||||||
|
|| compareIf(a, b, isStructurallyEquatable, compareStructuralEquality, cache)
|
||||||
|
|| compareIf(a, b, isIterableEquatable, compareIterableEquality, cache)
|
||||||
|
|| cacheComparison(a, b, fail, cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isBoxed(value) {
|
||||||
|
return value instanceof String
|
||||||
|
|| value instanceof Number
|
||||||
|
|| value instanceof Boolean
|
||||||
|
|| typeof Symbol === 'function' && value instanceof Symbol
|
||||||
|
|| typeof BigInt === 'function' && value instanceof BigInt;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValueOfEquatable(value) {
|
||||||
|
return value instanceof Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareValueOfEquality(a, b) {
|
||||||
|
return compareIf(a.valueOf(), b.valueOf(), isPrimitiveEquatable, comparePrimitiveEquality)
|
||||||
|
|| NOT_EQUAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isToStringEquatable(value) {
|
||||||
|
return value instanceof RegExp;
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareToStringEquality(a, b) {
|
||||||
|
return compareIf(a.toString(), b.toString(), isPrimitiveEquatable, comparePrimitiveEquality)
|
||||||
|
|| NOT_EQUAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isArrayLikeEquatable(value) {
|
||||||
|
return (Array.isArray ? Array.isArray(value) : value instanceof Array)
|
||||||
|
|| (typeof Uint8Array === 'function' && value instanceof Uint8Array)
|
||||||
|
|| (typeof Uint8ClampedArray === 'function' && value instanceof Uint8ClampedArray)
|
||||||
|
|| (typeof Uint16Array === 'function' && value instanceof Uint16Array)
|
||||||
|
|| (typeof Uint32Array === 'function' && value instanceof Uint32Array)
|
||||||
|
|| (typeof Int8Array === 'function' && value instanceof Int8Array)
|
||||||
|
|| (typeof Int16Array === 'function' && value instanceof Int16Array)
|
||||||
|
|| (typeof Int32Array === 'function' && value instanceof Int32Array)
|
||||||
|
|| (typeof Float32Array === 'function' && value instanceof Float32Array)
|
||||||
|
|| (typeof Float64Array === 'function' && value instanceof Float64Array)
|
||||||
|
|| (typeof BigUint64Array === 'function' && value instanceof BigUint64Array)
|
||||||
|
|| (typeof BigInt64Array === 'function' && value instanceof BigInt64Array);
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareArrayLikeEquality(a, b, cache) {
|
||||||
|
if (a.length !== b.length) return NOT_EQUAL;
|
||||||
|
for (var i = 0; i < a.length; i++) {
|
||||||
|
if (compareEquality(a[i], b[i], cache) === NOT_EQUAL) {
|
||||||
|
return NOT_EQUAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return EQUAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStructurallyEquatable(value) {
|
||||||
|
return !(typeof Promise === 'function' && value instanceof Promise // only comparable by reference
|
||||||
|
|| typeof WeakMap === 'function' && value instanceof WeakMap // only comparable by reference
|
||||||
|
|| typeof WeakSet === 'function' && value instanceof WeakSet // only comparable by reference
|
||||||
|
|| typeof Map === 'function' && value instanceof Map // comparable via @@iterator
|
||||||
|
|| typeof Set === 'function' && value instanceof Set); // comparable via @@iterator
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareStructuralEquality(a, b, cache) {
|
||||||
|
var aKeys = [];
|
||||||
|
for (var key in a) aKeys.push(key);
|
||||||
|
|
||||||
|
var bKeys = [];
|
||||||
|
for (var key in b) bKeys.push(key);
|
||||||
|
|
||||||
|
if (aKeys.length !== bKeys.length) {
|
||||||
|
return NOT_EQUAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
aKeys.sort();
|
||||||
|
bKeys.sort();
|
||||||
|
|
||||||
|
for (var i = 0; i < aKeys.length; i++) {
|
||||||
|
var aKey = aKeys[i];
|
||||||
|
var bKey = bKeys[i];
|
||||||
|
if (compareEquality(aKey, bKey, cache) === NOT_EQUAL) {
|
||||||
|
return NOT_EQUAL;
|
||||||
|
}
|
||||||
|
if (compareEquality(a[aKey], b[bKey], cache) === NOT_EQUAL) {
|
||||||
|
return NOT_EQUAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return compareIf(a, b, isIterableEquatable, compareIterableEquality, cache)
|
||||||
|
|| EQUAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isIterableEquatable(value) {
|
||||||
|
return typeof Symbol === 'function'
|
||||||
|
&& typeof value[Symbol.iterator] === 'function';
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareIteratorEquality(a, b, cache) {
|
||||||
|
if (typeof Map === 'function' && a instanceof Map && b instanceof Map ||
|
||||||
|
typeof Set === 'function' && a instanceof Set && b instanceof Set) {
|
||||||
|
if (a.size !== b.size) return NOT_EQUAL; // exit early if we detect a difference in size
|
||||||
|
}
|
||||||
|
|
||||||
|
var ar, br;
|
||||||
|
while (true) {
|
||||||
|
ar = a.next();
|
||||||
|
br = b.next();
|
||||||
|
if (ar.done) {
|
||||||
|
if (br.done) return EQUAL;
|
||||||
|
if (b.return) b.return();
|
||||||
|
return NOT_EQUAL;
|
||||||
|
}
|
||||||
|
if (br.done) {
|
||||||
|
if (a.return) a.return();
|
||||||
|
return NOT_EQUAL;
|
||||||
|
}
|
||||||
|
if (compareEquality(ar.value, br.value, cache) === NOT_EQUAL) {
|
||||||
|
if (a.return) a.return();
|
||||||
|
if (b.return) b.return();
|
||||||
|
return NOT_EQUAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareIterableEquality(a, b, cache) {
|
||||||
|
return compareIteratorEquality(a[Symbol.iterator](), b[Symbol.iterator](), cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cacheComparison(a, b, compare, cache) {
|
||||||
|
var result = compare(a, b, cache);
|
||||||
|
if (cache && (result === EQUAL || result === NOT_EQUAL)) {
|
||||||
|
setCache(cache, a, b, /** @type {EQUAL | NOT_EQUAL} */(result));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fail() {
|
||||||
|
return NOT_EQUAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCache(cache, left, right, result) {
|
||||||
|
var otherCache;
|
||||||
|
|
||||||
|
otherCache = cache.get(left);
|
||||||
|
if (!otherCache) cache.set(left, otherCache = new Map());
|
||||||
|
otherCache.set(right, result);
|
||||||
|
|
||||||
|
otherCache = cache.get(right);
|
||||||
|
if (!otherCache) cache.set(right, otherCache = new Map());
|
||||||
|
otherCache.set(left, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCache(cache, left, right) {
|
||||||
|
var otherCache;
|
||||||
|
var result;
|
||||||
|
|
||||||
|
otherCache = cache.get(left);
|
||||||
|
result = otherCache && otherCache.get(right);
|
||||||
|
if (result) return result;
|
||||||
|
|
||||||
|
otherCache = cache.get(right);
|
||||||
|
result = otherCache && otherCache.get(left);
|
||||||
|
if (result) return result;
|
||||||
|
|
||||||
|
return UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
return deepEqual;
|
||||||
|
})();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user