UNPKG

9.06 kBJavaScriptView Raw
1import isDate from 'is-date-object';
2import isArguments from 'is-arguments';
3import isPrimitive from 'is-primitive';
4import isObject from 'is-object';
5import isBuffer from 'is-buffer';
6import isString from 'is-string';
7import isError from 'is-error-x';
8import isMap from 'is-map-x';
9import isSet from 'is-set-x';
10import isNil from 'is-nil-x';
11import isRegExp from 'is-regexp-x';
12import indexOf from 'index-of-x';
13import slice from 'array-slice-x';
14import some from 'array-some-x';
15import filter from 'array-filter-x';
16import sort from 'stable';
17import $keys from 'object-keys-x';
18import $getPrototypeOf from 'get-prototype-of-x';
19import hasBoxedString from 'has-boxed-string-x'; // Check failure of by-index access of string characters (IE < 9)
20// and failure of `0 in boxedString` (Rhino)
21
22var hasBoxedStringBug = hasBoxedString === false; // Used to detect unsigned integer values.
23
24var reIsUint = /^(?:0|[1-9]\d*)$/;
25/* eslint-disable-next-line compat/compat */
26
27var hasMapEnumerables = typeof Map === 'function' ? $keys(new Map()) : [];
28/* eslint-disable-next-line compat/compat */
29
30var hasSetEnumerables = typeof Set === 'function' ? $keys(new Set()) : [];
31var hasErrorEnumerables;
32
33try {
34 // noinspection ExceptionCaughtLocallyJS
35 throw new Error('a');
36} catch (e) {
37 hasErrorEnumerables = $keys(e);
38}
39
40var indexNotFound = -1;
41var maxSafeIndex = 4294967295; // (2^32)-1
42
43/**
44 * Checks if `value` is a valid string index. Specifically for boxed string
45 * bug fix and not general purpose.
46 *
47 * @private
48 * @param {*} value - The value to check.
49 * @returns {boolean} Returns `true` if `value` is valid index, else `false`.
50 */
51
52var isIndex = function _isIndex(value) {
53 var num = indexNotFound;
54
55 if (reIsUint.test(value)) {
56 num = Number(value);
57 }
58
59 return num > indexNotFound && num % 1 === 0 && num < maxSafeIndex;
60};
61/**
62 * Get an object's key avoiding boxed string bug. Specifically for boxed
63 * string bug fix and not general purpose.
64 *
65 * @private
66 * @param {Array|string|object} object - The object to get the `value` from.
67 * @param {string|number} key - The `key` reference to the `value`.
68 * @param {boolean} isStr - Is the object a string.
69 * @param {boolean} isIdx - Is the `key` a character index.
70 * @returns {*} Returns the `value` referenced by the `key`.
71 */
72
73
74var getItem = function _getItem(object, key, isStr, isIdx) {
75 return isStr && isIdx ? object.charAt(key) : object[key];
76};
77/**
78 * Filter `keys` of unwanted Error enumerables. Specifically for Error has
79 * unwanted enumerables fix and not general purpose.
80 *
81 * @private
82 * @param {Array} keys - The Error object's keys.
83 * @param {Array} unwanted - The unwanted keys.
84 * @returns {Array} Returns the filtered keys.
85 */
86
87
88var filterUnwanted = function _filterUnwanted(keys, unwanted) {
89 return unwanted.length ? filter(keys, function _filter(key) {
90 return indexOf(unwanted, key) === indexNotFound;
91 }) : keys;
92};
93/**
94 * Tests for deep equality. Primitive values are compared with the equal
95 * comparison operator ( == ). This only considers enumerable properties.
96 * It does not test object prototypes, attached symbols, or non-enumerable
97 * properties. This can lead to some potentially surprising results. If
98 * `strict` is `true` then Primitive values are compared with the strict
99 * equal comparison operator ( === ).
100 *
101 * @private
102 * @param {*} actual - First comparison object.
103 * @param {*} expected - Second comparison object.
104 * @param {boolean} [strict] - Comparison mode. If set to `true` use `===`.
105 * @param {object} previousStack - The circular stack.
106 * @returns {boolean} `true` if `actual` and `expected` are deemed equal,
107 * otherwise `false`.
108 */
109
110
111var baseDeepEqual = function _baseDeepEqual(actual, expected, strict, previousStack) {
112 // 7.1. All identical values are equivalent, as determined by ===.
113 if (actual === expected) {
114 return true;
115 }
116
117 if (isBuffer(actual) && isBuffer(expected)) {
118 return actual.length === expected.length && some(actual, function _some1(item, index) {
119 return item !== expected[index];
120 }) === false;
121 } // 7.2. If the expected value is a Date object, the actual value is
122 // equivalent if it is also a Date object that refers to the same time.
123
124
125 if (isDate(actual) && isDate(expected)) {
126 return actual.getTime() === expected.getTime();
127 } // 7.3 If the expected value is a RegExp object, the actual value is
128 // equivalent if it is also a RegExp object with the same `source` and
129 // properties (`global`, `multiline`, `lastIndex`, `ignoreCase` & `sticky`).
130
131
132 if (isRegExp(actual) && isRegExp(expected)) {
133 return actual.toString() === expected.toString() && actual.lastIndex === expected.lastIndex;
134 } // 7.4. Other pairs that do not both pass typeof value == 'object',
135 // equivalence is determined by == or strict ===.
136
137
138 if (isObject(actual) === false && isObject(expected) === false) {
139 if (strict) {
140 return actual === expected;
141 } // noinspection EqualityComparisonWithCoercionJS
142
143
144 return actual == expected;
145 /* eslint-disable-line eqeqeq */
146 } // 7.5 For all other Object pairs, including Array objects, equivalence is
147 // determined by having the same number of owned properties (as verified
148 // with Object.prototype.hasOwnProperty.call), the same set of keys
149 // (although not necessarily the same order), equivalent values for every
150 // corresponding key, and an identical 'prototype' property. Note: this
151 // accounts for both named and indexed properties on Arrays.
152
153
154 if (isNil(actual) || isNil(expected)) {
155 return false;
156 }
157 /* jshint eqnull:false */
158 // This only considers enumerable properties. It does not test object
159 // prototypes, attached symbols, or non-enumerable properties. This can
160 // lead to some potentially surprising results.
161
162
163 if (strict && $getPrototypeOf(actual) !== $getPrototypeOf(expected)) {
164 return false;
165 } // if one is actual primitive, the other must be same
166
167
168 if (isPrimitive(actual) || isPrimitive(expected)) {
169 return actual === expected;
170 }
171
172 var ka = isArguments(actual);
173 var kb = isArguments(expected);
174 var aNotB = ka && kb === false;
175 var bNotA = ka === false && kb;
176
177 if (aNotB || bNotA) {
178 return false;
179 }
180
181 if (ka) {
182 if (ka.length !== kb.length) {
183 return false;
184 }
185
186 return baseDeepEqual(slice(actual), slice(expected), strict, null);
187 }
188
189 ka = $keys(actual);
190 kb = $keys(expected); // having the same number of owned properties (keys incorporates hasOwnProperty)
191
192 if (ka.length !== kb.length) {
193 return false;
194 }
195
196 if (isObject(actual)) {
197 if (isError(actual)) {
198 ka = filterUnwanted(ka, hasErrorEnumerables);
199 } else if (isMap(actual)) {
200 ka = filterUnwanted(ka, hasMapEnumerables);
201 } else if (isSet(actual)) {
202 ka = filterUnwanted(ka, hasSetEnumerables);
203 }
204 }
205
206 if (isObject(expected)) {
207 if (isError(expected)) {
208 kb = filterUnwanted(kb, hasErrorEnumerables);
209 } else if (isMap(expected)) {
210 kb = filterUnwanted(kb, hasMapEnumerables);
211 } else if (isSet(expected)) {
212 kb = filterUnwanted(kb, hasSetEnumerables);
213 }
214 } // the same set of keys (although not necessarily the same order),
215
216
217 sort.inplace(ka);
218 sort.inplace(kb);
219 var aIsString;
220 var bIsString;
221
222 if (hasBoxedStringBug) {
223 aIsString = isString(actual);
224 bIsString = isString(expected);
225 } // ~~~cheap key test
226 // equivalent values for every corresponding key, and
227 // ~~~possibly expensive deep test
228
229
230 return some(ka, function _some2(key, index) {
231 if (key !== kb[index]) {
232 return true;
233 }
234
235 var isIdx = (aIsString || bIsString) && isIndex(key);
236 var stack = previousStack || [actual];
237 var item = getItem(actual, key, aIsString, isIdx);
238 var isPrim = isPrimitive(item);
239
240 if (isPrim === false) {
241 if (indexOf(stack, item) !== indexNotFound) {
242 throw new RangeError('Circular object');
243 }
244
245 stack.push(item);
246 }
247
248 var result = baseDeepEqual(item, getItem(expected, key, bIsString, isIdx), strict, stack) === false;
249
250 if (isPrim === false) {
251 stack.pop();
252 }
253
254 return result;
255 }) === false;
256};
257/**
258 * Tests for deep equality. Primitive values are compared with the equal
259 * comparison operator ( == ). This only considers enumerable properties.
260 * It does not test object prototypes, attached symbols, or non-enumerable
261 * properties. This can lead to some potentially surprising results. If
262 * `strict` is `true` then Primitive values are compared with the strict
263 * equal comparison operator ( === ).
264 *
265 * @param {*} actual - First comparison object.
266 * @param {*} expected - Second comparison object.
267 * @param {boolean} [strict] - Comparison mode. If set to `true` use `===`.
268 * @returns {boolean} `true` if `actual` and `expected` are deemed equal,
269 * otherwise `false`.
270 * @see https://nodejs.org/api/assert.html
271 */
272
273
274var deepEqual = function deepEqual(actual, expected, strict) {
275 return baseDeepEqual(actual, expected, strict);
276};
277
278export default deepEqual;
279
280//# sourceMappingURL=deep-equal-x.esm.js.map
\No newline at end of file