eslint-plugin-effector 0.3.1 → 0.5.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 +40 -9
- package/config/react.js +5 -0
- package/config/recommended.js +3 -0
- package/config/scope.js +5 -0
- package/index.js +8 -1
- package/package.json +13 -15
- package/rules/enforce-effect-naming-convention/enforce-effect-naming-convention.js +19 -17
- package/rules/enforce-gate-naming-convention/enforce-gate-naming-convention.js +114 -0
- package/rules/enforce-gate-naming-convention/enforce-gate-naming-convention.md +11 -0
- package/rules/enforce-store-naming-convention/enforce-store-naming-convention.js +46 -40
- package/rules/enforce-store-naming-convention/enforce-store-naming-convention.md +12 -4
- package/rules/no-ambiguity-target/no-ambiguity-target.js +8 -4
- package/rules/no-duplicate-on/no-duplicate-on.js +128 -0
- package/rules/no-duplicate-on/no-duplicate-on.md +16 -0
- package/rules/no-getState/no-getState.js +11 -36
- package/rules/no-unnecessary-combination/no-unnecessary-combination.js +96 -0
- package/rules/no-unnecessary-combination/no-unnecessary-combination.md +25 -0
- package/rules/no-unnecessary-duplication/no-unnecessary-duplication.js +9 -5
- package/rules/no-useless-methods/no-useless-methods.js +18 -4
- package/rules/no-watch/no-watch.js +53 -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 +8 -4
- package/rules/strict-effect-handlers/strict-effect-handlers.js +74 -0
- package/rules/strict-effect-handlers/strict-effect-handlers.md +38 -0
- package/rules/tsconfig.json +2 -1
- package/utils/create-link-to-rule.js +5 -0
- package/utils/extract-imported-from.js +9 -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/naming.js +47 -0
- package/utils/node-type-is.js +55 -0
- package/utils/validate-store-name-convention.js +7 -7
- package/.github/workflows/ci.yml +0 -43
- package/CHANGELOG.md +0 -28
- package/jest.config.js +0 -7
- 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,12 +39,43 @@ To configure individual rules:
|
|
|
39
39
|
}
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
### Available presets
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
57
|
+
|
|
58
|
+
- [effector/enforce-store-naming-convention](rules/enforce-store-naming-convention/enforce-store-naming-convention.md)
|
|
59
|
+
- [effector/enforce-effect-naming-convention](rules/enforce-effect-naming-convention/enforce-effect-naming-convention.md)
|
|
60
|
+
- [effector/no-getState](rules/no-getState/no-getState.md)
|
|
61
|
+
- [effector/no-unnecessary-duplication](rules/no-unnecessary-duplication/no-unnecessary-duplication.md)
|
|
62
|
+
- [effector/no-useless-methods](rules/no-useless-methods/no-useless-methods.md)
|
|
63
|
+
- [effector/no-ambiguity-target](rules/no-ambiguity-target/no-ambiguity-target.md)
|
|
64
|
+
- [effector/prefer-sample-over-forward-with-mapping](rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping.md)
|
|
65
|
+
- [effector/no-watch](rules/no-watch/no-watch.md)
|
|
66
|
+
- [effector/no-unnecessary-combination](rules/no-unnecessary-combination/no-unnecessary-combination.md)
|
|
67
|
+
- [effector/no-duplicate-on](rules/no-duplicate-on/no-duplicate-on.md)
|
|
68
|
+
- [effector/strict-effect-handlers](rules/strict-effect-handlers/strict-effect-handlers.md)
|
|
69
|
+
- [effector/enforce-gate-naming-convention](rules/enforce-gate-naming-convention/enforce-gate-naming-convention.md)
|
|
70
|
+
|
|
71
|
+
## Maintenance
|
|
72
|
+
|
|
73
|
+
### Release flow
|
|
74
|
+
|
|
75
|
+
1. Bump `version` in [package.json](package.json)
|
|
76
|
+
2. Fill [CHANGELOG.md](CHANGELOG.md)
|
|
77
|
+
3. Commit changes by `git commit -m "Release X.X.X"`
|
|
78
|
+
4. Create git tag for release by `git tag -a vX.X.X -m "vX.X.X"`
|
|
79
|
+
5. Push changes to remote by `git push --follow-tags`
|
|
80
|
+
6. Release package to registry by `yarn clean-publish`
|
|
81
|
+
7. Fill release page with changelog on GitHub
|
package/config/react.js
ADDED
package/config/recommended.js
CHANGED
|
@@ -7,5 +7,8 @@ module.exports = {
|
|
|
7
7
|
"effector/no-unnecessary-duplication": "warn",
|
|
8
8
|
"effector/prefer-sample-over-forward-with-mapping": "warn",
|
|
9
9
|
"effector/no-ambiguity-target": "warn",
|
|
10
|
+
"effector/no-watch": "warn",
|
|
11
|
+
"effector/no-unnecessary-combination": "warn",
|
|
12
|
+
"effector/no-duplicate-on": "error",
|
|
10
13
|
},
|
|
11
14
|
};
|
package/config/scope.js
ADDED
package/index.js
CHANGED
|
@@ -7,8 +7,15 @@ 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"),
|
|
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"),
|
|
10
15
|
},
|
|
11
16
|
configs: {
|
|
12
17
|
recommended: require("./config/recommended"),
|
|
13
|
-
|
|
18
|
+
scope: require("./config/scope"),
|
|
19
|
+
react: require("./config/react"),
|
|
20
|
+
},
|
|
14
21
|
};
|
package/package.json
CHANGED
|
@@ -1,33 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-effector",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
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"
|
|
27
|
+
},
|
|
28
|
+
"nano-staged": {
|
|
29
|
+
"*.{js,ts,md}": "prettier --write"
|
|
32
30
|
}
|
|
33
|
-
}
|
|
31
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
const {
|
|
2
|
-
|
|
3
|
-
} = require("../../utils/
|
|
1
|
+
const { extractImportedFrom } = require("../../utils/extract-imported-from");
|
|
2
|
+
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
3
|
+
const { nodeTypeIs } = require("../../utils/node-type-is");
|
|
4
|
+
const { namingOf } = require("../../utils/naming");
|
|
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;
|
|
@@ -24,13 +27,10 @@ module.exports = {
|
|
|
24
27
|
if (parserServices.hasFullTypeInformation) {
|
|
25
28
|
return {
|
|
26
29
|
VariableDeclarator(node) {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const isEffectorEffect =
|
|
32
|
-
type?.symbol?.escapedName === "Effect" &&
|
|
33
|
-
type?.symbol?.parent?.escapedName?.includes("effector");
|
|
30
|
+
const isEffectorEffect = nodeTypeIs.effect({
|
|
31
|
+
node,
|
|
32
|
+
context,
|
|
33
|
+
});
|
|
34
34
|
|
|
35
35
|
if (!isEffectorEffect) {
|
|
36
36
|
return;
|
|
@@ -38,11 +38,9 @@ module.exports = {
|
|
|
38
38
|
|
|
39
39
|
const effectName = node.id.name;
|
|
40
40
|
|
|
41
|
-
if (effectName
|
|
42
|
-
|
|
41
|
+
if (namingOf.effect.isInvalid({ name: effectName })) {
|
|
42
|
+
reportEffectNameConventionViolation({ context, node, effectName });
|
|
43
43
|
}
|
|
44
|
-
|
|
45
|
-
reportEffectNameConventionViolation({ context, node, effectName });
|
|
46
44
|
},
|
|
47
45
|
};
|
|
48
46
|
}
|
|
@@ -51,7 +49,11 @@ module.exports = {
|
|
|
51
49
|
const importedFromEffector = new Map();
|
|
52
50
|
return {
|
|
53
51
|
ImportDeclaration(node) {
|
|
54
|
-
|
|
52
|
+
extractImportedFrom({
|
|
53
|
+
importMap: importedFromEffector,
|
|
54
|
+
node,
|
|
55
|
+
packageName: "effector",
|
|
56
|
+
});
|
|
55
57
|
},
|
|
56
58
|
CallExpression(node) {
|
|
57
59
|
// Effect creation with method
|
|
@@ -74,7 +76,7 @@ module.exports = {
|
|
|
74
76
|
}
|
|
75
77
|
|
|
76
78
|
const effectName = node.parent.id.name;
|
|
77
|
-
if (
|
|
79
|
+
if (namingOf.effect.isValid({ name: effectName })) {
|
|
78
80
|
continue;
|
|
79
81
|
}
|
|
80
82
|
|
|
@@ -98,7 +100,7 @@ module.exports = {
|
|
|
98
100
|
}
|
|
99
101
|
|
|
100
102
|
const effectName = node.parent.id.name;
|
|
101
|
-
if (
|
|
103
|
+
if (namingOf.effect.isValid({ name: effectName })) {
|
|
102
104
|
return;
|
|
103
105
|
}
|
|
104
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
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# effector/enforce-gate-naming-convention
|
|
2
|
+
|
|
3
|
+
Enforcing naming conventions helps keep the codebase consistent, and reduces overhead when thinking about how to name a variable with gate. Every gate is a react-component, so it should be named as regular react-compoent.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
// 👍 nice name
|
|
7
|
+
const MyFavoritePageGate = createGate();
|
|
8
|
+
|
|
9
|
+
// 👎 bad name
|
|
10
|
+
const otherFavoritePageGate = createGate();
|
|
11
|
+
```
|
|
@@ -1,26 +1,34 @@
|
|
|
1
|
+
const { extractImportedFrom } = require("../../utils/extract-imported-from");
|
|
2
|
+
const { namingOf } = require("../../utils/naming");
|
|
1
3
|
const {
|
|
2
|
-
|
|
3
|
-
} = require("../../utils/
|
|
4
|
-
const {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const {
|
|
4
|
+
validateStoreNameConvention,
|
|
5
|
+
} = require("../../utils/validate-store-name-convention");
|
|
6
|
+
const {
|
|
7
|
+
getStoreNameConvention,
|
|
8
|
+
} = require("../../utils/get-store-name-convention");
|
|
9
|
+
const {
|
|
10
|
+
getCorrectedStoreName,
|
|
11
|
+
} = require("../../utils/get-corrected-store-name");
|
|
12
|
+
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
13
|
+
const { nodeTypeIs } = require("../../utils/node-type-is");
|
|
8
14
|
|
|
9
15
|
module.exports = {
|
|
10
16
|
meta: {
|
|
11
17
|
type: "problem",
|
|
12
18
|
docs: {
|
|
13
19
|
description:
|
|
14
|
-
|
|
20
|
+
"Enforce $ as a prefix or postfix for any store created by Effector methods",
|
|
15
21
|
category: "Naming",
|
|
16
22
|
recommended: true,
|
|
23
|
+
url: createLinkToRule("enforce-store-naming-convention"),
|
|
17
24
|
},
|
|
18
25
|
messages: {
|
|
19
26
|
invalidName:
|
|
20
|
-
|
|
27
|
+
'Store "{{ storeName }}" should be named with {{ storeNameConvention }}, rename it to "{{ correctedStoreName }}"',
|
|
21
28
|
renameStore: 'Rename "{{ storeName }}" to "{{ correctedStoreName }}"',
|
|
22
29
|
},
|
|
23
30
|
schema: [],
|
|
31
|
+
hasSuggestions: true,
|
|
24
32
|
},
|
|
25
33
|
create(context) {
|
|
26
34
|
const { parserServices } = context;
|
|
@@ -31,13 +39,10 @@ module.exports = {
|
|
|
31
39
|
if (parserServices.hasFullTypeInformation) {
|
|
32
40
|
return {
|
|
33
41
|
VariableDeclarator(node) {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const isEffectorStore =
|
|
39
|
-
type?.symbol?.escapedName === "Store" &&
|
|
40
|
-
type?.symbol?.parent?.escapedName?.includes("effector");
|
|
42
|
+
const isEffectorStore = nodeTypeIs.store({
|
|
43
|
+
node,
|
|
44
|
+
context,
|
|
45
|
+
});
|
|
41
46
|
|
|
42
47
|
if (!isEffectorStore) {
|
|
43
48
|
return;
|
|
@@ -45,15 +50,13 @@ module.exports = {
|
|
|
45
50
|
|
|
46
51
|
const storeName = node.id.name;
|
|
47
52
|
|
|
48
|
-
if (
|
|
49
|
-
|
|
53
|
+
if (namingOf.store.isInvalid({ name: storeName, context })) {
|
|
54
|
+
reportStoreNameConventionViolation({
|
|
55
|
+
context,
|
|
56
|
+
node,
|
|
57
|
+
storeName,
|
|
58
|
+
});
|
|
50
59
|
}
|
|
51
|
-
|
|
52
|
-
reportStoreNameConventionViolation({
|
|
53
|
-
context,
|
|
54
|
-
node,
|
|
55
|
-
storeName
|
|
56
|
-
});
|
|
57
60
|
},
|
|
58
61
|
};
|
|
59
62
|
}
|
|
@@ -62,7 +65,11 @@ module.exports = {
|
|
|
62
65
|
const importedFromEffector = new Map();
|
|
63
66
|
return {
|
|
64
67
|
ImportDeclaration(node) {
|
|
65
|
-
|
|
68
|
+
extractImportedFrom({
|
|
69
|
+
importMap: importedFromEffector,
|
|
70
|
+
node,
|
|
71
|
+
packageName: "effector",
|
|
72
|
+
});
|
|
66
73
|
},
|
|
67
74
|
CallExpression(node) {
|
|
68
75
|
// Store creation with method
|
|
@@ -79,21 +86,21 @@ module.exports = {
|
|
|
79
86
|
}
|
|
80
87
|
|
|
81
88
|
const resultSavedInVariable =
|
|
82
|
-
|
|
89
|
+
node.parent.type === "VariableDeclarator";
|
|
83
90
|
if (!resultSavedInVariable) {
|
|
84
91
|
continue;
|
|
85
92
|
}
|
|
86
93
|
|
|
87
94
|
const storeName = node.parent.id.name;
|
|
88
95
|
|
|
89
|
-
if (
|
|
96
|
+
if (namingOf.store.isValid({ name: storeName, context })) {
|
|
90
97
|
continue;
|
|
91
98
|
}
|
|
92
99
|
|
|
93
100
|
reportStoreNameConventionViolation({
|
|
94
101
|
context,
|
|
95
102
|
node: node.parent,
|
|
96
|
-
storeName
|
|
103
|
+
storeName,
|
|
97
104
|
});
|
|
98
105
|
return;
|
|
99
106
|
}
|
|
@@ -102,27 +109,28 @@ module.exports = {
|
|
|
102
109
|
if (node.callee?.property?.name === "map") {
|
|
103
110
|
const storeNameCreatedFromMap = node.callee?.object?.name;
|
|
104
111
|
|
|
105
|
-
if (
|
|
112
|
+
if (
|
|
113
|
+
namingOf.store.isInvalid({ name: storeNameCreatedFromMap, context })
|
|
114
|
+
) {
|
|
106
115
|
return;
|
|
107
116
|
}
|
|
108
117
|
|
|
109
118
|
const resultSavedInVariable =
|
|
110
|
-
|
|
119
|
+
node.parent.type === "VariableDeclarator";
|
|
111
120
|
if (!resultSavedInVariable) {
|
|
112
121
|
return;
|
|
113
122
|
}
|
|
114
123
|
|
|
115
124
|
const storeName = node.parent.id.name;
|
|
116
125
|
|
|
117
|
-
if (
|
|
126
|
+
if (namingOf.store.isValid({ name: storeName, context })) {
|
|
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,24 +138,24 @@ 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
|
}
|
|
140
148
|
|
|
141
149
|
const storeName = node.parent.id.name;
|
|
142
150
|
|
|
143
|
-
if (
|
|
151
|
+
if (namingOf.store.isValid({ name: storeName, context })) {
|
|
144
152
|
return;
|
|
145
153
|
}
|
|
146
154
|
|
|
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,12 +174,12 @@ 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
|
{
|
|
174
181
|
messageId: "renameStore",
|
|
175
|
-
data: { storeName },
|
|
182
|
+
data: { storeName, correctedStoreName },
|
|
176
183
|
fix(fixer) {
|
|
177
184
|
return fixer.replaceTextRange(node.id.range, correctedStoreName);
|
|
178
185
|
},
|
|
@@ -180,4 +187,3 @@ function reportStoreNameConventionViolation({ context, node, storeName }) {
|
|
|
180
187
|
],
|
|
181
188
|
});
|
|
182
189
|
}
|
|
183
|
-
|
|
@@ -3,7 +3,9 @@
|
|
|
3
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
4
|
|
|
5
5
|
## Prefix convention
|
|
6
|
+
|
|
6
7
|
When configured as:
|
|
8
|
+
|
|
7
9
|
```js
|
|
8
10
|
module.exports = {
|
|
9
11
|
rules: {
|
|
@@ -11,17 +13,21 @@ module.exports = {
|
|
|
11
13
|
},
|
|
12
14
|
};
|
|
13
15
|
```
|
|
16
|
+
|
|
14
17
|
Prefix convention will be enforced:
|
|
18
|
+
|
|
15
19
|
```ts
|
|
16
20
|
// 👍 nice name
|
|
17
21
|
const $name = createStore(null);
|
|
18
22
|
|
|
19
23
|
// 👎 bad name
|
|
20
|
-
const name =
|
|
24
|
+
const name = createStore(null);
|
|
21
25
|
```
|
|
26
|
+
|
|
22
27
|
## Postfix convention
|
|
23
28
|
|
|
24
29
|
When configured as:
|
|
30
|
+
|
|
25
31
|
```js
|
|
26
32
|
module.exports = {
|
|
27
33
|
rules: {
|
|
@@ -29,12 +35,14 @@ module.exports = {
|
|
|
29
35
|
},
|
|
30
36
|
settings: {
|
|
31
37
|
effector: {
|
|
32
|
-
storeNameConvention: "postfix"
|
|
33
|
-
}
|
|
34
|
-
}
|
|
38
|
+
storeNameConvention: "postfix",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
35
41
|
};
|
|
36
42
|
```
|
|
43
|
+
|
|
37
44
|
Postfix convention will be enforced:
|
|
45
|
+
|
|
38
46
|
```ts
|
|
39
47
|
// 👍 nice name
|
|
40
48
|
const name$ = createStore(null);
|
|
@@ -1,7 +1,6 @@
|
|
|
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");
|
|
3
|
+
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
5
4
|
|
|
6
5
|
module.exports = {
|
|
7
6
|
meta: {
|
|
@@ -10,6 +9,7 @@ module.exports = {
|
|
|
10
9
|
description: "Forbids ambiguity targets in `sample` and `guard`",
|
|
11
10
|
category: "Quality",
|
|
12
11
|
recommended: true,
|
|
12
|
+
url: createLinkToRule("no-ambiguity-target"),
|
|
13
13
|
},
|
|
14
14
|
messages: {
|
|
15
15
|
ambiguityTarget:
|
|
@@ -22,7 +22,11 @@ module.exports = {
|
|
|
22
22
|
|
|
23
23
|
return {
|
|
24
24
|
ImportDeclaration(node) {
|
|
25
|
-
|
|
25
|
+
extractImportedFrom({
|
|
26
|
+
importMap: importedFromEffector,
|
|
27
|
+
node,
|
|
28
|
+
packageName: "effector",
|
|
29
|
+
});
|
|
26
30
|
},
|
|
27
31
|
CallExpression(node) {
|
|
28
32
|
const POSSIBLE_USELESS_METHODS = ["sample", "guard"];
|