All files / assert / equal.ts

99.31% Branches 144/145
100.00% Functions 10/10
100.00% Lines 161/161
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
214
215
216
217
218
219
220
221
222
223
224
 
 
 
 
x208340
x208340
x208340
 
x209997
x209997
x209997
x209997
x209997
x209997
x209997
 
x208142
x208142
x208142
x208142
x208142
 
 
x208159
x208159
x208159
x208159
x208159
x208159
 
x38
x38
 
x38
x76
x574
x574
x76
x76
 
x38
x38
 
 
x1429
 
 
x1429
x1429
x1429
x1429
x1429
x1429
x1429
x1429
x1429
x1429
x1429
x1429
x1429
x1429
x1429
 
 
x606186
x606186
x606186
x606186
x606186
x606186
x606186
x606186
 
 
x1429
x1699
x1699
x1699
x10028482
x10028482
x1686
x1699
 
 
x1429
x11064635
x11064635
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x1429
x35381
x35381
x1036153
x1036153
 
x1036153
x169
x169
x1036153
x209997
x15
x15
x209997
x1695
x1695
x209997
x209997
x209997
x209997
x4
x4
x4
 
x4
x209518
x2
x2
x209518
x2
x2
x209518
x3
x3
x209518
x2
x2
x209518
x33
x33
x208241
x209997
x99
x5
x5
 
x94
x94
x98
x77
x34
x34
 
x66
x164548
x164548
x164548
x164548
x12
x12
x164548
x31
x31
 
x17
 
x59
x34
 
 
 
x56
 
x56
x56
x56
x56
x27
x27
x27
x56
x34
 
x17
x17
 
x208142
 
x208286
 
x208083
x209512
 
x40
x59
 
x19
x19
 
x209490
 
x836147
x81
x81
x836147
x2
x2
x836147
x208019
x208019
x18
x35381
x35381











































I


















































































































































































// Copyright 2018-2026 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.
 *
 * This function is based on value equality, but for some cases (such as data
 * that can only be read asynchronously or function properties) value equality
 * is not possible to determine. In such cases, reference equality is used
 * instead, which may cause false negatives for objects such as `Blob`s or
 * `Request`s.
 *
 * @param a The actual value
 * @param b The expected value
 * @returns `true` if the values are deeply equal, `false` otherwise
 *
 * @throws {TypeError} If either value is a `WeakMap` or `WeakSet`.
 *
 * @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 ||
        (globalThis.SharedArrayBuffer && 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);
}