1 | 'use strict';
|
2 |
|
3 | var objectKeys = require('object-keys');
|
4 | var isArguments = require('is-arguments');
|
5 | var is = require('object-is');
|
6 | var isRegex = require('is-regex');
|
7 | var flags = require('regexp.prototype.flags');
|
8 | var isArray = require('isarray');
|
9 | var isDate = require('is-date-object');
|
10 | var whichBoxedPrimitive = require('which-boxed-primitive');
|
11 | var GetIntrinsic = require('get-intrinsic');
|
12 | var callBound = require('call-bind/callBound');
|
13 | var whichCollection = require('which-collection');
|
14 | var getIterator = require('es-get-iterator');
|
15 | var getSideChannel = require('side-channel');
|
16 | var whichTypedArray = require('which-typed-array');
|
17 | var assign = require('object.assign');
|
18 |
|
19 | var $getTime = callBound('Date.prototype.getTime');
|
20 | var gPO = Object.getPrototypeOf;
|
21 | var $objToString = callBound('Object.prototype.toString');
|
22 |
|
23 | var $Set = GetIntrinsic('%Set%', true);
|
24 | var $mapHas = callBound('Map.prototype.has', true);
|
25 | var $mapGet = callBound('Map.prototype.get', true);
|
26 | var $mapSize = callBound('Map.prototype.size', true);
|
27 | var $setAdd = callBound('Set.prototype.add', true);
|
28 | var $setDelete = callBound('Set.prototype.delete', true);
|
29 | var $setHas = callBound('Set.prototype.has', true);
|
30 | var $setSize = callBound('Set.prototype.size', true);
|
31 |
|
32 |
|
33 | function 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)) {
|
38 |
|
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
|
48 | function findLooseMatchingPrimitives(prim) {
|
49 | if (typeof prim === 'undefined') {
|
50 | return null;
|
51 | }
|
52 | if (typeof prim === 'object') {
|
53 | return void 0;
|
54 | }
|
55 | if (typeof prim === 'symbol') {
|
56 | return false;
|
57 | }
|
58 | if (typeof prim === 'string' || typeof prim === 'number') {
|
59 |
|
60 | return +prim === +prim;
|
61 | }
|
62 | return true;
|
63 | }
|
64 |
|
65 |
|
66 | function 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 |
|
76 | || !internalDeepEqual(item, curB, looseOpts, channel)
|
77 | ) {
|
78 | return false;
|
79 | }
|
80 |
|
81 | return !$mapHas(a, altValue) && internalDeepEqual(item, curB, looseOpts, channel);
|
82 | }
|
83 |
|
84 |
|
85 | function 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 |
|
95 | function 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 |
|
103 | internalDeepEqual(key1, key2, opts, channel)
|
104 |
|
105 | && internalDeepEqual(item1, $mapGet(map, key2), opts, channel)
|
106 | ) {
|
107 | $setDelete(set, key2);
|
108 | return true;
|
109 | }
|
110 | }
|
111 |
|
112 | return false;
|
113 | }
|
114 |
|
115 | function internalDeepEqual(actual, expected, options, channel) {
|
116 | var opts = options || {};
|
117 |
|
118 |
|
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 |
|
130 | if (!actual || !expected || (typeof actual !== 'object' && typeof expected !== 'object')) {
|
131 | return opts.strict ? is(actual, expected) : actual == expected;
|
132 | }
|
133 |
|
134 | |
135 |
|
136 |
|
137 |
|
138 |
|
139 |
|
140 |
|
141 |
|
142 |
|
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 |
|
158 | return objEquiv(actual, expected, opts, channel);
|
159 | }
|
160 |
|
161 | function 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 |
|
175 | function 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 |
|
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 |
|
217 | function 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 |
|
271 | function objEquiv(a, b, opts, channel) {
|
272 |
|
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 |
|
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) {
|
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) {
|
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 |
|
329 | if (ka.length !== kb.length) { return false; }
|
330 |
|
331 |
|
332 | ka.sort();
|
333 | kb.sort();
|
334 |
|
335 | for (i = ka.length - 1; i >= 0; i--) {
|
336 | if (ka[i] != kb[i]) { return false; }
|
337 | }
|
338 |
|
339 |
|
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') {
|
351 | return setEquiv(a, b, opts, channel);
|
352 | }
|
353 | if (aCollection === 'Map') {
|
354 | return mapEquiv(a, b, opts, channel);
|
355 | }
|
356 |
|
357 | return true;
|
358 | }
|
359 |
|
360 | module.exports = function deepEqual(a, b, opts) {
|
361 | return internalDeepEqual(a, b, opts, getSideChannel());
|
362 | };
|