fast-equals 3.0.3 → 4.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/.prettierrc +4 -0
- package/README.md +172 -41
- package/dist/fast-equals.cjs.js +346 -274
- package/dist/fast-equals.cjs.js.map +1 -1
- package/dist/fast-equals.esm.js +345 -274
- package/dist/fast-equals.esm.js.map +1 -1
- package/dist/fast-equals.js +346 -274
- package/dist/fast-equals.js.map +1 -1
- package/dist/fast-equals.min.js +1 -1
- package/dist/fast-equals.mjs +345 -274
- package/dist/fast-equals.mjs.map +1 -1
- package/index.d.ts +26 -25
- package/package.json +21 -19
- package/src/arrays.ts +36 -0
- package/src/comparator.ts +146 -0
- package/src/dates.ts +12 -0
- package/src/index.ts +135 -0
- package/src/maps.ts +66 -0
- package/src/objects.ts +61 -0
- package/src/regexps.ts +11 -0
- package/src/sets.ts +61 -0
- package/src/utils.ts +130 -0
package/src/objects.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { createIsCircular } from './utils';
|
|
2
|
+
|
|
3
|
+
import type { InternalEqualityComparator } from './utils';
|
|
4
|
+
|
|
5
|
+
interface Dictionary<Value> {
|
|
6
|
+
[key: string]: Value;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const OWNER = '_owner';
|
|
10
|
+
const { hasOwnProperty } = Object.prototype;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Whether the objects are equal in value.
|
|
14
|
+
*/
|
|
15
|
+
export function areObjectsEqual(
|
|
16
|
+
a: Dictionary<any>,
|
|
17
|
+
b: Dictionary<any>,
|
|
18
|
+
isEqual: InternalEqualityComparator,
|
|
19
|
+
meta: any,
|
|
20
|
+
): boolean {
|
|
21
|
+
const keysA = Object.keys(a);
|
|
22
|
+
|
|
23
|
+
let index = keysA.length;
|
|
24
|
+
|
|
25
|
+
if (Object.keys(b).length !== index) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let key: string;
|
|
30
|
+
|
|
31
|
+
// Decrementing `while` showed faster results than either incrementing or
|
|
32
|
+
// decrementing `for` loop and than an incrementing `while` loop. Declarative
|
|
33
|
+
// methods like `some` / `every` were not used to avoid incurring the garbage
|
|
34
|
+
// cost of anonymous callbacks.
|
|
35
|
+
while (index-- > 0) {
|
|
36
|
+
key = keysA[index];
|
|
37
|
+
|
|
38
|
+
if (key === OWNER) {
|
|
39
|
+
const reactElementA = !!a.$$typeof;
|
|
40
|
+
const reactElementB = !!b.$$typeof;
|
|
41
|
+
|
|
42
|
+
if ((reactElementA || reactElementB) && reactElementA !== reactElementB) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (
|
|
48
|
+
!hasOwnProperty.call(b, key) ||
|
|
49
|
+
!isEqual(a[key], b[key], key, key, a, b, meta)
|
|
50
|
+
) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Whether the objects are equal in value, including circular references.
|
|
60
|
+
*/
|
|
61
|
+
export const areObjectsEqualCircular = createIsCircular(areObjectsEqual);
|
package/src/regexps.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether the regexps passed are equal in value.
|
|
3
|
+
*
|
|
4
|
+
* @NOTE
|
|
5
|
+
* This is a standalone function instead of done inline in the comparator
|
|
6
|
+
* to allow for overrides. An example of this would be supporting a
|
|
7
|
+
* pre-ES2015 environment where the `flags` property is not available.
|
|
8
|
+
*/
|
|
9
|
+
export function areRegExpsEqual(a: RegExp, b: RegExp): boolean {
|
|
10
|
+
return a.source === b.source && a.flags === b.flags;
|
|
11
|
+
}
|
package/src/sets.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { createIsCircular } from './utils';
|
|
2
|
+
|
|
3
|
+
import type { InternalEqualityComparator } from './utils';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Whether the `Set`s are equal in value.
|
|
7
|
+
*/
|
|
8
|
+
export function areSetsEqual(
|
|
9
|
+
a: Set<any>,
|
|
10
|
+
b: Set<any>,
|
|
11
|
+
isEqual: InternalEqualityComparator,
|
|
12
|
+
meta: any,
|
|
13
|
+
): boolean {
|
|
14
|
+
let isValueEqual = a.size === b.size;
|
|
15
|
+
|
|
16
|
+
if (!isValueEqual) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!a.size) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// The use of `forEach()` is to avoid the transpilation cost of `for...of` comparisons, and
|
|
25
|
+
// the inability to control the performance of the resulting code. It also avoids excessive
|
|
26
|
+
// iteration compared to doing comparisons of `keys()` and `values()`. As a result, though,
|
|
27
|
+
// we cannot short-circuit the iterations; bookkeeping must be done to short-circuit the
|
|
28
|
+
// equality checks themselves.
|
|
29
|
+
|
|
30
|
+
const matchedIndices: Record<number, true> = {};
|
|
31
|
+
|
|
32
|
+
a.forEach((aValue, aKey) => {
|
|
33
|
+
if (!isValueEqual) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let hasMatch = false;
|
|
38
|
+
let matchIndex = 0;
|
|
39
|
+
|
|
40
|
+
b.forEach((bValue, bKey) => {
|
|
41
|
+
if (
|
|
42
|
+
!hasMatch &&
|
|
43
|
+
!matchedIndices[matchIndex] &&
|
|
44
|
+
(hasMatch = isEqual(aValue, bValue, aKey, bKey, a, b, meta))
|
|
45
|
+
) {
|
|
46
|
+
matchedIndices[matchIndex] = true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
matchIndex++;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
isValueEqual = hasMatch;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return isValueEqual;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Whether the `Set`s are equal in value, including circular references.
|
|
60
|
+
*/
|
|
61
|
+
export const areSetsEqualCircular = createIsCircular(areSetsEqual);
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
export type InternalEqualityComparator = (
|
|
2
|
+
a: any,
|
|
3
|
+
b: any,
|
|
4
|
+
indexOrKeyA: any,
|
|
5
|
+
indexOrKeyB: any,
|
|
6
|
+
parentA: any,
|
|
7
|
+
parentB: any,
|
|
8
|
+
meta: any,
|
|
9
|
+
) => boolean;
|
|
10
|
+
|
|
11
|
+
export type EqualityComparator<Meta> = <A, B>(
|
|
12
|
+
a: A,
|
|
13
|
+
b: B,
|
|
14
|
+
meta?: Meta,
|
|
15
|
+
) => boolean;
|
|
16
|
+
|
|
17
|
+
export type EqualityComparatorCreator<Meta> = (
|
|
18
|
+
fn: EqualityComparator<Meta>,
|
|
19
|
+
) => InternalEqualityComparator;
|
|
20
|
+
|
|
21
|
+
export type NativeEqualityComparator = <A, B>(a: A, b: B) => boolean;
|
|
22
|
+
|
|
23
|
+
export type TypeEqualityComparator<Type, Meta> = (
|
|
24
|
+
a: Type,
|
|
25
|
+
b: Type,
|
|
26
|
+
isEqual: InternalEqualityComparator,
|
|
27
|
+
meta: Meta,
|
|
28
|
+
) => boolean;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Default equality comparator pass-through, used as the standard `isEqual` creator for
|
|
32
|
+
* use inside the built comparator.
|
|
33
|
+
*/
|
|
34
|
+
export function createDefaultIsNestedEqual<Meta>(
|
|
35
|
+
comparator: EqualityComparator<Meta>,
|
|
36
|
+
): InternalEqualityComparator {
|
|
37
|
+
return function isEqual<A, B>(
|
|
38
|
+
a: A,
|
|
39
|
+
b: B,
|
|
40
|
+
indexOrKeyA: any,
|
|
41
|
+
indexOrKeyB: any,
|
|
42
|
+
parentA: any,
|
|
43
|
+
parentB: any,
|
|
44
|
+
meta: Meta,
|
|
45
|
+
) {
|
|
46
|
+
return comparator(a, b, meta);
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Wrap the provided `areItemsEqual` method to manage the circular cache, allowing
|
|
52
|
+
* for circular references to be safely included in the comparison without creating
|
|
53
|
+
* stack overflows.
|
|
54
|
+
*/
|
|
55
|
+
export function createIsCircular<
|
|
56
|
+
AreItemsEqual extends TypeEqualityComparator<any, any>,
|
|
57
|
+
>(areItemsEqual: AreItemsEqual): AreItemsEqual {
|
|
58
|
+
return function isCircular(
|
|
59
|
+
a: any,
|
|
60
|
+
b: any,
|
|
61
|
+
isEqual: InternalEqualityComparator,
|
|
62
|
+
cache: WeakMap<any, any>,
|
|
63
|
+
) {
|
|
64
|
+
if (!a || !b || typeof a !== 'object' || typeof b !== 'object') {
|
|
65
|
+
return areItemsEqual(a, b, isEqual, cache);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const cachedA = cache.get(a);
|
|
69
|
+
const cachedB = cache.get(b);
|
|
70
|
+
|
|
71
|
+
if (cachedA && cachedB) {
|
|
72
|
+
return cachedA === b && cachedB === a;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
cache.set(a, b);
|
|
76
|
+
cache.set(b, a);
|
|
77
|
+
|
|
78
|
+
const result = areItemsEqual(a, b, isEqual, cache);
|
|
79
|
+
|
|
80
|
+
cache.delete(a);
|
|
81
|
+
cache.delete(b);
|
|
82
|
+
|
|
83
|
+
return result;
|
|
84
|
+
} as AreItemsEqual;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Targeted shallow merge of two objects.
|
|
89
|
+
*
|
|
90
|
+
* @NOTE
|
|
91
|
+
* This exists as a tinier compiled version of the `__assign` helper that
|
|
92
|
+
* `tsc` injects in case of `Object.assign` not being present.
|
|
93
|
+
*/
|
|
94
|
+
export function merge<A extends object, B extends object>(a: A, b: B): A & B {
|
|
95
|
+
const merged: Record<string, any> = {};
|
|
96
|
+
|
|
97
|
+
for (const key in a) {
|
|
98
|
+
merged[key] = a[key];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
for (const key in b) {
|
|
102
|
+
merged[key] = b[key];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return merged as A & B;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Whether the value is a plain object.
|
|
110
|
+
*
|
|
111
|
+
* @NOTE
|
|
112
|
+
* This is a same-realm compariosn only.
|
|
113
|
+
*/
|
|
114
|
+
export function isPlainObject(value: any): boolean {
|
|
115
|
+
return value.constructor === Object || value.constructor == null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* When the value is `Promise`-like, aka "then-able".
|
|
120
|
+
*/
|
|
121
|
+
export function isPromiseLike(value: any): boolean {
|
|
122
|
+
return typeof value.then === 'function';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Whether the values passed are strictly equal or both NaN.
|
|
127
|
+
*/
|
|
128
|
+
export function sameValueZeroEqual(a: any, b: any): boolean {
|
|
129
|
+
return a === b || (a !== a && b !== b);
|
|
130
|
+
}
|