Spaces:
Sleeping
Sleeping
| ; | |
| 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; | |
| } | |
| }; | |