You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
316 lines
7.6 KiB
316 lines
7.6 KiB
'use strict';
|
|
|
|
// Load modules
|
|
|
|
|
|
// Declare internals
|
|
|
|
const internals = {
|
|
arrayType: Symbol('array'),
|
|
bufferType: Symbol('buffer'),
|
|
dateType: Symbol('date'),
|
|
errorType: Symbol('error'),
|
|
genericType: Symbol('generic'),
|
|
mapType: Symbol('map'),
|
|
regexType: Symbol('regex'),
|
|
setType: Symbol('set'),
|
|
weakMapType: Symbol('weak-map'),
|
|
weakSetType: Symbol('weak-set'),
|
|
mismatched: Symbol('mismatched')
|
|
};
|
|
|
|
|
|
internals.typeMap = {
|
|
'[object Array]': internals.arrayType,
|
|
'[object Date]': internals.dateType,
|
|
'[object Error]': internals.errorType,
|
|
'[object Map]': internals.mapType,
|
|
'[object RegExp]': internals.regexType,
|
|
'[object Set]': internals.setType,
|
|
'[object WeakMap]': internals.weakMapType,
|
|
'[object WeakSet]': internals.weakSetType
|
|
};
|
|
|
|
|
|
internals.SeenEntry = class {
|
|
|
|
constructor(obj, ref) {
|
|
|
|
this.obj = obj;
|
|
this.ref = ref;
|
|
}
|
|
|
|
isSame(obj, ref) {
|
|
|
|
return this.obj === obj && this.ref === ref;
|
|
}
|
|
};
|
|
|
|
|
|
internals.getInternalType = function (obj) {
|
|
|
|
const { typeMap, bufferType, genericType } = internals;
|
|
|
|
if (obj instanceof Buffer) {
|
|
return bufferType;
|
|
}
|
|
|
|
const objName = Object.prototype.toString.call(obj);
|
|
return typeMap[objName] || genericType;
|
|
};
|
|
|
|
|
|
internals.getSharedType = function (obj, ref, checkPrototype) {
|
|
|
|
if (checkPrototype) {
|
|
if (Object.getPrototypeOf(obj) !== Object.getPrototypeOf(ref)) {
|
|
return internals.mismatched;
|
|
}
|
|
|
|
return internals.getInternalType(obj);
|
|
}
|
|
|
|
const type = internals.getInternalType(obj);
|
|
if (type !== internals.getInternalType(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 === internals.arrayType) {
|
|
if (options.part) {
|
|
// Check if any index match any other index
|
|
|
|
for (let i = 0; i < obj.length; ++i) {
|
|
const objValue = obj[i];
|
|
for (let j = 0; j < ref.length; ++j) {
|
|
if (isDeepEqual(objValue, ref[j], 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 === internals.setType) {
|
|
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 === internals.mapType) {
|
|
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 === internals.errorType) {
|
|
// 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) {
|
|
return false;
|
|
}
|
|
|
|
for (let i = 0; i < objKeys.length; ++i) {
|
|
const key = objKeys[i];
|
|
|
|
if (!hasOwnEnumerableProperty(ref, key)) {
|
|
return false;
|
|
}
|
|
|
|
if (!isDeepEqual(obj[key], ref[key], options, seen)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Check symbols
|
|
|
|
if (options.symbols) {
|
|
const objSymbols = getOwnPropertySymbols(obj);
|
|
const refSymbols = new Set(getOwnPropertySymbols(ref));
|
|
|
|
for (let i = 0; i < objSymbols.length; ++i) {
|
|
const key = objSymbols[i];
|
|
|
|
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.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 (type !== 'object' ||
|
|
obj === null ||
|
|
ref === null) {
|
|
|
|
return obj !== obj && ref !== ref; // NaN
|
|
}
|
|
|
|
const instanceType = internals.getSharedType(obj, ref, !!options.prototype);
|
|
switch (instanceType) {
|
|
case internals.bufferType:
|
|
return Buffer.prototype.equals.call(obj, ref);
|
|
case internals.regexType:
|
|
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();
|
|
}
|
|
};
|
|
|
|
|
|
module.exports = function (obj, ref, options) {
|
|
|
|
options = options || { prototype: true };
|
|
|
|
return !!internals.isDeepEqual(obj, ref, options, []);
|
|
};
|