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
			| 
											3 years ago
										 | '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, []); | ||
|  | }; |