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
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { CreateCustomComparatorConfig, CreateState, EqualityComparator } from './internalTypes';
|
|
2
|
+
import { createInternalComparator, sameValueZeroEqual } from './utils';
|
|
3
|
+
export { sameValueZeroEqual };
|
|
4
|
+
interface DefaultEqualCreatorOptions<Meta> {
|
|
5
|
+
comparator?: EqualityComparator<Meta>;
|
|
6
|
+
circular?: boolean;
|
|
7
|
+
strict?: boolean;
|
|
8
|
+
}
|
|
9
|
+
interface CustomEqualCreatorOptions<Meta> extends DefaultEqualCreatorOptions<Meta> {
|
|
10
|
+
createCustomConfig?: CreateCustomComparatorConfig<Meta>;
|
|
11
|
+
createInternalComparator?: typeof createInternalComparator;
|
|
12
|
+
createState?: CreateState<Meta>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Whether the items passed are deeply-equal in value.
|
|
16
|
+
*/
|
|
17
|
+
export declare const deepEqual: <A, B>(a: A, b: B) => boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Whether the items passed are deeply-equal in value based on strict comparison.
|
|
20
|
+
*/
|
|
21
|
+
export declare const strictDeepEqual: <A, B>(a: A, b: B) => boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Whether the items passed are deeply-equal in value, including circular references.
|
|
24
|
+
*/
|
|
25
|
+
export declare const circularDeepEqual: <A, B>(a: A, b: B) => boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Whether the items passed are deeply-equal in value, including circular references,
|
|
28
|
+
* based on strict comparison.
|
|
29
|
+
*/
|
|
30
|
+
export declare const strictCircularDeepEqual: <A, B>(a: A, b: B) => boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Whether the items passed are shallowly-equal in value.
|
|
33
|
+
*/
|
|
34
|
+
export declare const shallowEqual: <A, B>(a: A, b: B) => boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Whether the items passed are shallowly-equal in value based on strict comparison
|
|
37
|
+
*/
|
|
38
|
+
export declare const strictShallowEqual: <A, B>(a: A, b: B) => boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Whether the items passed are shallowly-equal in value, including circular references.
|
|
41
|
+
*/
|
|
42
|
+
export declare const circularShallowEqual: <A, B>(a: A, b: B) => boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Whether the items passed are shallowly-equal in value, including circular references,
|
|
45
|
+
* based on strict comparison.
|
|
46
|
+
*/
|
|
47
|
+
export declare const strictCircularShallowEqual: <A, B>(a: A, b: B) => boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Create a custom equality comparison method.
|
|
50
|
+
*
|
|
51
|
+
* This can be done to create very targeted comparisons in extreme hot-path scenarios
|
|
52
|
+
* where the standard methods are not performant enough, but can also be used to provide
|
|
53
|
+
* support for legacy environments that do not support expected features like
|
|
54
|
+
* `RegExp.prototype.flags` out of the box.
|
|
55
|
+
*/
|
|
56
|
+
export declare function createCustomEqual<Meta>(options?: CustomEqualCreatorOptions<Meta>): <A, B>(a: A, b: B, metaOverride?: Meta) => boolean;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface BaseCircular extends Pick<WeakMap<any, any>, 'delete' | 'get'> {
|
|
2
|
+
set(key: object, value: any): any;
|
|
3
|
+
}
|
|
4
|
+
export type State<Meta> = CircularState<Meta> | DefaultState<Meta>;
|
|
5
|
+
export interface CircularState<Meta> {
|
|
6
|
+
readonly cache: BaseCircular;
|
|
7
|
+
readonly equals: InternalEqualityComparator<Meta>;
|
|
8
|
+
meta: Meta;
|
|
9
|
+
readonly strict: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface DefaultState<Meta> {
|
|
12
|
+
readonly cache: undefined;
|
|
13
|
+
readonly equals: InternalEqualityComparator<Meta>;
|
|
14
|
+
meta: Meta;
|
|
15
|
+
readonly strict: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface Dictionary<Value = any> {
|
|
18
|
+
[key: string | symbol]: Value;
|
|
19
|
+
$$typeof?: any;
|
|
20
|
+
}
|
|
21
|
+
export interface ComparatorConfig<Meta> {
|
|
22
|
+
areArraysEqual: TypeEqualityComparator<any, Meta>;
|
|
23
|
+
areDatesEqual: TypeEqualityComparator<any, Meta>;
|
|
24
|
+
areMapsEqual: TypeEqualityComparator<any, Meta>;
|
|
25
|
+
areObjectsEqual: TypeEqualityComparator<any, Meta>;
|
|
26
|
+
arePrimitiveWrappersEqual: TypeEqualityComparator<any, Meta>;
|
|
27
|
+
areRegExpsEqual: TypeEqualityComparator<any, Meta>;
|
|
28
|
+
areSetsEqual: TypeEqualityComparator<any, Meta>;
|
|
29
|
+
}
|
|
30
|
+
export type CreateCustomComparatorConfig<Meta> = (config: ComparatorConfig<Meta>) => Partial<ComparatorConfig<Meta>>;
|
|
31
|
+
export type CreateState<Meta> = (comparator: EqualityComparator<Meta>) => Partial<State<Meta>>;
|
|
32
|
+
export type EqualityComparator<Meta> = <A, B>(a: A, b: B, state: State<Meta>) => boolean;
|
|
33
|
+
export type AnyEqualityComparator<Meta> = (a: any, b: any, state: State<Meta>) => boolean;
|
|
34
|
+
export type EqualityComparatorCreator<Meta> = (fn: EqualityComparator<Meta>) => InternalEqualityComparator<Meta>;
|
|
35
|
+
export type InternalEqualityComparator<Meta> = (a: any, b: any, indexOrKeyA: any, indexOrKeyB: any, parentA: any, parentB: any, state: State<Meta>) => boolean;
|
|
36
|
+
export type TypeEqualityComparator<Type, Meta = undefined> = (a: Type, b: Type, state: State<Meta>) => boolean;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { AnyEqualityComparator, Dictionary, EqualityComparator, InternalEqualityComparator, State, TypeEqualityComparator } from './internalTypes';
|
|
2
|
+
export declare function combineComparators<Meta>(comparatorA: AnyEqualityComparator<Meta>, comparatorB: AnyEqualityComparator<Meta>): <A, B>(a: A, b: B, state: State<Meta>) => boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Default equality comparator pass-through, used as the standard `isEqual` creator for
|
|
5
|
+
* use inside the built comparator.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createInternalComparator<Meta>(compare: EqualityComparator<Meta>): InternalEqualityComparator<Meta>;
|
|
8
|
+
/**
|
|
9
|
+
* Wrap the provided `areItemsEqual` method to manage the circular state, allowing
|
|
10
|
+
* for circular references to be safely included in the comparison without creating
|
|
11
|
+
* stack overflows.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createIsCircular<AreItemsEqual extends TypeEqualityComparator<any, any>>(areItemsEqual: AreItemsEqual): AreItemsEqual;
|
|
14
|
+
export declare function getStrictProperties(object: Dictionary): Array<string | symbol>;
|
|
15
|
+
export declare const hasOwn: (o: object, v: PropertyKey) => boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Whether the values passed are strictly equal or both NaN.
|
|
18
|
+
*/
|
|
19
|
+
export declare function sameValueZeroEqual(a: any, b: any): boolean;
|
package/package.json
CHANGED
|
@@ -1,53 +1,69 @@
|
|
|
1
1
|
{
|
|
2
2
|
"author": "tony_quetano@planttheidea.com",
|
|
3
|
-
"browser": "dist/
|
|
3
|
+
"browser": "dist/umd/index.js",
|
|
4
4
|
"bugs": {
|
|
5
5
|
"url": "https://github.com/planttheidea/fast-equals/issues"
|
|
6
6
|
},
|
|
7
7
|
"description": "A blazing fast equality comparison, either shallow or deep",
|
|
8
8
|
"devDependencies": {
|
|
9
|
-
"@rollup/plugin-
|
|
10
|
-
"@
|
|
9
|
+
"@rollup/plugin-commonjs": "^24.0.0",
|
|
10
|
+
"@rollup/plugin-node-resolve": "^15.0.1",
|
|
11
|
+
"@rollup/plugin-replace": "^5.0.2",
|
|
12
|
+
"@rollup/plugin-terser": "^0.4.0",
|
|
13
|
+
"@rollup/plugin-typescript": "^11.0.0",
|
|
14
|
+
"@types/jest": "^29.2.5",
|
|
11
15
|
"@types/lodash": "^4.14.184",
|
|
12
|
-
"@types/node": "^18.
|
|
13
|
-
"@types/ramda": "^0.28.
|
|
14
|
-
"@types/react": "^18.0.
|
|
15
|
-
"@
|
|
16
|
-
"@typescript-eslint/
|
|
17
|
-
"
|
|
18
|
-
"cli-table3": "^0.6.1",
|
|
16
|
+
"@types/node": "^18.14.0",
|
|
17
|
+
"@types/ramda": "^0.28.23",
|
|
18
|
+
"@types/react": "^18.0.28",
|
|
19
|
+
"@types/react-dom": "^18.0.11",
|
|
20
|
+
"@typescript-eslint/eslint-plugin": "^5.52.0",
|
|
21
|
+
"@typescript-eslint/parser": "^5.52.0",
|
|
19
22
|
"decircularize": "^1.0.0",
|
|
20
23
|
"deep-eql": "^4.1.0",
|
|
21
24
|
"deep-equal": "^2.0.5",
|
|
22
|
-
"eslint": "^8.
|
|
23
|
-
"eslint-
|
|
24
|
-
"eslint-plugin
|
|
25
|
-
"eslint-plugin-jsx-a11y": "^6.6.1",
|
|
26
|
-
"eslint-plugin-react": "^7.31.0",
|
|
27
|
-
"eslint-webpack-plugin": "^3.2.0",
|
|
25
|
+
"eslint": "^8.34.0",
|
|
26
|
+
"eslint-friendly-formatter": "^4.0.1",
|
|
27
|
+
"eslint-webpack-plugin": "^4.0.0",
|
|
28
28
|
"fast-deep-equal": "^3.1.3",
|
|
29
|
-
"fs-extra": "^10.0.0",
|
|
30
29
|
"html-webpack-plugin": "^5.5.0",
|
|
31
30
|
"in-publish": "^2.0.0",
|
|
32
|
-
"jest": "^
|
|
31
|
+
"jest": "^29.4.3",
|
|
32
|
+
"jest-environment-jsdom": "^29.4.3",
|
|
33
|
+
"jest-expect-message": "^1.1.3",
|
|
33
34
|
"lodash": "^4.17.21",
|
|
34
35
|
"nano-equal": "^2.0.2",
|
|
35
|
-
"prettier": "^2.
|
|
36
|
+
"prettier": "^2.8.4",
|
|
36
37
|
"react": "^18.2.0",
|
|
37
38
|
"react-dom": "^18.2.0",
|
|
38
39
|
"react-fast-compare": "^3.2.0",
|
|
39
|
-
"release-it": "^15.
|
|
40
|
-
"rollup": "^
|
|
41
|
-
"rollup-plugin-terser": "^7.0.2",
|
|
42
|
-
"rollup-plugin-typescript2": "^0.33.0",
|
|
40
|
+
"release-it": "^15.6.0",
|
|
41
|
+
"rollup": "^3.16.0",
|
|
43
42
|
"shallow-equal-fuzzy": "^0.0.2",
|
|
44
|
-
"
|
|
45
|
-
"ts-
|
|
46
|
-
"
|
|
43
|
+
"tinybench": "^2.3.1",
|
|
44
|
+
"ts-jest": "^29.0.3",
|
|
45
|
+
"ts-loader": "^9.4.2",
|
|
46
|
+
"typescript": "^4.9.5",
|
|
47
47
|
"underscore": "^1.13.4",
|
|
48
|
-
"webpack": "^5.
|
|
49
|
-
"webpack-cli": "^
|
|
50
|
-
"webpack-dev-server": "^4.
|
|
48
|
+
"webpack": "^5.75.0",
|
|
49
|
+
"webpack-cli": "^5.0.1",
|
|
50
|
+
"webpack-dev-server": "^4.11.1"
|
|
51
|
+
},
|
|
52
|
+
"exports": {
|
|
53
|
+
".": {
|
|
54
|
+
"import": {
|
|
55
|
+
"types": "./dist/esm/types/index.d.ts",
|
|
56
|
+
"default": "./dist/esm/index.mjs"
|
|
57
|
+
},
|
|
58
|
+
"require": {
|
|
59
|
+
"types": "./dist/cjs/types/index.d.ts",
|
|
60
|
+
"default": "./dist/cjs/index.cjs"
|
|
61
|
+
},
|
|
62
|
+
"default": {
|
|
63
|
+
"types": "./dist/umd/types/index.d.ts",
|
|
64
|
+
"default": "./dist/umd/index.js"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
51
67
|
},
|
|
52
68
|
"homepage": "https://github.com/planttheidea/fast-equals#readme",
|
|
53
69
|
"keywords": [
|
|
@@ -58,32 +74,37 @@
|
|
|
58
74
|
"equivalent"
|
|
59
75
|
],
|
|
60
76
|
"license": "MIT",
|
|
61
|
-
"main": "dist/
|
|
62
|
-
"module": "dist/
|
|
77
|
+
"main": "dist/cjs/index.cjs",
|
|
78
|
+
"module": "dist/esm/index.mjs",
|
|
63
79
|
"name": "fast-equals",
|
|
64
80
|
"repository": {
|
|
65
81
|
"type": "git",
|
|
66
82
|
"url": "git+https://github.com/planttheidea/fast-equals.git"
|
|
67
83
|
},
|
|
68
84
|
"scripts": {
|
|
69
|
-
"benchmark": "npm run
|
|
70
|
-
"build": "
|
|
71
|
-
"
|
|
85
|
+
"benchmark": "npm run build:esm && node benchmark/index.js",
|
|
86
|
+
"build": "npm run build:esm && npm run build:cjs && npm run build:umd && npm run build:min",
|
|
87
|
+
"build:cjs": "rimraf dist/cjs && NODE_ENV=production rollup -c build/rollup/config.cjs.js && tsc -p ./build/tsconfig/cjs.json",
|
|
88
|
+
"build:esm": "rimraf dist/esm && NODE_ENV=production rollup -c build/rollup/config.esm.js && tsc -p ./build/tsconfig/esm.json",
|
|
89
|
+
"build:min": "rimraf dist/min && NODE_ENV=production rollup -c build/rollup/config.min.js && tsc -p ./build/tsconfig/min.json",
|
|
90
|
+
"build:umd": "rimraf dist/umd && NODE_ENV=production rollup -c build/rollup/config.umd.js && tsc -p ./build/tsconfig/umd.json",
|
|
91
|
+
"dev": "NODE_ENV=development webpack serve --progress --config=build/webpack.config.js",
|
|
72
92
|
"dist": "rimraf dist && npm run build",
|
|
73
93
|
"format": "prettier **/*.ts --write",
|
|
74
94
|
"lint": "eslint src/*.ts",
|
|
75
95
|
"lint:fix": "npm run lint -- --fix",
|
|
76
96
|
"start": "npm run dev",
|
|
77
97
|
"prepublish": "if in-publish; then npm run prepublish:compile; fi",
|
|
78
|
-
"prepublish:compile": "npm run typecheck && npm run lint && npm run test && npm run dist",
|
|
79
98
|
"release": "release-it",
|
|
80
99
|
"release:beta": "release-it --config=.release-it.beta.json",
|
|
100
|
+
"release:scripts": "npm run typecheck && npm run lint && npm run test && npm run dist",
|
|
81
101
|
"test": "NODE_PATH=. jest",
|
|
82
|
-
"test:coverage": "npm test -- --coverage",
|
|
102
|
+
"test:coverage": "rimraf coverage && npm test -- --coverage",
|
|
83
103
|
"test:watch": "npm test -- --watch",
|
|
84
104
|
"typecheck": "tsc --noEmit"
|
|
85
105
|
},
|
|
86
106
|
"sideEffects": false,
|
|
107
|
+
"type": "module",
|
|
87
108
|
"types": "index.d.ts",
|
|
88
|
-
"version": "
|
|
109
|
+
"version": "5.0.0-beta.0"
|
|
89
110
|
}
|
|
@@ -18,11 +18,9 @@ interface SpecialObject {
|
|
|
18
18
|
const areObjectsEqual: TypeEqualityComparator<SpecialObject, undefined> = (
|
|
19
19
|
a,
|
|
20
20
|
b,
|
|
21
|
-
) =>
|
|
22
|
-
return a.foo === b.foo && a.bar.baz === b.bar.baz;
|
|
23
|
-
};
|
|
21
|
+
) => a.foo === b.foo && a.bar.baz === b.bar.baz;
|
|
24
22
|
|
|
25
|
-
const isSpecialObjectEqual = createCustomEqual(
|
|
26
|
-
areObjectsEqual,
|
|
27
|
-
})
|
|
23
|
+
const isSpecialObjectEqual = createCustomEqual({
|
|
24
|
+
createCustomConfig: () => ({ areObjectsEqual }),
|
|
25
|
+
});
|
|
28
26
|
```
|
|
@@ -3,14 +3,9 @@
|
|
|
3
3
|
Starting in `4.x.x`, `WeakMap` is expected to be available in the environment. All modern browsers support this global object, however there may be situations where a legacy environmental support is required (example: IE11). If you need to support such an environment, creating a custom comparator that uses a custom cache implementation with the same contract is a simple solution.
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
|
-
import { createCustomEqual, sameValueZeroEqual } from '
|
|
6
|
+
import { createCustomEqual, sameValueZeroEqual } from 'fast-equals';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
customMethod(): void;
|
|
10
|
-
customValue: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function getCache(): Cache {
|
|
8
|
+
function getCache(): BaseCircular {
|
|
14
9
|
const entries: Array<[object, any]> = [];
|
|
15
10
|
|
|
16
11
|
return {
|
|
@@ -45,22 +40,35 @@ function getCache(): Cache {
|
|
|
45
40
|
|
|
46
41
|
return this;
|
|
47
42
|
},
|
|
48
|
-
|
|
49
|
-
customMethod() {
|
|
50
|
-
console.log('hello!');
|
|
51
|
-
},
|
|
52
|
-
customValue: 'goodbye',
|
|
53
43
|
};
|
|
54
44
|
}
|
|
55
45
|
|
|
56
|
-
|
|
46
|
+
interface Meta {
|
|
47
|
+
customMethod(): void;
|
|
48
|
+
customValue: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const meta = {
|
|
52
|
+
customMethod() {
|
|
53
|
+
console.log('hello!');
|
|
54
|
+
},
|
|
55
|
+
customValue: 'goodbye',
|
|
56
|
+
};
|
|
57
57
|
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
const circularDeepEqual = createCustomEqual<Cache>({
|
|
59
|
+
circular: true,
|
|
60
|
+
createState: () => ({
|
|
61
|
+
cache: getCache(),
|
|
62
|
+
meta,
|
|
63
|
+
}),
|
|
64
|
+
});
|
|
61
65
|
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
const circularShallowEqual = createCustomEqual<Cache>({
|
|
67
|
+
circular: true,
|
|
68
|
+
comparator: sameValueZeroEqual,
|
|
69
|
+
createState: () => ({
|
|
70
|
+
cache: getCache(),
|
|
71
|
+
meta,
|
|
72
|
+
}),
|
|
73
|
+
});
|
|
66
74
|
```
|
|
@@ -6,21 +6,20 @@ Starting in `4.x.x`, `RegExp.prototype.flags` is expected to be available in the
|
|
|
6
6
|
import { createCustomEqual, sameValueZeroEqual } from 'deep-Equals';
|
|
7
7
|
import type { TypeEqualityComparator } from 'fast-equals';
|
|
8
8
|
|
|
9
|
-
const areRegExpsEqual: TypeEqualityComparator<RegExp, undefined> = (a, b) =>
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
a.lastIndex === b.lastIndex
|
|
18
|
-
);
|
|
19
|
-
};
|
|
9
|
+
const areRegExpsEqual: TypeEqualityComparator<RegExp, undefined> = (a, b) =>
|
|
10
|
+
a.source === b.source &&
|
|
11
|
+
a.global === b.global &&
|
|
12
|
+
a.ignoreCase === b.ignoreCase &&
|
|
13
|
+
a.multiline === b.multiline &&
|
|
14
|
+
a.unicode === b.unicode &&
|
|
15
|
+
a.sticky === b.sticky &&
|
|
16
|
+
a.lastIndex === b.lastIndex;
|
|
20
17
|
|
|
21
|
-
const deepEqual = createCustomEqual(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
const deepEqual = createCustomEqual({
|
|
19
|
+
createCustomConfig: () => ({ areRegExpsEqual }),
|
|
20
|
+
});
|
|
21
|
+
const shallowEqual = createCustomEqual({
|
|
22
|
+
comparator: sameValueZero,
|
|
23
|
+
createCustomConfig: () => ({ areRegExpsEqual }),
|
|
24
|
+
});
|
|
26
25
|
```
|
|
@@ -1,34 +1,46 @@
|
|
|
1
1
|
# Non-standard properties
|
|
2
2
|
|
|
3
|
-
Sometimes, objects require a comparison that extend beyond its own keys
|
|
4
|
-
|
|
5
|
-
Using a custom object comparator with `createCustomEqual` allows these kinds of comparisons.
|
|
3
|
+
Sometimes, objects require a comparison that extend beyond its own keys, or even its own properties or symbols. Using a custom object comparator with `createCustomEqual` allows these kinds of comparisons.
|
|
6
4
|
|
|
7
5
|
```ts
|
|
8
6
|
import { createCustomEqual } from 'fast-equals';
|
|
9
7
|
import type { TypeEqualityComparator } from 'fast-equals';
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
if (propertiesA.length !== propertiesB.length) {
|
|
25
|
-
return false;
|
|
9
|
+
class HiddenProperty {
|
|
10
|
+
visible: boolean;
|
|
11
|
+
#hidden: string;
|
|
12
|
+
|
|
13
|
+
constructor(value: string) {
|
|
14
|
+
this.visible = true;
|
|
15
|
+
this.#hidden = value;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get hidden() {
|
|
19
|
+
return this.#hidden;
|
|
26
20
|
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function createAreObjectsEqual(
|
|
24
|
+
areObjectsEqual: TypeEqualityComparator<Record<any, any>, undefined>,
|
|
25
|
+
): TypeEqualityComparator<Record<any, any>, undefined> {
|
|
26
|
+
return function (a, b, state) {
|
|
27
|
+
if (!areObjectsEqual(a, b, state)) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const aInstance = a instanceof HiddenProperty;
|
|
32
|
+
const bInstance = b instanceof HiddenProperty;
|
|
33
|
+
|
|
34
|
+
if (aInstance || bInstance) {
|
|
35
|
+
return aInstance && bInstance && a.hidden === b.hidden;
|
|
36
|
+
}
|
|
27
37
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
};
|
|
38
|
+
return true;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
32
41
|
|
|
33
|
-
const deepEqual = createCustomEqual(
|
|
42
|
+
const deepEqual = createCustomEqual({
|
|
43
|
+
createCustomConfig: ({ areObjectsEqual }) =>
|
|
44
|
+
createAreObjectsEqual(areObjectsEqual),
|
|
45
|
+
});
|
|
34
46
|
```
|
|
@@ -1,23 +1,20 @@
|
|
|
1
1
|
# Using `meta` in comparison
|
|
2
2
|
|
|
3
|
-
Sometimes a "pure" equality between two objects is insufficient, because the comparison relies on some external state. While these kinds of scenarios should generally be avoided, it is possible to handle them with a custom comparator that checks `meta` values.
|
|
3
|
+
Sometimes a "pure" equality between two objects is insufficient, because the comparison relies on some external state. While these kinds of scenarios should generally be avoided, it is possible to handle them with a custom internal comparator that checks `meta` values.
|
|
4
4
|
|
|
5
5
|
```ts
|
|
6
6
|
import { createCustomEqual } from 'fast-equals';
|
|
7
|
-
import type {
|
|
8
|
-
EqualityComparator,
|
|
9
|
-
InternalEqualityComparator,
|
|
10
|
-
} from 'fast-equals';
|
|
11
7
|
|
|
12
|
-
interface
|
|
13
|
-
|
|
8
|
+
interface Meta {
|
|
9
|
+
value: string;
|
|
14
10
|
}
|
|
15
11
|
|
|
16
|
-
const
|
|
12
|
+
const meta: Meta = { value: 'baz' };
|
|
17
13
|
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
const deepEqual = createCustomEqual<Meta>({
|
|
15
|
+
createInternalComparator:
|
|
16
|
+
(compare) => (a, b, _keyA, _keyB, _parentA, _parentB, state) =>
|
|
17
|
+
compare(a, b, state) || a === state.meta.value || b === state.meta.value,
|
|
18
|
+
createState: () => ({ meta }),
|
|
19
|
+
});
|
|
23
20
|
```
|
package/src/comparator.ts
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
import { isPlainObject, isPromiseLike, sameValueZeroEqual } from './utils';
|
|
2
|
-
|
|
3
1
|
import type {
|
|
4
|
-
|
|
2
|
+
ComparatorConfig,
|
|
5
3
|
EqualityComparator,
|
|
6
|
-
|
|
4
|
+
State,
|
|
5
|
+
} from './internalTypes';
|
|
7
6
|
|
|
8
7
|
const ARGUMENTS_TAG = '[object Arguments]';
|
|
9
8
|
const BOOLEAN_TAG = '[object Boolean]';
|
|
10
9
|
const DATE_TAG = '[object Date]';
|
|
11
|
-
const REG_EXP_TAG = '[object RegExp]';
|
|
12
10
|
const MAP_TAG = '[object Map]';
|
|
13
11
|
const NUMBER_TAG = '[object Number]';
|
|
14
12
|
const OBJECT_TAG = '[object Object]';
|
|
13
|
+
const REG_EXP_TAG = '[object RegExp]';
|
|
15
14
|
const SET_TAG = '[object Set]';
|
|
16
15
|
const STRING_TAG = '[object String]';
|
|
17
16
|
|
|
18
|
-
const {
|
|
17
|
+
const { isArray } = Array;
|
|
18
|
+
const getTag = Object.prototype.toString.call.bind(
|
|
19
|
+
Object.prototype.toString,
|
|
20
|
+
) as (a: object) => string;
|
|
19
21
|
|
|
20
22
|
export function createComparator<Meta>({
|
|
21
23
|
areArraysEqual,
|
|
22
24
|
areDatesEqual,
|
|
23
25
|
areMapsEqual,
|
|
24
26
|
areObjectsEqual,
|
|
27
|
+
arePrimitiveWrappersEqual,
|
|
25
28
|
areRegExpsEqual,
|
|
26
29
|
areSetsEqual,
|
|
27
|
-
|
|
28
|
-
}: CreateComparatorCreatorOptions<Meta>): EqualityComparator<Meta> {
|
|
29
|
-
const isEqual = createIsNestedEqual(comparator as EqualityComparator<Meta>);
|
|
30
|
-
|
|
30
|
+
}: ComparatorConfig<Meta>): EqualityComparator<Meta> {
|
|
31
31
|
/**
|
|
32
32
|
* compare the value of the two objects and return true if they are equivalent in values
|
|
33
33
|
*/
|
|
34
|
-
function comparator(a: any, b: any,
|
|
34
|
+
return function comparator(a: any, b: any, state: State<Meta>): boolean {
|
|
35
35
|
// If the items are strictly equal, no need to do a value comparison.
|
|
36
36
|
if (a === b) {
|
|
37
37
|
return true;
|
|
@@ -57,64 +57,69 @@ export function createComparator<Meta>({
|
|
|
57
57
|
// `isPlainObject` only checks against the object's own realm. Cross-realm
|
|
58
58
|
// comparisons are rare, and will be handled in the ultimate fallback, so
|
|
59
59
|
// we can avoid the `toString.call()` cost unless necessary.
|
|
60
|
-
if (
|
|
61
|
-
return areObjectsEqual(a, b,
|
|
60
|
+
if (a.constructor === Object && b.constructor === Object) {
|
|
61
|
+
return areObjectsEqual(a, b, state);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
// `isArray()` works on subclasses and is cross-realm, so we can again avoid
|
|
65
65
|
// the `toString.call()` cost unless necessary by just checking if either
|
|
66
66
|
// and then both are arrays.
|
|
67
|
-
const aArray =
|
|
68
|
-
const bArray =
|
|
67
|
+
const aArray = isArray(a);
|
|
68
|
+
const bArray = isArray(b);
|
|
69
69
|
|
|
70
70
|
if (aArray || bArray) {
|
|
71
|
-
return aArray === bArray && areArraysEqual(a, b,
|
|
71
|
+
return aArray === bArray && areArraysEqual(a, b, state);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
// Since this is a custom object, use the classic `toString.call()` to get its
|
|
75
75
|
// type. This is reasonably performant in modern environments like v8 and
|
|
76
76
|
// SpiderMonkey, and allows for cross-realm comparison when other checks like
|
|
77
77
|
// `instanceof` do not.
|
|
78
|
-
const
|
|
78
|
+
const tag = getTag(a);
|
|
79
79
|
|
|
80
|
-
if (
|
|
80
|
+
if (tag !== getTag(b)) {
|
|
81
81
|
return false;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
// or the unary `+` operator.
|
|
87
|
-
return areDatesEqual(a, b, isEqual, meta);
|
|
84
|
+
if (tag === DATE_TAG) {
|
|
85
|
+
return areDatesEqual(a, b, state);
|
|
88
86
|
}
|
|
89
87
|
|
|
90
|
-
if (
|
|
91
|
-
return areRegExpsEqual(a, b,
|
|
88
|
+
if (tag === REG_EXP_TAG) {
|
|
89
|
+
return areRegExpsEqual(a, b, state);
|
|
92
90
|
}
|
|
93
91
|
|
|
94
|
-
if (
|
|
95
|
-
return areMapsEqual(a, b,
|
|
92
|
+
if (tag === MAP_TAG) {
|
|
93
|
+
return areMapsEqual(a, b, state);
|
|
96
94
|
}
|
|
97
95
|
|
|
98
|
-
if (
|
|
99
|
-
return areSetsEqual(a, b,
|
|
96
|
+
if (tag === SET_TAG) {
|
|
97
|
+
return areSetsEqual(a, b, state);
|
|
100
98
|
}
|
|
101
99
|
|
|
102
100
|
// If a simple object tag, then we can prioritize a simple object comparison because
|
|
103
|
-
// it is likely a custom class.
|
|
104
|
-
|
|
105
|
-
if (aTag === OBJECT_TAG || aTag === ARGUMENTS_TAG) {
|
|
101
|
+
// it is likely a custom class.
|
|
102
|
+
if (tag === OBJECT_TAG) {
|
|
106
103
|
// The exception for value comparison is `Promise`-like contracts. These should be
|
|
107
|
-
// treated the same as standard `Promise` objects, which means strict equality
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
104
|
+
// treated the same as standard `Promise` objects, which means strict equality, and if
|
|
105
|
+
// it reaches this point then that strict equality comparison has already failed.
|
|
106
|
+
return (
|
|
107
|
+
typeof a.then !== 'function' &&
|
|
108
|
+
typeof b.then !== 'function' &&
|
|
109
|
+
areObjectsEqual(a, b, state)
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// If an arguments tag, it should be treated as a standard object.
|
|
114
|
+
if (tag === ARGUMENTS_TAG) {
|
|
115
|
+
return areObjectsEqual(a, b, state);
|
|
111
116
|
}
|
|
112
117
|
|
|
113
118
|
// As the penultimate fallback, check if the values passed are primitive wrappers. This
|
|
114
119
|
// is very rare in modern JS, which is why it is deprioritized compared to all other object
|
|
115
120
|
// types.
|
|
116
|
-
if (
|
|
117
|
-
return
|
|
121
|
+
if (tag === BOOLEAN_TAG || tag === NUMBER_TAG || tag === STRING_TAG) {
|
|
122
|
+
return arePrimitiveWrappersEqual(a, b, state);
|
|
118
123
|
}
|
|
119
124
|
|
|
120
125
|
// If not matching any tags that require a specific type of comparison, then we hard-code false because
|
|
@@ -129,7 +134,5 @@ export function createComparator<Meta>({
|
|
|
129
134
|
// In all cases, these decisions should be reevaluated based on changes to the language and
|
|
130
135
|
// common development practices.
|
|
131
136
|
return false;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return comparator as EqualityComparator<Meta>;
|
|
137
|
+
};
|
|
135
138
|
}
|