eslint-plugin-effector 0.5.1 → 0.7.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/.nvmrc +1 -0
- package/README.md +22 -13
- package/config/future.js +7 -0
- package/config/react.js +1 -0
- package/config/recommended.js +1 -0
- package/index.js +5 -0
- package/package.json +2 -2
- package/rules/keep-options-order/config.js +3 -0
- package/rules/keep-options-order/keep-options-order.js +107 -0
- package/rules/mandatory-useEvent/mandatory-useEvent.js +63 -0
- package/rules/no-ambiguity-target/no-ambiguity-target.js +32 -35
- package/rules/no-duplicate-on/no-duplicate-on.js +10 -1
- package/rules/no-forward/no-forward.js +73 -0
- package/rules/no-guard/no-guard.js +78 -0
- package/rules/no-unnecessary-combination/no-unnecessary-combination.js +33 -41
- package/rules/no-unnecessary-duplication/no-unnecessary-duplication.js +35 -40
- package/rules/no-useless-methods/no-useless-methods.js +50 -53
- package/rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping.js +27 -15
- package/rules/strict-effect-handlers/strict-effect-handlers.js +4 -2
- package/utils/builders.js +19 -0
- package/utils/extract-config.js +13 -0
- package/utils/extract-imported-from.js +2 -1
- package/utils/method.js +23 -0
- package/utils/node-type-is.js +16 -0
- package/utils/react.js +184 -0
- package/utils/read-example.js +38 -1
- package/utils/replace-by-sample.js +94 -0
- package/rules/enforce-effect-naming-convention/enforce-effect-naming-convention.md +0 -11
- package/rules/enforce-gate-naming-convention/enforce-gate-naming-convention.md +0 -11
- package/rules/enforce-store-naming-convention/enforce-store-naming-convention.md +0 -52
- package/rules/no-ambiguity-target/no-ambiguity-target.md +0 -12
- package/rules/no-duplicate-on/no-duplicate-on.md +0 -16
- package/rules/no-getState/no-getState.md +0 -20
- package/rules/no-unnecessary-combination/no-unnecessary-combination.md +0 -25
- 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/strict-effect-handlers/strict-effect-handlers.md +0 -38
- package/rules/tsconfig.json +0 -10
package/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
16.10.0
|
package/README.md
CHANGED
|
@@ -39,34 +39,43 @@ To configure individual rules:
|
|
|
39
39
|
}
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
### Available
|
|
42
|
+
### Available rules by preset
|
|
43
43
|
|
|
44
44
|
#### plugin:effector/recommended
|
|
45
45
|
|
|
46
46
|
This preset is recommended for most projects.
|
|
47
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
48
|
- [effector/enforce-store-naming-convention](rules/enforce-store-naming-convention/enforce-store-naming-convention.md)
|
|
59
49
|
- [effector/enforce-effect-naming-convention](rules/enforce-effect-naming-convention/enforce-effect-naming-convention.md)
|
|
60
50
|
- [effector/no-getState](rules/no-getState/no-getState.md)
|
|
61
|
-
- [effector/no-unnecessary-duplication](rules/no-unnecessary-duplication/no-unnecessary-duplication.md)
|
|
62
51
|
- [effector/no-useless-methods](rules/no-useless-methods/no-useless-methods.md)
|
|
63
|
-
- [effector/no-
|
|
52
|
+
- [effector/no-unnecessary-duplication](rules/no-unnecessary-duplication/no-unnecessary-duplication.md)
|
|
64
53
|
- [effector/prefer-sample-over-forward-with-mapping](rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping.md)
|
|
54
|
+
- [effector/no-ambiguity-target](rules/no-ambiguity-target/no-ambiguity-target.md)
|
|
65
55
|
- [effector/no-watch](rules/no-watch/no-watch.md)
|
|
66
56
|
- [effector/no-unnecessary-combination](rules/no-unnecessary-combination/no-unnecessary-combination.md)
|
|
67
57
|
- [effector/no-duplicate-on](rules/no-duplicate-on/no-duplicate-on.md)
|
|
58
|
+
- [effector/keep-options-order](rules/keep-options-order/keep-options-order.md)
|
|
59
|
+
|
|
60
|
+
#### plugin:effector/scope
|
|
61
|
+
|
|
62
|
+
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).
|
|
63
|
+
|
|
68
64
|
- [effector/strict-effect-handlers](rules/strict-effect-handlers/strict-effect-handlers.md)
|
|
65
|
+
|
|
66
|
+
#### plugin:effector/react
|
|
67
|
+
|
|
68
|
+
This preset is recommended for projects that use [React](https://reactjs.org) with Effector.
|
|
69
|
+
|
|
69
70
|
- [effector/enforce-gate-naming-convention](rules/enforce-gate-naming-convention/enforce-gate-naming-convention.md)
|
|
71
|
+
- [effector/mandatory-useEvent](rules/mandatory-useEvent/mandatory-useEvent.md)
|
|
72
|
+
|
|
73
|
+
#### plugin:effector/future
|
|
74
|
+
|
|
75
|
+
This preset contains rules wich enforce _future-effector_ code-style.
|
|
76
|
+
|
|
77
|
+
- [effector/no-forward](rules/no-forward/no-forward.md)
|
|
78
|
+
- [effector/no-guard](rules/no-guard/no-guard.md)
|
|
70
79
|
|
|
71
80
|
## Maintenance
|
|
72
81
|
|
package/config/future.js
ADDED
package/config/react.js
CHANGED
package/config/recommended.js
CHANGED
package/index.js
CHANGED
|
@@ -12,10 +12,15 @@ module.exports = {
|
|
|
12
12
|
"no-duplicate-on": require("./rules/no-duplicate-on/no-duplicate-on"),
|
|
13
13
|
"strict-effect-handlers": require("./rules/strict-effect-handlers/strict-effect-handlers"),
|
|
14
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"),
|
|
17
|
+
"no-guard": require("./rules/no-guard/no-guard"),
|
|
18
|
+
"mandatory-useEvent": require("./rules/mandatory-useEvent/mandatory-useEvent"),
|
|
15
19
|
},
|
|
16
20
|
configs: {
|
|
17
21
|
recommended: require("./config/recommended"),
|
|
18
22
|
scope: require("./config/scope"),
|
|
19
23
|
react: require("./config/react"),
|
|
24
|
+
future: require("./config/future"),
|
|
20
25
|
},
|
|
21
26
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-effector",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Enforcing best practices for Effector",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eslint",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"access": "public"
|
|
17
17
|
},
|
|
18
18
|
"engines": {
|
|
19
|
-
"node": "^14 || ^16"
|
|
19
|
+
"node": "^14 || ^16 || ^18"
|
|
20
20
|
},
|
|
21
21
|
"peerDependencies": {
|
|
22
22
|
"effector": "*",
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
2
|
+
const { isInsideReactComponent } = require("../../utils/react");
|
|
3
|
+
const { nodeTypeIs } = require("../../utils/node-type-is");
|
|
4
|
+
const { traverseParentByType } = require("../../utils/traverse-parent-by-type");
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
meta: {
|
|
8
|
+
type: "problem",
|
|
9
|
+
docs: {
|
|
10
|
+
description:
|
|
11
|
+
"Forbids `Event` and `Effect` usage without `useEvent` in React components.",
|
|
12
|
+
category: "Quality",
|
|
13
|
+
recommended: true,
|
|
14
|
+
url: createLinkToRule("mandatory-useEvent"),
|
|
15
|
+
},
|
|
16
|
+
messages: {
|
|
17
|
+
useEventNeeded:
|
|
18
|
+
"{{ unitName }} must be wrapped with `useEvent` from `effector-react` before usage inside React components",
|
|
19
|
+
},
|
|
20
|
+
schema: [],
|
|
21
|
+
},
|
|
22
|
+
create(context) {
|
|
23
|
+
const parserServices = context.parserServices;
|
|
24
|
+
|
|
25
|
+
// TypeScript-only rule, since units can be imported from anywhere
|
|
26
|
+
if (parserServices.hasFullTypeInformation) {
|
|
27
|
+
return {
|
|
28
|
+
Identifier(node) {
|
|
29
|
+
if (isInsideReactComponent(node)) {
|
|
30
|
+
if (
|
|
31
|
+
nodeTypeIs.effect({ node, context }) ||
|
|
32
|
+
nodeTypeIs.event({ node, context })
|
|
33
|
+
) {
|
|
34
|
+
if (!isInsideUseEventCall({ node, context })) {
|
|
35
|
+
context.report({
|
|
36
|
+
node,
|
|
37
|
+
messageId: "useEventNeeded",
|
|
38
|
+
data: {
|
|
39
|
+
unitName: node.name,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {};
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
function isInsideUseEventCall({ node, context }) {
|
|
54
|
+
const calleeParentNode = traverseParentByType(node.parent, "CallExpression");
|
|
55
|
+
|
|
56
|
+
if (!calleeParentNode?.callee) return false;
|
|
57
|
+
|
|
58
|
+
return nodeTypeIs.effectorReactHook({
|
|
59
|
+
node: calleeParentNode.callee,
|
|
60
|
+
context,
|
|
61
|
+
hook: ["useEvent", "useUnit"],
|
|
62
|
+
});
|
|
63
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { extractImportedFrom } = require("../../utils/extract-imported-from");
|
|
2
2
|
const { traverseParentByType } = require("../../utils/traverse-parent-by-type");
|
|
3
3
|
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
4
|
+
const { method } = require("../../utils/method");
|
|
4
5
|
|
|
5
6
|
module.exports = {
|
|
6
7
|
meta: {
|
|
@@ -29,46 +30,42 @@ module.exports = {
|
|
|
29
30
|
});
|
|
30
31
|
},
|
|
31
32
|
CallExpression(node) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
if (
|
|
34
|
+
method.isNot(["sample", "guard"], {
|
|
35
|
+
node,
|
|
36
|
+
importMap: importedFromEffector,
|
|
37
|
+
})
|
|
38
|
+
) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
const configHasTarget = node?.arguments?.[0]?.properties?.some(
|
|
43
|
+
(prop) => prop?.key.name === "target"
|
|
44
|
+
);
|
|
45
|
+
if (!configHasTarget) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
43
48
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
const resultAssignedInVariable = traverseParentByType(
|
|
50
|
+
node,
|
|
51
|
+
"VariableDeclarator",
|
|
52
|
+
{ stopOnTypes: ["BlockStatement"] }
|
|
53
|
+
);
|
|
54
|
+
const resultPartOfChain = traverseParentByType(
|
|
55
|
+
node,
|
|
56
|
+
"ObjectExpression"
|
|
57
|
+
);
|
|
50
58
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"VariableDeclarator",
|
|
54
|
-
{ stopOnTypes: ["BlockStatement"] }
|
|
55
|
-
);
|
|
56
|
-
const resultPartOfChain = traverseParentByType(
|
|
59
|
+
if (resultAssignedInVariable || resultPartOfChain) {
|
|
60
|
+
context.report({
|
|
57
61
|
node,
|
|
58
|
-
"
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
node,
|
|
64
|
-
messageId: "ambiguityTarget",
|
|
65
|
-
data: {
|
|
66
|
-
methodName: node?.callee?.name,
|
|
67
|
-
},
|
|
68
|
-
});
|
|
62
|
+
messageId: "ambiguityTarget",
|
|
63
|
+
data: {
|
|
64
|
+
methodName: node?.callee?.name,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
69
67
|
|
|
70
|
-
|
|
71
|
-
}
|
|
68
|
+
return;
|
|
72
69
|
}
|
|
73
70
|
},
|
|
74
71
|
};
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
2
2
|
const { getNestedObjectName } = require("../../utils/get-nested-object-name");
|
|
3
|
+
const {
|
|
4
|
+
traverseNestedObjectNode,
|
|
5
|
+
} = require("../../utils/traverse-nested-object-node");
|
|
3
6
|
const { is } = require("../../utils/is");
|
|
4
7
|
|
|
5
8
|
module.exports = {
|
|
@@ -56,7 +59,9 @@ module.exports = {
|
|
|
56
59
|
|
|
57
60
|
return {
|
|
58
61
|
'CallExpression[callee.property.name="on"]'(node) {
|
|
59
|
-
const storeObject =
|
|
62
|
+
const storeObject = traverseNestedObjectNode(
|
|
63
|
+
getNestedCallee(node) ?? getAssignedVariable(node)
|
|
64
|
+
);
|
|
60
65
|
const storeName = getStoreName(storeObject);
|
|
61
66
|
|
|
62
67
|
if (!is.store({ context, node: storeObject })) {
|
|
@@ -96,6 +101,10 @@ module.exports = {
|
|
|
96
101
|
};
|
|
97
102
|
|
|
98
103
|
function normalizePossibleArrayNode(node) {
|
|
104
|
+
if (!node) {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
|
|
99
108
|
if (node.type === "ArrayExpression") {
|
|
100
109
|
return node.elements;
|
|
101
110
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const { extractImportedFrom } = require("../../utils/extract-imported-from");
|
|
2
|
+
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
3
|
+
const { method } = require("../../utils/method");
|
|
4
|
+
const { replaceForwardBySample } = require("../../utils/replace-by-sample");
|
|
5
|
+
const { extractConfig } = require("../../utils/extract-config");
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
meta: {
|
|
9
|
+
type: "problem",
|
|
10
|
+
docs: {
|
|
11
|
+
description: "Prefer `sample` over `forward`",
|
|
12
|
+
category: "Quality",
|
|
13
|
+
recommended: true,
|
|
14
|
+
url: createLinkToRule("no-forward"),
|
|
15
|
+
},
|
|
16
|
+
messages: {
|
|
17
|
+
noForward:
|
|
18
|
+
"Instead of `forward` you can use `sample`, it is more extendable.",
|
|
19
|
+
replaceWithSample: "Repalce `forward` with `sample`.",
|
|
20
|
+
},
|
|
21
|
+
schema: [],
|
|
22
|
+
hasSuggestions: true,
|
|
23
|
+
},
|
|
24
|
+
create(context) {
|
|
25
|
+
const importNodes = new Map();
|
|
26
|
+
const importedFromEffector = new Map();
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
ImportDeclaration(node) {
|
|
30
|
+
extractImportedFrom({
|
|
31
|
+
importMap: importedFromEffector,
|
|
32
|
+
nodeMap: importNodes,
|
|
33
|
+
node,
|
|
34
|
+
packageName: "effector",
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
CallExpression(node) {
|
|
38
|
+
if (
|
|
39
|
+
method.isNot("forward", {
|
|
40
|
+
node,
|
|
41
|
+
importMap: importedFromEffector,
|
|
42
|
+
})
|
|
43
|
+
) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const forwardConfig = extractConfig(["from", "to"], { node });
|
|
48
|
+
|
|
49
|
+
if (!forwardConfig.from || !forwardConfig.to) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
context.report({
|
|
54
|
+
messageId: "noForward",
|
|
55
|
+
node,
|
|
56
|
+
suggest: [
|
|
57
|
+
{
|
|
58
|
+
messageId: "replaceWithSample",
|
|
59
|
+
*fix(fixer) {
|
|
60
|
+
yield* replaceForwardBySample(forwardConfig, {
|
|
61
|
+
fixer,
|
|
62
|
+
node,
|
|
63
|
+
context,
|
|
64
|
+
importNodes,
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const { extractImportedFrom } = require("../../utils/extract-imported-from");
|
|
2
|
+
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
3
|
+
const { method } = require("../../utils/method");
|
|
4
|
+
const { replaceGuardBySample } = require("../../utils/replace-by-sample");
|
|
5
|
+
const { extractConfig } = require("../../utils/extract-config");
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
meta: {
|
|
9
|
+
type: "problem",
|
|
10
|
+
docs: {
|
|
11
|
+
description: "Prefer `sample` over `guard`",
|
|
12
|
+
category: "Quality",
|
|
13
|
+
recommended: true,
|
|
14
|
+
url: createLinkToRule("no-guard"),
|
|
15
|
+
},
|
|
16
|
+
messages: {
|
|
17
|
+
noGuard:
|
|
18
|
+
"Instead of `guard` you can use `sample`, it is more extendable.",
|
|
19
|
+
replaceWithSample: "Repalce `guard` with `sample`.",
|
|
20
|
+
},
|
|
21
|
+
schema: [],
|
|
22
|
+
hasSuggestions: true,
|
|
23
|
+
},
|
|
24
|
+
create(context) {
|
|
25
|
+
const importNodes = new Map();
|
|
26
|
+
const importedFromEffector = new Map();
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
ImportDeclaration(node) {
|
|
30
|
+
extractImportedFrom({
|
|
31
|
+
importMap: importedFromEffector,
|
|
32
|
+
nodeMap: importNodes,
|
|
33
|
+
node,
|
|
34
|
+
packageName: "effector",
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
CallExpression(node) {
|
|
38
|
+
if (
|
|
39
|
+
method.isNot("guard", {
|
|
40
|
+
node,
|
|
41
|
+
importMap: importedFromEffector,
|
|
42
|
+
})
|
|
43
|
+
) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const guardConfig = extractConfig(
|
|
48
|
+
["source", "clock", "target", "filter"],
|
|
49
|
+
{
|
|
50
|
+
node,
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
if (!guardConfig.clock || !guardConfig.filter) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
context.report({
|
|
59
|
+
messageId: "noGuard",
|
|
60
|
+
node,
|
|
61
|
+
suggest: [
|
|
62
|
+
{
|
|
63
|
+
messageId: "replaceWithSample",
|
|
64
|
+
*fix(fixer) {
|
|
65
|
+
yield* replaceGuardBySample(guardConfig, {
|
|
66
|
+
fixer,
|
|
67
|
+
node,
|
|
68
|
+
context,
|
|
69
|
+
importNodes,
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
});
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { extractImportedFrom } = require("../../utils/extract-imported-from");
|
|
2
2
|
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
3
|
+
const { method } = require("../../utils/method");
|
|
3
4
|
|
|
4
5
|
module.exports = {
|
|
5
6
|
meta: {
|
|
@@ -29,16 +30,10 @@ module.exports = {
|
|
|
29
30
|
});
|
|
30
31
|
},
|
|
31
32
|
CallExpression(node) {
|
|
32
|
-
const METHODS_WITH_POSSIBLE_UNNECESSARY_COMBINATION = [
|
|
33
|
-
"sample",
|
|
34
|
-
"guard",
|
|
35
|
-
"forward",
|
|
36
|
-
];
|
|
37
|
-
|
|
38
33
|
const CONFIG_ARG_PROPERTIES = ["source", "clock", "from"];
|
|
39
34
|
|
|
40
|
-
function toLocalMethod(
|
|
41
|
-
return importedFromEffector.get(
|
|
35
|
+
function toLocalMethod(methodName) {
|
|
36
|
+
return importedFromEffector.get(methodName);
|
|
42
37
|
}
|
|
43
38
|
|
|
44
39
|
const UNNECESSARY_METHODS = {
|
|
@@ -47,48 +42,45 @@ module.exports = {
|
|
|
47
42
|
from: ["merge"].map(toLocalMethod).filter(Boolean),
|
|
48
43
|
};
|
|
49
44
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
45
|
+
if (
|
|
46
|
+
method.isNot(["sample", "guard", "forward"], {
|
|
47
|
+
node,
|
|
48
|
+
importMap: importedFromEffector,
|
|
49
|
+
})
|
|
50
|
+
) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
55
53
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
54
|
+
const candidates =
|
|
55
|
+
node?.arguments?.[0]?.properties?.filter((n) =>
|
|
56
|
+
CONFIG_ARG_PROPERTIES.includes(n.key.name)
|
|
57
|
+
) ?? [];
|
|
60
58
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
) ?? [];
|
|
59
|
+
if (candidates.length === 0) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
65
62
|
|
|
66
|
-
|
|
63
|
+
for (const candidate of candidates) {
|
|
64
|
+
const candidateName = candidate?.value?.callee?.name;
|
|
65
|
+
const argProp = candidate?.key?.name;
|
|
66
|
+
if (!candidateName || !argProp) {
|
|
67
67
|
continue;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
const candidateName = candidate?.value?.callee?.name;
|
|
72
|
-
const argProp = candidate?.key?.name;
|
|
73
|
-
if (!candidateName || !argProp) {
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const localUnnecessaryMethods = UNNECESSARY_METHODS[argProp];
|
|
78
|
-
|
|
79
|
-
const UnnecessaryMethodIsEffectorMethod =
|
|
80
|
-
localUnnecessaryMethods.some((m) => m === candidateName);
|
|
70
|
+
const localUnnecessaryMethods = UNNECESSARY_METHODS[argProp];
|
|
81
71
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
72
|
+
const UnnecessaryMethodIsEffectorMethod =
|
|
73
|
+
localUnnecessaryMethods.some((m) => m === candidateName);
|
|
85
74
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
messageId: "unnecessaryCombination",
|
|
89
|
-
data: { methodName: candidateName },
|
|
90
|
-
});
|
|
75
|
+
if (!UnnecessaryMethodIsEffectorMethod) {
|
|
76
|
+
continue;
|
|
91
77
|
}
|
|
78
|
+
|
|
79
|
+
context.report({
|
|
80
|
+
node: candidate?.value,
|
|
81
|
+
messageId: "unnecessaryCombination",
|
|
82
|
+
data: { methodName: candidateName },
|
|
83
|
+
});
|
|
92
84
|
}
|
|
93
85
|
},
|
|
94
86
|
};
|