| | '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) { |
| | 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; |
| | } |
| |
|
| | |
| | } |
| | else if (type !== 'object') { |
| | return obj !== obj && ref !== ref; |
| | } |
| |
|
| | const instanceType = internals.getSharedType(obj, ref, !!options.prototype); |
| | switch (instanceType) { |
| | case Types.buffer: |
| | return Buffer && Buffer.prototype.equals.call(obj, ref); |
| | 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; |
| | } |
| | } |
| |
|
| | 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 Set.prototype.values.call(obj)) { |
| | if (!Set.prototype.has.call(ref, 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) { |
| |
|
| | |
| |
|
| | 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)) { |
| |
|
| | |
| |
|
| | const ref2 = new Set(Set.prototype.values.call(ref)); |
| | for (const objEntry of Set.prototype.values.call(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 Map.prototype.entries.call(obj)) { |
| | if (value === undefined && !Map.prototype.has.call(ref, key)) { |
| | return false; |
| | } |
| |
|
| | if (!isDeepEqual(value, Map.prototype.get.call(ref, key), options, seen)) { |
| | return false; |
| | } |
| | } |
| | } |
| | else if (instanceType === Types.error) { |
| |
|
| | |
| |
|
| | if (obj.name !== ref.name || |
| | obj.message !== ref.message) { |
| |
|
| | return false; |
| | } |
| | } |
| |
|
| | |
| |
|
| | const valueOfObj = valueOf(obj); |
| | const valueOfRef = valueOf(ref); |
| | if ((obj !== valueOfObj || ref !== valueOfRef) && |
| | !isDeepEqual(valueOfObj, valueOfRef, options, seen)) { |
| |
|
| | return false; |
| | } |
| |
|
| | |
| |
|
| | 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; |
| | } |
| |
|
| | |
| |
|
| | if (options.symbols !== false) { |
| | 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; |
| | } |
| | }; |
| |
|