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.
Files changed (40) hide show
  1. package/.nvmrc +1 -0
  2. package/README.md +22 -13
  3. package/config/future.js +7 -0
  4. package/config/react.js +1 -0
  5. package/config/recommended.js +1 -0
  6. package/index.js +5 -0
  7. package/package.json +2 -2
  8. package/rules/keep-options-order/config.js +3 -0
  9. package/rules/keep-options-order/keep-options-order.js +107 -0
  10. package/rules/mandatory-useEvent/mandatory-useEvent.js +63 -0
  11. package/rules/no-ambiguity-target/no-ambiguity-target.js +32 -35
  12. package/rules/no-duplicate-on/no-duplicate-on.js +10 -1
  13. package/rules/no-forward/no-forward.js +73 -0
  14. package/rules/no-guard/no-guard.js +78 -0
  15. package/rules/no-unnecessary-combination/no-unnecessary-combination.js +33 -41
  16. package/rules/no-unnecessary-duplication/no-unnecessary-duplication.js +35 -40
  17. package/rules/no-useless-methods/no-useless-methods.js +50 -53
  18. package/rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping.js +27 -15
  19. package/rules/strict-effect-handlers/strict-effect-handlers.js +4 -2
  20. package/utils/builders.js +19 -0
  21. package/utils/extract-config.js +13 -0
  22. package/utils/extract-imported-from.js +2 -1
  23. package/utils/method.js +23 -0
  24. package/utils/node-type-is.js +16 -0
  25. package/utils/react.js +184 -0
  26. package/utils/read-example.js +38 -1
  27. package/utils/replace-by-sample.js +94 -0
  28. package/rules/enforce-effect-naming-convention/enforce-effect-naming-convention.md +0 -11
  29. package/rules/enforce-gate-naming-convention/enforce-gate-naming-convention.md +0 -11
  30. package/rules/enforce-store-naming-convention/enforce-store-naming-convention.md +0 -52
  31. package/rules/no-ambiguity-target/no-ambiguity-target.md +0 -12
  32. package/rules/no-duplicate-on/no-duplicate-on.md +0 -16
  33. package/rules/no-getState/no-getState.md +0 -20
  34. package/rules/no-unnecessary-combination/no-unnecessary-combination.md +0 -25
  35. package/rules/no-unnecessary-duplication/no-unnecessary-duplication.md +0 -32
  36. package/rules/no-useless-methods/no-useless-methods.md +0 -14
  37. package/rules/no-watch/no-watch.md +0 -42
  38. package/rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping.md +0 -27
  39. package/rules/strict-effect-handlers/strict-effect-handlers.md +0 -38
  40. 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 presets
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-ambiguity-target](rules/no-ambiguity-target/no-ambiguity-target.md)
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
 
@@ -0,0 +1,7 @@
1
+ module.exports = {
2
+ rules: {
3
+ "effector/prefer-sample-over-forward-with-mapping": "off",
4
+ "effector/no-forward": "warn",
5
+ "effector/no-guard": "warn",
6
+ },
7
+ };
package/config/react.js CHANGED
@@ -1,5 +1,6 @@
1
1
  module.exports = {
2
2
  rules: {
3
3
  "effector/enforce-gate-naming-convention": "error",
4
+ "effector/mandatory-useEvent": "error",
4
5
  },
5
6
  };
@@ -10,5 +10,6 @@ module.exports = {
10
10
  "effector/no-watch": "warn",
11
11
  "effector/no-unnecessary-combination": "warn",
12
12
  "effector/no-duplicate-on": "error",
13
+ "effector/keep-options-order": "warn",
13
14
  },
14
15
  };
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.5.1",
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,3 @@
1
+ const correctOrder = ["clock", "source", "filter", "fn", "target"];
2
+
3
+ module.exports = { correctOrder };
@@ -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
- const POSSIBLE_USELESS_METHODS = ["sample", "guard"];
33
- for (const method of POSSIBLE_USELESS_METHODS) {
34
- const localMethod = importedFromEffector.get(method);
35
- if (!localMethod) {
36
- continue;
37
- }
33
+ if (
34
+ method.isNot(["sample", "guard"], {
35
+ node,
36
+ importMap: importedFromEffector,
37
+ })
38
+ ) {
39
+ return;
40
+ }
38
41
 
39
- const isEffectorMethod = node?.callee?.name === localMethod;
40
- if (!isEffectorMethod) {
41
- continue;
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
- const configHasTarget = node?.arguments?.[0]?.properties?.some(
45
- (prop) => prop?.key.name === "target"
46
- );
47
- if (!configHasTarget) {
48
- continue;
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
- const resultAssignedInVariable = traverseParentByType(
52
- node,
53
- "VariableDeclarator",
54
- { stopOnTypes: ["BlockStatement"] }
55
- );
56
- const resultPartOfChain = traverseParentByType(
59
+ if (resultAssignedInVariable || resultPartOfChain) {
60
+ context.report({
57
61
  node,
58
- "ObjectExpression"
59
- );
60
-
61
- if (resultAssignedInVariable || resultPartOfChain) {
62
- context.report({
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
- continue;
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 = getNestedCallee(node) ?? getAssignedVariable(node);
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(method) {
41
- return importedFromEffector.get(method);
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
- for (const method of METHODS_WITH_POSSIBLE_UNNECESSARY_COMBINATION) {
51
- const localMethod = importedFromEffector.get(method);
52
- if (!localMethod) {
53
- continue;
54
- }
45
+ if (
46
+ method.isNot(["sample", "guard", "forward"], {
47
+ node,
48
+ importMap: importedFromEffector,
49
+ })
50
+ ) {
51
+ return;
52
+ }
55
53
 
56
- const isEffectorMethod = node?.callee?.name === localMethod;
57
- if (!isEffectorMethod) {
58
- continue;
59
- }
54
+ const candidates =
55
+ node?.arguments?.[0]?.properties?.filter((n) =>
56
+ CONFIG_ARG_PROPERTIES.includes(n.key.name)
57
+ ) ?? [];
60
58
 
61
- const candidates =
62
- node?.arguments?.[0]?.properties?.filter((n) =>
63
- CONFIG_ARG_PROPERTIES.includes(n.key.name)
64
- ) ?? [];
59
+ if (candidates.length === 0) {
60
+ return;
61
+ }
65
62
 
66
- if (candidates.length === 0) {
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
- for (const candidate of candidates) {
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
- if (!UnnecessaryMethodIsEffectorMethod) {
83
- continue;
84
- }
72
+ const UnnecessaryMethodIsEffectorMethod =
73
+ localUnnecessaryMethods.some((m) => m === candidateName);
85
74
 
86
- context.report({
87
- node: candidate?.value,
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
  };