UNPKG

13.3 kBJavaScriptView Raw
1'use strict';
2
3var assign = require('object.assign');
4var callBound = require('call-bind/callBound');
5var flags = require('regexp.prototype.flags');
6var GetIntrinsic = require('get-intrinsic');
7var getIterator = require('es-get-iterator');
8var getSideChannel = require('side-channel');
9var is = require('object-is');
10var isArguments = require('is-arguments');
11var isArray = require('isarray');
12var isArrayBuffer = require('is-array-buffer');
13var isDate = require('is-date-object');
14var isRegex = require('is-regex');
15var isSharedArrayBuffer = require('is-shared-array-buffer');
16var objectKeys = require('object-keys');
17var whichBoxedPrimitive = require('which-boxed-primitive');
18var whichCollection = require('which-collection');
19var whichTypedArray = require('which-typed-array');
20var byteLength = require('array-buffer-byte-length');
21
22var sabByteLength = callBound('SharedArrayBuffer.prototype.byteLength', true);
23
24var $getTime = callBound('Date.prototype.getTime');
25var gPO = Object.getPrototypeOf;
26var $objToString = callBound('Object.prototype.toString');
27
28var $Set = GetIntrinsic('%Set%', true);
29var $mapHas = callBound('Map.prototype.has', true);
30var $mapGet = callBound('Map.prototype.get', true);
31var $mapSize = callBound('Map.prototype.size', true);
32var $setAdd = callBound('Set.prototype.add', true);
33var $setDelete = callBound('Set.prototype.delete', true);
34var $setHas = callBound('Set.prototype.has', true);
35var $setSize = callBound('Set.prototype.size', true);
36
37// taken from https://github.com/browserify/commonjs-assert/blob/bba838e9ba9e28edf3127ce6974624208502f6bc/internal/util/comparisons.js#L401-L414
38function setHasEqualElement(set, val1, opts, channel) {
39 var i = getIterator(set);
40 var result;
41 while ((result = i.next()) && !result.done) {
42 if (internalDeepEqual(val1, result.value, opts, channel)) { // eslint-disable-line no-use-before-define
43 // Remove the matching element to make sure we do not check that again.
44 $setDelete(set, result.value);
45 return true;
46 }
47 }
48
49 return false;
50}
51
52// taken from https://github.com/browserify/commonjs-assert/blob/bba838e9ba9e28edf3127ce6974624208502f6bc/internal/util/comparisons.js#L416-L439
53function findLooseMatchingPrimitives(prim) {
54 if (typeof prim === 'undefined') {
55 return null;
56 }
57 if (typeof prim === 'object') { // Only pass in null as object!
58 return void 0;
59 }
60 if (typeof prim === 'symbol') {
61 return false;
62 }
63 if (typeof prim === 'string' || typeof prim === 'number') {
64 // Loose equal entries exist only if the string is possible to convert to a regular number and not NaN.
65 return +prim === +prim; // eslint-disable-line no-implicit-coercion
66 }
67 return true;
68}
69
70// taken from https://github.com/browserify/commonjs-assert/blob/bba838e9ba9e28edf3127ce6974624208502f6bc/internal/util/comparisons.js#L449-L460
71function mapMightHaveLoosePrim(a, b, prim, item, opts, channel) {
72 var altValue = findLooseMatchingPrimitives(prim);
73 if (altValue != null) {
74 return altValue;
75 }
76 var curB = $mapGet(b, altValue);
77 var looseOpts = assign({}, opts, { strict: false });
78 if (
79 (typeof curB === 'undefined' && !$mapHas(b, altValue))
80 // eslint-disable-next-line no-use-before-define
81 || !internalDeepEqual(item, curB, looseOpts, channel)
82 ) {
83 return false;
84 }
85 // eslint-disable-next-line no-use-before-define
86 return !$mapHas(a, altValue) && internalDeepEqual(item, curB, looseOpts, channel);
87}
88
89// taken from https://github.com/browserify/commonjs-assert/blob/bba838e9ba9e28edf3127ce6974624208502f6bc/internal/util/comparisons.js#L441-L447
90function setMightHaveLoosePrim(a, b, prim) {
91 var altValue = findLooseMatchingPrimitives(prim);
92 if (altValue != null) {
93 return altValue;
94 }
95
96 return $setHas(b, altValue) && !$setHas(a, altValue);
97}
98
99// taken from https://github.com/browserify/commonjs-assert/blob/bba838e9ba9e28edf3127ce6974624208502f6bc/internal/util/comparisons.js#L518-L533
100function mapHasEqualEntry(set, map, key1, item1, opts, channel) {
101 var i = getIterator(set);
102 var result;
103 var key2;
104 while ((result = i.next()) && !result.done) {
105 key2 = result.value;
106 if (
107 // eslint-disable-next-line no-use-before-define
108 internalDeepEqual(key1, key2, opts, channel)
109 // eslint-disable-next-line no-use-before-define
110 && internalDeepEqual(item1, $mapGet(map, key2), opts, channel)
111 ) {
112 $setDelete(set, key2);
113 return true;
114 }
115 }
116
117 return false;
118}
119
120function internalDeepEqual(actual, expected, options, channel) {
121 var opts = options || {};
122
123 // 7.1. All identical values are equivalent, as determined by ===.
124 if (opts.strict ? is(actual, expected) : actual === expected) {
125 return true;
126 }
127
128 var actualBoxed = whichBoxedPrimitive(actual);
129 var expectedBoxed = whichBoxedPrimitive(expected);
130 if (actualBoxed !== expectedBoxed) {
131 return false;
132 }
133
134 // 7.3. Other pairs that do not both pass typeof value == 'object', equivalence is determined by ==.
135 if (!actual || !expected || (typeof actual !== 'object' && typeof expected !== 'object')) {
136 return opts.strict ? is(actual, expected) : actual == expected; // eslint-disable-line eqeqeq
137 }
138
139 /*
140 * 7.4. For all other Object pairs, including Array objects, equivalence is
141 * determined by having the same number of owned properties (as verified
142 * with Object.prototype.hasOwnProperty.call), the same set of keys
143 * (although not necessarily the same order), equivalent values for every
144 * corresponding key, and an identical 'prototype' property. Note: this
145 * accounts for both named and indexed properties on Arrays.
146 */
147 // see https://github.com/nodejs/node/commit/d3aafd02efd3a403d646a3044adcf14e63a88d32 for memos/channel inspiration
148
149 var hasActual = channel.has(actual);
150 var hasExpected = channel.has(expected);
151 var sentinel;
152 if (hasActual && hasExpected) {
153 if (channel.get(actual) === channel.get(expected)) {
154 return true;
155 }
156 } else {
157 sentinel = {};
158 }
159 if (!hasActual) { channel.set(actual, sentinel); }
160 if (!hasExpected) { channel.set(expected, sentinel); }
161
162 // eslint-disable-next-line no-use-before-define
163 return objEquiv(actual, expected, opts, channel);
164}
165
166function isBuffer(x) {
167 if (!x || typeof x !== 'object' || typeof x.length !== 'number') {
168 return false;
169 }
170 if (typeof x.copy !== 'function' || typeof x.slice !== 'function') {
171 return false;
172 }
173 if (x.length > 0 && typeof x[0] !== 'number') {
174 return false;
175 }
176
177 return !!(x.constructor && x.constructor.isBuffer && x.constructor.isBuffer(x));
178}
179
180function setEquiv(a, b, opts, channel) {
181 if ($setSize(a) !== $setSize(b)) {
182 return false;
183 }
184 var iA = getIterator(a);
185 var iB = getIterator(b);
186 var resultA;
187 var resultB;
188 var set;
189 while ((resultA = iA.next()) && !resultA.done) {
190 if (resultA.value && typeof resultA.value === 'object') {
191 if (!set) { set = new $Set(); }
192 $setAdd(set, resultA.value);
193 } else if (!$setHas(b, resultA.value)) {
194 if (opts.strict) { return false; }
195 if (!setMightHaveLoosePrim(a, b, resultA.value)) {
196 return false;
197 }
198 if (!set) { set = new $Set(); }
199 $setAdd(set, resultA.value);
200 }
201 }
202 if (set) {
203 while ((resultB = iB.next()) && !resultB.done) {
204 // We have to check if a primitive value is already matching and only if it's not, go hunting for it.
205 if (resultB.value && typeof resultB.value === 'object') {
206 if (!setHasEqualElement(set, resultB.value, opts.strict, channel)) {
207 return false;
208 }
209 } else if (
210 !opts.strict
211 && !$setHas(a, resultB.value)
212 && !setHasEqualElement(set, resultB.value, opts.strict, channel)
213 ) {
214 return false;
215 }
216 }
217 return $setSize(set) === 0;
218 }
219 return true;
220}
221
222function mapEquiv(a, b, opts, channel) {
223 if ($mapSize(a) !== $mapSize(b)) {
224 return false;
225 }
226 var iA = getIterator(a);
227 var iB = getIterator(b);
228 var resultA;
229 var resultB;
230 var set;
231 var key;
232 var item1;
233 var item2;
234 while ((resultA = iA.next()) && !resultA.done) {
235 key = resultA.value[0];
236 item1 = resultA.value[1];
237 if (key && typeof key === 'object') {
238 if (!set) { set = new $Set(); }
239 $setAdd(set, key);
240 } else {
241 item2 = $mapGet(b, key);
242 if ((typeof item2 === 'undefined' && !$mapHas(b, key)) || !internalDeepEqual(item1, item2, opts, channel)) {
243 if (opts.strict) {
244 return false;
245 }
246 if (!mapMightHaveLoosePrim(a, b, key, item1, opts, channel)) {
247 return false;
248 }
249 if (!set) { set = new $Set(); }
250 $setAdd(set, key);
251 }
252 }
253 }
254
255 if (set) {
256 while ((resultB = iB.next()) && !resultB.done) {
257 key = resultB.value[0];
258 item2 = resultB.value[1];
259 if (key && typeof key === 'object') {
260 if (!mapHasEqualEntry(set, a, key, item2, opts, channel)) {
261 return false;
262 }
263 } else if (
264 !opts.strict
265 && (!a.has(key) || !internalDeepEqual($mapGet(a, key), item2, opts, channel))
266 && !mapHasEqualEntry(set, a, key, item2, assign({}, opts, { strict: false }), channel)
267 ) {
268 return false;
269 }
270 }
271 return $setSize(set) === 0;
272 }
273 return true;
274}
275
276function objEquiv(a, b, opts, channel) {
277 /* eslint max-statements: [2, 100], max-lines-per-function: [2, 120], max-depth: [2, 5], max-lines: [2, 400] */
278 var i, key;
279
280 if (typeof a !== typeof b) { return false; }
281 if (a == null || b == null) { return false; }
282
283 if ($objToString(a) !== $objToString(b)) { return false; }
284
285 if (isArguments(a) !== isArguments(b)) { return false; }
286
287 var aIsArray = isArray(a);
288 var bIsArray = isArray(b);
289 if (aIsArray !== bIsArray) { return false; }
290
291 // TODO: replace when a cross-realm brand check is available
292 var aIsError = a instanceof Error;
293 var bIsError = b instanceof Error;
294 if (aIsError !== bIsError) { return false; }
295 if (aIsError || bIsError) {
296 if (a.name !== b.name || a.message !== b.message) { return false; }
297 }
298
299 var aIsRegex = isRegex(a);
300 var bIsRegex = isRegex(b);
301 if (aIsRegex !== bIsRegex) { return false; }
302 if ((aIsRegex || bIsRegex) && (a.source !== b.source || flags(a) !== flags(b))) {
303 return false;
304 }
305
306 var aIsDate = isDate(a);
307 var bIsDate = isDate(b);
308 if (aIsDate !== bIsDate) { return false; }
309 if (aIsDate || bIsDate) { // && would work too, because both are true or both false here
310 if ($getTime(a) !== $getTime(b)) { return false; }
311 }
312 if (opts.strict && gPO && gPO(a) !== gPO(b)) { return false; }
313
314 var aWhich = whichTypedArray(a);
315 var bWhich = whichTypedArray(b);
316 if (aWhich !== bWhich) {
317 return false;
318 }
319 if (aWhich || bWhich) { // && would work too, because both are true or both false here
320 if (a.length !== b.length) { return false; }
321 for (i = 0; i < a.length; i++) {
322 if (a[i] !== b[i]) { return false; }
323 }
324 return true;
325 }
326
327 var aIsBuffer = isBuffer(a);
328 var bIsBuffer = isBuffer(b);
329 if (aIsBuffer !== bIsBuffer) { return false; }
330 if (aIsBuffer || bIsBuffer) { // && would work too, because both are true or both false here
331 if (a.length !== b.length) { return false; }
332 for (i = 0; i < a.length; i++) {
333 if (a[i] !== b[i]) { return false; }
334 }
335 return true;
336 }
337
338 var aIsArrayBuffer = isArrayBuffer(a);
339 var bIsArrayBuffer = isArrayBuffer(b);
340 if (aIsArrayBuffer !== bIsArrayBuffer) { return false; }
341 if (aIsArrayBuffer || bIsArrayBuffer) { // && would work too, because both are true or both false here
342 if (byteLength(a) !== byteLength(b)) { return false; }
343 return typeof Uint8Array === 'function' && internalDeepEqual(new Uint8Array(a), new Uint8Array(b), opts, channel);
344 }
345
346 var aIsSAB = isSharedArrayBuffer(a);
347 var bIsSAB = isSharedArrayBuffer(b);
348 if (aIsSAB !== bIsSAB) { return false; }
349 if (aIsSAB || bIsSAB) { // && would work too, because both are true or both false here
350 if (sabByteLength(a) !== sabByteLength(b)) { return false; }
351 return typeof Uint8Array === 'function' && internalDeepEqual(new Uint8Array(a), new Uint8Array(b), opts, channel);
352 }
353
354 if (typeof a !== typeof b) { return false; }
355
356 var ka = objectKeys(a);
357 var kb = objectKeys(b);
358 // having the same number of owned properties (keys incorporates hasOwnProperty)
359 if (ka.length !== kb.length) { return false; }
360
361 // the same set of keys (although not necessarily the same order),
362 ka.sort();
363 kb.sort();
364 // ~~~cheap key test
365 for (i = ka.length - 1; i >= 0; i--) {
366 if (ka[i] != kb[i]) { return false; } // eslint-disable-line eqeqeq
367 }
368
369 // equivalent values for every corresponding key, and ~~~possibly expensive deep test
370 for (i = ka.length - 1; i >= 0; i--) {
371 key = ka[i];
372 if (!internalDeepEqual(a[key], b[key], opts, channel)) { return false; }
373 }
374
375 var aCollection = whichCollection(a);
376 var bCollection = whichCollection(b);
377 if (aCollection !== bCollection) {
378 return false;
379 }
380 if (aCollection === 'Set' || bCollection === 'Set') { // aCollection === bCollection
381 return setEquiv(a, b, opts, channel);
382 }
383 if (aCollection === 'Map') { // aCollection === bCollection
384 return mapEquiv(a, b, opts, channel);
385 }
386
387 return true;
388}
389
390module.exports = function deepEqual(a, b, opts) {
391 return internalDeepEqual(a, b, opts, getSideChannel());
392};