318 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			318 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
'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) {                                                      // 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 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) {
 | 
						|
 | 
						|
            // 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(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) {
 | 
						|
 | 
						|
        // 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;
 | 
						|
    }
 | 
						|
};
 |