1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213 |
x208790
x208790
x208790
x210407
x210407
x210407
x210407
x210407
x210407
x210407
x208612
x208612
x208612
x208612
x208612
x208629
x208629
x208629
x208629
x208629
x208629
x1216
x1216
x1216
x1292
x1866
x1866
x1292
x1292
x1216
x1216
x1178
x1178
x1178
x1178
x1178
x1178
x1178
x1178
x1178
x1178
x1178
x1178
x1178
x1178
x1178
x1178
x595919
x595919
x595919
x595919
x595919
x595919
x595919
x595919
x1178
x2827
x2827
x2827
x10156025
x10156025
x4464
x2827
x1178
x11131864
x11131864
x1178
x22649
x22649
x1000137
x1000137
x1000137
x1973181
x1973181
x1000137
x2182184
x2182199
x2182199
x2182184
x2393051
x2393051
x2182184
x2389920
x2389920
x2389920
x2389920
x2597643
x2597645
x2597645
x2597643
x2597645
x2597645
x2597643
x2597646
x2597646
x2597643
x2597645
x2597645
x2597643
x2597676
x2597676
x2805166
x2182184
x2390001
x2390006
x2390006
x7170519
x2390173
x2390089
x2390156
x2390187
x2390187
x2390270
x2544757
x2544757
x2544757
x2544757
x2544769
x2544769
x2544757
x2390238
x2390238
x2390077
x2390060
x2390094
x2390150
x2390150
x2390150
x2390150
x2390150
x2390177
x2390177
x2390177
x2390150
x2390094
x2390077
x2390077
x3012646
x2805212
x12050348
x2597634
x2597733
x2597693
x2597712
x2597712
x3013039
x3814492
x4616024
x4616024
x3814492
x3814494
x3814494
x3814492
x3012650
x3012650
x1972973
x22649
x22649 |
|
// Copyright 2018-2025 the Deno authors. MIT license.
// This module is browser compatible.
type KeyedCollection = Set<unknown> | Map<unknown, unknown>;
function isKeyedCollection(x: unknown): x is KeyedCollection {
return x instanceof Set || x instanceof Map;
}
function prototypesEqual(a: object, b: object) {
const pa = Object.getPrototypeOf(a);
const pb = Object.getPrototypeOf(b);
return pa === pb ||
pa === Object.prototype && pb === null ||
pa === null && pb === Object.prototype;
}
function isBasicObjectOrArray(obj: object) {
const proto = Object.getPrototypeOf(obj);
return proto === null || proto === Object.prototype ||
proto === Array.prototype;
}
// Slightly faster than Reflect.ownKeys in V8 as of 12.9.202.13-rusty (2024-10-28)
function ownKeys(obj: object) {
return [
...Object.getOwnPropertyNames(obj),
...Object.getOwnPropertySymbols(obj),
];
}
function getKeysDeep(obj: object) {
const keys = new Set<string | symbol>();
while (obj !== Object.prototype && obj !== Array.prototype && obj != null) {
for (const key of ownKeys(obj)) {
keys.add(key);
}
obj = Object.getPrototypeOf(obj);
}
return keys;
}
// deno-lint-ignore no-explicit-any
const Temporal = (globalThis as any).Temporal ?? Object.create(null);
/** A non-exhaustive list of prototypes that can be accurately fast-path compared with `String(instance)` */
const stringComparablePrototypes = new Set<unknown>(
[
Intl.Locale,
RegExp,
Temporal.Duration,
Temporal.Instant,
Temporal.PlainDate,
Temporal.PlainDateTime,
Temporal.PlainTime,
Temporal.PlainYearMonth,
Temporal.PlainMonthDay,
Temporal.ZonedDateTime,
URL,
URLSearchParams,
].filter((x) => x != null).map((x) => x.prototype),
);
function isPrimitive(x: unknown) {
return typeof x === "string" ||
typeof x === "number" ||
typeof x === "boolean" ||
typeof x === "bigint" ||
typeof x === "symbol" ||
x == null;
}
type TypedArray = Pick<Uint8Array | BigUint64Array, "length" | number>;
const TypedArray = Object.getPrototypeOf(Uint8Array);
function compareTypedArrays(a: TypedArray, b: TypedArray) {
if (a.length !== b.length) return false;
for (let i = 0; i < b.length; i++) {
if (!sameValueZero(a[i], b[i])) return false;
}
return true;
}
/** Check both strict equality (`0 == -0`) and `Object.is` (`NaN == NaN`) */
function sameValueZero(a: unknown, b: unknown) {
return a === b || Object.is(a, b);
}
/**
* Deep equality comparison used in assertions.
*
* @param a The actual value
* @param b The expected value
* @returns `true` if the values are deeply equal, `false` otherwise
*
* @example Usage
* ```ts
* import { equal } from "@std/assert/equal";
*
* equal({ foo: "bar" }, { foo: "bar" }); // Returns `true`
* equal({ foo: "bar" }, { foo: "baz" }); // Returns `false`
* ```
*/
export function equal(a: unknown, b: unknown): boolean {
const seen = new Map<unknown, unknown>();
return (function compare(a: unknown, b: unknown): boolean {
if (sameValueZero(a, b)) return true;
if (isPrimitive(a) || isPrimitive(b)) return false;
if (a instanceof Date && b instanceof Date) {
return Object.is(a.getTime(), b.getTime());
}
if (a && typeof a === "object" && b && typeof b === "object") {
if (!prototypesEqual(a, b)) {
return false;
}
if (a instanceof TypedArray) {
return compareTypedArrays(a as TypedArray, b as TypedArray);
}
if (a instanceof ArrayBuffer || a instanceof SharedArrayBuffer) {
return compareTypedArrays(
new Uint8Array(a),
new Uint8Array(b as ArrayBuffer | SharedArrayBuffer),
);
}
if (a instanceof WeakMap) {
throw new TypeError("Cannot compare WeakMap instances");
}
if (a instanceof WeakSet) {
throw new TypeError("Cannot compare WeakSet instances");
}
if (a instanceof WeakRef) {
return compare(a.deref(), (b as WeakRef<WeakKey>).deref());
}
if (seen.get(a) === b) {
return true;
}
if (Object.keys(a).length !== Object.keys(b).length) {
return false;
}
seen.set(a, b);
if (isKeyedCollection(a) && isKeyedCollection(b)) {
if (a.size !== b.size) {
return false;
}
const aKeys = [...a.keys()];
const primitiveKeysFastPath = aKeys.every(isPrimitive);
if (primitiveKeysFastPath) {
if (a instanceof Set) {
return a.symmetricDifference(b).size === 0;
}
for (const key of aKeys) {
if (
!b.has(key) ||
!compare(a.get(key), (b as Map<unknown, unknown>).get(key))
) {
return false;
}
}
return true;
}
let unmatchedEntries = a.size;
for (const [aKey, aValue] of a.entries()) {
for (const [bKey, bValue] of b.entries()) {
/* Given that Map keys can be references, we need
* to ensure that they are also deeply equal */
if (!compare(aKey, bKey)) continue;
if (
(aKey === aValue && bKey === bValue) ||
(compare(aValue, bValue))
) {
unmatchedEntries--;
break;
}
}
}
return unmatchedEntries === 0;
}
let keys: Iterable<string | symbol>;
if (isBasicObjectOrArray(a)) {
// fast path
keys = ownKeys({ ...a, ...b });
} else if (stringComparablePrototypes.has(Object.getPrototypeOf(a))) {
// medium path
return String(a) === String(b);
} else {
// slow path
keys = getKeysDeep(a).union(getKeysDeep(b));
}
for (const key of keys) {
type Key = keyof typeof a;
if (!compare(a[key as Key], b[key as Key])) {
return false;
}
if (((key in a) && (!(key in b))) || ((key in b) && (!(key in a)))) {
return false;
}
}
return true;
}
return false;
})(a, b);
}
|