eslint-plugin-effector 0.15.0 → 0.17.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 +24 -37
- package/dist/index.cjs +1259 -0
- package/dist/index.d.cts +177 -0
- package/dist/index.d.mts +178 -0
- package/dist/index.mjs +1233 -0
- package/package.json +71 -17
- package/.nvmrc +0 -1
- package/config/future.js +0 -7
- package/config/patronum.js +0 -5
- package/config/react.js +0 -7
- package/config/recommended.js +0 -15
- package/config/scope.js +0 -6
- package/index.js +0 -31
- package/rules/enforce-effect-naming-convention/enforce-effect-naming-convention.js +0 -143
- package/rules/enforce-gate-naming-convention/enforce-gate-naming-convention.js +0 -122
- package/rules/enforce-store-naming-convention/enforce-store-naming-convention.js +0 -205
- package/rules/keep-options-order/config.js +0 -3
- package/rules/keep-options-order/keep-options-order.js +0 -107
- package/rules/mandatory-scope-binding/mandatory-scope-binding.js +0 -81
- package/rules/no-ambiguity-target/no-ambiguity-target.js +0 -74
- package/rules/no-duplicate-clock-or-source-array-values/no-duplicate-clock-or-source-array-values.js +0 -120
- package/rules/no-duplicate-on/no-duplicate-on.js +0 -137
- package/rules/no-forward/no-forward.js +0 -73
- package/rules/no-getState/no-getState.js +0 -50
- package/rules/no-guard/no-guard.js +0 -78
- package/rules/no-patronum-debug/no-patronum-debug.js +0 -133
- package/rules/no-unnecessary-combination/no-unnecessary-combination.js +0 -88
- package/rules/no-unnecessary-duplication/no-unnecessary-duplication.js +0 -115
- package/rules/no-useless-methods/no-useless-methods.js +0 -93
- package/rules/no-watch/no-watch.js +0 -61
- package/rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping.js +0 -111
- package/rules/prefer-useUnit/prefer-useUnit.js +0 -56
- package/rules/require-pickup-in-persist/require-pickup-in-persist.js +0 -47
- package/rules/strict-effect-handlers/strict-effect-handlers.js +0 -76
- package/utils/are-nodes-same-in-text.js +0 -22
- package/utils/builders.js +0 -19
- package/utils/create-link-to-rule.js +0 -5
- package/utils/extract-config.js +0 -26
- package/utils/extract-imported-from.js +0 -18
- package/utils/get-corrected-store-name.js +0 -45
- package/utils/get-nested-object-name.js +0 -18
- package/utils/get-store-name-convention.js +0 -6
- package/utils/is.js +0 -39
- package/utils/method.js +0 -23
- package/utils/naming.js +0 -47
- package/utils/node-is-type.js +0 -5
- package/utils/node-type-is.js +0 -106
- package/utils/react.js +0 -214
- package/utils/read-example.js +0 -63
- package/utils/replace-by-sample.js +0 -98
- package/utils/traverse-nested-object-node.js +0 -9
- package/utils/traverse-parent-by-type.js +0 -15
- package/utils/validate-store-name-convention.js +0 -13
|
@@ -1,107 +0,0 @@
|
|
|
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,81 +0,0 @@
|
|
|
1
|
-
const { ESLintUtils } = require("@typescript-eslint/utils");
|
|
2
|
-
|
|
3
|
-
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
4
|
-
const { isInsideReactComponent } = require("../../utils/react");
|
|
5
|
-
const { nodeTypeIs } = require("../../utils/node-type-is");
|
|
6
|
-
const { traverseParentByType } = require("../../utils/traverse-parent-by-type");
|
|
7
|
-
const { nodeIsType } = require("../../utils/node-is-type");
|
|
8
|
-
|
|
9
|
-
module.exports = {
|
|
10
|
-
meta: {
|
|
11
|
-
type: "problem",
|
|
12
|
-
docs: {
|
|
13
|
-
description:
|
|
14
|
-
"Forbids `Event` and `Effect` usage without `useUnit` in React components.",
|
|
15
|
-
category: "Quality",
|
|
16
|
-
recommended: true,
|
|
17
|
-
url: createLinkToRule("mandatory-scope-binding"),
|
|
18
|
-
},
|
|
19
|
-
messages: {
|
|
20
|
-
useEventNeeded:
|
|
21
|
-
"{{ unitName }} must be wrapped with `useUnit` from `effector-react` before usage inside React components",
|
|
22
|
-
},
|
|
23
|
-
schema: [],
|
|
24
|
-
},
|
|
25
|
-
create(context) {
|
|
26
|
-
let parserServices;
|
|
27
|
-
try {
|
|
28
|
-
parserServices = ESLintUtils.getParserServices(context);
|
|
29
|
-
} catch (err) {
|
|
30
|
-
// no types information
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// TypeScript-only rule, since units can be imported from anywhere
|
|
34
|
-
if (!parserServices?.program) {
|
|
35
|
-
return {};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
Identifier(node) {
|
|
40
|
-
if (!isInsideReactComponent(node)) {
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (nodeIsType({ node })) {
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (
|
|
49
|
-
nodeTypeIs.not.effect({ node, context }) &&
|
|
50
|
-
nodeTypeIs.not.event({ node, context })
|
|
51
|
-
) {
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (isInsideEffectorHook({ node, context })) {
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
context.report({
|
|
60
|
-
node,
|
|
61
|
-
messageId: "useEventNeeded",
|
|
62
|
-
data: {
|
|
63
|
-
unitName: node.name,
|
|
64
|
-
},
|
|
65
|
-
});
|
|
66
|
-
},
|
|
67
|
-
};
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
function isInsideEffectorHook({ node, context }) {
|
|
72
|
-
const calleeParentNode = traverseParentByType(node.parent, "CallExpression");
|
|
73
|
-
|
|
74
|
-
if (!calleeParentNode?.callee) return false;
|
|
75
|
-
|
|
76
|
-
return nodeTypeIs.effectorReactHook({
|
|
77
|
-
node: calleeParentNode.callee,
|
|
78
|
-
context,
|
|
79
|
-
hook: ["useEvent", "useUnit", "useStore"],
|
|
80
|
-
});
|
|
81
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
const { extractImportedFrom } = require("../../utils/extract-imported-from");
|
|
2
|
-
const { traverseParentByType } = require("../../utils/traverse-parent-by-type");
|
|
3
|
-
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
4
|
-
const { method } = require("../../utils/method");
|
|
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
|
-
url: createLinkToRule("no-ambiguity-target"),
|
|
14
|
-
},
|
|
15
|
-
messages: {
|
|
16
|
-
ambiguityTarget:
|
|
17
|
-
"Method `{{ methodName }}` returns `target` and assigns the result to a variable. Consider removing one of them.",
|
|
18
|
-
},
|
|
19
|
-
schema: [],
|
|
20
|
-
},
|
|
21
|
-
create(context) {
|
|
22
|
-
const importedFromEffector = new Map();
|
|
23
|
-
|
|
24
|
-
return {
|
|
25
|
-
ImportDeclaration(node) {
|
|
26
|
-
extractImportedFrom({
|
|
27
|
-
importMap: importedFromEffector,
|
|
28
|
-
node,
|
|
29
|
-
packageName: "effector",
|
|
30
|
-
});
|
|
31
|
-
},
|
|
32
|
-
CallExpression(node) {
|
|
33
|
-
if (
|
|
34
|
-
method.isNot(["sample", "guard"], {
|
|
35
|
-
node,
|
|
36
|
-
importMap: importedFromEffector,
|
|
37
|
-
})
|
|
38
|
-
) {
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const configHasTarget = node?.arguments?.[0]?.properties?.some(
|
|
43
|
-
(prop) => prop?.key.name === "target"
|
|
44
|
-
);
|
|
45
|
-
if (!configHasTarget) {
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const resultAssignedInVariable = traverseParentByType(
|
|
50
|
-
node,
|
|
51
|
-
"VariableDeclarator",
|
|
52
|
-
{ stopOnTypes: ["BlockStatement"] }
|
|
53
|
-
);
|
|
54
|
-
const resultPartOfChain = traverseParentByType(
|
|
55
|
-
node,
|
|
56
|
-
"ObjectExpression",
|
|
57
|
-
{ stopOnTypes: ["BlockStatement"] }
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
if (resultAssignedInVariable || resultPartOfChain) {
|
|
61
|
-
context.report({
|
|
62
|
-
node,
|
|
63
|
-
messageId: "ambiguityTarget",
|
|
64
|
-
data: {
|
|
65
|
-
methodName: node?.callee?.name,
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
};
|
|
73
|
-
},
|
|
74
|
-
};
|
package/rules/no-duplicate-clock-or-source-array-values/no-duplicate-clock-or-source-array-values.js
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
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 {} = require("../../utils/traverse-nested-object-node");
|
|
5
|
-
|
|
6
|
-
module.exports = {
|
|
7
|
-
meta: {
|
|
8
|
-
type: "problem",
|
|
9
|
-
hasSuggestions: true,
|
|
10
|
-
docs: {
|
|
11
|
-
description: "Forbids unit duplicates on `source` and `clock``",
|
|
12
|
-
category: "Quality",
|
|
13
|
-
recommended: true,
|
|
14
|
-
url: createLinkToRule("no-duplicate-clock-or-source-array-values"),
|
|
15
|
-
},
|
|
16
|
-
messages: {
|
|
17
|
-
duplicatesInClock: "Clock contains duplicate units - {{ memberPath }}.",
|
|
18
|
-
duplicatesInSource: "Source contains duplicate units - {{ memberPath }}.",
|
|
19
|
-
removeDuplicate: "Remove duplicate {{ memberPath }}.",
|
|
20
|
-
},
|
|
21
|
-
schema: [],
|
|
22
|
-
},
|
|
23
|
-
create(context) {
|
|
24
|
-
const importedFromEffector = new Map();
|
|
25
|
-
|
|
26
|
-
return {
|
|
27
|
-
ImportDeclaration(node) {
|
|
28
|
-
extractImportedFrom({
|
|
29
|
-
importMap: importedFromEffector,
|
|
30
|
-
node,
|
|
31
|
-
packageName: "effector",
|
|
32
|
-
});
|
|
33
|
-
},
|
|
34
|
-
CallExpression(node) {
|
|
35
|
-
if (
|
|
36
|
-
method.isNot(["sample", "guard"], {
|
|
37
|
-
node,
|
|
38
|
-
importMap: importedFromEffector,
|
|
39
|
-
})
|
|
40
|
-
) {
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const properties = getSourceOrClockProperties(node.arguments[0]);
|
|
45
|
-
|
|
46
|
-
properties.forEach(({ key, value }) => {
|
|
47
|
-
const propType = key.name;
|
|
48
|
-
const elements = value.elements;
|
|
49
|
-
|
|
50
|
-
const usedUnits = new Set();
|
|
51
|
-
|
|
52
|
-
for (const node of elements) {
|
|
53
|
-
const memberPath = createMemberExpressionPath(node);
|
|
54
|
-
|
|
55
|
-
if (usedUnits.has(memberPath)) {
|
|
56
|
-
const messageId = getMessageIdByPropType(propType);
|
|
57
|
-
|
|
58
|
-
context.report({
|
|
59
|
-
node,
|
|
60
|
-
messageId,
|
|
61
|
-
data: {
|
|
62
|
-
memberPath,
|
|
63
|
-
},
|
|
64
|
-
suggest: [
|
|
65
|
-
{
|
|
66
|
-
messageId: "removeDuplicate",
|
|
67
|
-
data: { memberPath },
|
|
68
|
-
fix(fixer) {
|
|
69
|
-
return fixer.remove(node);
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
],
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
usedUnits.add(memberPath);
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
},
|
|
82
|
-
};
|
|
83
|
-
},
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
function createMemberExpressionPath(node, chain = "") {
|
|
87
|
-
const compactStrings = (...args) => args.filter(Boolean).join(".");
|
|
88
|
-
|
|
89
|
-
if (node.type === "MemberExpression") {
|
|
90
|
-
const propertyName = node.property.name;
|
|
91
|
-
|
|
92
|
-
const updatedChain = compactStrings(propertyName, chain);
|
|
93
|
-
|
|
94
|
-
return createMemberExpressionPath(node.object, updatedChain);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
chain = compactStrings(node.name, chain);
|
|
98
|
-
|
|
99
|
-
// remove last dot
|
|
100
|
-
return chain.slice(0, -1);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function getSourceOrClockProperties(node) {
|
|
104
|
-
if (node.type !== "ObjectExpression") return [];
|
|
105
|
-
|
|
106
|
-
const allowedProps = ["clock", "source"];
|
|
107
|
-
|
|
108
|
-
const isClockOrSourceArray = (prop) => {
|
|
109
|
-
return (
|
|
110
|
-
allowedProps.includes(prop.key.name) &&
|
|
111
|
-
prop.value.type === "ArrayExpression"
|
|
112
|
-
);
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
return node.properties.filter(isClockOrSourceArray);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function getMessageIdByPropType(propType) {
|
|
119
|
-
return propType === "clock" ? "duplicatesInClock" : "duplicatesInSource";
|
|
120
|
-
}
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
2
|
-
const { getNestedObjectName } = require("../../utils/get-nested-object-name");
|
|
3
|
-
const {
|
|
4
|
-
traverseNestedObjectNode,
|
|
5
|
-
} = require("../../utils/traverse-nested-object-node");
|
|
6
|
-
const { is } = require("../../utils/is");
|
|
7
|
-
|
|
8
|
-
module.exports = {
|
|
9
|
-
meta: {
|
|
10
|
-
type: "problem",
|
|
11
|
-
docs: {
|
|
12
|
-
description: "Forbids duplicate `.on` calls on store",
|
|
13
|
-
category: "Quality",
|
|
14
|
-
recommended: true,
|
|
15
|
-
url: createLinkToRule("no-duplicate-on"),
|
|
16
|
-
},
|
|
17
|
-
messages: {
|
|
18
|
-
duplicateOn:
|
|
19
|
-
"Method `.on` is called on store `{{ storeName }}` more than once for {{ unitName }}.",
|
|
20
|
-
},
|
|
21
|
-
schema: [],
|
|
22
|
-
},
|
|
23
|
-
create(context) {
|
|
24
|
-
const usedOns = new Map();
|
|
25
|
-
|
|
26
|
-
function isEventUsedInStoreOn(scope, storeName, unitName) {
|
|
27
|
-
const usedOnsOnScope = usedOns.get(scope);
|
|
28
|
-
|
|
29
|
-
if (!usedOnsOnScope) {
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const usedUnits = usedOnsOnScope.get(storeName);
|
|
34
|
-
|
|
35
|
-
if (!usedUnits) {
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return usedUnits.has(unitName);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function markUnitAsUsedInStoreOn(scope, storeName, unitNames) {
|
|
43
|
-
let usedOnsOnScope = usedOns.get(scope);
|
|
44
|
-
|
|
45
|
-
if (!usedOnsOnScope) {
|
|
46
|
-
usedOnsOnScope = new Map();
|
|
47
|
-
usedOns.set(scope, usedOnsOnScope);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
let usedUnits = usedOnsOnScope.get(storeName);
|
|
51
|
-
|
|
52
|
-
if (!usedUnits) {
|
|
53
|
-
usedUnits = new Set();
|
|
54
|
-
usedOnsOnScope.set(storeName, usedUnits);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
usedUnits.add(...unitNames);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
'CallExpression[callee.property.name="on"]'(node) {
|
|
62
|
-
const storeObject = traverseNestedObjectNode(
|
|
63
|
-
getNestedCallee(node) ?? getAssignedVariable(node)
|
|
64
|
-
);
|
|
65
|
-
const storeName = getStoreName(storeObject);
|
|
66
|
-
|
|
67
|
-
if (!is.store({ context, node: storeObject })) {
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const triggerObjects = normalizePossibleArrayNode(node.arguments[0]);
|
|
72
|
-
const unitNames = triggerObjects.map(getNestedObjectName);
|
|
73
|
-
|
|
74
|
-
const scope = context.getScope();
|
|
75
|
-
|
|
76
|
-
for (const unitName of unitNames) {
|
|
77
|
-
const unitAlreadyUsed = isEventUsedInStoreOn(
|
|
78
|
-
scope,
|
|
79
|
-
storeName,
|
|
80
|
-
unitName
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
if (unitAlreadyUsed) {
|
|
84
|
-
context.report({
|
|
85
|
-
node,
|
|
86
|
-
messageId: "duplicateOn",
|
|
87
|
-
data: {
|
|
88
|
-
storeName,
|
|
89
|
-
unitName,
|
|
90
|
-
},
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
markUnitAsUsedInStoreOn(scope, storeName, unitNames);
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
},
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
function normalizePossibleArrayNode(node) {
|
|
104
|
-
if (!node) {
|
|
105
|
-
return [];
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (node.type === "ArrayExpression") {
|
|
109
|
-
return node.elements;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return [node];
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function getNestedCallee(node) {
|
|
116
|
-
const { callee } = node;
|
|
117
|
-
|
|
118
|
-
if (callee.object?.type === "CallExpression") {
|
|
119
|
-
return getNestedCallee(callee.object);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return callee.object;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function getAssignedVariable(node) {
|
|
126
|
-
const { parent } = node;
|
|
127
|
-
|
|
128
|
-
if (parent.type === "VariableDeclarator") {
|
|
129
|
-
return parent;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return getAssignedVariable(parent);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function getStoreName(node) {
|
|
136
|
-
return node?.name ?? node?.id?.name;
|
|
137
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
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: "Replace `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
|
-
};
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
const {
|
|
2
|
-
traverseNestedObjectNode,
|
|
3
|
-
} = require("../../utils/traverse-nested-object-node");
|
|
4
|
-
const { createLinkToRule } = require("../../utils/create-link-to-rule");
|
|
5
|
-
const { is } = require("../../utils/is");
|
|
6
|
-
|
|
7
|
-
module.exports = {
|
|
8
|
-
meta: {
|
|
9
|
-
type: "problem",
|
|
10
|
-
docs: {
|
|
11
|
-
description: "Forbids `.getState` calls on any Effector store",
|
|
12
|
-
category: "Quality",
|
|
13
|
-
recommended: true,
|
|
14
|
-
url: createLinkToRule("no-getState"),
|
|
15
|
-
},
|
|
16
|
-
messages: {
|
|
17
|
-
abusiveCall:
|
|
18
|
-
"Method `.getState` called on store `{{ storeName }}` can lead to race conditions. Replace it with `sample` or `guard`.",
|
|
19
|
-
},
|
|
20
|
-
schema: [],
|
|
21
|
-
},
|
|
22
|
-
create(context) {
|
|
23
|
-
return {
|
|
24
|
-
'CallExpression[callee.property.name="getState"]'(node) {
|
|
25
|
-
const storeNode = traverseNestedObjectNode(node.callee?.object);
|
|
26
|
-
|
|
27
|
-
const isEffectorStore = is.store({
|
|
28
|
-
context,
|
|
29
|
-
node: storeNode,
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
if (!isEffectorStore) {
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
reportGetStateCall({ context, node, storeName: storeNode.name });
|
|
37
|
-
},
|
|
38
|
-
};
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
function reportGetStateCall({ context, node, storeName }) {
|
|
43
|
-
context.report({
|
|
44
|
-
node,
|
|
45
|
-
messageId: "abusiveCall",
|
|
46
|
-
data: {
|
|
47
|
-
storeName,
|
|
48
|
-
},
|
|
49
|
-
});
|
|
50
|
-
}
|