Skip to content

Commit 23a4c21

Browse files
committed
frontend: change equal argument types to unknown for safer typing
Using `unknown` enforces type safety by requiring explicit type checks or assertions before performing operations on the values. Also updated `Object.keys` usage with a more precise type assertion to satisfy TypeScript's stricter checks when working with `unknown`. This version now also supports date and regexp.
1 parent 7f35dcd commit 23a4c21

File tree

2 files changed

+70
-11
lines changed

2 files changed

+70
-11
lines changed

frontends/web/src/utils/equal.test.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,5 +114,33 @@ describe('equal', () => {
114114
expect(equal(a, null)).toBeFalsy();
115115
expect(equal(null, a)).toBeFalsy();
116116
});
117+
118+
it('deep compares nested structures', () => {
119+
const a = { foo: [1, { bar: 'baz' }] };
120+
const b = { foo: [1, { bar: 'baz' }] };
121+
expect(equal(a, b)).toBeTruthy();
122+
});
123+
124+
});
125+
126+
describe('RegExp, functions and dates are currently not supported', () => {
127+
128+
it('compares RegExp objects correctly', () => {
129+
expect(equal(/foo/g, /foo/g)).toBeTruthy();
130+
expect(equal(/foo/g, /bar/g)).toBeFalsy();
131+
});
132+
133+
it('compares Date objects correctly', () => {
134+
expect(equal(new Date('2020-01-01'), new Date('2020-01-01'))).toBeTruthy();
135+
expect(equal(new Date('2020-01-01'), new Date('2021-01-01'))).toBeFalsy();
136+
});
137+
138+
it('does not consider functions equal', () => {
139+
const a = () => {};
140+
const b = () => {};
141+
expect(equal(a, b)).toBeFalsy();
142+
});
143+
117144
});
145+
118146
});

frontends/web/src/utils/equal.ts

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,46 @@
1515
*/
1616

1717
const isArray = Array.isArray;
18-
const keyList = Object.keys;
1918
const hasProp = Object.prototype.hasOwnProperty;
19+
const typedKeys = <T extends object>(obj: Readonly<T>): readonly (keyof T)[] => {
20+
return Object.keys(obj) as (keyof T)[];
21+
};
2022

21-
export const equal = (a: any, b: any): boolean => {
23+
/**
24+
* Performs a deep equality check between two values.
25+
*
26+
* This function compares primitive types, arrays, plain objects, Date instances,
27+
* and RegExp objects. It returns true if the values are deeply equal, false otherwise.
28+
*
29+
* - Uses `Object.is` for primitive comparison (handles `NaN`, `-0`, etc.)
30+
* - Recursively checks array contents and object properties
31+
* - Properly compares Date and RegExp objects
32+
* - Returns false for functions, symbols, maps, sets, or class instances (not handled)
33+
*
34+
* @param a - The first value to compare.
35+
* @param b - The second value to compare.
36+
* @returns `true` if values are deeply equal, `false` otherwise.
37+
*/
38+
export const equal = (a: unknown, b: unknown): boolean => {
2239
if (Object.is(a, b)) {
2340
return true;
2441
}
2542

43+
if (a instanceof Date && b instanceof Date) {
44+
return a.getTime() === b.getTime();
45+
}
46+
47+
if (a instanceof RegExp && b instanceof RegExp) {
48+
return a.toString() === b.toString();
49+
}
50+
51+
if (
52+
(a instanceof Date) !== (b instanceof Date)
53+
|| (a instanceof RegExp) !== (b instanceof RegExp)
54+
) {
55+
return false;
56+
}
57+
2658
if (a && b && typeof a === 'object' && typeof b === 'object') {
2759
if (isArray(a) && isArray(b)) {
2860
if (a.length !== b.length) {
@@ -40,19 +72,18 @@ export const equal = (a: any, b: any): boolean => {
4072
return false;
4173
}
4274

43-
const length = keyList(a).length;
44-
if (length !== keyList(b).length) {
75+
const aKeys = typedKeys(a);
76+
const bKeys = typedKeys(b);
77+
78+
if (aKeys.length !== bKeys.length) {
4579
return false;
4680
}
4781

48-
for (let i = 0; i < length; i++) {
49-
if (!hasProp.call(b, keyList(a)[i])) {
82+
for (const key of aKeys) {
83+
if (!hasProp.call(b, key)) {
5084
return false;
5185
}
52-
}
53-
54-
for (let i = 0; i < length; i++) {
55-
if (!equal(a[keyList(a)[i]], b[keyList(a)[i]])) {
86+
if (!equal(a[key], b[key])) {
5687
return false;
5788
}
5889
}
@@ -61,4 +92,4 @@ export const equal = (a: any, b: any): boolean => {
6192
}
6293

6394
return false;
64-
}
95+
};

0 commit comments

Comments
 (0)