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 |
x208789
x208789
x208789
x210402
x210402
x210402
x210402
x210402
x210402
x210402
x208613
x208613
x208613
x208613
x208613
x208639
x208639
x208639
x208639
x208639
x208639
x1270
x1270
x1270
x1358
x1992
x1992
x1358
x1358
x1270
x1270
x1226
x3678
x1226
x1226
x1226
x1226
x1226
x1226
x1226
x1226
x1226
x1226
x1226
x1226
x1226
x1226
x1226
x574823
x574823
x574823
x574823
x574823
x574823
x574823
x574823
x1226
x2870
x2870
x2870
x10303810
x10303810
x4503
x2870
x1226
x11312171
x11312171
x1226
x22807
x22807
x1032812
x1032812
x1032812
x2038328
x2038328
x1032812
x2247278
x2247293
x2247293
x2247278
x2458092
x2458092
x2247278
x2454958
x2454958
x2454956
x2454958
x2454958
x2454956
x2454959
x2454959
x2454956
x2454958
x2454958
x2454956
x2454989
x2454989
x2662431
x2247278
x2455044
x2455049
x2455049
x7365642
x2455214
x2455131
x2455197
x2455227
x2455227
x2455311
x2589769
x2589769
x2589769
x2589769
x2589781
x2589781
x2589769
x2455279
x2455279
x2455120
x2455103
x2455137
x2455193
x2455193
x2455193
x2455193
x2455193
x2455220
x2455220
x2455220
x2455193
x2455137
x2455120
x2455120
x3077544
x2870157
x12309928
x2662628
x2662730
x2662690
x2662712
x2662712
x3077933
x3931822
x4785785
x4785785
x3931822
x3931824
x3931824
x3931822
x3077552
x3077552
x2038120
x22807
x22807 |
|
// 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: any = (globalThis as any).Temporal ??
new Proxy({}, { get: () => {} });
/** 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 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);
}
|