eslint-plugin-effector 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -5
- package/config/recommended.js +1 -0
- package/index.js +2 -0
- package/package.json +1 -1
- package/rules/keep-options-order/config.js +3 -0
- package/rules/keep-options-order/keep-options-order.js +107 -0
- package/rules/no-ambiguity-target/no-ambiguity-target.js +32 -35
- package/rules/no-duplicate-on/no-duplicate-on.js +10 -1
- package/rules/no-forward/no-forward.js +77 -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 +25 -9
- package/utils/builders.js +19 -0
- package/utils/extract-imported-from.js +2 -1
- package/utils/method.js +23 -0
- package/utils/replace-by-sample.js +66 -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/README.md
CHANGED
|
@@ -57,16 +57,18 @@ This preset is recommended for projects that use [React](https://reactjs.org) wi
|
|
|
57
57
|
|
|
58
58
|
- [effector/enforce-store-naming-convention](rules/enforce-store-naming-convention/enforce-store-naming-convention.md)
|
|
59
59
|
- [effector/enforce-effect-naming-convention](rules/enforce-effect-naming-convention/enforce-effect-naming-convention.md)
|
|
60
|
-
- [effector/
|
|
60
|
+
- [effector/enforce-gate-naming-convention](rules/enforce-gate-naming-convention/enforce-gate-naming-convention.md)
|
|
61
61
|
- [effector/no-unnecessary-duplication](rules/no-unnecessary-duplication/no-unnecessary-duplication.md)
|
|
62
|
+
- [effector/no-unnecessary-combination](rules/no-unnecessary-combination/no-unnecessary-combination.md)
|
|
62
63
|
- [effector/no-useless-methods](rules/no-useless-methods/no-useless-methods.md)
|
|
64
|
+
- [effector/no-forward](rules/no-forward/no-forward.md)
|
|
63
65
|
- [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
66
|
- [effector/no-duplicate-on](rules/no-duplicate-on/no-duplicate-on.md)
|
|
67
|
+
- [effector/no-getState](rules/no-getState/no-getState.md)
|
|
68
|
+
- [effector/no-watch](rules/no-watch/no-watch.md)
|
|
69
|
+
- [effector/prefer-sample-over-forward-with-mapping](rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping.md)
|
|
68
70
|
- [effector/strict-effect-handlers](rules/strict-effect-handlers/strict-effect-handlers.md)
|
|
69
|
-
- [effector/
|
|
71
|
+
- [effector/keep-options-order](rules/keep-options-order/keep-options-order.md)
|
|
70
72
|
|
|
71
73
|
## Maintenance
|
|
72
74
|
|
package/config/recommended.js
CHANGED
package/index.js
CHANGED
|
@@ -12,6 +12,8 @@ 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"),
|
|
15
17
|
},
|
|
16
18
|
configs: {
|
|
17
19
|
recommended: require("./config/recommended"),
|
package/package.json
CHANGED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
const { extractImportedFrom } = require("../../utils/extract-imported-from");
|
|
2
|
+
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
3
|
+
const { buildObjectInText } = require("../../utils/builders");
|
|
4
|
+
|
|
5
|
+
const { correctOrder } = require("./config");
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
meta: {
|
|
9
|
+
type: "problem",
|
|
10
|
+
docs: {
|
|
11
|
+
description: "Enforce options order for Effector methods",
|
|
12
|
+
category: "Style",
|
|
13
|
+
recommended: true,
|
|
14
|
+
url: createLinkToRule("keep-options-order"),
|
|
15
|
+
},
|
|
16
|
+
messages: {
|
|
17
|
+
invalidOrder: `Order of options should be ${makeTag(
|
|
18
|
+
correctOrder
|
|
19
|
+
)}, but found {{ incorrectOrderTag }}`,
|
|
20
|
+
changeOrder: "Sort options",
|
|
21
|
+
},
|
|
22
|
+
schema: [],
|
|
23
|
+
hasSuggestions: true,
|
|
24
|
+
},
|
|
25
|
+
create(context) {
|
|
26
|
+
const importedFromEffector = new Map();
|
|
27
|
+
return {
|
|
28
|
+
ImportDeclaration(node) {
|
|
29
|
+
extractImportedFrom({
|
|
30
|
+
importMap: importedFromEffector,
|
|
31
|
+
node,
|
|
32
|
+
packageName: "effector",
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
CallExpression(node) {
|
|
36
|
+
// Effect creation with method
|
|
37
|
+
const OPTIONS_METHODS = ["sample", "guard"];
|
|
38
|
+
for (const method of OPTIONS_METHODS) {
|
|
39
|
+
const localMethod = importedFromEffector.get(method);
|
|
40
|
+
if (!localMethod) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const isEffectorMethods = node.callee.name === localMethod;
|
|
45
|
+
if (!isEffectorMethods) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const configNode = node?.arguments?.[0];
|
|
50
|
+
const optionsNodes = configNode?.properties;
|
|
51
|
+
|
|
52
|
+
const optionsOrder = optionsNodes?.map((prop) => prop?.key.name);
|
|
53
|
+
|
|
54
|
+
const idealOrder = filteredCorrectOrder(optionsOrder);
|
|
55
|
+
|
|
56
|
+
if (isCorrectOrder(optionsOrder, idealOrder)) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
context.report({
|
|
61
|
+
node,
|
|
62
|
+
messageId: "invalidOrder",
|
|
63
|
+
data: {
|
|
64
|
+
incorrectOrderTag: makeTag(optionsOrder),
|
|
65
|
+
},
|
|
66
|
+
suggest: [
|
|
67
|
+
{
|
|
68
|
+
messageId: "changeOrder",
|
|
69
|
+
fix(fixer) {
|
|
70
|
+
const newConfig = buildObjectInText.fromArrayOfNodes({
|
|
71
|
+
properties: sortNodesByName(optionsNodes, idealOrder),
|
|
72
|
+
context,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return fixer.replaceText(configNode, newConfig);
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
function makeTag(order) {
|
|
87
|
+
return order.join("->");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isCorrectOrder(checkOrder, idealOrder) {
|
|
91
|
+
return idealOrder.every((refItem, index) => checkOrder?.[index] === refItem);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function filteredCorrectOrder(checkOrder) {
|
|
95
|
+
return correctOrder.filter((item) => checkOrder?.includes(item));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function sortNodesByName(nodes, nameOrder) {
|
|
99
|
+
const newNodes = [];
|
|
100
|
+
|
|
101
|
+
for (const name of nameOrder) {
|
|
102
|
+
const node = nodes.find((node) => node?.key.name === name);
|
|
103
|
+
newNodes.push(node);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return newNodes;
|
|
107
|
+
}
|
|
@@ -1,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,77 @@
|
|
|
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
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
meta: {
|
|
8
|
+
type: "problem",
|
|
9
|
+
docs: {
|
|
10
|
+
description: "Prefer `sample` over `forward`",
|
|
11
|
+
category: "Quality",
|
|
12
|
+
recommended: true,
|
|
13
|
+
url: createLinkToRule("no-forward"),
|
|
14
|
+
},
|
|
15
|
+
messages: {
|
|
16
|
+
noForward:
|
|
17
|
+
"Instead of `forward` you can use `sample`, it is more extendable.",
|
|
18
|
+
replaceWithSample: "Repalce `forward` with `sample`.",
|
|
19
|
+
},
|
|
20
|
+
schema: [],
|
|
21
|
+
hasSuggestions: true,
|
|
22
|
+
},
|
|
23
|
+
create(context) {
|
|
24
|
+
const importNodes = new Map();
|
|
25
|
+
const importedFromEffector = new Map();
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
ImportDeclaration(node) {
|
|
29
|
+
extractImportedFrom({
|
|
30
|
+
importMap: importedFromEffector,
|
|
31
|
+
nodeMap: importNodes,
|
|
32
|
+
node,
|
|
33
|
+
packageName: "effector",
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
CallExpression(node) {
|
|
37
|
+
if (
|
|
38
|
+
method.isNot("forward", {
|
|
39
|
+
node,
|
|
40
|
+
importMap: importedFromEffector,
|
|
41
|
+
})
|
|
42
|
+
) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const forwardConfig = {
|
|
47
|
+
from: node.arguments?.[0]?.properties.find(
|
|
48
|
+
(n) => n.key?.name === "from"
|
|
49
|
+
),
|
|
50
|
+
to: node.arguments?.[0]?.properties.find((n) => n.key?.name === "to"),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
if (!forwardConfig.from || !forwardConfig.to) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
context.report({
|
|
58
|
+
messageId: "noForward",
|
|
59
|
+
node,
|
|
60
|
+
suggest: [
|
|
61
|
+
{
|
|
62
|
+
messageId: "replaceWithSample",
|
|
63
|
+
*fix(fixer) {
|
|
64
|
+
yield* replaceForwardBySample(forwardConfig, {
|
|
65
|
+
fixer,
|
|
66
|
+
node,
|
|
67
|
+
context,
|
|
68
|
+
importNodes,
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
};
|
|
@@ -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
|
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
const { extractImportedFrom } = require("../../utils/extract-imported-from");
|
|
2
2
|
const { areNodesSameInText } = require("../../utils/are-nodes-same-in-text");
|
|
3
3
|
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
4
|
+
const { buildObjectInText } = require("../../utils/builders");
|
|
5
|
+
const { method } = require("../../utils/method");
|
|
4
6
|
|
|
5
7
|
module.exports = {
|
|
6
8
|
meta: {
|
|
@@ -32,45 +34,41 @@ module.exports = {
|
|
|
32
34
|
});
|
|
33
35
|
},
|
|
34
36
|
CallExpression(node) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (!isEffectorMethod) {
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const params = {
|
|
48
|
-
source: node?.arguments?.[0]?.properties?.find(
|
|
49
|
-
(n) => n.key.name === "source"
|
|
50
|
-
),
|
|
51
|
-
clock: node?.arguments?.[0]?.properties?.find(
|
|
52
|
-
(n) => n.key.name === "clock"
|
|
53
|
-
),
|
|
54
|
-
};
|
|
55
|
-
if (!params.source || !params.clock) {
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
37
|
+
if (
|
|
38
|
+
method.isNot(["sample", "guard"], {
|
|
39
|
+
node,
|
|
40
|
+
importMap: importedFromEffector,
|
|
41
|
+
})
|
|
42
|
+
) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
58
45
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
46
|
+
const params = {
|
|
47
|
+
source: node?.arguments?.[0]?.properties?.find(
|
|
48
|
+
(n) => n.key.name === "source"
|
|
49
|
+
),
|
|
50
|
+
clock: node?.arguments?.[0]?.properties?.find(
|
|
51
|
+
(n) => n.key.name === "clock"
|
|
52
|
+
),
|
|
53
|
+
};
|
|
54
|
+
if (!params.source || !params.clock) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
66
57
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
58
|
+
const sameSourceAndClock = areNodesSameInText({
|
|
59
|
+
context,
|
|
60
|
+
nodes: [params.source?.value, params.clock?.value],
|
|
61
|
+
});
|
|
62
|
+
if (!sameSourceAndClock) {
|
|
63
|
+
return;
|
|
73
64
|
}
|
|
65
|
+
|
|
66
|
+
reportUnnecessaryDuplication({
|
|
67
|
+
context,
|
|
68
|
+
node,
|
|
69
|
+
params,
|
|
70
|
+
firstArgument: node?.arguments?.[0],
|
|
71
|
+
});
|
|
74
72
|
},
|
|
75
73
|
};
|
|
76
74
|
},
|
|
@@ -86,11 +84,8 @@ function reportUnnecessaryDuplication({
|
|
|
86
84
|
const properties = objectNode?.properties?.filter?.(
|
|
87
85
|
(p) => p !== paramToExcludeNode
|
|
88
86
|
);
|
|
89
|
-
const newPropertiesText = properties
|
|
90
|
-
.map((p) => context.getSourceCode().getText(p))
|
|
91
|
-
.join(", ");
|
|
92
87
|
|
|
93
|
-
return
|
|
88
|
+
return buildObjectInText.fromArrayOfNodes({ properties, context });
|
|
94
89
|
}
|
|
95
90
|
|
|
96
91
|
context.report({
|
|
@@ -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,67 +30,63 @@ module.exports = {
|
|
|
29
30
|
});
|
|
30
31
|
},
|
|
31
32
|
CallExpression(node) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const localMethod = importedFromEffector.get(method);
|
|
35
|
-
if (!localMethod) {
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const isEffectorMethod = node?.callee?.name === localMethod;
|
|
40
|
-
if (!isEffectorMethod) {
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const resultAssignedInVariable = traverseParentByType(
|
|
33
|
+
if (
|
|
34
|
+
method.isNot(["sample", "guard"], {
|
|
45
35
|
node,
|
|
46
|
-
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
36
|
+
importMap: importedFromEffector,
|
|
37
|
+
})
|
|
38
|
+
) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
51
41
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
42
|
+
const resultAssignedInVariable = traverseParentByType(
|
|
43
|
+
node,
|
|
44
|
+
"VariableDeclarator"
|
|
45
|
+
);
|
|
46
|
+
if (resultAssignedInVariable) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
59
49
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
50
|
+
const resultReturnedFromFactory = traverseParentByType(
|
|
51
|
+
node,
|
|
52
|
+
"ReturnStatement"
|
|
53
|
+
);
|
|
54
|
+
if (resultReturnedFromFactory) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
67
57
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
58
|
+
const resultPartOfChain = traverseParentByType(
|
|
59
|
+
node,
|
|
60
|
+
"ObjectExpression"
|
|
61
|
+
);
|
|
62
|
+
if (resultPartOfChain) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
74
65
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
66
|
+
const configHasTarget = node?.arguments?.[0]?.properties?.some(
|
|
67
|
+
(prop) => prop?.key.name === "target"
|
|
68
|
+
);
|
|
69
|
+
if (configHasTarget) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
79
72
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
73
|
+
const resultIsWatched = node?.parent?.property?.name === "watch";
|
|
74
|
+
if (resultIsWatched) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
84
77
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
data: {
|
|
89
|
-
methodName: node?.callee?.name,
|
|
90
|
-
},
|
|
91
|
-
});
|
|
78
|
+
const resultIsArgument = node?.parent?.type === "CallExpression";
|
|
79
|
+
if (resultIsArgument) {
|
|
80
|
+
return;
|
|
92
81
|
}
|
|
82
|
+
|
|
83
|
+
context.report({
|
|
84
|
+
node,
|
|
85
|
+
messageId: "uselessMethod",
|
|
86
|
+
data: {
|
|
87
|
+
methodName: node?.callee?.name,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
93
90
|
},
|
|
94
91
|
};
|
|
95
92
|
},
|
package/rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping.js
CHANGED
|
@@ -3,6 +3,8 @@ const {
|
|
|
3
3
|
traverseNestedObjectNode,
|
|
4
4
|
} = require("../../utils/traverse-nested-object-node");
|
|
5
5
|
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
6
|
+
const { method } = require("../../utils/method");
|
|
7
|
+
const { replaceForwardBySample } = require("../../utils/replace-by-sample");
|
|
6
8
|
|
|
7
9
|
module.exports = {
|
|
8
10
|
meta: {
|
|
@@ -18,28 +20,31 @@ module.exports = {
|
|
|
18
20
|
"Instead of `forward` with `{{ eventName }}.map` you can use `sample`",
|
|
19
21
|
overPrepend:
|
|
20
22
|
"Instead of `forward` with `{{ eventName }}.prepend` you can use `sample`",
|
|
23
|
+
replaceWithSample: "Repalce `forward` with `sample`.",
|
|
21
24
|
},
|
|
22
25
|
schema: [],
|
|
26
|
+
hasSuggestions: true,
|
|
23
27
|
},
|
|
24
28
|
create(context) {
|
|
29
|
+
const importNodes = new Map();
|
|
25
30
|
const importedFromEffector = new Map();
|
|
26
31
|
|
|
27
32
|
return {
|
|
28
33
|
ImportDeclaration(node) {
|
|
29
34
|
extractImportedFrom({
|
|
30
35
|
importMap: importedFromEffector,
|
|
36
|
+
nodeMap: importNodes,
|
|
31
37
|
node,
|
|
32
38
|
packageName: "effector",
|
|
33
39
|
});
|
|
34
40
|
},
|
|
35
41
|
CallExpression(node) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (!isEffectorMethod) {
|
|
42
|
+
if (
|
|
43
|
+
method.isNot("forward", {
|
|
44
|
+
node,
|
|
45
|
+
importMap: importedFromEffector,
|
|
46
|
+
})
|
|
47
|
+
) {
|
|
43
48
|
return;
|
|
44
49
|
}
|
|
45
50
|
|
|
@@ -72,14 +77,25 @@ module.exports = {
|
|
|
72
77
|
return;
|
|
73
78
|
}
|
|
74
79
|
|
|
75
|
-
// console.log(eventName);
|
|
76
|
-
|
|
77
80
|
context.report({
|
|
78
81
|
node,
|
|
79
82
|
messageId,
|
|
80
83
|
data: {
|
|
81
84
|
eventName,
|
|
82
85
|
},
|
|
86
|
+
suggest: [
|
|
87
|
+
{
|
|
88
|
+
messageId: "replaceWithSample",
|
|
89
|
+
*fix(fixer) {
|
|
90
|
+
yield* replaceForwardBySample(forwardConfig, {
|
|
91
|
+
fixer,
|
|
92
|
+
node,
|
|
93
|
+
context,
|
|
94
|
+
importNodes,
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
],
|
|
83
99
|
});
|
|
84
100
|
}
|
|
85
101
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const buildObjectInText = {
|
|
2
|
+
fromArrayOfNodes({ properties, context }) {
|
|
3
|
+
const content = properties
|
|
4
|
+
.map((property) => context.getSourceCode().getText(property))
|
|
5
|
+
.join(", ");
|
|
6
|
+
|
|
7
|
+
return `{ ${content} }`;
|
|
8
|
+
},
|
|
9
|
+
fromMapOfNodes({ properties, context }) {
|
|
10
|
+
const content = Object.entries(properties)
|
|
11
|
+
.filter(([_, node]) => Boolean(node))
|
|
12
|
+
.map(([key, node]) => `${key}: ${context.getSourceCode().getText(node)}`)
|
|
13
|
+
.join(", ");
|
|
14
|
+
|
|
15
|
+
return `{ ${content} }`;
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
module.exports = { buildObjectInText };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
function extractImportedFrom({ importMap, node, packageName }) {
|
|
1
|
+
function extractImportedFrom({ importMap, nodeMap, node, packageName }) {
|
|
2
2
|
if (node.source.value === packageName) {
|
|
3
3
|
for (const s of node.specifiers) {
|
|
4
4
|
importMap.set(s.imported.name, s.local.name);
|
|
5
|
+
nodeMap?.set(s.imported.name, s);
|
|
5
6
|
}
|
|
6
7
|
}
|
|
7
8
|
}
|
package/utils/method.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
function isSomeMethod(methodName, { node, importMap }) {
|
|
2
|
+
const normalizedMethodNames = Array.isArray(methodName)
|
|
3
|
+
? methodName
|
|
4
|
+
: [methodName];
|
|
5
|
+
|
|
6
|
+
return normalizedMethodNames.some((method) => {
|
|
7
|
+
const localMethod = importMap.get(method);
|
|
8
|
+
if (!localMethod) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const isEffectorMethod = node?.callee?.name === localMethod;
|
|
13
|
+
|
|
14
|
+
return isEffectorMethod;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const method = {
|
|
19
|
+
is: (...args) => isSomeMethod(...args),
|
|
20
|
+
isNot: (...args) => !isSomeMethod(...args),
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
module.exports = { method };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const { buildObjectInText } = require("./builders");
|
|
2
|
+
|
|
3
|
+
function* replaceForwardBySample(
|
|
4
|
+
forwardConfig,
|
|
5
|
+
{ fixer, node, context, importNodes }
|
|
6
|
+
) {
|
|
7
|
+
let mapperFunctionNode = null;
|
|
8
|
+
|
|
9
|
+
let clockMapperUsed = false;
|
|
10
|
+
let targetMapperUsed = false;
|
|
11
|
+
|
|
12
|
+
let clockNode = forwardConfig.from.value;
|
|
13
|
+
let targetNode = forwardConfig.to.value;
|
|
14
|
+
|
|
15
|
+
if (
|
|
16
|
+
clockNode.type === "CallExpression" &&
|
|
17
|
+
clockNode?.callee?.property?.name === "map"
|
|
18
|
+
) {
|
|
19
|
+
mapperFunctionNode = clockNode?.arguments?.[0];
|
|
20
|
+
clockNode = clockNode.callee.object;
|
|
21
|
+
clockMapperUsed = true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (
|
|
25
|
+
targetNode.type === "CallExpression" &&
|
|
26
|
+
targetNode?.callee?.property?.name === "prepend"
|
|
27
|
+
) {
|
|
28
|
+
mapperFunctionNode = targetNode?.arguments?.[0];
|
|
29
|
+
targetNode = targetNode.callee.object;
|
|
30
|
+
targetMapperUsed = true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// We cannot apply two mappers in one sample
|
|
34
|
+
// Let's revert mappers and use .map + .prepend
|
|
35
|
+
if (clockMapperUsed && targetMapperUsed) {
|
|
36
|
+
mapperFunctionNode = null;
|
|
37
|
+
clockNode = forwardConfig.from.value;
|
|
38
|
+
targetNode = forwardConfig.to.value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
yield* replaceBySample(
|
|
42
|
+
{ clockNode, targetNode, mapperFunctionNode },
|
|
43
|
+
{ node, fixer, context, importNodes, methodName: "forward" }
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function* replaceBySample(
|
|
48
|
+
{ clockNode, targetNode, mapperFunctionNode },
|
|
49
|
+
{ node, fixer, context, importNodes, methodName }
|
|
50
|
+
) {
|
|
51
|
+
yield fixer.replaceText(
|
|
52
|
+
node,
|
|
53
|
+
`sample(${buildObjectInText.fromMapOfNodes({
|
|
54
|
+
properties: {
|
|
55
|
+
clock: clockNode,
|
|
56
|
+
fn: mapperFunctionNode,
|
|
57
|
+
target: targetNode,
|
|
58
|
+
},
|
|
59
|
+
context,
|
|
60
|
+
})})`
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
yield fixer.replaceText(importNodes.get(methodName), "sample");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = { replaceForwardBySample };
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# effector/enforce-effect-naming-convention
|
|
2
|
-
|
|
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,11 +0,0 @@
|
|
|
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,52 +0,0 @@
|
|
|
1
|
-
# effector/enforce-store-naming-convention
|
|
2
|
-
|
|
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
|
-
|
|
7
|
-
When configured as:
|
|
8
|
-
|
|
9
|
-
```js
|
|
10
|
-
module.exports = {
|
|
11
|
-
rules: {
|
|
12
|
-
"effector/enforce-store-naming-convention": "error",
|
|
13
|
-
},
|
|
14
|
-
};
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
Prefix convention will be enforced:
|
|
18
|
-
|
|
19
|
-
```ts
|
|
20
|
-
// 👍 nice name
|
|
21
|
-
const $name = createStore(null);
|
|
22
|
-
|
|
23
|
-
// 👎 bad name
|
|
24
|
-
const name = createStore(null);
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Postfix convention
|
|
28
|
-
|
|
29
|
-
When configured as:
|
|
30
|
-
|
|
31
|
-
```js
|
|
32
|
-
module.exports = {
|
|
33
|
-
rules: {
|
|
34
|
-
"effector/enforce-store-naming-convention": "error",
|
|
35
|
-
},
|
|
36
|
-
settings: {
|
|
37
|
-
effector: {
|
|
38
|
-
storeNameConvention: "postfix",
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
Postfix convention will be enforced:
|
|
45
|
-
|
|
46
|
-
```ts
|
|
47
|
-
// 👍 nice name
|
|
48
|
-
const name$ = createStore(null);
|
|
49
|
-
|
|
50
|
-
// 👎 bad name
|
|
51
|
-
const name = createStrore(null);
|
|
52
|
-
```
|
|
@@ -1,12 +0,0 @@
|
|
|
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,16 +0,0 @@
|
|
|
1
|
-
# effector/no-duplicate-on
|
|
2
|
-
|
|
3
|
-
Disallows duplcates `on`-handlers on particular store.
|
|
4
|
-
|
|
5
|
-
```ts
|
|
6
|
-
const increment = createEvent();
|
|
7
|
-
|
|
8
|
-
// 👍 all explicitly
|
|
9
|
-
const $goodCounter = createStore(0).on(increment, (counter) => counter + 1);
|
|
10
|
-
|
|
11
|
-
// 👎 so, which handler should we choose?
|
|
12
|
-
// it's better to remove one of them
|
|
13
|
-
const $badCounter = createStore(0)
|
|
14
|
-
.on(increment, (counter) => counter + 1)
|
|
15
|
-
.on(increment, (counter) => counter + 2);
|
|
16
|
-
```
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
# effector/no-getState
|
|
2
|
-
|
|
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,25 +0,0 @@
|
|
|
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({
|
|
8
|
-
clock: combine($store1, $store2),
|
|
9
|
-
filter: $filter,
|
|
10
|
-
});
|
|
11
|
-
const badEventOne = guard({
|
|
12
|
-
clock: combine($store1, $store2, (store1, store2) => ({
|
|
13
|
-
x: store1,
|
|
14
|
-
y: store2,
|
|
15
|
-
})),
|
|
16
|
-
filter: $filter,
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
// 👍 better
|
|
20
|
-
const goodEventOne = guard({ clock: [$store1, $store2], filter: $filter });
|
|
21
|
-
const goodEventTwo = guard({
|
|
22
|
-
clock: { x: $store1, x: $store2 },
|
|
23
|
-
filter: $filter,
|
|
24
|
-
});
|
|
25
|
-
```
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# effector/no-unnecessary-duplication
|
|
2
|
-
|
|
3
|
-
Same `clock`/`source` in `sample` and `guard` don't make sense, any of these fields can be omitted in this case.
|
|
4
|
-
|
|
5
|
-
```ts
|
|
6
|
-
const $data = createStore(null);
|
|
7
|
-
|
|
8
|
-
// 👎 can be simplified
|
|
9
|
-
const target1 = sample({
|
|
10
|
-
source: $data,
|
|
11
|
-
clock: $data,
|
|
12
|
-
fn(data) {
|
|
13
|
-
return data.length;
|
|
14
|
-
},
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
// 👍 better
|
|
18
|
-
const target2 = sample({
|
|
19
|
-
source: $data,
|
|
20
|
-
fn(data) {
|
|
21
|
-
return data.length;
|
|
22
|
-
},
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
// 👍 also nice solution
|
|
26
|
-
const target3 = sample({
|
|
27
|
-
clock: $data,
|
|
28
|
-
fn(data) {
|
|
29
|
-
return data.length;
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
```
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# effector/no-useless-methods
|
|
2
|
-
|
|
3
|
-
Call of `gaurd`/`sample` without `target` or variable assignment is useless. It can be omitted from source code.
|
|
4
|
-
|
|
5
|
-
```ts
|
|
6
|
-
// 👎 can be omitted
|
|
7
|
-
guard({ clock: trigger, filter: Boolean });
|
|
8
|
-
|
|
9
|
-
// 👍 makes sense
|
|
10
|
-
const target1 = guard({ clock: trigger, filter: Boolean });
|
|
11
|
-
|
|
12
|
-
// 👍 make sense too
|
|
13
|
-
guard({ clock: trigger, filter: Boolean, target: target2 });
|
|
14
|
-
```
|
|
@@ -1,42 +0,0 @@
|
|
|
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.md
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# effector/prefer-sample-over-forward-with-mapping
|
|
2
|
-
|
|
3
|
-
Prefer `sample` over `forward` with `.map`/`.prepend`.
|
|
4
|
-
|
|
5
|
-
```js
|
|
6
|
-
const eventOne = createEvent();
|
|
7
|
-
const eventTwo = createEvent();
|
|
8
|
-
|
|
9
|
-
// 👎 looks weird
|
|
10
|
-
forward({
|
|
11
|
-
from: eventOne.map((items) => items.length),
|
|
12
|
-
to: eventTwo,
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
// 👎 weird too
|
|
16
|
-
forward({
|
|
17
|
-
from: eventOne,
|
|
18
|
-
to: eventTwo.prepend((items) => items.length),
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
// 👍 better
|
|
22
|
-
sample({
|
|
23
|
-
source: eventOne,
|
|
24
|
-
fn: (items) => items.length,
|
|
25
|
-
target: eventTwo,
|
|
26
|
-
});
|
|
27
|
-
```
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# effector/strict-effect-handlers
|
|
2
|
-
|
|
3
|
-
[Related documentation](https://effector.dev/docs/api/effector/scope#imperative-effects-calls-with-scope)
|
|
4
|
-
|
|
5
|
-
When effect calls another effects then it should call only effects, not common async functions and effect calls should have await:
|
|
6
|
-
|
|
7
|
-
```ts
|
|
8
|
-
// 👍 effect without inner effects:
|
|
9
|
-
const delayFx = createEffect(async () => {
|
|
10
|
-
await new Promise((rs) => setTimeout(rs, 80));
|
|
11
|
-
});
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
```ts
|
|
15
|
-
const authUserFx = createEffect();
|
|
16
|
-
const sendMessageFx = createEffect();
|
|
17
|
-
|
|
18
|
-
// 👍 effect with inner effects
|
|
19
|
-
const sendWithAuthFx = createEffect(async () => {
|
|
20
|
-
await authUserFx();
|
|
21
|
-
await delayFx();
|
|
22
|
-
await sendMessageFx();
|
|
23
|
-
});
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
```ts
|
|
27
|
-
// 👎 effect with inner effects and common async functions
|
|
28
|
-
|
|
29
|
-
const sendWithAuthFx = createEffect(async () => {
|
|
30
|
-
await authUserFx();
|
|
31
|
-
//WRONG! wrap that in effect
|
|
32
|
-
await new Promise((rs) => setTimeout(rs, 80));
|
|
33
|
-
//context lost
|
|
34
|
-
await sendMessageFx();
|
|
35
|
-
});
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
So, any effect might either call another effects or perform some async computations but not both.
|