eslint-plugin-effector 0.4.2 → 0.6.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/README.md +25 -6
- package/config/react.js +5 -0
- package/config/recommended.js +2 -0
- package/config/scope.js +5 -0
- package/index.js +7 -0
- package/package.json +4 -1
- package/rules/enforce-effect-naming-convention/enforce-effect-naming-convention.js +16 -17
- package/rules/enforce-gate-naming-convention/enforce-gate-naming-convention.js +114 -0
- package/rules/enforce-store-naming-convention/enforce-store-naming-convention.js +25 -25
- package/rules/keep-options-order/config.js +3 -0
- package/rules/keep-options-order/keep-options-order.js +107 -0
- package/rules/no-ambiguity-target/no-ambiguity-target.js +38 -39
- package/rules/no-duplicate-on/no-duplicate-on.js +137 -0
- package/rules/no-forward/no-forward.js +77 -0
- package/rules/no-getState/no-getState.js +9 -36
- package/rules/no-unnecessary-combination/no-unnecessary-combination.js +39 -45
- package/rules/no-unnecessary-duplication/no-unnecessary-duplication.js +41 -45
- package/rules/no-useless-methods/no-useless-methods.js +56 -57
- package/rules/no-watch/no-watch.js +6 -12
- package/rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping.js +31 -13
- package/rules/strict-effect-handlers/strict-effect-handlers.js +76 -0
- package/utils/builders.js +19 -0
- package/utils/extract-imported-from.js +10 -0
- package/utils/get-corrected-store-name.js +12 -14
- package/utils/get-nested-object-name.js +18 -0
- package/utils/get-store-name-convention.js +3 -3
- package/utils/is.js +30 -0
- package/utils/method.js +23 -0
- package/utils/naming.js +47 -0
- package/utils/node-type-is.js +59 -0
- package/utils/replace-by-sample.js +66 -0
- package/utils/validate-store-name-convention.js +7 -7
- package/rules/enforce-effect-naming-convention/enforce-effect-naming-convention.md +0 -11
- package/rules/enforce-store-naming-convention/enforce-store-naming-convention.md +0 -44
- package/rules/no-ambiguity-target/no-ambiguity-target.md +0 -12
- package/rules/no-getState/no-getState.md +0 -20
- package/rules/no-unnecessary-combination/no-unnecessary-combination.md +0 -14
- package/rules/no-unnecessary-duplication/no-unnecessary-duplication.md +0 -32
- package/rules/no-useless-methods/no-useless-methods.md +0 -14
- package/rules/no-watch/no-watch.md +0 -42
- package/rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping.md +0 -27
- package/rules/tsconfig.json +0 -9
- package/utils/extract-imported-from-effector.js +0 -8
- package/utils/is-store-name-valid.js +0 -22
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ Add `effector` to the plugins section of your `.eslintrc` configuration file. Yo
|
|
|
25
25
|
```json
|
|
26
26
|
{
|
|
27
27
|
"plugins": ["effector"],
|
|
28
|
-
"extends": ["plugin:effector/recommended"]
|
|
28
|
+
"extends": ["plugin:effector/recommended", "plugin:effector/scope"]
|
|
29
29
|
}
|
|
30
30
|
```
|
|
31
31
|
|
|
@@ -39,17 +39,36 @@ To configure individual rules:
|
|
|
39
39
|
}
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
### Available presets
|
|
43
|
+
|
|
44
|
+
#### plugin:effector/recommended
|
|
45
|
+
|
|
46
|
+
This preset is recommended for most projects.
|
|
47
|
+
|
|
48
|
+
#### plugin:effector/scope
|
|
49
|
+
|
|
50
|
+
This preset is recommended for projects that use [Fork API](https://effector.dev/docs/api/effector/scope). You can read more about Fork API in [an article](https://dev.to/effector/the-best-part-of-effector-4c27).
|
|
51
|
+
|
|
52
|
+
#### plugin:effector/react
|
|
53
|
+
|
|
54
|
+
This preset is recommended for projects that use [React](https://reactjs.org) with Effector.
|
|
55
|
+
|
|
56
|
+
### Supported rules
|
|
43
57
|
|
|
44
58
|
- [effector/enforce-store-naming-convention](rules/enforce-store-naming-convention/enforce-store-naming-convention.md)
|
|
45
59
|
- [effector/enforce-effect-naming-convention](rules/enforce-effect-naming-convention/enforce-effect-naming-convention.md)
|
|
46
|
-
- [effector/
|
|
60
|
+
- [effector/enforce-gate-naming-convention](rules/enforce-gate-naming-convention/enforce-gate-naming-convention.md)
|
|
47
61
|
- [effector/no-unnecessary-duplication](rules/no-unnecessary-duplication/no-unnecessary-duplication.md)
|
|
62
|
+
- [effector/no-unnecessary-combination](rules/no-unnecessary-combination/no-unnecessary-combination.md)
|
|
48
63
|
- [effector/no-useless-methods](rules/no-useless-methods/no-useless-methods.md)
|
|
64
|
+
- [effector/no-forward](rules/no-forward/no-forward.md)
|
|
49
65
|
- [effector/no-ambiguity-target](rules/no-ambiguity-target/no-ambiguity-target.md)
|
|
50
|
-
- [effector/
|
|
66
|
+
- [effector/no-duplicate-on](rules/no-duplicate-on/no-duplicate-on.md)
|
|
67
|
+
- [effector/no-getState](rules/no-getState/no-getState.md)
|
|
51
68
|
- [effector/no-watch](rules/no-watch/no-watch.md)
|
|
52
|
-
- [effector/
|
|
69
|
+
- [effector/prefer-sample-over-forward-with-mapping](rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping.md)
|
|
70
|
+
- [effector/strict-effect-handlers](rules/strict-effect-handlers/strict-effect-handlers.md)
|
|
71
|
+
- [effector/keep-options-order](rules/keep-options-order/keep-options-order.md)
|
|
53
72
|
|
|
54
73
|
## Maintenance
|
|
55
74
|
|
|
@@ -57,7 +76,7 @@ To configure individual rules:
|
|
|
57
76
|
|
|
58
77
|
1. Bump `version` in [package.json](package.json)
|
|
59
78
|
2. Fill [CHANGELOG.md](CHANGELOG.md)
|
|
60
|
-
3. Commit changes by `git
|
|
79
|
+
3. Commit changes by `git commit -m "Release X.X.X"`
|
|
61
80
|
4. Create git tag for release by `git tag -a vX.X.X -m "vX.X.X"`
|
|
62
81
|
5. Push changes to remote by `git push --follow-tags`
|
|
63
82
|
6. Release package to registry by `yarn clean-publish`
|
package/config/react.js
ADDED
package/config/recommended.js
CHANGED
package/config/scope.js
ADDED
package/index.js
CHANGED
|
@@ -9,8 +9,15 @@ module.exports = {
|
|
|
9
9
|
"no-ambiguity-target": require("./rules/no-ambiguity-target/no-ambiguity-target"),
|
|
10
10
|
"no-watch": require("./rules/no-watch/no-watch"),
|
|
11
11
|
"no-unnecessary-combination": require("./rules/no-unnecessary-combination/no-unnecessary-combination"),
|
|
12
|
+
"no-duplicate-on": require("./rules/no-duplicate-on/no-duplicate-on"),
|
|
13
|
+
"strict-effect-handlers": require("./rules/strict-effect-handlers/strict-effect-handlers"),
|
|
14
|
+
"enforce-gate-naming-convention": require("./rules/enforce-gate-naming-convention/enforce-gate-naming-convention"),
|
|
15
|
+
"keep-options-order": require("./rules/keep-options-order/keep-options-order"),
|
|
16
|
+
"no-forward": require("./rules/no-forward/no-forward"),
|
|
12
17
|
},
|
|
13
18
|
configs: {
|
|
14
19
|
recommended: require("./config/recommended"),
|
|
20
|
+
scope: require("./config/scope"),
|
|
21
|
+
react: require("./config/react"),
|
|
15
22
|
},
|
|
16
23
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-effector",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Enforcing best practices for Effector",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eslint",
|
|
@@ -24,5 +24,8 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"prettier": "^2.3.2"
|
|
27
|
+
},
|
|
28
|
+
"nano-staged": {
|
|
29
|
+
"*.{js,ts,md}": "prettier --write"
|
|
27
30
|
}
|
|
28
31
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
const {
|
|
2
|
-
extractImportedFromEffector,
|
|
3
|
-
} = require("../../utils/extract-imported-from-effector");
|
|
1
|
+
const { extractImportedFrom } = require("../../utils/extract-imported-from");
|
|
4
2
|
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
3
|
+
const { nodeTypeIs } = require("../../utils/node-type-is");
|
|
4
|
+
const { namingOf } = require("../../utils/naming");
|
|
5
5
|
|
|
6
6
|
module.exports = {
|
|
7
7
|
meta: {
|
|
@@ -27,13 +27,10 @@ module.exports = {
|
|
|
27
27
|
if (parserServices.hasFullTypeInformation) {
|
|
28
28
|
return {
|
|
29
29
|
VariableDeclarator(node) {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const isEffectorEffect =
|
|
35
|
-
type?.symbol?.escapedName === "Effect" &&
|
|
36
|
-
type?.symbol?.parent?.escapedName?.includes("effector");
|
|
30
|
+
const isEffectorEffect = nodeTypeIs.effect({
|
|
31
|
+
node,
|
|
32
|
+
context,
|
|
33
|
+
});
|
|
37
34
|
|
|
38
35
|
if (!isEffectorEffect) {
|
|
39
36
|
return;
|
|
@@ -41,11 +38,9 @@ module.exports = {
|
|
|
41
38
|
|
|
42
39
|
const effectName = node.id.name;
|
|
43
40
|
|
|
44
|
-
if (effectName
|
|
45
|
-
|
|
41
|
+
if (namingOf.effect.isInvalid({ name: effectName })) {
|
|
42
|
+
reportEffectNameConventionViolation({ context, node, effectName });
|
|
46
43
|
}
|
|
47
|
-
|
|
48
|
-
reportEffectNameConventionViolation({ context, node, effectName });
|
|
49
44
|
},
|
|
50
45
|
};
|
|
51
46
|
}
|
|
@@ -54,7 +49,11 @@ module.exports = {
|
|
|
54
49
|
const importedFromEffector = new Map();
|
|
55
50
|
return {
|
|
56
51
|
ImportDeclaration(node) {
|
|
57
|
-
|
|
52
|
+
extractImportedFrom({
|
|
53
|
+
importMap: importedFromEffector,
|
|
54
|
+
node,
|
|
55
|
+
packageName: "effector",
|
|
56
|
+
});
|
|
58
57
|
},
|
|
59
58
|
CallExpression(node) {
|
|
60
59
|
// Effect creation with method
|
|
@@ -77,7 +76,7 @@ module.exports = {
|
|
|
77
76
|
}
|
|
78
77
|
|
|
79
78
|
const effectName = node.parent.id.name;
|
|
80
|
-
if (
|
|
79
|
+
if (namingOf.effect.isValid({ name: effectName })) {
|
|
81
80
|
continue;
|
|
82
81
|
}
|
|
83
82
|
|
|
@@ -101,7 +100,7 @@ module.exports = {
|
|
|
101
100
|
}
|
|
102
101
|
|
|
103
102
|
const effectName = node.parent.id.name;
|
|
104
|
-
if (
|
|
103
|
+
if (namingOf.effect.isValid({ name: effectName })) {
|
|
105
104
|
return;
|
|
106
105
|
}
|
|
107
106
|
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
const { extractImportedFrom } = require("../../utils/extract-imported-from");
|
|
2
|
+
const { nodeTypeIs } = require("../../utils/node-type-is");
|
|
3
|
+
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
4
|
+
const { namingOf } = require("../../utils/naming");
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
meta: {
|
|
8
|
+
type: "problem",
|
|
9
|
+
docs: {
|
|
10
|
+
description: "Enforce first capital letter for gate naming",
|
|
11
|
+
category: "Naming",
|
|
12
|
+
recommended: true,
|
|
13
|
+
url: createLinkToRule("enforce-gate-naming-convention"),
|
|
14
|
+
},
|
|
15
|
+
messages: {
|
|
16
|
+
invalidName:
|
|
17
|
+
'Gate "{{ gateName }}" should be named with first capital letter, rename it to "{{ correctedGateName }}"',
|
|
18
|
+
renameGate: 'Rename "{{ gateName }}" to "{{ correctedGateName }}"',
|
|
19
|
+
},
|
|
20
|
+
schema: [],
|
|
21
|
+
hasSuggestions: true,
|
|
22
|
+
},
|
|
23
|
+
create(context) {
|
|
24
|
+
const parserServices = context.parserServices;
|
|
25
|
+
// TypeScript-way
|
|
26
|
+
if (parserServices.hasFullTypeInformation) {
|
|
27
|
+
return {
|
|
28
|
+
VariableDeclarator(node) {
|
|
29
|
+
const isEffectorGate = nodeTypeIs.gate({
|
|
30
|
+
node,
|
|
31
|
+
context,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (!isEffectorGate) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const gateName = node.id.name;
|
|
39
|
+
|
|
40
|
+
if (namingOf.gate.isInvalid({ name: gateName })) {
|
|
41
|
+
reportGateNameConventionViolation({ context, node, gateName });
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// JavaScript-way
|
|
48
|
+
const importedFromEffectorReact = new Map();
|
|
49
|
+
return {
|
|
50
|
+
ImportDeclaration(node) {
|
|
51
|
+
extractImportedFrom({
|
|
52
|
+
importMap: importedFromEffectorReact,
|
|
53
|
+
node,
|
|
54
|
+
packageName: "effector-react",
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
CallExpression(node) {
|
|
58
|
+
// Effect creation with method
|
|
59
|
+
const GATE_CREATION_METHODS = ["createGate"];
|
|
60
|
+
for (const method of GATE_CREATION_METHODS) {
|
|
61
|
+
const localMethod = importedFromEffectorReact.get(method);
|
|
62
|
+
if (!localMethod) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const isEffectorGateCreation = node.callee.name === localMethod;
|
|
67
|
+
if (!isEffectorGateCreation) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const resultSavedInVariable =
|
|
72
|
+
node.parent.type === "VariableDeclarator";
|
|
73
|
+
if (!resultSavedInVariable) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const gateName = node.parent.id.name;
|
|
78
|
+
if (namingOf.gate.isValid({ name: gateName })) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
reportGateNameConventionViolation({
|
|
83
|
+
context,
|
|
84
|
+
node: node.parent,
|
|
85
|
+
gateName,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
function reportGateNameConventionViolation({ context, node, gateName }) {
|
|
94
|
+
const [firstChar, ...restChars] = gateName.split("");
|
|
95
|
+
const correctedGateName = [firstChar.toUpperCase(), ...restChars].join("");
|
|
96
|
+
|
|
97
|
+
context.report({
|
|
98
|
+
node,
|
|
99
|
+
messageId: "invalidName",
|
|
100
|
+
data: {
|
|
101
|
+
gateName,
|
|
102
|
+
correctedGateName,
|
|
103
|
+
},
|
|
104
|
+
suggest: [
|
|
105
|
+
{
|
|
106
|
+
messageId: "renameGate",
|
|
107
|
+
data: { gateName, correctedGateName },
|
|
108
|
+
fix(fixer) {
|
|
109
|
+
return fixer.replaceTextRange(node.id.range, correctedGateName);
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
});
|
|
114
|
+
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
const {
|
|
2
|
-
|
|
3
|
-
} = require("../../utils/extract-imported-from-effector");
|
|
4
|
-
const { isStoreNameValid } = require("../../utils/is-store-name-valid");
|
|
1
|
+
const { extractImportedFrom } = require("../../utils/extract-imported-from");
|
|
2
|
+
const { namingOf } = require("../../utils/naming");
|
|
5
3
|
const {
|
|
6
4
|
validateStoreNameConvention,
|
|
7
5
|
} = require("../../utils/validate-store-name-convention");
|
|
@@ -12,6 +10,7 @@ const {
|
|
|
12
10
|
getCorrectedStoreName,
|
|
13
11
|
} = require("../../utils/get-corrected-store-name");
|
|
14
12
|
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
13
|
+
const { nodeTypeIs } = require("../../utils/node-type-is");
|
|
15
14
|
|
|
16
15
|
module.exports = {
|
|
17
16
|
meta: {
|
|
@@ -40,13 +39,10 @@ module.exports = {
|
|
|
40
39
|
if (parserServices.hasFullTypeInformation) {
|
|
41
40
|
return {
|
|
42
41
|
VariableDeclarator(node) {
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const isEffectorStore =
|
|
48
|
-
type?.symbol?.escapedName === "Store" &&
|
|
49
|
-
type?.symbol?.parent?.escapedName?.includes("effector");
|
|
42
|
+
const isEffectorStore = nodeTypeIs.store({
|
|
43
|
+
node,
|
|
44
|
+
context,
|
|
45
|
+
});
|
|
50
46
|
|
|
51
47
|
if (!isEffectorStore) {
|
|
52
48
|
return;
|
|
@@ -54,15 +50,13 @@ module.exports = {
|
|
|
54
50
|
|
|
55
51
|
const storeName = node.id.name;
|
|
56
52
|
|
|
57
|
-
if (
|
|
58
|
-
|
|
53
|
+
if (namingOf.store.isInvalid({ name: storeName, context })) {
|
|
54
|
+
reportStoreNameConventionViolation({
|
|
55
|
+
context,
|
|
56
|
+
node,
|
|
57
|
+
storeName,
|
|
58
|
+
});
|
|
59
59
|
}
|
|
60
|
-
|
|
61
|
-
reportStoreNameConventionViolation({
|
|
62
|
-
context,
|
|
63
|
-
node,
|
|
64
|
-
storeName,
|
|
65
|
-
});
|
|
66
60
|
},
|
|
67
61
|
};
|
|
68
62
|
}
|
|
@@ -71,7 +65,11 @@ module.exports = {
|
|
|
71
65
|
const importedFromEffector = new Map();
|
|
72
66
|
return {
|
|
73
67
|
ImportDeclaration(node) {
|
|
74
|
-
|
|
68
|
+
extractImportedFrom({
|
|
69
|
+
importMap: importedFromEffector,
|
|
70
|
+
node,
|
|
71
|
+
packageName: "effector",
|
|
72
|
+
});
|
|
75
73
|
},
|
|
76
74
|
CallExpression(node) {
|
|
77
75
|
// Store creation with method
|
|
@@ -95,7 +93,7 @@ module.exports = {
|
|
|
95
93
|
|
|
96
94
|
const storeName = node.parent.id.name;
|
|
97
95
|
|
|
98
|
-
if (
|
|
96
|
+
if (namingOf.store.isValid({ name: storeName, context })) {
|
|
99
97
|
continue;
|
|
100
98
|
}
|
|
101
99
|
|
|
@@ -111,7 +109,9 @@ module.exports = {
|
|
|
111
109
|
if (node.callee?.property?.name === "map") {
|
|
112
110
|
const storeNameCreatedFromMap = node.callee?.object?.name;
|
|
113
111
|
|
|
114
|
-
if (
|
|
112
|
+
if (
|
|
113
|
+
namingOf.store.isInvalid({ name: storeNameCreatedFromMap, context })
|
|
114
|
+
) {
|
|
115
115
|
return;
|
|
116
116
|
}
|
|
117
117
|
|
|
@@ -123,7 +123,7 @@ module.exports = {
|
|
|
123
123
|
|
|
124
124
|
const storeName = node.parent.id.name;
|
|
125
125
|
|
|
126
|
-
if (
|
|
126
|
+
if (namingOf.store.isValid({ name: storeName, context })) {
|
|
127
127
|
return;
|
|
128
128
|
}
|
|
129
129
|
|
|
@@ -148,7 +148,7 @@ module.exports = {
|
|
|
148
148
|
|
|
149
149
|
const storeName = node.parent.id.name;
|
|
150
150
|
|
|
151
|
-
if (
|
|
151
|
+
if (namingOf.store.isValid({ name: storeName, context })) {
|
|
152
152
|
return;
|
|
153
153
|
}
|
|
154
154
|
|
|
@@ -179,7 +179,7 @@ function reportStoreNameConventionViolation({ context, node, storeName }) {
|
|
|
179
179
|
suggest: [
|
|
180
180
|
{
|
|
181
181
|
messageId: "renameStore",
|
|
182
|
-
data: { storeName },
|
|
182
|
+
data: { storeName, correctedStoreName },
|
|
183
183
|
fix(fixer) {
|
|
184
184
|
return fixer.replaceTextRange(node.id.range, correctedStoreName);
|
|
185
185
|
},
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
const { extractImportedFrom } = require("../../utils/extract-imported-from");
|
|
2
|
+
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
3
|
+
const { buildObjectInText } = require("../../utils/builders");
|
|
4
|
+
|
|
5
|
+
const { correctOrder } = require("./config");
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
meta: {
|
|
9
|
+
type: "problem",
|
|
10
|
+
docs: {
|
|
11
|
+
description: "Enforce options order for Effector methods",
|
|
12
|
+
category: "Style",
|
|
13
|
+
recommended: true,
|
|
14
|
+
url: createLinkToRule("keep-options-order"),
|
|
15
|
+
},
|
|
16
|
+
messages: {
|
|
17
|
+
invalidOrder: `Order of options should be ${makeTag(
|
|
18
|
+
correctOrder
|
|
19
|
+
)}, but found {{ incorrectOrderTag }}`,
|
|
20
|
+
changeOrder: "Sort options",
|
|
21
|
+
},
|
|
22
|
+
schema: [],
|
|
23
|
+
hasSuggestions: true,
|
|
24
|
+
},
|
|
25
|
+
create(context) {
|
|
26
|
+
const importedFromEffector = new Map();
|
|
27
|
+
return {
|
|
28
|
+
ImportDeclaration(node) {
|
|
29
|
+
extractImportedFrom({
|
|
30
|
+
importMap: importedFromEffector,
|
|
31
|
+
node,
|
|
32
|
+
packageName: "effector",
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
CallExpression(node) {
|
|
36
|
+
// Effect creation with method
|
|
37
|
+
const OPTIONS_METHODS = ["sample", "guard"];
|
|
38
|
+
for (const method of OPTIONS_METHODS) {
|
|
39
|
+
const localMethod = importedFromEffector.get(method);
|
|
40
|
+
if (!localMethod) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const isEffectorMethods = node.callee.name === localMethod;
|
|
45
|
+
if (!isEffectorMethods) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const configNode = node?.arguments?.[0];
|
|
50
|
+
const optionsNodes = configNode?.properties;
|
|
51
|
+
|
|
52
|
+
const optionsOrder = optionsNodes?.map((prop) => prop?.key.name);
|
|
53
|
+
|
|
54
|
+
const idealOrder = filteredCorrectOrder(optionsOrder);
|
|
55
|
+
|
|
56
|
+
if (isCorrectOrder(optionsOrder, idealOrder)) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
context.report({
|
|
61
|
+
node,
|
|
62
|
+
messageId: "invalidOrder",
|
|
63
|
+
data: {
|
|
64
|
+
incorrectOrderTag: makeTag(optionsOrder),
|
|
65
|
+
},
|
|
66
|
+
suggest: [
|
|
67
|
+
{
|
|
68
|
+
messageId: "changeOrder",
|
|
69
|
+
fix(fixer) {
|
|
70
|
+
const newConfig = buildObjectInText.fromArrayOfNodes({
|
|
71
|
+
properties: sortNodesByName(optionsNodes, idealOrder),
|
|
72
|
+
context,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return fixer.replaceText(configNode, newConfig);
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
function makeTag(order) {
|
|
87
|
+
return order.join("->");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isCorrectOrder(checkOrder, idealOrder) {
|
|
91
|
+
return idealOrder.every((refItem, index) => checkOrder?.[index] === refItem);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function filteredCorrectOrder(checkOrder) {
|
|
95
|
+
return correctOrder.filter((item) => checkOrder?.includes(item));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function sortNodesByName(nodes, nameOrder) {
|
|
99
|
+
const newNodes = [];
|
|
100
|
+
|
|
101
|
+
for (const name of nameOrder) {
|
|
102
|
+
const node = nodes.find((node) => node?.key.name === name);
|
|
103
|
+
newNodes.push(node);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return newNodes;
|
|
107
|
+
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
const {
|
|
2
|
-
extractImportedFromEffector,
|
|
3
|
-
} = require("../../utils/extract-imported-from-effector");
|
|
1
|
+
const { extractImportedFrom } = require("../../utils/extract-imported-from");
|
|
4
2
|
const { traverseParentByType } = require("../../utils/traverse-parent-by-type");
|
|
5
3
|
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
4
|
+
const { method } = require("../../utils/method");
|
|
6
5
|
|
|
7
6
|
module.exports = {
|
|
8
7
|
meta: {
|
|
@@ -24,49 +23,49 @@ module.exports = {
|
|
|
24
23
|
|
|
25
24
|
return {
|
|
26
25
|
ImportDeclaration(node) {
|
|
27
|
-
|
|
26
|
+
extractImportedFrom({
|
|
27
|
+
importMap: importedFromEffector,
|
|
28
|
+
node,
|
|
29
|
+
packageName: "effector",
|
|
30
|
+
});
|
|
28
31
|
},
|
|
29
32
|
CallExpression(node) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
if (
|
|
34
|
+
method.isNot(["sample", "guard"], {
|
|
35
|
+
node,
|
|
36
|
+
importMap: importedFromEffector,
|
|
37
|
+
})
|
|
38
|
+
) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
36
41
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
const configHasTarget = node?.arguments?.[0]?.properties?.some(
|
|
43
|
+
(prop) => prop?.key.name === "target"
|
|
44
|
+
);
|
|
45
|
+
if (!configHasTarget) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
41
48
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
const resultAssignedInVariable = traverseParentByType(
|
|
50
|
+
node,
|
|
51
|
+
"VariableDeclarator",
|
|
52
|
+
{ stopOnTypes: ["BlockStatement"] }
|
|
53
|
+
);
|
|
54
|
+
const resultPartOfChain = traverseParentByType(
|
|
55
|
+
node,
|
|
56
|
+
"ObjectExpression"
|
|
57
|
+
);
|
|
48
58
|
|
|
49
|
-
|
|
59
|
+
if (resultAssignedInVariable || resultPartOfChain) {
|
|
60
|
+
context.report({
|
|
50
61
|
node,
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
"ObjectExpression"
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
if (resultAssignedInVariable || resultPartOfChain) {
|
|
60
|
-
context.report({
|
|
61
|
-
node,
|
|
62
|
-
messageId: "ambiguityTarget",
|
|
63
|
-
data: {
|
|
64
|
-
methodName: node?.callee?.name,
|
|
65
|
-
},
|
|
66
|
-
});
|
|
62
|
+
messageId: "ambiguityTarget",
|
|
63
|
+
data: {
|
|
64
|
+
methodName: node?.callee?.name,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
}
|
|
68
|
+
return;
|
|
70
69
|
}
|
|
71
70
|
},
|
|
72
71
|
};
|