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, []);
 | |
| };
 |