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.
Files changed (53) hide show
  1. package/README.md +24 -37
  2. package/dist/index.cjs +1259 -0
  3. package/dist/index.d.cts +177 -0
  4. package/dist/index.d.mts +178 -0
  5. package/dist/index.mjs +1233 -0
  6. package/package.json +71 -17
  7. package/.nvmrc +0 -1
  8. package/config/future.js +0 -7
  9. package/config/patronum.js +0 -5
  10. package/config/react.js +0 -7
  11. package/config/recommended.js +0 -15
  12. package/config/scope.js +0 -6
  13. package/index.js +0 -31
  14. package/rules/enforce-effect-naming-convention/enforce-effect-naming-convention.js +0 -143
  15. package/rules/enforce-gate-naming-convention/enforce-gate-naming-convention.js +0 -122
  16. package/rules/enforce-store-naming-convention/enforce-store-naming-convention.js +0 -205
  17. package/rules/keep-options-order/config.js +0 -3
  18. package/rules/keep-options-order/keep-options-order.js +0 -107
  19. package/rules/mandatory-scope-binding/mandatory-scope-binding.js +0 -81
  20. package/rules/no-ambiguity-target/no-ambiguity-target.js +0 -74
  21. package/rules/no-duplicate-clock-or-source-array-values/no-duplicate-clock-or-source-array-values.js +0 -120
  22. package/rules/no-duplicate-on/no-duplicate-on.js +0 -137
  23. package/rules/no-forward/no-forward.js +0 -73
  24. package/rules/no-getState/no-getState.js +0 -50
  25. package/rules/no-guard/no-guard.js +0 -78
  26. package/rules/no-patronum-debug/no-patronum-debug.js +0 -133
  27. package/rules/no-unnecessary-combination/no-unnecessary-combination.js +0 -88
  28. package/rules/no-unnecessary-duplication/no-unnecessary-duplication.js +0 -115
  29. package/rules/no-useless-methods/no-useless-methods.js +0 -93
  30. package/rules/no-watch/no-watch.js +0 -61
  31. package/rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping.js +0 -111
  32. package/rules/prefer-useUnit/prefer-useUnit.js +0 -56
  33. package/rules/require-pickup-in-persist/require-pickup-in-persist.js +0 -47
  34. package/rules/strict-effect-handlers/strict-effect-handlers.js +0 -76
  35. package/utils/are-nodes-same-in-text.js +0 -22
  36. package/utils/builders.js +0 -19
  37. package/utils/create-link-to-rule.js +0 -5
  38. package/utils/extract-config.js +0 -26
  39. package/utils/extract-imported-from.js +0 -18
  40. package/utils/get-corrected-store-name.js +0 -45
  41. package/utils/get-nested-object-name.js +0 -18
  42. package/utils/get-store-name-convention.js +0 -6
  43. package/utils/is.js +0 -39
  44. package/utils/method.js +0 -23
  45. package/utils/naming.js +0 -47
  46. package/utils/node-is-type.js +0 -5
  47. package/utils/node-type-is.js +0 -106
  48. package/utils/react.js +0 -214
  49. package/utils/read-example.js +0 -63
  50. package/utils/replace-by-sample.js +0 -98
  51. package/utils/traverse-nested-object-node.js +0 -9
  52. package/utils/traverse-parent-by-type.js +0 -15
  53. 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
- };
@@ -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
- }