@gjsify/assert 0.0.4 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -1
- package/cjs-compat.cjs +6 -0
- package/lib/esm/assertion-error.js +71 -0
- package/lib/esm/deep-equal.js +525 -0
- package/lib/esm/index.js +462 -4
- package/lib/esm/inspect-fallback.js +75 -0
- package/lib/esm/strict/index.js +41 -4
- package/lib/types/assertion-error.d.ts +17 -0
- package/lib/types/deep-equal.d.ts +2 -0
- package/lib/types/index.d.ts +45 -0
- package/lib/types/inspect-fallback.d.ts +1 -0
- package/lib/types/strict/index.d.ts +2 -0
- package/package.json +15 -30
- package/src/assertion-error.ts +89 -0
- package/src/deep-equal.ts +630 -0
- package/src/index.spec.ts +487 -6
- package/src/index.ts +569 -3
- package/src/inspect-fallback.ts +101 -0
- package/src/strict/index.ts +21 -3
- package/tsconfig.json +21 -9
- package/tsconfig.tsbuildinfo +1 -0
- package/lib/cjs/index.js +0 -6
- package/lib/cjs/strict/index.js +0 -6
- package/test.gjs.js +0 -34758
- package/test.gjs.mjs +0 -34692
- package/test.gjs.mjs.meta.json +0 -1
- package/test.node.js +0 -1226
- package/test.node.mjs +0 -315
- package/tsconfig.types.json +0 -8
|
@@ -0,0 +1,630 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Adapted from Deno (refs/deno/ext/node/polyfills/internal/util/comparisons.ts)
|
|
3
|
+
// Copyright (c) 2018-2026 the Deno authors. MIT license.
|
|
4
|
+
// Modifications: No primordials, no Buffer.compare, no C++ bindings, no kPartial mode
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Deep equality comparison for Node.js assert module.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
type Memo = {
|
|
11
|
+
set: Set<unknown> | undefined;
|
|
12
|
+
a: unknown;
|
|
13
|
+
b: unknown;
|
|
14
|
+
c: unknown;
|
|
15
|
+
d: unknown;
|
|
16
|
+
deep: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const enum ValueType {
|
|
20
|
+
noIterator,
|
|
21
|
+
isArray,
|
|
22
|
+
isSet,
|
|
23
|
+
isMap,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const kStrict = 1;
|
|
27
|
+
const kLoose = 0;
|
|
28
|
+
|
|
29
|
+
// Type detection helpers (single-realm, instanceof is safe)
|
|
30
|
+
function isDate(v: unknown): v is Date { return v instanceof Date; }
|
|
31
|
+
function isRegExp(v: unknown): v is RegExp { return v instanceof RegExp; }
|
|
32
|
+
function isMap(v: unknown): v is Map<unknown, unknown> { return v instanceof Map; }
|
|
33
|
+
function isSet(v: unknown): v is Set<unknown> { return v instanceof Set; }
|
|
34
|
+
function isError(v: unknown): v is Error { return v instanceof Error; }
|
|
35
|
+
function isAnyArrayBuffer(v: unknown): v is ArrayBuffer | SharedArrayBuffer {
|
|
36
|
+
return v instanceof ArrayBuffer || (typeof SharedArrayBuffer !== 'undefined' && v instanceof SharedArrayBuffer);
|
|
37
|
+
}
|
|
38
|
+
function isArrayBufferView(v: unknown): v is ArrayBufferView {
|
|
39
|
+
return ArrayBuffer.isView(v);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isBoxedPrimitive(v: unknown): boolean {
|
|
43
|
+
return v instanceof Number || v instanceof String || v instanceof Boolean ||
|
|
44
|
+
v instanceof BigInt || v instanceof Symbol;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isNumberObject(v: unknown): v is Number { return v instanceof Number; }
|
|
48
|
+
function isStringObject(v: unknown): v is String { return v instanceof String; }
|
|
49
|
+
function isBooleanObject(v: unknown): v is Boolean { return v instanceof Boolean; }
|
|
50
|
+
function isBigIntObject(v: unknown): boolean {
|
|
51
|
+
return typeof BigInt !== 'undefined' && v instanceof Object && Object.prototype.toString.call(v) === '[object BigInt]';
|
|
52
|
+
}
|
|
53
|
+
function isSymbolObject(v: unknown): boolean {
|
|
54
|
+
return v instanceof Object && Object.prototype.toString.call(v) === '[object Symbol]';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isFloatTypedArray(v: unknown): boolean {
|
|
58
|
+
return v instanceof Float32Array || v instanceof Float64Array;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const hasOwn = (obj: unknown, prop: PropertyKey) => Object.prototype.hasOwnProperty.call(obj, prop);
|
|
62
|
+
const hasEnumerable = (obj: unknown, prop: PropertyKey) => Object.prototype.propertyIsEnumerable.call(obj, prop);
|
|
63
|
+
|
|
64
|
+
// Well-known constructors whose prototype check can be replaced with constructor check
|
|
65
|
+
const wellKnownConstructors = new Set<Function>([
|
|
66
|
+
Array, ArrayBuffer, Boolean, DataView, Date, Error,
|
|
67
|
+
Float32Array, Float64Array, Function,
|
|
68
|
+
Int8Array, Int16Array, Int32Array,
|
|
69
|
+
Map, Number, Object, Promise, RegExp, Set, String, Symbol,
|
|
70
|
+
Uint8Array, Uint16Array, Uint32Array, Uint8ClampedArray,
|
|
71
|
+
BigInt64Array, BigUint64Array,
|
|
72
|
+
WeakMap, WeakSet,
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get own non-index properties of an object.
|
|
77
|
+
* JS approximation of the C++ binding used in Node/Deno.
|
|
78
|
+
*/
|
|
79
|
+
function getOwnNonIndexProperties(obj: object, skipSymbols: boolean): (string | symbol)[] {
|
|
80
|
+
const keys = Object.getOwnPropertyNames(obj);
|
|
81
|
+
const result: (string | symbol)[] = [];
|
|
82
|
+
|
|
83
|
+
for (const key of keys) {
|
|
84
|
+
// Skip array indices
|
|
85
|
+
const num = Number(key);
|
|
86
|
+
if (Number.isInteger(num) && num >= 0 && num < 2 ** 32 - 1 && String(num) === key) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
// Only include enumerable properties (matches Node/Deno ONLY_ENUMERABLE behavior)
|
|
90
|
+
if (!hasEnumerable(obj, key)) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
result.push(key);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!skipSymbols) {
|
|
97
|
+
const symbols = Object.getOwnPropertySymbols(obj);
|
|
98
|
+
for (const sym of symbols) {
|
|
99
|
+
if (hasEnumerable(obj, sym)) {
|
|
100
|
+
result.push(sym);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function areSimilarRegExps(a: RegExp, b: RegExp): boolean {
|
|
109
|
+
return a.source === b.source && a.flags === b.flags && a.lastIndex === b.lastIndex;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function areSimilarFloatArrays(a: ArrayBufferView, b: ArrayBufferView): boolean {
|
|
113
|
+
if (a.byteLength !== b.byteLength) return false;
|
|
114
|
+
const viewA = a as unknown as ArrayLike<number>;
|
|
115
|
+
const viewB = b as unknown as ArrayLike<number>;
|
|
116
|
+
for (let i = 0; i < viewA.length; i++) {
|
|
117
|
+
if (viewA[i] !== viewB[i]) return false;
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function areSimilarTypedArrays(a: ArrayBufferView, b: ArrayBufferView): boolean {
|
|
123
|
+
if (a.byteLength !== b.byteLength) return false;
|
|
124
|
+
const viewA = new Uint8Array(a.buffer, a.byteOffset, a.byteLength);
|
|
125
|
+
const viewB = new Uint8Array(b.buffer, b.byteOffset, b.byteLength);
|
|
126
|
+
for (let i = 0; i < viewA.length; i++) {
|
|
127
|
+
if (viewA[i] !== viewB[i]) return false;
|
|
128
|
+
}
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function areEqualArrayBuffers(buf1: ArrayBuffer | SharedArrayBuffer, buf2: ArrayBuffer | SharedArrayBuffer): boolean {
|
|
133
|
+
if (buf1.byteLength !== buf2.byteLength) return false;
|
|
134
|
+
const a = new Uint8Array(buf1);
|
|
135
|
+
const b = new Uint8Array(buf2);
|
|
136
|
+
for (let i = 0; i < a.length; i++) {
|
|
137
|
+
if (a[i] !== b[i]) return false;
|
|
138
|
+
}
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function isEqualBoxedPrimitive(val1: unknown, val2: unknown): boolean {
|
|
143
|
+
if (isNumberObject(val1)) {
|
|
144
|
+
return isNumberObject(val2) && Object.is((val1 as Number).valueOf(), (val2 as Number).valueOf());
|
|
145
|
+
}
|
|
146
|
+
if (isStringObject(val1)) {
|
|
147
|
+
return isStringObject(val2) && (val1 as String).valueOf() === (val2 as String).valueOf();
|
|
148
|
+
}
|
|
149
|
+
if (isBooleanObject(val1)) {
|
|
150
|
+
return isBooleanObject(val2) && (val1 as Boolean).valueOf() === (val2 as Boolean).valueOf();
|
|
151
|
+
}
|
|
152
|
+
if (isBigIntObject(val1)) {
|
|
153
|
+
return isBigIntObject(val2) &&
|
|
154
|
+
(val1 as object & { [Symbol.toPrimitive]: (hint: string) => unknown })[Symbol.toPrimitive]('number') === (val2 as object & { [Symbol.toPrimitive]: (hint: string) => unknown })[Symbol.toPrimitive]('number');
|
|
155
|
+
}
|
|
156
|
+
if (isSymbolObject(val1)) {
|
|
157
|
+
return isSymbolObject(val2) &&
|
|
158
|
+
Symbol.prototype.valueOf.call(val1) === Symbol.prototype.valueOf.call(val2);
|
|
159
|
+
}
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function getTypedArrayTag(val: unknown): string | undefined {
|
|
164
|
+
return Object.prototype.toString.call(val).slice(8, -1);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function innerDeepEqual(
|
|
168
|
+
val1: unknown,
|
|
169
|
+
val2: unknown,
|
|
170
|
+
mode: number,
|
|
171
|
+
memos: Memo | null | undefined,
|
|
172
|
+
): boolean {
|
|
173
|
+
// Identical values
|
|
174
|
+
if (val1 === val2) {
|
|
175
|
+
return val1 !== 0 || Object.is(val1, val2) || mode === kLoose;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (mode !== kLoose) {
|
|
179
|
+
// Strict mode
|
|
180
|
+
if (typeof val1 === 'number') {
|
|
181
|
+
// NaN check
|
|
182
|
+
return val1 !== val1 && val2 !== val2;
|
|
183
|
+
}
|
|
184
|
+
if (typeof val2 !== 'object' || typeof val1 !== 'object' || val1 === null || val2 === null) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
// Loose mode
|
|
189
|
+
if (val1 === null || typeof val1 !== 'object') {
|
|
190
|
+
return (val2 === null || typeof val2 !== 'object') &&
|
|
191
|
+
// eslint-disable-next-line eqeqeq
|
|
192
|
+
(val1 == val2 || (val1 !== val1 && val2 !== val2));
|
|
193
|
+
}
|
|
194
|
+
if (val2 === null || typeof val2 !== 'object') {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return objectComparisonStart(val1 as object, val2 as object, mode, memos);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function objectComparisonStart(
|
|
203
|
+
val1: object,
|
|
204
|
+
val2: object,
|
|
205
|
+
mode: number,
|
|
206
|
+
memos: Memo | null | undefined,
|
|
207
|
+
): boolean {
|
|
208
|
+
// In strict mode, check constructors/prototypes
|
|
209
|
+
if (mode === kStrict) {
|
|
210
|
+
if (
|
|
211
|
+
wellKnownConstructors.has(val1.constructor as Function) ||
|
|
212
|
+
(val1.constructor !== undefined && !hasOwn(val1, 'constructor'))
|
|
213
|
+
) {
|
|
214
|
+
if (val1.constructor !== val2.constructor) {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
} else if (Object.getPrototypeOf(val1) !== Object.getPrototypeOf(val2)) {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const val1Tag = Object.prototype.toString.call(val1);
|
|
223
|
+
const val2Tag = Object.prototype.toString.call(val2);
|
|
224
|
+
|
|
225
|
+
if (val1Tag !== val2Tag) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (Array.isArray(val1)) {
|
|
230
|
+
if (!Array.isArray(val2) || val1.length !== val2.length) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
const keys2 = getOwnNonIndexProperties(val2, mode === kLoose);
|
|
234
|
+
if (keys2.length !== getOwnNonIndexProperties(val1, mode === kLoose).length) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
return keyCheck(val1, val2, mode, memos, ValueType.isArray, keys2);
|
|
238
|
+
} else if (val1Tag === '[object Object]') {
|
|
239
|
+
return keyCheck(val1, val2, mode, memos, ValueType.noIterator);
|
|
240
|
+
} else if (isDate(val1)) {
|
|
241
|
+
if (!isDate(val2)) return false;
|
|
242
|
+
const time1 = val1.getTime();
|
|
243
|
+
const time2 = val2.getTime();
|
|
244
|
+
if (time1 !== time2 && !(Number.isNaN(time1) && Number.isNaN(time2))) {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
} else if (isRegExp(val1)) {
|
|
248
|
+
if (!isRegExp(val2) || !areSimilarRegExps(val1, val2)) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
} else if (isArrayBufferView(val1)) {
|
|
252
|
+
if (getTypedArrayTag(val1) !== getTypedArrayTag(val2)) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
if (mode === kLoose && isFloatTypedArray(val1)) {
|
|
256
|
+
if (!areSimilarFloatArrays(val1, val2 as ArrayBufferView)) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
} else if (!areSimilarTypedArrays(val1, val2 as ArrayBufferView)) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
const keys2 = getOwnNonIndexProperties(val2, mode === kLoose);
|
|
263
|
+
if (keys2.length !== getOwnNonIndexProperties(val1, mode === kLoose).length) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
return keyCheck(val1, val2, mode, memos, ValueType.noIterator, keys2);
|
|
267
|
+
} else if (isSet(val1)) {
|
|
268
|
+
if (!isSet(val2) || val1.size !== val2.size) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
return keyCheck(val1, val2, mode, memos, ValueType.isSet);
|
|
272
|
+
} else if (isMap(val1)) {
|
|
273
|
+
if (!isMap(val2) || val1.size !== val2.size) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
return keyCheck(val1, val2, mode, memos, ValueType.isMap);
|
|
277
|
+
} else if (isAnyArrayBuffer(val1)) {
|
|
278
|
+
if (!isAnyArrayBuffer(val2) || !areEqualArrayBuffers(val1, val2 as ArrayBuffer)) {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
} else if (isError(val1)) {
|
|
282
|
+
if (
|
|
283
|
+
!isError(val2) ||
|
|
284
|
+
val1.message !== val2.message ||
|
|
285
|
+
val1.name !== val2.name
|
|
286
|
+
) {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
// Check cause if it exists
|
|
290
|
+
if (hasOwn(val1, 'cause') !== hasOwn(val2, 'cause')) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
if (hasOwn(val1, 'cause') && !innerDeepEqual((val1 as Error & { cause?: unknown }).cause, (val2 as Error & { cause?: unknown }).cause, mode, memos)) {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
} else if (isBoxedPrimitive(val1)) {
|
|
297
|
+
if (!isEqualBoxedPrimitive(val1, val2)) {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
} else if (
|
|
301
|
+
Array.isArray(val2) || isArrayBufferView(val2) || isSet(val2) || isMap(val2) ||
|
|
302
|
+
isDate(val2) || isRegExp(val2) || isAnyArrayBuffer(val2) || isBoxedPrimitive(val2) ||
|
|
303
|
+
isError(val2)
|
|
304
|
+
) {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return keyCheck(val1, val2, mode, memos, ValueType.noIterator);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function getEnumerables(val: unknown, keys: symbol[]): symbol[] {
|
|
312
|
+
return keys.filter((key) => hasEnumerable(val, key));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function keyCheck(
|
|
316
|
+
val1: unknown,
|
|
317
|
+
val2: unknown,
|
|
318
|
+
mode: number,
|
|
319
|
+
memos: Memo | null | undefined,
|
|
320
|
+
iterationType: ValueType,
|
|
321
|
+
keys2?: (string | symbol)[],
|
|
322
|
+
): boolean {
|
|
323
|
+
const isArrayLikeObject = keys2 !== undefined;
|
|
324
|
+
|
|
325
|
+
if (keys2 === undefined) {
|
|
326
|
+
keys2 = Object.keys(val2 as object);
|
|
327
|
+
}
|
|
328
|
+
let keys1: string[] | undefined;
|
|
329
|
+
|
|
330
|
+
if (!isArrayLikeObject) {
|
|
331
|
+
if (keys2.length !== (keys1 = Object.keys(val1 as object)).length) {
|
|
332
|
+
return false;
|
|
333
|
+
} else if (mode === kStrict) {
|
|
334
|
+
// Check symbol keys in strict mode
|
|
335
|
+
const symbolKeysA = Object.getOwnPropertySymbols(val1 as object);
|
|
336
|
+
if (symbolKeysA.length !== 0) {
|
|
337
|
+
let count = 0;
|
|
338
|
+
for (const key of symbolKeysA) {
|
|
339
|
+
if (hasEnumerable(val1, key)) {
|
|
340
|
+
if (!hasEnumerable(val2, key)) return false;
|
|
341
|
+
(keys2 as (string | symbol)[]).push(key);
|
|
342
|
+
count++;
|
|
343
|
+
} else if (hasEnumerable(val2, key)) {
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
const symbolKeysB = Object.getOwnPropertySymbols(val2 as object);
|
|
348
|
+
if (symbolKeysA.length !== symbolKeysB.length &&
|
|
349
|
+
getEnumerables(val2, symbolKeysB).length !== count) {
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
} else {
|
|
353
|
+
const symbolKeysB = Object.getOwnPropertySymbols(val2 as object);
|
|
354
|
+
if (symbolKeysB.length !== 0 && getEnumerables(val2, symbolKeysB).length !== 0) {
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (keys2.length === 0 &&
|
|
362
|
+
(iterationType === ValueType.noIterator ||
|
|
363
|
+
(iterationType === ValueType.isArray && (val2 as unknown[]).length === 0) ||
|
|
364
|
+
(val2 as unknown as { size?: number }).size === 0)) {
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (memos === null) {
|
|
369
|
+
return objEquiv(val1, val2, mode, keys1, keys2, memos, iterationType);
|
|
370
|
+
}
|
|
371
|
+
return handleCycles(val1, val2, mode, keys1, keys2, memos, iterationType);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function handleCycles(
|
|
375
|
+
val1: unknown,
|
|
376
|
+
val2: unknown,
|
|
377
|
+
mode: number,
|
|
378
|
+
keys1: string[] | undefined,
|
|
379
|
+
keys2: (string | symbol)[],
|
|
380
|
+
memos: Memo | undefined,
|
|
381
|
+
iterationType: ValueType,
|
|
382
|
+
): boolean {
|
|
383
|
+
if (memos === undefined) {
|
|
384
|
+
memos = {
|
|
385
|
+
set: undefined,
|
|
386
|
+
a: val1,
|
|
387
|
+
b: val2,
|
|
388
|
+
c: undefined,
|
|
389
|
+
d: undefined,
|
|
390
|
+
deep: false,
|
|
391
|
+
};
|
|
392
|
+
return objEquiv(val1, val2, mode, keys1, keys2, memos, iterationType);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (memos.set === undefined) {
|
|
396
|
+
if (memos.deep === false) {
|
|
397
|
+
if (memos.a === val1) return memos.b === val2;
|
|
398
|
+
if (memos.b === val2) return false;
|
|
399
|
+
memos.c = val1;
|
|
400
|
+
memos.d = val2;
|
|
401
|
+
memos.deep = true;
|
|
402
|
+
const result = objEquiv(val1, val2, mode, keys1, keys2, memos, iterationType);
|
|
403
|
+
memos.deep = false;
|
|
404
|
+
return result;
|
|
405
|
+
}
|
|
406
|
+
memos.set = new Set();
|
|
407
|
+
memos.set.add(memos.a);
|
|
408
|
+
memos.set.add(memos.b);
|
|
409
|
+
memos.set.add(memos.c);
|
|
410
|
+
memos.set.add(memos.d);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const { set } = memos;
|
|
414
|
+
const originalSize = set.size;
|
|
415
|
+
set.add(val1);
|
|
416
|
+
set.add(val2);
|
|
417
|
+
if (originalSize !== set.size - 2) {
|
|
418
|
+
return originalSize === set.size;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const areEq = objEquiv(val1, val2, mode, keys1, keys2, memos, iterationType);
|
|
422
|
+
set.delete(val1);
|
|
423
|
+
set.delete(val2);
|
|
424
|
+
return areEq;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function findLooseMatchingPrimitives(prim: unknown): boolean | undefined | null {
|
|
428
|
+
switch (typeof prim) {
|
|
429
|
+
case 'undefined': return null;
|
|
430
|
+
case 'object': return undefined;
|
|
431
|
+
case 'symbol': return false;
|
|
432
|
+
case 'string':
|
|
433
|
+
prim = +prim;
|
|
434
|
+
// falls through
|
|
435
|
+
case 'number':
|
|
436
|
+
if (prim !== prim) return false; // NaN
|
|
437
|
+
}
|
|
438
|
+
return true;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function setMightHaveLoosePrim(a: Set<unknown>, b: Set<unknown>, prim: unknown): boolean {
|
|
442
|
+
const altValue = findLooseMatchingPrimitives(prim);
|
|
443
|
+
if (altValue != null) return altValue as boolean;
|
|
444
|
+
return !b.has(altValue) && a.has(altValue);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function mapMightHaveLoosePrim(
|
|
448
|
+
a: Map<unknown, unknown>, b: Map<unknown, unknown>,
|
|
449
|
+
prim: unknown, item2: unknown, memo: Memo,
|
|
450
|
+
): boolean {
|
|
451
|
+
const altValue = findLooseMatchingPrimitives(prim);
|
|
452
|
+
if (altValue != null) return altValue as boolean;
|
|
453
|
+
const item1 = a.get(altValue);
|
|
454
|
+
if ((item1 === undefined && !a.has(altValue)) || !innerDeepEqual(item1, item2, kLoose, memo)) {
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
return !b.has(altValue) && innerDeepEqual(item1, item2, kLoose, memo);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function setEquiv(a: Set<unknown>, b: Set<unknown>, mode: number, memo: Memo | null): boolean {
|
|
461
|
+
let array: unknown[] | undefined;
|
|
462
|
+
|
|
463
|
+
for (const val of b) {
|
|
464
|
+
if (!a.has(val)) {
|
|
465
|
+
if ((typeof val !== 'object' || val === null) &&
|
|
466
|
+
(mode !== kLoose || !setMightHaveLoosePrim(a, b, val))) {
|
|
467
|
+
return false;
|
|
468
|
+
}
|
|
469
|
+
if (array === undefined) array = [];
|
|
470
|
+
array.push(val);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (array === undefined) return true;
|
|
475
|
+
|
|
476
|
+
for (const val1 of a) {
|
|
477
|
+
if (typeof val1 === 'object' && val1 !== null) {
|
|
478
|
+
if (!b.has(val1)) {
|
|
479
|
+
let found = false;
|
|
480
|
+
for (let i = 0; i < array.length; i++) {
|
|
481
|
+
if (innerDeepEqual(val1, array[i], mode, memo)) {
|
|
482
|
+
array.splice(i, 1);
|
|
483
|
+
found = true;
|
|
484
|
+
break;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if (!found) return false;
|
|
488
|
+
}
|
|
489
|
+
} else if (
|
|
490
|
+
!b.has(val1) &&
|
|
491
|
+
(mode !== kLoose || !setMightHaveLoosePrim(b, a, val1))
|
|
492
|
+
) {
|
|
493
|
+
let found = false;
|
|
494
|
+
for (let i = 0; i < array.length; i++) {
|
|
495
|
+
if (innerDeepEqual(val1, array[i], mode, memo)) {
|
|
496
|
+
array.splice(i, 1);
|
|
497
|
+
found = true;
|
|
498
|
+
break;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
if (!found) return false;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return array.length === 0;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function mapEquiv(a: Map<unknown, unknown>, b: Map<unknown, unknown>, mode: number, memo: Memo): boolean {
|
|
509
|
+
let array: unknown[] | undefined;
|
|
510
|
+
|
|
511
|
+
for (const [key2, item2] of b) {
|
|
512
|
+
if (typeof key2 === 'object' && key2 !== null) {
|
|
513
|
+
if (array === undefined) {
|
|
514
|
+
if (a.size === 1) {
|
|
515
|
+
const [key1, item1] = a.entries().next().value!;
|
|
516
|
+
return innerDeepEqual(key1, key2, mode, memo) && innerDeepEqual(item1, item2, mode, memo);
|
|
517
|
+
}
|
|
518
|
+
array = [];
|
|
519
|
+
}
|
|
520
|
+
array.push(key2);
|
|
521
|
+
} else {
|
|
522
|
+
const item1 = a.get(key2);
|
|
523
|
+
if ((item1 === undefined && !a.has(key2)) || !innerDeepEqual(item1, item2, mode, memo)) {
|
|
524
|
+
if (mode !== kLoose) return false;
|
|
525
|
+
if (!mapMightHaveLoosePrim(a, b, key2, item2, memo)) return false;
|
|
526
|
+
if (array === undefined) array = [];
|
|
527
|
+
array.push(key2);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (array === undefined) return true;
|
|
533
|
+
|
|
534
|
+
for (const [key1, item1] of a) {
|
|
535
|
+
if (typeof key1 === 'object' && key1 !== null) {
|
|
536
|
+
if (!b.has(key1)) {
|
|
537
|
+
let found = false;
|
|
538
|
+
for (let i = 0; i < array.length; i++) {
|
|
539
|
+
const key2 = array[i];
|
|
540
|
+
if (innerDeepEqual(key1, key2, mode, memo) && innerDeepEqual(item1, b.get(key2), mode, memo)) {
|
|
541
|
+
array.splice(i, 1);
|
|
542
|
+
found = true;
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
if (!found) return false;
|
|
547
|
+
}
|
|
548
|
+
} else if (
|
|
549
|
+
mode === kLoose && typeof key1 !== 'object' &&
|
|
550
|
+
(!a.has(key1) || !innerDeepEqual(item1, a.get(key1), mode, memo))
|
|
551
|
+
) {
|
|
552
|
+
// Loose mode primitive key mismatch handled above
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return array.length === 0;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function objEquiv(
|
|
560
|
+
a: unknown,
|
|
561
|
+
b: unknown,
|
|
562
|
+
mode: number,
|
|
563
|
+
keys1: string[] | undefined,
|
|
564
|
+
keys2: (string | symbol)[],
|
|
565
|
+
memos: Memo | null,
|
|
566
|
+
iterationType: ValueType,
|
|
567
|
+
): boolean {
|
|
568
|
+
if (keys2.length > 0) {
|
|
569
|
+
const aRec = a as Record<PropertyKey, unknown>;
|
|
570
|
+
const bRec = b as Record<PropertyKey, unknown>;
|
|
571
|
+
let i = 0;
|
|
572
|
+
if (keys1 !== undefined) {
|
|
573
|
+
for (; i < keys2.length; i++) {
|
|
574
|
+
const key = keys2[i];
|
|
575
|
+
if (keys1[i] !== key) break;
|
|
576
|
+
if (!innerDeepEqual(aRec[key], bRec[key], mode, memos)) return false;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
for (; i < keys2.length; i++) {
|
|
580
|
+
const key = keys2[i];
|
|
581
|
+
const descriptor = Object.getOwnPropertyDescriptor(a, key);
|
|
582
|
+
if (!descriptor?.enumerable ||
|
|
583
|
+
!innerDeepEqual(
|
|
584
|
+
descriptor.value !== undefined ? descriptor.value : aRec[key],
|
|
585
|
+
bRec[key], mode, memos)) {
|
|
586
|
+
return false;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (iterationType === ValueType.isArray) {
|
|
592
|
+
const aArr = a as unknown[];
|
|
593
|
+
const bArr = b as unknown[];
|
|
594
|
+
for (let i = 0; i < aArr.length; i++) {
|
|
595
|
+
if (bArr[i] === undefined && !hasOwn(b as object, i)) {
|
|
596
|
+
// Sparse array
|
|
597
|
+
if (aArr[i] !== undefined || hasOwn(a as object, i)) return false;
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
if (mode !== kLoose && aArr[i] === undefined && !hasOwn(a as object, i)) {
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
if (!innerDeepEqual(aArr[i], bArr[i], mode, memos)) return false;
|
|
604
|
+
}
|
|
605
|
+
} else if (iterationType === ValueType.isSet) {
|
|
606
|
+
if (!setEquiv(a as Set<unknown>, b as Set<unknown>, mode, memos)) return false;
|
|
607
|
+
} else if (iterationType === ValueType.isMap) {
|
|
608
|
+
if (!mapEquiv(a as Map<unknown, unknown>, b as Map<unknown, unknown>, mode, memos as Memo)) return false;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
return true;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// Cycle detection: start without memos, enable on stack overflow
|
|
615
|
+
let detectCycles = function (val1: unknown, val2: unknown, mode: number): boolean {
|
|
616
|
+
try {
|
|
617
|
+
return innerDeepEqual(val1, val2, mode, null);
|
|
618
|
+
} catch {
|
|
619
|
+
detectCycles = (v1: unknown, v2: unknown, m: number) => innerDeepEqual(v1, v2, m, undefined);
|
|
620
|
+
return innerDeepEqual(val1, val2, mode, undefined);
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
export function isDeepEqual(val1: unknown, val2: unknown): boolean {
|
|
625
|
+
return detectCycles(val1, val2, kLoose);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
export function isDeepStrictEqual(val1: unknown, val2: unknown): boolean {
|
|
629
|
+
return detectCycles(val1, val2, kStrict);
|
|
630
|
+
}
|