UNPKG

deepy

Version:

A fast algorithm for comparing equality of values with strict equality for value types.

191 lines (182 loc) 6.13 kB
'use strict'; var util = require('util'); function isBuffer (value) { if (global.Buffer && typeof global.Buffer.isBuffer === 'function') { return global.Buffer.isBuffer(value); } return !!(value != null && value._isBuffer); }; function compare (a, b) { if (a === b) { return 0; } var x = a.length; var y = b.length; for (var i = 0, len = Math.min(x, y); i < len; ++i) { if (a[i] !== b[i]) { x = a[i]; y = b[i]; break; } } if (x < y) { return -1; } if (y < x) { return 1; } return 0; }; function isView (arrbuf) { if (isBuffer(arrbuf)) { return false; } if (typeof global.ArrayBuffer !== 'function') { return false; } if (typeof ArrayBuffer.isView === 'function') { return ArrayBuffer.isView(arrbuf); } if (!arrbuf) { return false; } if (arrbuf instanceof DataView) { return true; } if (arrbuf.buffer && arrbuf.buffer instanceof ArrayBuffer) { return true; } return false; }; function callToString (obj) { return Object.prototype.toString.call(obj); }; function isObject (value) { var type = typeof value; /* tslint:disable */ return !!value && (type == 'object' || type == 'function'); /* tslint:enable */ }; /* tslint:disable */ function isArguments (object) { return Object.prototype.toString.call(object) == '[object Arguments]'; }; /* tslint:enable */ function isObjectLike(value) { /* tslint:disable */ return !!value && typeof value == 'object'; /* tslint:enable */ } function deepy(actual, expected, strict, memos) { if (actual === expected) { return true; } // Identical objects are equal. `0 === -0`, but they aren't identical. // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). if (actual === expected) { /* tslint:disable */ return actual !== 0 || 1 / actual == 1 / expected; } if (actual == null || expected == null || (!isObject(actual) && !isObjectLike(expected))) { return actual !== actual && expected !== expected; } if (actual instanceof Error && expected instanceof Error) { /* tslint:disable */ return actual.message == expected.message; } if (isBuffer(actual) && isBuffer(expected)) { return compare(actual, expected) === 0; } if (util.isDate(actual) && util.isDate(expected)) { return actual.getTime() === expected.getTime(); } if (util.isRegExp(actual) && util.isRegExp(expected)) { return actual.source === expected.source && actual.global === expected.global && actual.multiline === expected.multiline && actual.lastIndex === expected.lastIndex && actual.ignoreCase === expected.ignoreCase; } if ((actual === null || typeof actual !== 'object') && (expected === null || typeof expected !== 'object')) { /* tslint:disable */ return strict ? actual === expected : actual == expected; } var className = toString.call(actual); if (className !== toString.call(expected)) { return false; } if (className === '[object RegExp]' || className === '[object String]') { // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is // equivalent to `new String("5")`. return '' + actual === '' + expected; } if (className === '[object Number]') { // `NaN`s are equivalent, but non-reflexive. // Object(NaN) is equivalent to NaN if (+actual !== +actual) { return +expected !== +expected; } // An `egal` comparison is performed for other numeric values. return +actual === 0 ? 1 / +actual === 1 / expected : +actual === +expected; } if (className === '[object Boolean]') { // Coerce dates and booleans to numeric primitive values. Dates are compared by their // millisecond representations. Note that invalid dates with millisecond representations // of `NaN` are not equivalent. return +actual === +expected; } if (isView(actual) && isView(expected) && callToString(actual) === callToString(expected) && !(actual instanceof Float32Array || actual instanceof Float64Array)) { return compare(new Uint8Array(actual.buffer), new Uint8Array(expected.buffer)) === 0; } if (isBuffer(actual) !== isBuffer(expected)) { return false; } memos = memos || { actual: [], expected: [] }; var actualIndex = memos.actual.indexOf(actual); if (actualIndex !== -1) { if (actualIndex === memos.expected.indexOf(expected)) { return true; } } memos.actual.push(actual); memos.expected.push(expected); if (actual === null || actual === undefined || expected === null || expected === undefined) { return false; } if (strict && Object.getPrototypeOf(actual) !== Object.getPrototypeOf(expected)) { return false; } var aIsArgs = isArguments(actual); var bIsArgs = isArguments(expected); if ((aIsArgs && !bIsArgs) || (!aIsArgs && bIsArgs)) { return false; } var ka = Object.keys(actual); var kb = Object.keys(expected); var key; var i; // having the same number of owned properties (keys incorporates // hasOwnProperty) if (ka.length !== kb.length) { return false; } // the same set of keys (although not necessarily the same order), ka.sort(); kb.sort(); // ~~~cheap key test for (i = ka.length - 1; i >= 0; i--) { if (ka[i] !== kb[i]) { return false; } } // equivalent values for every corresponding key, and // ~~~possibly expensive deep test for (i = ka.length - 1; i >= 0; i--) { key = ka[i]; if (!deepy(actual[key], expected[key], strict, memos)) { return false; } } return true; } module.exports = deepy;