|
|
'use strict';
const Types = require('./types');
const internals = { mismatched: null };
module.exports = function (obj, ref, options) {
options = Object.assign({ prototype: true }, options);
return !!internals.isDeepEqual(obj, ref, options, []); };
internals.isDeepEqual = function (obj, ref, options, seen) {
if (obj === ref) { // Copied from Deep-eql, copyright(c) 2013 Jake Luer, jake@alogicalparadox.com, MIT Licensed, https://github.com/chaijs/deep-eql
return obj !== 0 || 1 / obj === 1 / ref; }
const type = typeof obj;
if (type !== typeof ref) { return false; }
if (obj === null || ref === null) {
return false; }
if (type === 'function') { if (!options.deepFunction || obj.toString() !== ref.toString()) {
return false; }
// Continue as object
} else if (type !== 'object') { return obj !== obj && ref !== ref; // NaN
}
const instanceType = internals.getSharedType(obj, ref, !!options.prototype); switch (instanceType) { case Types.buffer: return Buffer && Buffer.prototype.equals.call(obj, ref); // $lab:coverage:ignore$
case Types.promise: return obj === ref; case Types.regex: return obj.toString() === ref.toString(); case internals.mismatched: return false; }
for (let i = seen.length - 1; i >= 0; --i) { if (seen[i].isSame(obj, ref)) { return true; // If previous comparison failed, it would have stopped execution
} }
seen.push(new internals.SeenEntry(obj, ref));
try { return !!internals.isDeepEqualObj(instanceType, obj, ref, options, seen); } finally { seen.pop(); } };
internals.getSharedType = function (obj, ref, checkPrototype) {
if (checkPrototype) { if (Object.getPrototypeOf(obj) !== Object.getPrototypeOf(ref)) { return internals.mismatched; }
return Types.getInternalProto(obj); }
const type = Types.getInternalProto(obj); if (type !== Types.getInternalProto(ref)) { return internals.mismatched; }
return type; };
internals.valueOf = function (obj) {
const objValueOf = obj.valueOf; if (objValueOf === undefined) { return obj; }
try { return objValueOf.call(obj); } catch (err) { return err; } };
internals.hasOwnEnumerableProperty = function (obj, key) {
return Object.prototype.propertyIsEnumerable.call(obj, key); };
internals.isSetSimpleEqual = function (obj, ref) {
for (const entry of obj) { if (!ref.has(entry)) { return false; } }
return true; };
internals.isDeepEqualObj = function (instanceType, obj, ref, options, seen) {
const { isDeepEqual, valueOf, hasOwnEnumerableProperty } = internals; const { keys, getOwnPropertySymbols } = Object;
if (instanceType === Types.array) { if (options.part) {
// Check if any index match any other index
for (const objValue of obj) { for (const refValue of ref) { if (isDeepEqual(objValue, refValue, options, seen)) { return true; } } } } else { if (obj.length !== ref.length) { return false; }
for (let i = 0; i < obj.length; ++i) { if (!isDeepEqual(obj[i], ref[i], options, seen)) { return false; } }
return true; } } else if (instanceType === Types.set) { if (obj.size !== ref.size) { return false; }
if (!internals.isSetSimpleEqual(obj, ref)) {
// Check for deep equality
const ref2 = new Set(ref); for (const objEntry of obj) { if (ref2.delete(objEntry)) { continue; }
let found = false; for (const refEntry of ref2) { if (isDeepEqual(objEntry, refEntry, options, seen)) { ref2.delete(refEntry); found = true; break; } }
if (!found) { return false; } } } } else if (instanceType === Types.map) { if (obj.size !== ref.size) { return false; }
for (const [key, value] of obj) { if (value === undefined && !ref.has(key)) { return false; }
if (!isDeepEqual(value, ref.get(key), options, seen)) { return false; } } } else if (instanceType === Types.error) {
// Always check name and message
if (obj.name !== ref.name || obj.message !== ref.message) {
return false; } }
// Check .valueOf()
const valueOfObj = valueOf(obj); const valueOfRef = valueOf(ref); if ((obj !== valueOfObj || ref !== valueOfRef) && !isDeepEqual(valueOfObj, valueOfRef, options, seen)) {
return false; }
// Check properties
const objKeys = keys(obj); if (!options.part && objKeys.length !== keys(ref).length && !options.skip) {
return false; }
let skipped = 0; for (const key of objKeys) { if (options.skip && options.skip.includes(key)) {
if (ref[key] === undefined) { ++skipped; }
continue; }
if (!hasOwnEnumerableProperty(ref, key)) { return false; }
if (!isDeepEqual(obj[key], ref[key], options, seen)) { return false; } }
if (!options.part && objKeys.length - skipped !== keys(ref).length) {
return false; }
// Check symbols
if (options.symbols !== false) { // Defaults to true
const objSymbols = getOwnPropertySymbols(obj); const refSymbols = new Set(getOwnPropertySymbols(ref));
for (const key of objSymbols) { if (!options.skip || !options.skip.includes(key)) {
if (hasOwnEnumerableProperty(obj, key)) { if (!hasOwnEnumerableProperty(ref, key)) { return false; }
if (!isDeepEqual(obj[key], ref[key], options, seen)) { return false; } } else if (hasOwnEnumerableProperty(ref, key)) { return false; } }
refSymbols.delete(key); }
for (const key of refSymbols) { if (hasOwnEnumerableProperty(ref, key)) { return false; } } }
return true; };
internals.SeenEntry = class {
constructor(obj, ref) {
this.obj = obj; this.ref = ref; }
isSame(obj, ref) {
return this.obj === obj && this.ref === ref; } };
|