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/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
+ }