Source code
Revision control
Copy as Markdown
Other Tools
// GENERATED, DO NOT EDIT
// file: deepEqual.js
// Copyright 2019 Ron Buckton. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.
/*---
description: >
Compare two values structurally
defines: [assert.deepEqual]
---*/
assert.deepEqual = function(actual, expected, message) {
var format = assert.deepEqual.format;
assert(
assert.deepEqual._compare(actual, expected),
`Expected ${format(actual)} to be structurally equal to ${format(expected)}. ${(message || '')}`
);
};
(function() {
let getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
let join = arr => arr.join(', ');
function stringFromTemplate(strings, ...subs) {
let parts = strings.map((str, i) => `${i === 0 ? '' : subs[i - 1]}${str}`);
return parts.join('');
}
function escapeKey(key) {
if (typeof key === 'symbol') return `[${String(key)}]`;
if (/^[a-zA-Z0-9_$]+$/.test(key)) return key;
return assert._formatIdentityFreeValue(key);
}
assert.deepEqual.format = function(value, seen) {
let basic = assert._formatIdentityFreeValue(value);
if (basic) return basic;
switch (value === null ? 'null' : typeof value) {
case 'string':
case 'bigint':
case 'number':
case 'boolean':
case 'undefined':
case 'null':
assert(false, 'values without identity should use basic formatting');
break;
case 'symbol':
case 'function':
case 'object':
break;
default:
return typeof value;
}
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);
// Properly communicating multiple references requires deferred rendering of
// all identity-bearing values until the outermost format call finishes,
// because the current value can also in appear in a not-yet-visited part of
// the object graph (which, when visited, will update the usage object).
//
// To preserve readability of the desired output formatting, we accomplish
// this deferral using tagged template literals.
// Evaluation closes over the usage object and returns a function that accepts
// "mapper" arguments for rendering the corresponding substitution values and
// returns an object with only a toString method which will itself be invoked
// when trying to use the result as a string in assert.deepEqual.
//
// For convenience, any absent mapper is presumed to be `String`, and the
// function itself has a toString method that self-invokes with no mappers
// (allowing returning the function directly when every mapper is `String`).
function lazyResult(strings, ...subs) {
function acceptMappers(...mappers) {
function toString() {
let renderings = subs.map((sub, i) => (mappers[i] || String)(sub));
let rendered = stringFromTemplate(strings, ...renderings);
if (usage.used) rendered += ` as #${usage.id}`;
return rendered;
}
return { toString };
}
acceptMappers.toString = () => String(acceptMappers());
return acceptMappers;
}
let format = assert.deepEqual.format;
function lazyString(strings, ...subs) {
return { toString: () => stringFromTemplate(strings, ...subs) };
}
if (typeof value === 'function') {
return lazyResult`function${value.name ? ` ${String(value.name)}` : ''}`;
}
if (typeof value !== 'object') {
// probably a symbol
return lazyResult`${value}`;
}
if (Array.isArray ? Array.isArray(value) : value instanceof Array) {
return lazyResult`[${value.map(value => format(value, seen))}]`(join);
}
if (value instanceof Date) {
return lazyResult`Date(${format(value.toISOString(), seen)})`;
}
if (value instanceof Error) {
return lazyResult`error ${value.name || 'Error'}(${format(value.message, seen)})`;
}
if (value instanceof RegExp) {
return lazyResult`${value}`;
}
if (typeof Map !== "undefined" && value instanceof Map) {
let contents = Array.from(value).map(pair => lazyString`${format(pair[0], seen)} => ${format(pair[1], seen)}`);
return lazyResult`Map {${contents}}`(join);
}
if (typeof Set !== "undefined" && value instanceof Set) {
let contents = Array.from(value).map(value => format(value, seen));
return lazyResult`Set {${contents}}`(join);
}
let tag = Symbol.toStringTag && Symbol.toStringTag in value
? value[Symbol.toStringTag]
: Object.getPrototypeOf(value) === null ? '[Object: null prototype]' : 'Object';
let keys = Reflect.ownKeys(value).filter(key => getOwnPropertyDescriptor(value, key).enumerable);
let contents = keys.map(key => lazyString`${escapeKey(key)}: ${format(value[key], seen)}`);
return lazyResult`${tag ? `${tag} ` : ''}{${contents}}`(String, join);
};
})();
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' || typeof value === 'function';
}
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;
})();