eslint-plugin-effector 0.3.0 → 0.4.2
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 +21 -7
- package/config/recommended.js +2 -0
- package/index.js +3 -1
- package/package.json +10 -15
- package/rules/enforce-effect-naming-convention/enforce-effect-naming-convention.js +3 -0
- package/rules/enforce-store-naming-convention/enforce-store-naming-convention.js +25 -19
- package/rules/enforce-store-naming-convention/enforce-store-naming-convention.md +1 -1
- package/rules/no-ambiguity-target/no-ambiguity-target.js +4 -1
- package/rules/no-getState/no-getState.js +2 -0
- package/rules/no-unnecessary-combination/no-unnecessary-combination.js +94 -0
- package/rules/no-unnecessary-combination/no-unnecessary-combination.md +14 -0
- package/rules/no-unnecessary-duplication/no-unnecessary-duplication.js +3 -0
- package/rules/no-useless-methods/no-useless-methods.js +12 -0
- package/rules/no-watch/no-watch.js +59 -0
- package/rules/no-watch/no-watch.md +42 -0
- package/rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping.js +2 -0
- package/rules/tsconfig.json +1 -1
- package/utils/create-link-to-rule.js +5 -0
- package/utils/traverse-parent-by-type.js +5 -3
- package/.github/workflows/ci.yml +0 -43
- package/CHANGELOG.md +0 -24
- package/jest.config.js +0 -7
package/README.md
CHANGED
|
@@ -41,10 +41,24 @@ To configure individual rules:
|
|
|
41
41
|
|
|
42
42
|
## Supported Rules
|
|
43
43
|
|
|
44
|
-
- [effector/enforce-store-naming-convention](
|
|
45
|
-
- [effector/enforce-effect-naming-convention](
|
|
46
|
-
- [effector/no-getState](
|
|
47
|
-
- [effector/no-unnecessary-duplication](
|
|
48
|
-
- [effector/no-useless-methods](
|
|
49
|
-
- [effector/no-ambiguity-target](
|
|
50
|
-
- [effector/prefer-sample-over-forward-with-mapping](
|
|
44
|
+
- [effector/enforce-store-naming-convention](rules/enforce-store-naming-convention/enforce-store-naming-convention.md)
|
|
45
|
+
- [effector/enforce-effect-naming-convention](rules/enforce-effect-naming-convention/enforce-effect-naming-convention.md)
|
|
46
|
+
- [effector/no-getState](rules/no-getState/no-getState.md)
|
|
47
|
+
- [effector/no-unnecessary-duplication](rules/no-unnecessary-duplication/no-unnecessary-duplication.md)
|
|
48
|
+
- [effector/no-useless-methods](rules/no-useless-methods/no-useless-methods.md)
|
|
49
|
+
- [effector/no-ambiguity-target](rules/no-ambiguity-target/no-ambiguity-target.md)
|
|
50
|
+
- [effector/prefer-sample-over-forward-with-mapping](rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping.md)
|
|
51
|
+
- [effector/no-watch](rules/no-watch/no-watch.md)
|
|
52
|
+
- [effector/no-unnecessary-combination](rules/no-unnecessary-combination/no-unnecessary-combination.md)
|
|
53
|
+
|
|
54
|
+
## Maintenance
|
|
55
|
+
|
|
56
|
+
### Release flow
|
|
57
|
+
|
|
58
|
+
1. Bump `version` in [package.json](package.json)
|
|
59
|
+
2. Fill [CHANGELOG.md](CHANGELOG.md)
|
|
60
|
+
3. Commit changes by `git commin -m "Release X.X.X"`
|
|
61
|
+
4. Create git tag for release by `git tag -a vX.X.X -m "vX.X.X"`
|
|
62
|
+
5. Push changes to remote by `git push --follow-tags`
|
|
63
|
+
6. Release package to registry by `yarn clean-publish`
|
|
64
|
+
7. Fill release page with changelog on GitHub
|
package/config/recommended.js
CHANGED
package/index.js
CHANGED
|
@@ -7,8 +7,10 @@ module.exports = {
|
|
|
7
7
|
"prefer-sample-over-forward-with-mapping": require("./rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping"),
|
|
8
8
|
"no-useless-methods": require("./rules/no-useless-methods/no-useless-methods"),
|
|
9
9
|
"no-ambiguity-target": require("./rules/no-ambiguity-target/no-ambiguity-target"),
|
|
10
|
+
"no-watch": require("./rules/no-watch/no-watch"),
|
|
11
|
+
"no-unnecessary-combination": require("./rules/no-unnecessary-combination/no-unnecessary-combination"),
|
|
10
12
|
},
|
|
11
13
|
configs: {
|
|
12
14
|
recommended: require("./config/recommended"),
|
|
13
|
-
}
|
|
15
|
+
},
|
|
14
16
|
};
|
package/package.json
CHANGED
|
@@ -1,33 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-effector",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Enforcing best practices for Effector",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"eslint",
|
|
7
|
+
"eslint-plugin",
|
|
8
|
+
"eslintplugin",
|
|
9
|
+
"effector"
|
|
10
|
+
],
|
|
11
|
+
"repository": "effector/eslint-plugin",
|
|
5
12
|
"main": "index.js",
|
|
6
13
|
"author": "Igor Kamyshev <igor@kamyshev.me>",
|
|
7
14
|
"license": "MIT",
|
|
8
|
-
"scripts": {
|
|
9
|
-
"test": "jest"
|
|
10
|
-
},
|
|
11
15
|
"publishConfig": {
|
|
12
16
|
"access": "public"
|
|
13
17
|
},
|
|
14
18
|
"engines": {
|
|
15
19
|
"node": "^14 || ^16"
|
|
16
20
|
},
|
|
17
|
-
"devDependencies": {
|
|
18
|
-
"@typescript-eslint/experimental-utils": "^4.29.3",
|
|
19
|
-
"@typescript-eslint/parser": "^4.29.3",
|
|
20
|
-
"effector": "^22.0.0",
|
|
21
|
-
"eslint": "^7.32.0",
|
|
22
|
-
"glob": "^7.1.7",
|
|
23
|
-
"jest": "^27.1.0",
|
|
24
|
-
"typescript": "^4.4.2"
|
|
25
|
-
},
|
|
26
21
|
"peerDependencies": {
|
|
27
22
|
"effector": "*",
|
|
28
|
-
"eslint": "
|
|
23
|
+
"eslint": "7 || 8"
|
|
29
24
|
},
|
|
30
25
|
"dependencies": {
|
|
31
26
|
"prettier": "^2.3.2"
|
|
32
27
|
}
|
|
33
|
-
}
|
|
28
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const {
|
|
2
2
|
extractImportedFromEffector,
|
|
3
3
|
} = require("../../utils/extract-imported-from-effector");
|
|
4
|
+
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
4
5
|
|
|
5
6
|
module.exports = {
|
|
6
7
|
meta: {
|
|
@@ -10,6 +11,7 @@ module.exports = {
|
|
|
10
11
|
"Enforce Fx as a suffix for any effect created by Effector methods",
|
|
11
12
|
category: "Naming",
|
|
12
13
|
recommended: true,
|
|
14
|
+
url: createLinkToRule("enforce-effect-naming-convention"),
|
|
13
15
|
},
|
|
14
16
|
messages: {
|
|
15
17
|
invalidName:
|
|
@@ -17,6 +19,7 @@ module.exports = {
|
|
|
17
19
|
renameEffect: 'Rename "{{ effectName }}" to "{{ effectName }}Fx"',
|
|
18
20
|
},
|
|
19
21
|
schema: [],
|
|
22
|
+
hasSuggestions: true,
|
|
20
23
|
},
|
|
21
24
|
create(context) {
|
|
22
25
|
const parserServices = context.parserServices;
|
|
@@ -2,25 +2,34 @@ const {
|
|
|
2
2
|
extractImportedFromEffector,
|
|
3
3
|
} = require("../../utils/extract-imported-from-effector");
|
|
4
4
|
const { isStoreNameValid } = require("../../utils/is-store-name-valid");
|
|
5
|
-
const {
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
const {
|
|
6
|
+
validateStoreNameConvention,
|
|
7
|
+
} = require("../../utils/validate-store-name-convention");
|
|
8
|
+
const {
|
|
9
|
+
getStoreNameConvention,
|
|
10
|
+
} = require("../../utils/get-store-name-convention");
|
|
11
|
+
const {
|
|
12
|
+
getCorrectedStoreName,
|
|
13
|
+
} = require("../../utils/get-corrected-store-name");
|
|
14
|
+
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
8
15
|
|
|
9
16
|
module.exports = {
|
|
10
17
|
meta: {
|
|
11
18
|
type: "problem",
|
|
12
19
|
docs: {
|
|
13
20
|
description:
|
|
14
|
-
|
|
21
|
+
"Enforce $ as a prefix or postfix for any store created by Effector methods",
|
|
15
22
|
category: "Naming",
|
|
16
23
|
recommended: true,
|
|
24
|
+
url: createLinkToRule("enforce-store-naming-convention"),
|
|
17
25
|
},
|
|
18
26
|
messages: {
|
|
19
27
|
invalidName:
|
|
20
|
-
|
|
28
|
+
'Store "{{ storeName }}" should be named with {{ storeNameConvention }}, rename it to "{{ correctedStoreName }}"',
|
|
21
29
|
renameStore: 'Rename "{{ storeName }}" to "{{ correctedStoreName }}"',
|
|
22
30
|
},
|
|
23
31
|
schema: [],
|
|
32
|
+
hasSuggestions: true,
|
|
24
33
|
},
|
|
25
34
|
create(context) {
|
|
26
35
|
const { parserServices } = context;
|
|
@@ -36,8 +45,8 @@ module.exports = {
|
|
|
36
45
|
const type = checker.getTypeAtLocation(originalNode.initializer);
|
|
37
46
|
|
|
38
47
|
const isEffectorStore =
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
type?.symbol?.escapedName === "Store" &&
|
|
49
|
+
type?.symbol?.parent?.escapedName?.includes("effector");
|
|
41
50
|
|
|
42
51
|
if (!isEffectorStore) {
|
|
43
52
|
return;
|
|
@@ -52,7 +61,7 @@ module.exports = {
|
|
|
52
61
|
reportStoreNameConventionViolation({
|
|
53
62
|
context,
|
|
54
63
|
node,
|
|
55
|
-
storeName
|
|
64
|
+
storeName,
|
|
56
65
|
});
|
|
57
66
|
},
|
|
58
67
|
};
|
|
@@ -79,7 +88,7 @@ module.exports = {
|
|
|
79
88
|
}
|
|
80
89
|
|
|
81
90
|
const resultSavedInVariable =
|
|
82
|
-
|
|
91
|
+
node.parent.type === "VariableDeclarator";
|
|
83
92
|
if (!resultSavedInVariable) {
|
|
84
93
|
continue;
|
|
85
94
|
}
|
|
@@ -93,7 +102,7 @@ module.exports = {
|
|
|
93
102
|
reportStoreNameConventionViolation({
|
|
94
103
|
context,
|
|
95
104
|
node: node.parent,
|
|
96
|
-
storeName
|
|
105
|
+
storeName,
|
|
97
106
|
});
|
|
98
107
|
return;
|
|
99
108
|
}
|
|
@@ -107,7 +116,7 @@ module.exports = {
|
|
|
107
116
|
}
|
|
108
117
|
|
|
109
118
|
const resultSavedInVariable =
|
|
110
|
-
|
|
119
|
+
node.parent.type === "VariableDeclarator";
|
|
111
120
|
if (!resultSavedInVariable) {
|
|
112
121
|
return;
|
|
113
122
|
}
|
|
@@ -118,11 +127,10 @@ module.exports = {
|
|
|
118
127
|
return;
|
|
119
128
|
}
|
|
120
129
|
|
|
121
|
-
|
|
122
130
|
reportStoreNameConventionViolation({
|
|
123
131
|
context,
|
|
124
132
|
node: node.parent,
|
|
125
|
-
storeName
|
|
133
|
+
storeName,
|
|
126
134
|
});
|
|
127
135
|
return;
|
|
128
136
|
}
|
|
@@ -130,10 +138,10 @@ module.exports = {
|
|
|
130
138
|
// Store creation in domain
|
|
131
139
|
const STORE_IN_DOMAIN_CREATION_METHODS = ["createStore", "store"];
|
|
132
140
|
if (
|
|
133
|
-
|
|
141
|
+
STORE_IN_DOMAIN_CREATION_METHODS.includes(node.callee?.property?.name)
|
|
134
142
|
) {
|
|
135
143
|
const resultSavedInVariable =
|
|
136
|
-
|
|
144
|
+
node.parent.type === "VariableDeclarator";
|
|
137
145
|
if (!resultSavedInVariable) {
|
|
138
146
|
return;
|
|
139
147
|
}
|
|
@@ -147,7 +155,7 @@ module.exports = {
|
|
|
147
155
|
reportStoreNameConventionViolation({
|
|
148
156
|
context,
|
|
149
157
|
node: node.parent,
|
|
150
|
-
storeName
|
|
158
|
+
storeName,
|
|
151
159
|
});
|
|
152
160
|
return;
|
|
153
161
|
}
|
|
@@ -157,7 +165,6 @@ module.exports = {
|
|
|
157
165
|
};
|
|
158
166
|
|
|
159
167
|
function reportStoreNameConventionViolation({ context, node, storeName }) {
|
|
160
|
-
|
|
161
168
|
const storeNameConvention = getStoreNameConvention(context);
|
|
162
169
|
const correctedStoreName = getCorrectedStoreName(storeName, context);
|
|
163
170
|
|
|
@@ -167,7 +174,7 @@ function reportStoreNameConventionViolation({ context, node, storeName }) {
|
|
|
167
174
|
data: {
|
|
168
175
|
storeName,
|
|
169
176
|
correctedStoreName,
|
|
170
|
-
storeNameConvention
|
|
177
|
+
storeNameConvention,
|
|
171
178
|
},
|
|
172
179
|
suggest: [
|
|
173
180
|
{
|
|
@@ -180,4 +187,3 @@ function reportStoreNameConventionViolation({ context, node, storeName }) {
|
|
|
180
187
|
],
|
|
181
188
|
});
|
|
182
189
|
}
|
|
183
|
-
|
|
@@ -2,6 +2,7 @@ const {
|
|
|
2
2
|
extractImportedFromEffector,
|
|
3
3
|
} = require("../../utils/extract-imported-from-effector");
|
|
4
4
|
const { traverseParentByType } = require("../../utils/traverse-parent-by-type");
|
|
5
|
+
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
5
6
|
|
|
6
7
|
module.exports = {
|
|
7
8
|
meta: {
|
|
@@ -10,6 +11,7 @@ module.exports = {
|
|
|
10
11
|
description: "Forbids ambiguity targets in `sample` and `guard`",
|
|
11
12
|
category: "Quality",
|
|
12
13
|
recommended: true,
|
|
14
|
+
url: createLinkToRule("no-ambiguity-target"),
|
|
13
15
|
},
|
|
14
16
|
messages: {
|
|
15
17
|
ambiguityTarget:
|
|
@@ -46,7 +48,8 @@ module.exports = {
|
|
|
46
48
|
|
|
47
49
|
const resultAssignedInVariable = traverseParentByType(
|
|
48
50
|
node,
|
|
49
|
-
"VariableDeclarator"
|
|
51
|
+
"VariableDeclarator",
|
|
52
|
+
{ stopOnTypes: ["BlockStatement"] }
|
|
50
53
|
);
|
|
51
54
|
const resultPartOfChain = traverseParentByType(
|
|
52
55
|
node,
|
|
@@ -2,6 +2,7 @@ const {
|
|
|
2
2
|
traverseNestedObjectNode,
|
|
3
3
|
} = require("../../utils/traverse-nested-object-node");
|
|
4
4
|
const { isStoreNameValid } = require("../../utils/is-store-name-valid");
|
|
5
|
+
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
5
6
|
|
|
6
7
|
module.exports = {
|
|
7
8
|
meta: {
|
|
@@ -10,6 +11,7 @@ module.exports = {
|
|
|
10
11
|
description: "Forbids `.getState` calls on any Effector store",
|
|
11
12
|
category: "Quality",
|
|
12
13
|
recommended: true,
|
|
14
|
+
url: createLinkToRule("no-getState"),
|
|
13
15
|
},
|
|
14
16
|
messages: {
|
|
15
17
|
abusiveCall:
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
const {
|
|
2
|
+
extractImportedFromEffector,
|
|
3
|
+
} = require("../../utils/extract-imported-from-effector");
|
|
4
|
+
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
meta: {
|
|
8
|
+
type: "problem",
|
|
9
|
+
docs: {
|
|
10
|
+
description:
|
|
11
|
+
"Forbids unnecessary combinations in `clock`, `source` and `forward`",
|
|
12
|
+
category: "Quality",
|
|
13
|
+
recommended: true,
|
|
14
|
+
url: createLinkToRule("no-unnecessary-combination"),
|
|
15
|
+
},
|
|
16
|
+
messages: {
|
|
17
|
+
unnecessaryCombination:
|
|
18
|
+
"Method {{ methodName }} is used under the hood, you can omit it.",
|
|
19
|
+
},
|
|
20
|
+
schema: [],
|
|
21
|
+
},
|
|
22
|
+
create(context) {
|
|
23
|
+
const importedFromEffector = new Map();
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
ImportDeclaration(node) {
|
|
27
|
+
extractImportedFromEffector(importedFromEffector, node);
|
|
28
|
+
},
|
|
29
|
+
CallExpression(node) {
|
|
30
|
+
const METHODS_WITH_POSSIBLE_UNNECESSARY_COMBINATION = [
|
|
31
|
+
"sample",
|
|
32
|
+
"guard",
|
|
33
|
+
"forward",
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const CONFIG_ARG_PROPERTIES = ["source", "clock", "from"];
|
|
37
|
+
|
|
38
|
+
function toLocalMethod(method) {
|
|
39
|
+
return importedFromEffector.get(method);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const UNNECESSARY_METHODS = {
|
|
43
|
+
source: ["combine", "merge"].map(toLocalMethod).filter(Boolean),
|
|
44
|
+
clock: ["merge"].map(toLocalMethod).filter(Boolean),
|
|
45
|
+
from: ["merge"].map(toLocalMethod).filter(Boolean),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
for (const method of METHODS_WITH_POSSIBLE_UNNECESSARY_COMBINATION) {
|
|
49
|
+
const localMethod = importedFromEffector.get(method);
|
|
50
|
+
if (!localMethod) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const isEffectorMethod = node?.callee?.name === localMethod;
|
|
55
|
+
if (!isEffectorMethod) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const candidates =
|
|
60
|
+
node?.arguments?.[0]?.properties?.filter((n) =>
|
|
61
|
+
CONFIG_ARG_PROPERTIES.includes(n.key.name)
|
|
62
|
+
) ?? [];
|
|
63
|
+
|
|
64
|
+
if (candidates.length === 0) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (const candidate of candidates) {
|
|
69
|
+
const candidateName = candidate?.value?.callee?.name;
|
|
70
|
+
const argProp = candidate?.key?.name;
|
|
71
|
+
if (!candidateName || !argProp) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const localUnnecessaryMethods = UNNECESSARY_METHODS[argProp];
|
|
76
|
+
|
|
77
|
+
const UnnecessaryMethodIsEffectorMethod =
|
|
78
|
+
localUnnecessaryMethods.some((m) => m === candidateName);
|
|
79
|
+
|
|
80
|
+
if (!UnnecessaryMethodIsEffectorMethod) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
context.report({
|
|
85
|
+
node: candidate?.value,
|
|
86
|
+
messageId: "unnecessaryCombination",
|
|
87
|
+
data: { methodName: candidateName },
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# effector/no-unnecessary-combination
|
|
2
|
+
|
|
3
|
+
Call of `combine`/`merge` in `clock`/`source` is unnecessary. It can be omitted from source code.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
// 👎 can be simplified
|
|
7
|
+
const badEventOne = guard({ clock: combine($store1, $store2), filter: $filter });
|
|
8
|
+
const badEventOne = guard({ clock: combine($store1, $store2, (store1, store2) => ({x: store1, y: store2})), filter: $filter });
|
|
9
|
+
|
|
10
|
+
// 👍 better
|
|
11
|
+
const goodEventOne = guard({ clock: [$store1, $store2], filter: $filter });
|
|
12
|
+
const goodEventTwo = guard({ clock: ({x: $store1, x: $store2}), filter: $filter });
|
|
13
|
+
```
|
|
14
|
+
|
|
@@ -2,6 +2,7 @@ const {
|
|
|
2
2
|
extractImportedFromEffector,
|
|
3
3
|
} = require("../../utils/extract-imported-from-effector");
|
|
4
4
|
const { areNodesSameInText } = require("../../utils/are-nodes-same-in-text");
|
|
5
|
+
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
5
6
|
|
|
6
7
|
module.exports = {
|
|
7
8
|
meta: {
|
|
@@ -10,6 +11,7 @@ module.exports = {
|
|
|
10
11
|
description: "Forbids unnecessary duplication in `clock` and `source`",
|
|
11
12
|
category: "Quality",
|
|
12
13
|
recommended: true,
|
|
14
|
+
url: createLinkToRule("no-unnecessary-duplication"),
|
|
13
15
|
},
|
|
14
16
|
messages: {
|
|
15
17
|
unnecessaryDuplication:
|
|
@@ -18,6 +20,7 @@ module.exports = {
|
|
|
18
20
|
removeSource: "Remove `source`",
|
|
19
21
|
},
|
|
20
22
|
schema: [],
|
|
23
|
+
hasSuggestions: true,
|
|
21
24
|
},
|
|
22
25
|
create(context) {
|
|
23
26
|
const importedFromEffector = new Map();
|
|
@@ -2,6 +2,7 @@ const {
|
|
|
2
2
|
extractImportedFromEffector,
|
|
3
3
|
} = require("../../utils/extract-imported-from-effector");
|
|
4
4
|
const { traverseParentByType } = require("../../utils/traverse-parent-by-type");
|
|
5
|
+
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
5
6
|
|
|
6
7
|
module.exports = {
|
|
7
8
|
meta: {
|
|
@@ -10,6 +11,7 @@ module.exports = {
|
|
|
10
11
|
description: "Forbids useless calls of `sample` and `guard`",
|
|
11
12
|
category: "Quality",
|
|
12
13
|
recommended: true,
|
|
14
|
+
url: createLinkToRule("no-useless-methods"),
|
|
13
15
|
},
|
|
14
16
|
messages: {
|
|
15
17
|
uselessMethod:
|
|
@@ -68,6 +70,16 @@ module.exports = {
|
|
|
68
70
|
continue;
|
|
69
71
|
}
|
|
70
72
|
|
|
73
|
+
const resultIsWatched = node?.parent?.property?.name === "watch";
|
|
74
|
+
if (resultIsWatched) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const resultIsArgument = node?.parent?.type === "CallExpression";
|
|
79
|
+
if (resultIsArgument) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
71
83
|
context.report({
|
|
72
84
|
node,
|
|
73
85
|
messageId: "uselessMethod",
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const {
|
|
2
|
+
traverseNestedObjectNode,
|
|
3
|
+
} = require("../../utils/traverse-nested-object-node");
|
|
4
|
+
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
meta: {
|
|
8
|
+
type: "suggestion",
|
|
9
|
+
docs: {
|
|
10
|
+
description: "Avoid `.watch` calls on any Effector unit or operator",
|
|
11
|
+
category: "Quality",
|
|
12
|
+
recommended: true,
|
|
13
|
+
url: createLinkToRule("no-watch"),
|
|
14
|
+
},
|
|
15
|
+
messages: {
|
|
16
|
+
abusiveCall:
|
|
17
|
+
"Method `.watch` leads to imperative code. Try to replace it with operators (`sample`, `guard`, etc) or use the `target` parameter of the operators.",
|
|
18
|
+
},
|
|
19
|
+
schema: [],
|
|
20
|
+
},
|
|
21
|
+
create(context) {
|
|
22
|
+
const { parserServices } = context;
|
|
23
|
+
if (!parserServices.hasFullTypeInformation) {
|
|
24
|
+
// JavaScript-way https://github.com/effector/eslint-plugin/issues/48#issuecomment-931107829
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
const checker = parserServices.program.getTypeChecker();
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
CallExpression(node) {
|
|
31
|
+
const methodName = node.callee?.property?.name;
|
|
32
|
+
if (methodName !== "watch") {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const object = traverseNestedObjectNode(node.callee?.object);
|
|
37
|
+
const originalNode = parserServices.esTreeNodeToTSNodeMap.get(object);
|
|
38
|
+
const type = checker.getTypeAtLocation(originalNode);
|
|
39
|
+
|
|
40
|
+
const isEffectorUnit =
|
|
41
|
+
["Effect", "Event", "Store"].includes(type?.symbol?.escapedName) &&
|
|
42
|
+
type?.symbol?.parent?.escapedName?.includes("effector");
|
|
43
|
+
|
|
44
|
+
if (!isEffectorUnit) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
reportWatchCall({ context, node });
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
function reportWatchCall({ context, node }) {
|
|
55
|
+
context.report({
|
|
56
|
+
node,
|
|
57
|
+
messageId: "abusiveCall",
|
|
58
|
+
});
|
|
59
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# effector/no-watch
|
|
2
|
+
|
|
3
|
+
Method `.watch` leads to imperative code. Try replacing it with operators (`forward`, `sample`, etc) or use the `target` parameter of the operators.
|
|
4
|
+
|
|
5
|
+
> Caution! This rule only works on projects using TypeScript.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
const myFx = createEffect();
|
|
9
|
+
const myEvent = createEvent();
|
|
10
|
+
const $awesome = createStore();
|
|
11
|
+
|
|
12
|
+
// 👍 good solutions
|
|
13
|
+
forward({
|
|
14
|
+
from: myFx.finally,
|
|
15
|
+
to: myEvent,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
guard({
|
|
19
|
+
clock: myEvent,
|
|
20
|
+
filter: Boolean,
|
|
21
|
+
target: myFx,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
sample({
|
|
25
|
+
from: $awesome.updates,
|
|
26
|
+
fn: identity,
|
|
27
|
+
to: myEvent,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// 👎 bad solutions
|
|
31
|
+
myFx.finally.watch(myEvent);
|
|
32
|
+
|
|
33
|
+
myEvent.watch((payload) => {
|
|
34
|
+
if (Boolean(payload)) {
|
|
35
|
+
myFx(payload);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
$awesome.updates.watch((data) => {
|
|
40
|
+
myEvent(identity(data));
|
|
41
|
+
});
|
|
42
|
+
```
|
package/rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping.js
CHANGED
|
@@ -4,6 +4,7 @@ const {
|
|
|
4
4
|
const {
|
|
5
5
|
traverseNestedObjectNode,
|
|
6
6
|
} = require("../../utils/traverse-nested-object-node");
|
|
7
|
+
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
7
8
|
|
|
8
9
|
module.exports = {
|
|
9
10
|
meta: {
|
|
@@ -12,6 +13,7 @@ module.exports = {
|
|
|
12
13
|
description: "Prefer `sample` over `forward` with `.map`/`.prepend`",
|
|
13
14
|
category: "Quality",
|
|
14
15
|
recommended: true,
|
|
16
|
+
url: createLinkToRule("prefer-sample-over-forward-with-mapping"),
|
|
15
17
|
},
|
|
16
18
|
messages: {
|
|
17
19
|
overMap:
|
package/rules/tsconfig.json
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
function traverseParentByType(node, type) {
|
|
2
|
-
|
|
1
|
+
function traverseParentByType(node, type, config) {
|
|
2
|
+
const stopOnTypes = config?.stopOnTypes ?? [];
|
|
3
|
+
|
|
4
|
+
if (!node || stopOnTypes.includes(node.type)) {
|
|
3
5
|
return null;
|
|
4
6
|
}
|
|
5
7
|
|
|
@@ -7,7 +9,7 @@ function traverseParentByType(node, type) {
|
|
|
7
9
|
return node;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
|
-
return traverseParentByType(node.parent, type);
|
|
12
|
+
return traverseParentByType(node.parent, type, config);
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
module.exports = { traverseParentByType };
|
package/.github/workflows/ci.yml
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [master]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [master]
|
|
8
|
-
|
|
9
|
-
jobs:
|
|
10
|
-
checks:
|
|
11
|
-
runs-on: ubuntu-latest
|
|
12
|
-
|
|
13
|
-
strategy:
|
|
14
|
-
matrix:
|
|
15
|
-
node-version: [14.x, 16.x]
|
|
16
|
-
|
|
17
|
-
steps:
|
|
18
|
-
- uses: actions/checkout@v2
|
|
19
|
-
- name: Use Node.js ${{ matrix.node-version }}
|
|
20
|
-
uses: actions/setup-node@v2
|
|
21
|
-
with:
|
|
22
|
-
node-version: ${{ matrix.node-version }}
|
|
23
|
-
cache: "yarn"
|
|
24
|
-
- run: yarn install
|
|
25
|
-
- run: yarn test --coverage
|
|
26
|
-
|
|
27
|
-
reports:
|
|
28
|
-
runs-on: ubuntu-latest
|
|
29
|
-
|
|
30
|
-
if: ${{ github.event_name == 'pull_request' }}
|
|
31
|
-
|
|
32
|
-
steps:
|
|
33
|
-
- uses: actions/checkout@v2
|
|
34
|
-
- name: Use Node.js
|
|
35
|
-
uses: actions/setup-node@v2
|
|
36
|
-
with:
|
|
37
|
-
cache: "yarn"
|
|
38
|
-
- run: yarn install
|
|
39
|
-
- run: yarn test --coverage
|
|
40
|
-
- name: Code Coverage Report
|
|
41
|
-
uses: romeovs/lcov-reporter-action@v0.2.11
|
|
42
|
-
with:
|
|
43
|
-
github-token: ${{ secrets.GITHUB_TOKEN }}
|
package/CHANGELOG.md
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## v0.3.0
|
|
4
|
-
|
|
5
|
-
- Add new rule: `no-useless-methods` ([PR #41](https://github.com/effector/eslint-plugin/pull/41))
|
|
6
|
-
- Add new rule: `no-ambiguity-target` ([PR #42](https://github.com/effector/eslint-plugin/pull/42))
|
|
7
|
-
- Add possibility to configure store's naming convention — suffix of prefix ([PR #37](https://github.com/effector/eslint-plugin/pull/37) by @ilyaryabchinski)
|
|
8
|
-
|
|
9
|
-
## v0.2.0
|
|
10
|
-
|
|
11
|
-
- Add tests against Effector 22
|
|
12
|
-
- Specify supported Node.JS versions
|
|
13
|
-
- Add new rule: `prefer-sample-over-forward-with-mapping` ([PR #34](https://github.com/igorkamyshev/eslint-plugin-effector/pull/34))
|
|
14
|
-
|
|
15
|
-
## v0.1.4
|
|
16
|
-
|
|
17
|
-
- Exclude test-coverage report from npm-package
|
|
18
|
-
- Fixed SyntaxError in `no-unnecessary-duplication` suggestions ([PR #28](https://github.com/igorkamyshev/eslint-plugin-effector/pull/28))
|
|
19
|
-
|
|
20
|
-
## v0.1.3
|
|
21
|
-
|
|
22
|
-
- Fixed false-positive in `no-unnecessary-duplication` with composite `clock`/`source` ([PR #22](https://github.com/igorkamyshev/eslint-plugin-effector/pull/22))
|
|
23
|
-
- Fixed TypeError in `enforce-store-naming-convention` ([PR #25](https://github.com/igorkamyshev/eslint-plugin-effector/pull/25))
|
|
24
|
-
- Fixed false-positive in `enforce-effect-naming-convention` with `combine` ([PR #26](https://github.com/igorkamyshev/eslint-plugin-effector/pull/26))
|