eslint-plugin-effector 0.1.2 → 0.3.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/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +5 -2
- package/config/recommended.js +3 -0
- package/index.js +4 -1
- package/package.json +9 -3
- package/rules/enforce-effect-naming-convention/enforce-effect-naming-convention.js +1 -1
- package/rules/enforce-effect-naming-convention/enforce-effect-naming-convention.md +9 -1
- package/rules/enforce-store-naming-convention/enforce-store-naming-convention.js +44 -21
- package/rules/enforce-store-naming-convention/enforce-store-naming-convention.md +43 -2
- package/rules/no-ambiguity-target/no-ambiguity-target.js +71 -0
- package/rules/no-ambiguity-target/no-ambiguity-target.md +12 -0
- package/rules/no-getState/no-getState.js +6 -13
- package/rules/no-getState/no-getState.md +18 -1
- package/rules/no-unnecessary-duplication/no-unnecessary-duplication.js +7 -2
- package/rules/no-unnecessary-duplication/no-unnecessary-duplication.md +5 -5
- package/rules/no-useless-methods/no-useless-methods.js +82 -0
- package/rules/no-useless-methods/no-useless-methods.md +14 -0
- package/rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping.js +95 -0
- package/rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping.md +27 -0
- package/utils/are-nodes-same-in-text.js +22 -0
- package/utils/get-corrected-store-name.js +23 -0
- package/utils/get-store-name-convention.js +6 -0
- package/utils/is-store-name-valid.js +22 -0
- package/utils/traverse-nested-object-node.js +9 -0
- package/utils/traverse-parent-by-type.js +13 -0
- package/utils/validate-store-name-convention.js +13 -0
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -79
- package/coverage/lcov-report/enforce-effect-naming-convention/enforce-effect-naming-convention.js.html +0 -482
- package/coverage/lcov-report/enforce-effect-naming-convention/index.html +0 -111
- package/coverage/lcov-report/enforce-store-naming-convention/enforce-store-naming-convention.js.html +0 -560
- package/coverage/lcov-report/enforce-store-naming-convention/index.html +0 -111
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +0 -141
- package/coverage/lcov-report/no-getState/index.html +0 -111
- package/coverage/lcov-report/no-getState/no-getState.js.html +0 -326
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -170
- package/coverage/lcov.info +0 -256
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
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))
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Igor Kamyshev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
Enforcing best practices for [Effector](http://effector.dev/)
|
|
4
4
|
|
|
5
|
-
> This plugin
|
|
5
|
+
> This plugin uses TypeScript for more precise results, but JavaScript is supported too.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
First, install [ESLint](http://eslint.org):
|
|
10
10
|
|
|
11
11
|
```
|
|
12
12
|
$ yarn add -D eslint
|
|
@@ -45,3 +45,6 @@ To configure individual rules:
|
|
|
45
45
|
- [effector/enforce-effect-naming-convention](/rules/enforce-effect-naming-convention/enforce-effect-naming-convention.md)
|
|
46
46
|
- [effector/no-getState](/rules/no-getState/no-getState.md)
|
|
47
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)
|
package/config/recommended.js
CHANGED
|
@@ -3,6 +3,9 @@ module.exports = {
|
|
|
3
3
|
"effector/enforce-store-naming-convention": "error",
|
|
4
4
|
"effector/enforce-effect-naming-convention": "error",
|
|
5
5
|
"effector/no-getState": "error",
|
|
6
|
+
"effector/no-useless-methods": "error",
|
|
6
7
|
"effector/no-unnecessary-duplication": "warn",
|
|
8
|
+
"effector/prefer-sample-over-forward-with-mapping": "warn",
|
|
9
|
+
"effector/no-ambiguity-target": "warn",
|
|
7
10
|
},
|
|
8
11
|
};
|
package/index.js
CHANGED
|
@@ -4,8 +4,11 @@ module.exports = {
|
|
|
4
4
|
"enforce-effect-naming-convention": require("./rules/enforce-effect-naming-convention/enforce-effect-naming-convention"),
|
|
5
5
|
"no-getState": require("./rules/no-getState/no-getState"),
|
|
6
6
|
"no-unnecessary-duplication": require("./rules/no-unnecessary-duplication/no-unnecessary-duplication"),
|
|
7
|
+
"prefer-sample-over-forward-with-mapping": require("./rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping"),
|
|
8
|
+
"no-useless-methods": require("./rules/no-useless-methods/no-useless-methods"),
|
|
9
|
+
"no-ambiguity-target": require("./rules/no-ambiguity-target/no-ambiguity-target"),
|
|
7
10
|
},
|
|
8
11
|
configs: {
|
|
9
12
|
recommended: require("./config/recommended"),
|
|
10
|
-
}
|
|
13
|
+
}
|
|
11
14
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-effector",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Enforcing best practices for Effector",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"author": "Igor Kamyshev <igor@kamyshev.me>",
|
|
@@ -11,10 +11,13 @@
|
|
|
11
11
|
"publishConfig": {
|
|
12
12
|
"access": "public"
|
|
13
13
|
},
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": "^14 || ^16"
|
|
16
|
+
},
|
|
14
17
|
"devDependencies": {
|
|
15
18
|
"@typescript-eslint/experimental-utils": "^4.29.3",
|
|
16
19
|
"@typescript-eslint/parser": "^4.29.3",
|
|
17
|
-
"effector": "^
|
|
20
|
+
"effector": "^22.0.0",
|
|
18
21
|
"eslint": "^7.32.0",
|
|
19
22
|
"glob": "^7.1.7",
|
|
20
23
|
"jest": "^27.1.0",
|
|
@@ -23,5 +26,8 @@
|
|
|
23
26
|
"peerDependencies": {
|
|
24
27
|
"effector": "*",
|
|
25
28
|
"eslint": "*"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"prettier": "^2.3.2"
|
|
26
32
|
}
|
|
27
|
-
}
|
|
33
|
+
}
|
|
@@ -55,7 +55,7 @@ module.exports = {
|
|
|
55
55
|
},
|
|
56
56
|
CallExpression(node) {
|
|
57
57
|
// Effect creation with method
|
|
58
|
-
const EFFECT_CREATION_METHODS = ["createEffect", "attach"
|
|
58
|
+
const EFFECT_CREATION_METHODS = ["createEffect", "attach"];
|
|
59
59
|
for (const method of EFFECT_CREATION_METHODS) {
|
|
60
60
|
const localMethod = importedFromEffector.get(method);
|
|
61
61
|
if (!localMethod) {
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
-
#
|
|
1
|
+
# effector/enforce-effect-naming-convention
|
|
2
2
|
|
|
3
3
|
Enforcing naming conventions helps keep the codebase consistent, and reduces overhead when thinking about how to name a variable with effect. Your effect should be distingueshed by a suffix `Fx`. For example, `fetchUserInfoFx` is a effect, `fetchUserInfo` is not.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
// 👍 nice name
|
|
7
|
+
const fetchNameFx = createEffect();
|
|
8
|
+
|
|
9
|
+
// 👎 bad name
|
|
10
|
+
const fetchName = createEffect();
|
|
11
|
+
```
|
|
@@ -1,25 +1,32 @@
|
|
|
1
1
|
const {
|
|
2
2
|
extractImportedFromEffector,
|
|
3
3
|
} = require("../../utils/extract-imported-from-effector");
|
|
4
|
+
const { isStoreNameValid } = require("../../utils/is-store-name-valid");
|
|
5
|
+
const { validateStoreNameConvention } = require("../../utils/validate-store-name-convention");
|
|
6
|
+
const { getStoreNameConvention } = require("../../utils/get-store-name-convention");
|
|
7
|
+
const { getCorrectedStoreName } = require("../../utils/get-corrected-store-name");
|
|
4
8
|
|
|
5
9
|
module.exports = {
|
|
6
10
|
meta: {
|
|
7
11
|
type: "problem",
|
|
8
12
|
docs: {
|
|
9
13
|
description:
|
|
10
|
-
|
|
14
|
+
"Enforce $ as a prefix or postfix for any store created by Effector methods",
|
|
11
15
|
category: "Naming",
|
|
12
16
|
recommended: true,
|
|
13
17
|
},
|
|
14
18
|
messages: {
|
|
15
19
|
invalidName:
|
|
16
|
-
|
|
17
|
-
renameStore: 'Rename "{{ storeName }}" to "
|
|
20
|
+
'Store "{{ storeName }}" should be named with {{ storeNameConvention }}, rename it to "{{ correctedStoreName }}"',
|
|
21
|
+
renameStore: 'Rename "{{ storeName }}" to "{{ correctedStoreName }}"',
|
|
18
22
|
},
|
|
19
23
|
schema: [],
|
|
20
24
|
},
|
|
21
25
|
create(context) {
|
|
22
|
-
const parserServices = context
|
|
26
|
+
const { parserServices } = context;
|
|
27
|
+
|
|
28
|
+
validateStoreNameConvention(context);
|
|
29
|
+
|
|
23
30
|
// TypeScript-way
|
|
24
31
|
if (parserServices.hasFullTypeInformation) {
|
|
25
32
|
return {
|
|
@@ -29,8 +36,8 @@ module.exports = {
|
|
|
29
36
|
const type = checker.getTypeAtLocation(originalNode.initializer);
|
|
30
37
|
|
|
31
38
|
const isEffectorStore =
|
|
32
|
-
|
|
33
|
-
|
|
39
|
+
type?.symbol?.escapedName === "Store" &&
|
|
40
|
+
type?.symbol?.parent?.escapedName?.includes("effector");
|
|
34
41
|
|
|
35
42
|
if (!isEffectorStore) {
|
|
36
43
|
return;
|
|
@@ -38,11 +45,15 @@ module.exports = {
|
|
|
38
45
|
|
|
39
46
|
const storeName = node.id.name;
|
|
40
47
|
|
|
41
|
-
if (storeName
|
|
48
|
+
if (isStoreNameValid(storeName, context)) {
|
|
42
49
|
return;
|
|
43
50
|
}
|
|
44
51
|
|
|
45
|
-
reportStoreNameConventionViolation({
|
|
52
|
+
reportStoreNameConventionViolation({
|
|
53
|
+
context,
|
|
54
|
+
node,
|
|
55
|
+
storeName
|
|
56
|
+
});
|
|
46
57
|
},
|
|
47
58
|
};
|
|
48
59
|
}
|
|
@@ -68,46 +79,50 @@ module.exports = {
|
|
|
68
79
|
}
|
|
69
80
|
|
|
70
81
|
const resultSavedInVariable =
|
|
71
|
-
|
|
82
|
+
node.parent.type === "VariableDeclarator";
|
|
72
83
|
if (!resultSavedInVariable) {
|
|
73
84
|
continue;
|
|
74
85
|
}
|
|
75
86
|
|
|
76
87
|
const storeName = node.parent.id.name;
|
|
77
|
-
|
|
88
|
+
|
|
89
|
+
if (isStoreNameValid(storeName, context)) {
|
|
78
90
|
continue;
|
|
79
91
|
}
|
|
80
92
|
|
|
81
93
|
reportStoreNameConventionViolation({
|
|
82
94
|
context,
|
|
83
95
|
node: node.parent,
|
|
84
|
-
storeName
|
|
96
|
+
storeName
|
|
85
97
|
});
|
|
86
98
|
return;
|
|
87
99
|
}
|
|
88
100
|
|
|
89
101
|
// Store creation with .map
|
|
90
102
|
if (node.callee?.property?.name === "map") {
|
|
91
|
-
const
|
|
92
|
-
|
|
103
|
+
const storeNameCreatedFromMap = node.callee?.object?.name;
|
|
104
|
+
|
|
105
|
+
if (!isStoreNameValid(storeNameCreatedFromMap, context)) {
|
|
93
106
|
return;
|
|
94
107
|
}
|
|
95
108
|
|
|
96
109
|
const resultSavedInVariable =
|
|
97
|
-
|
|
110
|
+
node.parent.type === "VariableDeclarator";
|
|
98
111
|
if (!resultSavedInVariable) {
|
|
99
112
|
return;
|
|
100
113
|
}
|
|
101
114
|
|
|
102
115
|
const storeName = node.parent.id.name;
|
|
103
|
-
|
|
116
|
+
|
|
117
|
+
if (isStoreNameValid(storeName, context)) {
|
|
104
118
|
return;
|
|
105
119
|
}
|
|
106
120
|
|
|
121
|
+
|
|
107
122
|
reportStoreNameConventionViolation({
|
|
108
123
|
context,
|
|
109
124
|
node: node.parent,
|
|
110
|
-
storeName
|
|
125
|
+
storeName
|
|
111
126
|
});
|
|
112
127
|
return;
|
|
113
128
|
}
|
|
@@ -115,23 +130,24 @@ module.exports = {
|
|
|
115
130
|
// Store creation in domain
|
|
116
131
|
const STORE_IN_DOMAIN_CREATION_METHODS = ["createStore", "store"];
|
|
117
132
|
if (
|
|
118
|
-
|
|
133
|
+
STORE_IN_DOMAIN_CREATION_METHODS.includes(node.callee?.property?.name)
|
|
119
134
|
) {
|
|
120
135
|
const resultSavedInVariable =
|
|
121
|
-
|
|
136
|
+
node.parent.type === "VariableDeclarator";
|
|
122
137
|
if (!resultSavedInVariable) {
|
|
123
138
|
return;
|
|
124
139
|
}
|
|
125
140
|
|
|
126
141
|
const storeName = node.parent.id.name;
|
|
127
|
-
|
|
142
|
+
|
|
143
|
+
if (isStoreNameValid(storeName, context)) {
|
|
128
144
|
return;
|
|
129
145
|
}
|
|
130
146
|
|
|
131
147
|
reportStoreNameConventionViolation({
|
|
132
148
|
context,
|
|
133
149
|
node: node.parent,
|
|
134
|
-
storeName
|
|
150
|
+
storeName
|
|
135
151
|
});
|
|
136
152
|
return;
|
|
137
153
|
}
|
|
@@ -141,20 +157,27 @@ module.exports = {
|
|
|
141
157
|
};
|
|
142
158
|
|
|
143
159
|
function reportStoreNameConventionViolation({ context, node, storeName }) {
|
|
160
|
+
|
|
161
|
+
const storeNameConvention = getStoreNameConvention(context);
|
|
162
|
+
const correctedStoreName = getCorrectedStoreName(storeName, context);
|
|
163
|
+
|
|
144
164
|
context.report({
|
|
145
165
|
node,
|
|
146
166
|
messageId: "invalidName",
|
|
147
167
|
data: {
|
|
148
168
|
storeName,
|
|
169
|
+
correctedStoreName,
|
|
170
|
+
storeNameConvention
|
|
149
171
|
},
|
|
150
172
|
suggest: [
|
|
151
173
|
{
|
|
152
174
|
messageId: "renameStore",
|
|
153
175
|
data: { storeName },
|
|
154
176
|
fix(fixer) {
|
|
155
|
-
return fixer.
|
|
177
|
+
return fixer.replaceTextRange(node.id.range, correctedStoreName);
|
|
156
178
|
},
|
|
157
179
|
},
|
|
158
180
|
],
|
|
159
181
|
});
|
|
160
182
|
}
|
|
183
|
+
|
|
@@ -1,3 +1,44 @@
|
|
|
1
|
-
#
|
|
1
|
+
# effector/enforce-store-naming-convention
|
|
2
2
|
|
|
3
|
-
Enforcing naming conventions helps keep the codebase consistent, and reduces overhead when thinking about how to name a variable with store.
|
|
3
|
+
Enforcing naming conventions helps keep the codebase consistent, and reduces overhead when thinking about how to name a variable with store. Depending on the configuration your stores should be distinguished by a prefix or a postfix $. Enforces prefix convention by default.
|
|
4
|
+
|
|
5
|
+
## Prefix convention
|
|
6
|
+
When configured as:
|
|
7
|
+
```js
|
|
8
|
+
module.exports = {
|
|
9
|
+
rules: {
|
|
10
|
+
"effector/enforce-store-naming-convention": "error",
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
```
|
|
14
|
+
Prefix convention will be enforced:
|
|
15
|
+
```ts
|
|
16
|
+
// 👍 nice name
|
|
17
|
+
const $name = createStore(null);
|
|
18
|
+
|
|
19
|
+
// 👎 bad name
|
|
20
|
+
const name = createStrore(null);
|
|
21
|
+
```
|
|
22
|
+
## Postfix convention
|
|
23
|
+
|
|
24
|
+
When configured as:
|
|
25
|
+
```js
|
|
26
|
+
module.exports = {
|
|
27
|
+
rules: {
|
|
28
|
+
"effector/enforce-store-naming-convention": "error",
|
|
29
|
+
},
|
|
30
|
+
settings: {
|
|
31
|
+
effector: {
|
|
32
|
+
storeNameConvention: "postfix"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
```
|
|
37
|
+
Postfix convention will be enforced:
|
|
38
|
+
```ts
|
|
39
|
+
// 👍 nice name
|
|
40
|
+
const name$ = createStore(null);
|
|
41
|
+
|
|
42
|
+
// 👎 bad name
|
|
43
|
+
const name = createStrore(null);
|
|
44
|
+
```
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const {
|
|
2
|
+
extractImportedFromEffector,
|
|
3
|
+
} = require("../../utils/extract-imported-from-effector");
|
|
4
|
+
const { traverseParentByType } = require("../../utils/traverse-parent-by-type");
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
meta: {
|
|
8
|
+
type: "problem",
|
|
9
|
+
docs: {
|
|
10
|
+
description: "Forbids ambiguity targets in `sample` and `guard`",
|
|
11
|
+
category: "Quality",
|
|
12
|
+
recommended: true,
|
|
13
|
+
},
|
|
14
|
+
messages: {
|
|
15
|
+
ambiguityTarget:
|
|
16
|
+
"Method `{{ methodName }}` returns `target` and assigns the result to a variable. Consider removing one of them.",
|
|
17
|
+
},
|
|
18
|
+
schema: [],
|
|
19
|
+
},
|
|
20
|
+
create(context) {
|
|
21
|
+
const importedFromEffector = new Map();
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
ImportDeclaration(node) {
|
|
25
|
+
extractImportedFromEffector(importedFromEffector, node);
|
|
26
|
+
},
|
|
27
|
+
CallExpression(node) {
|
|
28
|
+
const POSSIBLE_USELESS_METHODS = ["sample", "guard"];
|
|
29
|
+
for (const method of POSSIBLE_USELESS_METHODS) {
|
|
30
|
+
const localMethod = importedFromEffector.get(method);
|
|
31
|
+
if (!localMethod) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const isEffectorMethod = node?.callee?.name === localMethod;
|
|
36
|
+
if (!isEffectorMethod) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const configHasTarget = node?.arguments?.[0]?.properties?.some(
|
|
41
|
+
(prop) => prop?.key.name === "target"
|
|
42
|
+
);
|
|
43
|
+
if (!configHasTarget) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const resultAssignedInVariable = traverseParentByType(
|
|
48
|
+
node,
|
|
49
|
+
"VariableDeclarator"
|
|
50
|
+
);
|
|
51
|
+
const resultPartOfChain = traverseParentByType(
|
|
52
|
+
node,
|
|
53
|
+
"ObjectExpression"
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
if (resultAssignedInVariable || resultPartOfChain) {
|
|
57
|
+
context.report({
|
|
58
|
+
node,
|
|
59
|
+
messageId: "ambiguityTarget",
|
|
60
|
+
data: {
|
|
61
|
+
methodName: node?.callee?.name,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# effector/no-ambiguity-target
|
|
2
|
+
|
|
3
|
+
Call of `gaurd`/`sample` with `target` and variable assignment is ambiguity. One of them should be omitted from source code.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
// 👎 should be rewritten
|
|
7
|
+
const result = guard({ clock: trigger, filter: Boolean, target });
|
|
8
|
+
|
|
9
|
+
// 👍 makes sense
|
|
10
|
+
guard({ clock: trigger, filter: Boolean, target });
|
|
11
|
+
const result = target;
|
|
12
|
+
```
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const {
|
|
2
|
-
|
|
3
|
-
} = require("../../utils/
|
|
2
|
+
traverseNestedObjectNode,
|
|
3
|
+
} = require("../../utils/traverse-nested-object-node");
|
|
4
|
+
const { isStoreNameValid } = require("../../utils/is-store-name-valid");
|
|
4
5
|
|
|
5
6
|
module.exports = {
|
|
6
7
|
meta: {
|
|
@@ -17,7 +18,7 @@ module.exports = {
|
|
|
17
18
|
schema: [],
|
|
18
19
|
},
|
|
19
20
|
create(context) {
|
|
20
|
-
const parserServices = context
|
|
21
|
+
const { parserServices } = context;
|
|
21
22
|
|
|
22
23
|
return {
|
|
23
24
|
CallExpression(node) {
|
|
@@ -26,7 +27,7 @@ module.exports = {
|
|
|
26
27
|
return;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
const object =
|
|
30
|
+
const object = traverseNestedObjectNode(node.callee?.object);
|
|
30
31
|
const objectName = object?.name;
|
|
31
32
|
|
|
32
33
|
if (!objectName) {
|
|
@@ -51,7 +52,7 @@ module.exports = {
|
|
|
51
52
|
}
|
|
52
53
|
// JavaScript-way
|
|
53
54
|
else {
|
|
54
|
-
const isEffectorStore = objectName
|
|
55
|
+
const isEffectorStore = isStoreNameValid(objectName, context);
|
|
55
56
|
if (!isEffectorStore) {
|
|
56
57
|
return;
|
|
57
58
|
}
|
|
@@ -72,11 +73,3 @@ function reportGetStateCall({ context, node, storeName }) {
|
|
|
72
73
|
},
|
|
73
74
|
});
|
|
74
75
|
}
|
|
75
|
-
|
|
76
|
-
function traverseNestedObject(node) {
|
|
77
|
-
if (node.type === "MemberExpression") {
|
|
78
|
-
return traverseNestedObject(node.property);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return node;
|
|
82
|
-
}
|
|
@@ -1,3 +1,20 @@
|
|
|
1
|
-
#
|
|
1
|
+
# effector/no-getState
|
|
2
2
|
|
|
3
3
|
`.getState` gives rise to difficult to debug imperative code and kind of race condition. Prefer declarative `sample` to pass data from store and `attach` for effects.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
const $username = createStore(null);
|
|
7
|
+
const userLoggedIn = createEvent();
|
|
8
|
+
|
|
9
|
+
// 👍 good solution
|
|
10
|
+
const fetchUserCommentsFx = createEffect((name) => /* ... */);
|
|
11
|
+
sample({ source: $username, clock: userLoggedIn, target: fetchUserCommentsFx });
|
|
12
|
+
|
|
13
|
+
// 👎 bad solution
|
|
14
|
+
const fetchUserCommentsInBadWayFx = createEffect(() => {
|
|
15
|
+
const name = $username.getState();
|
|
16
|
+
|
|
17
|
+
/* ... */
|
|
18
|
+
});
|
|
19
|
+
forward({ from: userLoggedIn, to: fetchUserCommentsInBadWayFx });
|
|
20
|
+
```
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const {
|
|
2
2
|
extractImportedFromEffector,
|
|
3
3
|
} = require("../../utils/extract-imported-from-effector");
|
|
4
|
+
const { areNodesSameInText } = require("../../utils/are-nodes-same-in-text");
|
|
4
5
|
|
|
5
6
|
module.exports = {
|
|
6
7
|
meta: {
|
|
@@ -20,6 +21,8 @@ module.exports = {
|
|
|
20
21
|
},
|
|
21
22
|
create(context) {
|
|
22
23
|
const importedFromEffector = new Map();
|
|
24
|
+
const sourceCode = context.getSourceCode();
|
|
25
|
+
|
|
23
26
|
return {
|
|
24
27
|
ImportDeclaration(node) {
|
|
25
28
|
extractImportedFromEffector(importedFromEffector, node);
|
|
@@ -49,8 +52,10 @@ module.exports = {
|
|
|
49
52
|
return;
|
|
50
53
|
}
|
|
51
54
|
|
|
52
|
-
const sameSourceAndClock =
|
|
53
|
-
|
|
55
|
+
const sameSourceAndClock = areNodesSameInText({
|
|
56
|
+
context,
|
|
57
|
+
nodes: [params.source?.value, params.clock?.value],
|
|
58
|
+
});
|
|
54
59
|
if (!sameSourceAndClock) {
|
|
55
60
|
return;
|
|
56
61
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# effector/no-unnecessary-duplication
|
|
2
2
|
|
|
3
3
|
Same `clock`/`source` in `sample` and `guard` don't make sense, any of these fields can be omitted in this case.
|
|
4
4
|
|
|
@@ -6,7 +6,7 @@ Same `clock`/`source` in `sample` and `guard` don't make sense, any of these fie
|
|
|
6
6
|
const $data = createStore(null);
|
|
7
7
|
|
|
8
8
|
// 👎 can be simplified
|
|
9
|
-
const
|
|
9
|
+
const target1 = sample({
|
|
10
10
|
source: $data,
|
|
11
11
|
clock: $data,
|
|
12
12
|
fn(data) {
|
|
@@ -15,15 +15,15 @@ const target = sample({
|
|
|
15
15
|
});
|
|
16
16
|
|
|
17
17
|
// 👍 better
|
|
18
|
-
const
|
|
18
|
+
const target2 = sample({
|
|
19
19
|
source: $data,
|
|
20
20
|
fn(data) {
|
|
21
21
|
return data.length;
|
|
22
22
|
},
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
// 👍 also
|
|
26
|
-
const
|
|
25
|
+
// 👍 also nice solution
|
|
26
|
+
const target3 = sample({
|
|
27
27
|
clock: $data,
|
|
28
28
|
fn(data) {
|
|
29
29
|
return data.length;
|