1 | 'use strict';
|
2 |
|
3 | var assign = require('object.assign');
|
4 | var callBound = require('call-bind/callBound');
|
5 | var flags = require('regexp.prototype.flags');
|
6 | var GetIntrinsic = require('get-intrinsic');
|
7 | var getIterator = require('es-get-iterator');
|
8 | var getSideChannel = require('side-channel');
|
9 | var is = require('object-is');
|
10 | var isArguments = require('is-arguments');
|
11 | var isArray = require('isarray');
|
12 | var isArrayBuffer = require('is-array-buffer');
|
13 | var isDate = require('is-date-object');
|
14 | var isRegex = require('is-regex');
|
15 | var isSharedArrayBuffer = require('is-shared-array-buffer');
|
16 | var objectKeys = require('object-keys');
|
17 | var whichBoxedPrimitive = require('which-boxed-primitive');
|
18 | var whichCollection = require('which-collection');
|
19 | var whichTypedArray = require('which-typed-array');
|
20 | var byteLength = require('array-buffer-byte-length');
|
21 |
|
22 | var sabByteLength = callBound('SharedArrayBuffer.prototype.byteLength', true);
|
23 |
|
24 | var $getTime = callBound('Date.prototype.getTime');
|
25 | var gPO = Object.getPrototypeOf;
|
26 | var $objToString = callBound('Object.prototype.toString');
|
27 |
|
28 | var $Set = GetIntrinsic('%Set%', true);
|
29 | var $mapHas = callBound('Map.prototype.has', true);
|
30 | var $mapGet = callBound('Map.prototype.get', true);
|
31 | var $mapSize = callBound('Map.prototype.size', true);
|
32 | var $setAdd = callBound('Set.prototype.add', true);
|
33 | var $setDelete = callBound('Set.prototype.delete', true);
|
34 | var $setHas = callBound('Set.prototype.has', true);
|
35 | var $setSize = callBound('Set.prototype.size', true);
|
36 |
|
37 |
|
38 | function 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)) {
|
43 |
|
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
|
53 | function findLooseMatchingPrimitives(prim) {
|
54 | if (typeof prim === 'undefined') {
|
55 | return null;
|
56 | }
|
57 | if (typeof prim === 'object') {
|
58 | return void 0;
|
59 | }
|
60 | if (typeof prim === 'symbol') {
|
61 | return false;
|
62 | }
|
63 | if (typeof prim === 'string' || typeof prim === 'number') {
|
64 |
|
65 | return +prim === +prim;
|
66 | }
|
67 | return true;
|
68 | }
|
69 |
|
70 |
|
71 | function 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 |
|
81 | || !internalDeepEqual(item, curB, looseOpts, channel)
|
82 | ) {
|
83 | return false;
|
84 | }
|
85 |
|
86 | return !$mapHas(a, altValue) && internalDeepEqual(item, curB, looseOpts, channel);
|
87 | }
|
88 |
|
89 |
|
90 | function 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 |
|
100 | function 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 |
|
108 | internalDeepEqual(key1, key2, opts, channel)
|
109 |
|
110 | && internalDeepEqual(item1, $mapGet(map, key2), opts, channel)
|
111 | ) {
|
112 | $setDelete(set, key2);
|
113 | return true;
|
114 | }
|
115 | }
|
116 |
|
117 | return false;
|
118 | }
|
119 |
|
120 | function internalDeepEqual(actual, expected, options, channel) {
|
121 | var opts = options || {};
|
122 |
|
123 |
|
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 |
|
135 | if (!actual || !expected || (typeof actual !== 'object' && typeof expected !== 'object')) {
|
136 | return opts.strict ? is(actual, expected) : actual == expected;
|
137 | }
|
138 |
|
139 | |
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
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 |
|
163 | return objEquiv(actual, expected, opts, channel);
|
164 | }
|
165 |
|
166 | function 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 |
|
180 | function 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 |
|
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 |
|
222 | function 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 |
|
276 | function objEquiv(a, b, opts, channel) {
|
277 |
|
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 |
|
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) {
|
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) {
|
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) {
|
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) {
|
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) {
|
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 |
|
359 | if (ka.length !== kb.length) { return false; }
|
360 |
|
361 |
|
362 | ka.sort();
|
363 | kb.sort();
|
364 |
|
365 | for (i = ka.length - 1; i >= 0; i--) {
|
366 | if (ka[i] != kb[i]) { return false; }
|
367 | }
|
368 |
|
369 |
|
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') {
|
381 | return setEquiv(a, b, opts, channel);
|
382 | }
|
383 | if (aCollection === 'Map') {
|
384 | return mapEquiv(a, b, opts, channel);
|
385 | }
|
386 |
|
387 | return true;
|
388 | }
|
389 |
|
390 | module.exports = function deepEqual(a, b, opts) {
|
391 | return internalDeepEqual(a, b, opts, getSideChannel());
|
392 | };
|