UNPKG

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