fast-equals 4.0.3 → 5.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.babelrc +34 -0
- package/.prettierrc +4 -0
- package/CHANGELOG.md +39 -0
- package/README.md +145 -39
- package/build/rollup/config.base.js +49 -0
- package/build/rollup/config.cjs.js +10 -0
- package/build/rollup/config.esm.js +10 -0
- package/build/rollup/config.min.js +13 -0
- package/build/rollup/config.umd.js +10 -0
- package/build/tsconfig/base.json +32 -0
- package/build/tsconfig/cjs.json +8 -0
- package/build/tsconfig/declarations.json +9 -0
- package/build/tsconfig/esm.json +8 -0
- package/build/tsconfig/min.json +8 -0
- package/build/tsconfig/umd.json +8 -0
- package/build/webpack.config.js +57 -0
- package/dist/cjs/index.cjs +510 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/cjs/types/comparator.d.ts +2 -0
- package/dist/cjs/types/equals.d.ts +46 -0
- package/dist/cjs/types/index.d.ts +56 -0
- package/dist/cjs/types/internalTypes.d.ts +36 -0
- package/dist/cjs/types/utils.d.ts +19 -0
- package/dist/esm/index.mjs +499 -0
- package/dist/esm/index.mjs.map +1 -0
- package/dist/esm/types/comparator.d.ts +2 -0
- package/dist/esm/types/equals.d.ts +46 -0
- package/dist/esm/types/index.d.ts +56 -0
- package/dist/esm/types/internalTypes.d.ts +36 -0
- package/dist/esm/types/utils.d.ts +19 -0
- package/dist/min/index.js +1 -0
- package/dist/min/types/comparator.d.ts +2 -0
- package/dist/min/types/equals.d.ts +46 -0
- package/dist/min/types/index.d.ts +56 -0
- package/dist/min/types/internalTypes.d.ts +36 -0
- package/dist/min/types/utils.d.ts +19 -0
- package/dist/umd/index.js +516 -0
- package/dist/umd/index.js.map +1 -0
- package/dist/umd/types/comparator.d.ts +2 -0
- package/dist/umd/types/equals.d.ts +46 -0
- package/dist/umd/types/index.d.ts +56 -0
- package/dist/umd/types/internalTypes.d.ts +36 -0
- package/dist/umd/types/utils.d.ts +19 -0
- package/package.json +58 -37
- package/recipes/explicit-property-check.md +4 -6
- package/recipes/legacy-circular-equal-support.md +28 -20
- package/recipes/legacy-regexp-support.md +15 -16
- package/recipes/non-standard-properties.md +35 -23
- package/recipes/using-meta-in-comparison.md +10 -13
- package/src/comparator.ts +43 -40
- package/src/equals.ts +263 -0
- package/src/index.ts +183 -79
- package/src/internalTypes.ts +74 -0
- package/src/utils.ts +42 -49
- package/dist/fast-equals.cjs.js +0 -432
- package/dist/fast-equals.cjs.js.map +0 -1
- package/dist/fast-equals.esm.js +0 -422
- package/dist/fast-equals.esm.js.map +0 -1
- package/dist/fast-equals.js +0 -438
- package/dist/fast-equals.js.map +0 -1
- package/dist/fast-equals.min.js +0 -1
- package/dist/fast-equals.mjs +0 -422
- package/dist/fast-equals.mjs.map +0 -1
- package/index.d.ts +0 -58
- package/recipes/strict-property-descriptor-check.md +0 -42
- package/src/arrays.ts +0 -36
- package/src/dates.ts +0 -12
- package/src/maps.ts +0 -66
- package/src/objects.ts +0 -62
- package/src/regexps.ts +0 -11
- package/src/sets.ts +0 -61
package/src/equals.ts
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { getStrictProperties, hasOwn, sameValueZeroEqual } from './utils';
|
|
2
|
+
import type { Dictionary, State } from './internalTypes';
|
|
3
|
+
|
|
4
|
+
const OWNER = '_owner';
|
|
5
|
+
|
|
6
|
+
const { getOwnPropertyDescriptor, keys } = Object;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Whether the arrays are equal in value.
|
|
10
|
+
*/
|
|
11
|
+
export function areArraysEqual(a: any[], b: any[], state: State<any>) {
|
|
12
|
+
let index = a.length;
|
|
13
|
+
|
|
14
|
+
if (b.length !== index) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
while (index-- > 0) {
|
|
19
|
+
if (!state.equals(a[index], b[index], index, index, a, b, state)) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Whether the dates passed are equal in value.
|
|
29
|
+
*
|
|
30
|
+
* @NOTE
|
|
31
|
+
* This is a standalone function instead of done inline in the comparator
|
|
32
|
+
* to allow for overrides.
|
|
33
|
+
*/
|
|
34
|
+
export function areDatesEqual(a: Date, b: Date): boolean {
|
|
35
|
+
return sameValueZeroEqual(a.getTime(), b.getTime());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Whether the `Map`s are equal in value.
|
|
40
|
+
*/
|
|
41
|
+
export function areMapsEqual(
|
|
42
|
+
a: Map<any, any>,
|
|
43
|
+
b: Map<any, any>,
|
|
44
|
+
state: State<any>,
|
|
45
|
+
): boolean {
|
|
46
|
+
let isValueEqual = a.size === b.size;
|
|
47
|
+
|
|
48
|
+
if (isValueEqual && a.size) {
|
|
49
|
+
// The use of `forEach()` is to avoid the transpilation cost of `for...of` comparisons, and
|
|
50
|
+
// the inability to control the performance of the resulting code. It also avoids excessive
|
|
51
|
+
// iteration compared to doing comparisons of `keys()` and `values()`. As a result, though,
|
|
52
|
+
// we cannot short-circuit the iterations; bookkeeping must be done to short-circuit the
|
|
53
|
+
// equality checks themselves.
|
|
54
|
+
|
|
55
|
+
const matchedIndices: Record<number, true> = {};
|
|
56
|
+
|
|
57
|
+
let indexA = 0;
|
|
58
|
+
|
|
59
|
+
a.forEach((aValue, aKey) => {
|
|
60
|
+
if (!isValueEqual) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let hasMatch = false;
|
|
65
|
+
let matchIndexB = 0;
|
|
66
|
+
|
|
67
|
+
b.forEach((bValue, bKey) => {
|
|
68
|
+
if (
|
|
69
|
+
!hasMatch &&
|
|
70
|
+
!matchedIndices[matchIndexB] &&
|
|
71
|
+
(hasMatch =
|
|
72
|
+
state.equals(aKey, bKey, indexA, matchIndexB, a, b, state) &&
|
|
73
|
+
state.equals(aValue, bValue, aKey, bKey, a, b, state))
|
|
74
|
+
) {
|
|
75
|
+
matchedIndices[matchIndexB] = true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
matchIndexB++;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
indexA++;
|
|
82
|
+
isValueEqual = hasMatch;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return isValueEqual;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Whether the objects are equal in value.
|
|
91
|
+
*/
|
|
92
|
+
export function areObjectsEqual(
|
|
93
|
+
a: Dictionary,
|
|
94
|
+
b: Dictionary,
|
|
95
|
+
state: State<any>,
|
|
96
|
+
): boolean {
|
|
97
|
+
const properties = keys(a);
|
|
98
|
+
|
|
99
|
+
let index = properties.length;
|
|
100
|
+
|
|
101
|
+
if (keys(b).length !== index) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let property: string | symbol;
|
|
106
|
+
|
|
107
|
+
// Decrementing `while` showed faster results than either incrementing or
|
|
108
|
+
// decrementing `for` loop and than an incrementing `while` loop. Declarative
|
|
109
|
+
// methods like `some` / `every` were not used to avoid incurring the garbage
|
|
110
|
+
// cost of anonymous callbacks.
|
|
111
|
+
while (index-- > 0) {
|
|
112
|
+
property = properties[index]!;
|
|
113
|
+
|
|
114
|
+
if (
|
|
115
|
+
property === OWNER &&
|
|
116
|
+
(a.$$typeof || b.$$typeof) &&
|
|
117
|
+
a.$$typeof !== b.$$typeof
|
|
118
|
+
) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (
|
|
123
|
+
!hasOwn(b, property) ||
|
|
124
|
+
!state.equals(a[property], b[property], property, property, a, b, state)
|
|
125
|
+
) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Whether the objects are equal in value with strict property checking.
|
|
135
|
+
*/
|
|
136
|
+
export function areObjectsEqualStrict(
|
|
137
|
+
a: Dictionary,
|
|
138
|
+
b: Dictionary,
|
|
139
|
+
state: State<any>,
|
|
140
|
+
): boolean {
|
|
141
|
+
const properties = getStrictProperties(a);
|
|
142
|
+
|
|
143
|
+
let index = properties.length;
|
|
144
|
+
|
|
145
|
+
if (getStrictProperties(b).length !== index) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let property: string | symbol;
|
|
150
|
+
let descriptorA: ReturnType<typeof getOwnPropertyDescriptor>;
|
|
151
|
+
let descriptorB: ReturnType<typeof getOwnPropertyDescriptor>;
|
|
152
|
+
|
|
153
|
+
// Decrementing `while` showed faster results than either incrementing or
|
|
154
|
+
// decrementing `for` loop and than an incrementing `while` loop. Declarative
|
|
155
|
+
// methods like `some` / `every` were not used to avoid incurring the garbage
|
|
156
|
+
// cost of anonymous callbacks.
|
|
157
|
+
while (index-- > 0) {
|
|
158
|
+
property = properties[index]!;
|
|
159
|
+
|
|
160
|
+
if (
|
|
161
|
+
property === OWNER &&
|
|
162
|
+
(a.$$typeof || b.$$typeof) &&
|
|
163
|
+
a.$$typeof !== b.$$typeof
|
|
164
|
+
) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (!hasOwn(b, property)) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (
|
|
173
|
+
!state.equals(a[property], b[property], property, property, a, b, state)
|
|
174
|
+
) {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
descriptorA = getOwnPropertyDescriptor(a, property);
|
|
179
|
+
descriptorB = getOwnPropertyDescriptor(b, property);
|
|
180
|
+
|
|
181
|
+
if (
|
|
182
|
+
(descriptorA || descriptorB) &&
|
|
183
|
+
(!descriptorA ||
|
|
184
|
+
!descriptorB ||
|
|
185
|
+
descriptorA.configurable !== descriptorB.configurable ||
|
|
186
|
+
descriptorA.enumerable !== descriptorB.enumerable ||
|
|
187
|
+
descriptorA.writable !== descriptorB.writable)
|
|
188
|
+
) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Whether the primitive wrappers passed are equal in value.
|
|
198
|
+
*
|
|
199
|
+
* @NOTE
|
|
200
|
+
* This is a standalone function instead of done inline in the comparator
|
|
201
|
+
* to allow for overrides.
|
|
202
|
+
*/
|
|
203
|
+
export function arePrimitiveWrappersEqual(a: Date, b: Date): boolean {
|
|
204
|
+
return sameValueZeroEqual(a.valueOf(), b.valueOf());
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Whether the regexps passed are equal in value.
|
|
209
|
+
*
|
|
210
|
+
* @NOTE
|
|
211
|
+
* This is a standalone function instead of done inline in the comparator
|
|
212
|
+
* to allow for overrides. An example of this would be supporting a
|
|
213
|
+
* pre-ES2015 environment where the `flags` property is not available.
|
|
214
|
+
*/
|
|
215
|
+
export function areRegExpsEqual(a: RegExp, b: RegExp): boolean {
|
|
216
|
+
return a.source === b.source && a.flags === b.flags;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Whether the `Set`s are equal in value.
|
|
221
|
+
*/
|
|
222
|
+
export function areSetsEqual(
|
|
223
|
+
a: Set<any>,
|
|
224
|
+
b: Set<any>,
|
|
225
|
+
state: State<any>,
|
|
226
|
+
): boolean {
|
|
227
|
+
let isValueEqual = a.size === b.size;
|
|
228
|
+
|
|
229
|
+
if (isValueEqual && a.size) {
|
|
230
|
+
// The use of `forEach()` is to avoid the transpilation cost of `for...of` comparisons, and
|
|
231
|
+
// the inability to control the performance of the resulting code. It also avoids excessive
|
|
232
|
+
// iteration compared to doing comparisons of `keys()` and `values()`. As a result, though,
|
|
233
|
+
// we cannot short-circuit the iterations; bookkeeping must be done to short-circuit the
|
|
234
|
+
// equality checks themselves.
|
|
235
|
+
|
|
236
|
+
const matchedIndices: Record<number, true> = {};
|
|
237
|
+
|
|
238
|
+
a.forEach((aValue, aKey) => {
|
|
239
|
+
if (!isValueEqual) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
let hasMatch = false;
|
|
244
|
+
let matchIndex = 0;
|
|
245
|
+
|
|
246
|
+
b.forEach((bValue, bKey) => {
|
|
247
|
+
if (
|
|
248
|
+
!hasMatch &&
|
|
249
|
+
!matchedIndices[matchIndex] &&
|
|
250
|
+
(hasMatch = state.equals(aValue, bValue, aKey, bKey, a, b, state))
|
|
251
|
+
) {
|
|
252
|
+
matchedIndices[matchIndex] = true;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
matchIndex++;
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
isValueEqual = hasMatch;
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return isValueEqual;
|
|
263
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,121 +1,225 @@
|
|
|
1
1
|
import { createComparator } from './comparator';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
import {
|
|
3
|
+
areArraysEqual,
|
|
4
|
+
areDatesEqual,
|
|
5
|
+
areMapsEqual,
|
|
6
|
+
areObjectsEqual,
|
|
7
|
+
areObjectsEqualStrict,
|
|
8
|
+
arePrimitiveWrappersEqual,
|
|
9
|
+
areRegExpsEqual,
|
|
10
|
+
areSetsEqual,
|
|
11
|
+
} from './equals';
|
|
10
12
|
import type {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
CircularState,
|
|
14
|
+
ComparatorConfig,
|
|
15
|
+
CreateCustomComparatorConfig,
|
|
16
|
+
CreateState,
|
|
17
|
+
DefaultState,
|
|
13
18
|
EqualityComparator,
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
} from './internalTypes';
|
|
20
|
+
import {
|
|
21
|
+
combineComparators,
|
|
22
|
+
createInternalComparator,
|
|
23
|
+
createIsCircular,
|
|
24
|
+
sameValueZeroEqual,
|
|
25
|
+
} from './utils';
|
|
16
26
|
|
|
17
27
|
export { sameValueZeroEqual };
|
|
18
28
|
|
|
19
|
-
|
|
20
|
-
|
|
29
|
+
interface DefaultEqualCreatorOptions<Meta> {
|
|
30
|
+
comparator?: EqualityComparator<Meta>;
|
|
31
|
+
circular?: boolean;
|
|
32
|
+
strict?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface CustomEqualCreatorOptions<Meta>
|
|
36
|
+
extends DefaultEqualCreatorOptions<Meta> {
|
|
37
|
+
createCustomConfig?: CreateCustomComparatorConfig<Meta>;
|
|
38
|
+
createInternalComparator?: typeof createInternalComparator;
|
|
39
|
+
createState?: CreateState<Meta>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function createComparatorConfig<Meta>({
|
|
43
|
+
circular,
|
|
44
|
+
strict,
|
|
45
|
+
}: DefaultEqualCreatorOptions<Meta>) {
|
|
46
|
+
const config: ComparatorConfig<Meta> = {
|
|
21
47
|
areArraysEqual,
|
|
22
48
|
areDatesEqual,
|
|
23
49
|
areMapsEqual,
|
|
24
50
|
areObjectsEqual,
|
|
51
|
+
arePrimitiveWrappersEqual,
|
|
25
52
|
areRegExpsEqual,
|
|
26
53
|
areSetsEqual,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
if (strict) {
|
|
57
|
+
config.areArraysEqual = areObjectsEqual;
|
|
58
|
+
config.areMapsEqual = combineComparators(areMapsEqual, areObjectsEqual);
|
|
59
|
+
config.areObjectsEqual = areObjectsEqualStrict;
|
|
60
|
+
config.areSetsEqual = combineComparators(areSetsEqual, areObjectsEqual);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (circular) {
|
|
64
|
+
config.areArraysEqual = createIsCircular(config.areArraysEqual);
|
|
65
|
+
config.areMapsEqual = createIsCircular(config.areMapsEqual);
|
|
66
|
+
config.areObjectsEqual = createIsCircular(config.areObjectsEqual);
|
|
67
|
+
config.areSetsEqual = createIsCircular(config.areSetsEqual);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return config;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function createDefaultEqualCreator(
|
|
74
|
+
options: DefaultEqualCreatorOptions<undefined> = {},
|
|
75
|
+
) {
|
|
76
|
+
const config = createComparatorConfig(options);
|
|
77
|
+
const isEqual = createComparator(config);
|
|
78
|
+
const isEqualComparator =
|
|
79
|
+
options.comparator || createInternalComparator(isEqual);
|
|
80
|
+
const strict = !!options.strict;
|
|
81
|
+
|
|
82
|
+
if (options.circular) {
|
|
83
|
+
return function equals<A, B>(a: A, b: B): boolean {
|
|
84
|
+
return isEqual(a, b, {
|
|
85
|
+
cache: new WeakMap(),
|
|
86
|
+
equals: isEqualComparator,
|
|
87
|
+
meta: undefined,
|
|
88
|
+
strict,
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const state = Object.freeze({
|
|
94
|
+
cache: undefined,
|
|
95
|
+
equals: isEqualComparator,
|
|
96
|
+
meta: undefined,
|
|
97
|
+
strict,
|
|
39
98
|
});
|
|
40
99
|
|
|
41
|
-
|
|
100
|
+
return function equals<A, B>(a: A, b: B): boolean {
|
|
101
|
+
return isEqual(a, b, state);
|
|
102
|
+
};
|
|
103
|
+
}
|
|
42
104
|
|
|
43
105
|
/**
|
|
44
106
|
* Whether the items passed are deeply-equal in value.
|
|
45
107
|
*/
|
|
46
|
-
export
|
|
47
|
-
return isDeepEqual(a, b, undefined);
|
|
48
|
-
}
|
|
108
|
+
export const deepEqual = createDefaultEqualCreator();
|
|
49
109
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
110
|
+
/**
|
|
111
|
+
* Whether the items passed are deeply-equal in value based on strict comparison.
|
|
112
|
+
*/
|
|
113
|
+
export const strictDeepEqual = createDefaultEqualCreator({ strict: true });
|
|
53
114
|
|
|
54
115
|
/**
|
|
55
|
-
* Whether the items passed are
|
|
116
|
+
* Whether the items passed are deeply-equal in value, including circular references.
|
|
56
117
|
*/
|
|
57
|
-
export
|
|
58
|
-
return isShallowEqual(a, b, undefined);
|
|
59
|
-
}
|
|
118
|
+
export const circularDeepEqual = createDefaultEqualCreator({ circular: true });
|
|
60
119
|
|
|
61
|
-
|
|
120
|
+
/**
|
|
121
|
+
* Whether the items passed are deeply-equal in value, including circular references,
|
|
122
|
+
* based on strict comparison.
|
|
123
|
+
*/
|
|
124
|
+
export const strictCircularDeepEqual = createDefaultEqualCreator({
|
|
125
|
+
circular: true,
|
|
126
|
+
strict: true,
|
|
127
|
+
});
|
|
62
128
|
|
|
63
129
|
/**
|
|
64
|
-
* Whether the items passed are
|
|
130
|
+
* Whether the items passed are shallowly-equal in value.
|
|
65
131
|
*/
|
|
66
|
-
export
|
|
67
|
-
|
|
68
|
-
}
|
|
132
|
+
export const shallowEqual = createDefaultEqualCreator({
|
|
133
|
+
comparator: sameValueZeroEqual,
|
|
134
|
+
});
|
|
69
135
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
136
|
+
/**
|
|
137
|
+
* Whether the items passed are shallowly-equal in value based on strict comparison
|
|
138
|
+
*/
|
|
139
|
+
export const strictShallowEqual = createDefaultEqualCreator({
|
|
140
|
+
comparator: sameValueZeroEqual,
|
|
141
|
+
strict: true,
|
|
142
|
+
});
|
|
75
143
|
|
|
76
144
|
/**
|
|
77
145
|
* Whether the items passed are shallowly-equal in value, including circular references.
|
|
78
146
|
*/
|
|
79
|
-
export
|
|
80
|
-
|
|
81
|
-
|
|
147
|
+
export const circularShallowEqual = createDefaultEqualCreator({
|
|
148
|
+
comparator: sameValueZeroEqual,
|
|
149
|
+
circular: true,
|
|
150
|
+
});
|
|
82
151
|
|
|
83
152
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* This can be done to create very targeted comparisons in extreme hot-path scenarios
|
|
87
|
-
* where the standard methods are not performant enough, but can also be used to provide
|
|
88
|
-
* support for legacy environments that do not support expected features like
|
|
89
|
-
* `RegExp.prototype.flags` out of the box.
|
|
153
|
+
* Whether the items passed are shallowly-equal in value, including circular references,
|
|
154
|
+
* based on strict comparison.
|
|
90
155
|
*/
|
|
91
|
-
export
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
);
|
|
97
|
-
}
|
|
156
|
+
export const strictCircularShallowEqual = createDefaultEqualCreator({
|
|
157
|
+
comparator: sameValueZeroEqual,
|
|
158
|
+
circular: true,
|
|
159
|
+
strict: true,
|
|
160
|
+
});
|
|
98
161
|
|
|
99
162
|
/**
|
|
100
|
-
* Create a custom equality comparison method
|
|
101
|
-
* similar to `createCustomEqual`, with the only difference being that `meta` expects to be
|
|
102
|
-
* populated with a `WeakMap`-like contract.
|
|
163
|
+
* Create a custom equality comparison method.
|
|
103
164
|
*
|
|
104
165
|
* This can be done to create very targeted comparisons in extreme hot-path scenarios
|
|
105
166
|
* where the standard methods are not performant enough, but can also be used to provide
|
|
106
167
|
* support for legacy environments that do not support expected features like
|
|
107
|
-
* `
|
|
168
|
+
* `RegExp.prototype.flags` out of the box.
|
|
108
169
|
*/
|
|
109
|
-
export function
|
|
110
|
-
Meta
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
170
|
+
export function createCustomEqual<Meta>(
|
|
171
|
+
options: CustomEqualCreatorOptions<Meta> = {},
|
|
172
|
+
) {
|
|
173
|
+
const {
|
|
174
|
+
comparator,
|
|
175
|
+
createInternalComparator: createCustomInternalComparator,
|
|
176
|
+
createCustomConfig,
|
|
177
|
+
createState,
|
|
178
|
+
} = options;
|
|
179
|
+
|
|
180
|
+
const baseConfig = createComparatorConfig(options);
|
|
181
|
+
const config = createCustomConfig
|
|
182
|
+
? Object.assign({}, baseConfig, createCustomConfig(baseConfig))
|
|
183
|
+
: baseConfig;
|
|
184
|
+
const isEqualCustom = comparator || createComparator(config);
|
|
185
|
+
const isEqualCustomComparator = createCustomInternalComparator
|
|
186
|
+
? createCustomInternalComparator(isEqualCustom)
|
|
187
|
+
: createInternalComparator(isEqualCustom);
|
|
188
|
+
const strict = !!options.strict;
|
|
189
|
+
|
|
190
|
+
if (createState) {
|
|
191
|
+
return function isEqual<A, B>(a: A, b: B, metaOverride?: Meta): boolean {
|
|
192
|
+
const customState = createState(isEqualCustom);
|
|
193
|
+
|
|
194
|
+
return isEqualCustom(a, b, {
|
|
195
|
+
cache: customState.cache || new WeakMap(),
|
|
196
|
+
equals: customState.equals || isEqualCustomComparator,
|
|
197
|
+
// @ts-expect-error - inferred `Meta` may be undefined, which is okay
|
|
198
|
+
meta: metaOverride !== undefined ? metaOverride : customState.meta,
|
|
199
|
+
strict: customState.strict !== undefined ? customState.strict : strict,
|
|
200
|
+
});
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (options.circular) {
|
|
205
|
+
return function equals<A, B>(a: A, b: B): boolean {
|
|
206
|
+
return isEqualCustom(a, b, {
|
|
207
|
+
cache: new WeakMap(),
|
|
208
|
+
equals: isEqualCustomComparator,
|
|
209
|
+
meta: undefined as Meta,
|
|
210
|
+
strict,
|
|
211
|
+
} as CircularState<Meta>);
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const state = Object.freeze({
|
|
216
|
+
cache: undefined,
|
|
217
|
+
equals: isEqualCustomComparator,
|
|
218
|
+
meta: undefined,
|
|
219
|
+
strict,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
return function equals<A, B>(a: A, b: B): boolean {
|
|
223
|
+
return isEqualCustom(a, b, state as DefaultState<Meta>);
|
|
224
|
+
};
|
|
121
225
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export interface BaseCircular
|
|
2
|
+
extends Pick<WeakMap<any, any>, 'delete' | 'get'> {
|
|
3
|
+
set(key: object, value: any): any;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export type State<Meta> = CircularState<Meta> | DefaultState<Meta>;
|
|
7
|
+
|
|
8
|
+
export interface CircularState<Meta> {
|
|
9
|
+
readonly cache: BaseCircular;
|
|
10
|
+
readonly equals: InternalEqualityComparator<Meta>;
|
|
11
|
+
meta: Meta;
|
|
12
|
+
readonly strict: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface DefaultState<Meta> {
|
|
16
|
+
readonly cache: undefined;
|
|
17
|
+
readonly equals: InternalEqualityComparator<Meta>;
|
|
18
|
+
meta: Meta;
|
|
19
|
+
readonly strict: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface Dictionary<Value = any> {
|
|
23
|
+
[key: string | symbol]: Value;
|
|
24
|
+
$$typeof?: any;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ComparatorConfig<Meta> {
|
|
28
|
+
areArraysEqual: TypeEqualityComparator<any, Meta>;
|
|
29
|
+
areDatesEqual: TypeEqualityComparator<any, Meta>;
|
|
30
|
+
areMapsEqual: TypeEqualityComparator<any, Meta>;
|
|
31
|
+
areObjectsEqual: TypeEqualityComparator<any, Meta>;
|
|
32
|
+
arePrimitiveWrappersEqual: TypeEqualityComparator<any, Meta>;
|
|
33
|
+
areRegExpsEqual: TypeEqualityComparator<any, Meta>;
|
|
34
|
+
areSetsEqual: TypeEqualityComparator<any, Meta>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type CreateCustomComparatorConfig<Meta> = (
|
|
38
|
+
config: ComparatorConfig<Meta>,
|
|
39
|
+
) => Partial<ComparatorConfig<Meta>>;
|
|
40
|
+
|
|
41
|
+
export type CreateState<Meta> = (
|
|
42
|
+
comparator: EqualityComparator<Meta>,
|
|
43
|
+
) => Partial<State<Meta>>;
|
|
44
|
+
|
|
45
|
+
export type EqualityComparator<Meta> = <A, B>(
|
|
46
|
+
a: A,
|
|
47
|
+
b: B,
|
|
48
|
+
state: State<Meta>,
|
|
49
|
+
) => boolean;
|
|
50
|
+
export type AnyEqualityComparator<Meta> = (
|
|
51
|
+
a: any,
|
|
52
|
+
b: any,
|
|
53
|
+
state: State<Meta>,
|
|
54
|
+
) => boolean;
|
|
55
|
+
|
|
56
|
+
export type EqualityComparatorCreator<Meta> = (
|
|
57
|
+
fn: EqualityComparator<Meta>,
|
|
58
|
+
) => InternalEqualityComparator<Meta>;
|
|
59
|
+
|
|
60
|
+
export type InternalEqualityComparator<Meta> = (
|
|
61
|
+
a: any,
|
|
62
|
+
b: any,
|
|
63
|
+
indexOrKeyA: any,
|
|
64
|
+
indexOrKeyB: any,
|
|
65
|
+
parentA: any,
|
|
66
|
+
parentB: any,
|
|
67
|
+
state: State<Meta>,
|
|
68
|
+
) => boolean;
|
|
69
|
+
|
|
70
|
+
export type TypeEqualityComparator<Type, Meta = undefined> = (
|
|
71
|
+
a: Type,
|
|
72
|
+
b: Type,
|
|
73
|
+
state: State<Meta>,
|
|
74
|
+
) => boolean;
|