All files / expect / _equal.ts

93.79% Branches 136/145
90.96% Lines 171/188
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
225
226
227
228
229
230
 
 
 
 
 
x104
 
 
x435
x435
x435
 
x114
 
 
 
x114
 
x31105
x31105
x31105
 
 
 
 
 
x31105
x31109
x31109
 
x31105
x31186
x31186
x31105
 
 
 
 
 
 
 
x104
x720
x720
 
x720
x31721
x31721
x31806
x31806
 
x31721
x62948
x63345
x63345
x63345
x63345
x63345
x63421
x63421
x63345
x63143
 
 
 
x31721
x31721
x31721
x31721
x31721
x31721
x62683
x62683
 
x31721
x62695
x62695
 
 
x62695
x62698
x62698
x62710
x62710
x31721
x62679
x62679
x31721
x92966
x92966
x31721
x62649
x62649
x63170
x62647
x62708
x62708
x62647
x124562
x124562
x31721
x62934
x62937
x62937
x62934
 
x63221
x63221
x62934
 
x63221
x63221
x63220
x63222
x63222
 
 
 
x62934
x62934
 
x62934
x63222
x63222
 
x63502
x63775
x63976
x64333
x64333
x64333
x64333
x64333
x64343
x64343
x64333
x63976
 
x63775
x63980
x64334
x64334
x64334
x64334
x64334
x64336
x64336
x64334
x63980
x63775
 
x63498
x62934
x63273
x63277
x63277
 
x189966
x63322
x113387
x113387
x113387
x113387
x113387
x113387
x63322
x63273
x63310
x63326
x63326
 
x63310
x93330
x93330
x93330
x93330
x93340
x93340
x93330
x63321
x63321
 
x63285
 
x63273
x63292
 
 
 
x63318
 
x63318
x63318
x63318
x63318
x63330
x63330
x63330
x63318
x63292
 
x63285
x63285
x254908
x63727
x63727
x63727
x63727
x63502
x63502
 
x63834
x64263
x64263
 
 
 
 
 
 
x63834
x62934
 
x63224
x63224
x63337
x63337
x62679
x720
x720













I
I






I


















































































I



I






I
I



































































































I




I






// Copyright 2018-2025 the Deno authors. MIT license.

// This file is copied from `std/assert`.

import type { EqualOptions } from "./_types.ts";
import { AsymmetricMatcher } from "./_asymmetric_matchers.ts";

type KeyedCollection = Set<unknown> | Map<unknown, unknown>;
function isKeyedCollection(x: unknown): x is KeyedCollection {
  return x instanceof Set || x instanceof Map;
}

function constructorsEqual(a: object, b: object) {
  return a.constructor === b.constructor ||
    a.constructor === Object && !b.constructor ||
    !a.constructor && b.constructor === Object;
}

function asymmetricEqual(a: unknown, b: unknown) {
  const asymmetricA = a instanceof AsymmetricMatcher;
  const asymmetricB = b instanceof AsymmetricMatcher;

  if (asymmetricA && asymmetricB) {
    return undefined;
  }

  if (asymmetricA) {
    return a.equals(b);
  }

  if (asymmetricB) {
    return b.equals(a);
  }
}

/**
 * Deep equality comparison used in assertions
 * @param c actual value
 * @param d expected value
 * @param options for the equality check
 */
export function equal(c: unknown, d: unknown, options?: EqualOptions): boolean {
  const { customTesters = [], strictCheck } = options ?? {};
  const seen = new Map();

  return (function compare(a: unknown, b: unknown): boolean {
    const asymmetric = asymmetricEqual(a, b);
    if (asymmetric !== undefined) {
      return asymmetric;
    }

    if (customTesters?.length) {
      for (const customTester of customTesters) {
        const testContext = {
          equal,
        };
        const pass = customTester.call(testContext, a, b, customTesters);
        if (pass !== undefined) {
          return pass;
        }
      }
    }

    // Have to render RegExp & Date for string comparison
    // unless it's mistreated as object
    if (
      a &&
      b &&
      ((a instanceof RegExp && b instanceof RegExp) ||
        (a instanceof URL && b instanceof URL))
    ) {
      return String(a) === String(b);
    }

    if (a instanceof Date && b instanceof Date) {
      const aTime = a.getTime();
      const bTime = b.getTime();
      // Check for NaN equality manually since NaN is not
      // equal to itself.
      if (Number.isNaN(aTime) && Number.isNaN(bTime)) {
        return true;
      }
      return aTime === bTime;
    }
    if (a instanceof Error && b instanceof Error) {
      return a.message === b.message;
    }
    if (typeof a === "number" && typeof b === "number") {
      return Number.isNaN(a) && Number.isNaN(b) || a === b;
    }
    if (a === null || b === null) {
      return a === b;
    }
    const className = Object.prototype.toString.call(a);
    if (className !== Object.prototype.toString.call(b)) {
      return false;
    }
    if (Object.is(a, b)) {
      return true;
    }
    if (a && typeof a === "object" && b && typeof b === "object") {
      if (strictCheck && a && b && !constructorsEqual(a, b)) {
        return false;
      }
      if (a instanceof WeakMap || b instanceof WeakMap) {
        if (!(a instanceof WeakMap && b instanceof WeakMap)) return false;
        throw new TypeError("Cannot compare WeakMap instances");
      }
      if (a instanceof WeakSet || b instanceof WeakSet) {
        if (!(a instanceof WeakSet && b instanceof WeakSet)) return false;
        throw new TypeError("Cannot compare WeakSet instances");
      }
      if (seen.get(a) === b) {
        return true;
      }

      const aKeys = Object.keys(a ?? {});
      const bKeys = Object.keys(b ?? {});
      let aLen = aKeys.length;
      let bLen = bKeys.length;

      if (strictCheck && aLen !== bLen) {
        return false;
      }

      if (!strictCheck) {
        if (aLen > 0) {
          for (let i = 0; i < aKeys.length; i += 1) {
            const key = aKeys[i]!;
            if (
              (key in a) && (a[key as keyof typeof a] === undefined) &&
              !(key in b)
            ) {
              aLen -= 1;
            }
          }
        }

        if (bLen > 0) {
          for (let i = 0; i < bKeys.length; i += 1) {
            const key = bKeys[i]!;
            if (
              (key in b) && (b[key as keyof typeof b] === undefined) &&
              !(key in a)
            ) {
              bLen -= 1;
            }
          }
        }
      }

      seen.set(a, b);
      if (isKeyedCollection(a) && isKeyedCollection(b)) {
        if (a.size !== b.size) {
          return false;
        }

        const aKeys = [...a.keys()];
        const primitiveKeysFastPath = aKeys.every((k) => {
          return typeof k === "string" ||
            typeof k === "number" ||
            typeof k === "boolean" ||
            typeof k === "bigint" ||
            typeof k === "symbol" ||
            k == null;
        });
        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;
      }
      const merged = { ...a, ...b };
      for (
        const key of [
          ...Object.getOwnPropertyNames(merged),
          ...Object.getOwnPropertySymbols(merged),
        ]
      ) {
        type Key = keyof typeof merged;
        if (!compare(a && a[key as Key], b && b[key as Key])) {
          return false;
        }
        if (
          ((key in a) && (a[key as Key] !== undefined) && (!(key in b))) ||
          ((key in b) && (b[key as Key] !== undefined) && (!(key in a)))
        ) {
          return false;
        }
      }
      if (a instanceof WeakRef || b instanceof WeakRef) {
        if (!(a instanceof WeakRef && b instanceof WeakRef)) return false;
        return compare(a.deref(), b.deref());
      }
      return true;
    }
    return false;
  })(c, d);
}