eslint-plugin-effector 0.7.2 → 0.7.5

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 CHANGED
@@ -72,7 +72,7 @@ This preset is recommended for projects that use [React](https://reactjs.org) wi
72
72
 
73
73
  #### plugin:effector/future
74
74
 
75
- This preset contains rules wich enforce _future-effector_ code-style.
75
+ This preset contains rules, which enforce _future-effector_ code-style.
76
76
 
77
77
  - [effector/no-forward](rules/no-forward/no-forward.md)
78
78
  - [effector/no-guard](rules/no-guard/no-guard.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-effector",
3
- "version": "0.7.2",
3
+ "version": "0.7.5",
4
4
  "description": "Enforcing best practices for Effector",
5
5
  "keywords": [
6
6
  "eslint",
@@ -2,6 +2,7 @@ const { createLinkToRule } = require("../../utils/create-link-to-rule");
2
2
  const { isInsideReactComponent } = require("../../utils/react");
3
3
  const { nodeTypeIs } = require("../../utils/node-type-is");
4
4
  const { traverseParentByType } = require("../../utils/traverse-parent-by-type");
5
+ const { nodeIsType } = require("../../utils/node-is-type");
5
6
 
6
7
  module.exports = {
7
8
  meta: {
@@ -23,34 +24,44 @@ module.exports = {
23
24
  const parserServices = context.parserServices;
24
25
 
25
26
  // 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
- };
27
+ if (!parserServices.hasFullTypeInformation) {
28
+ return {};
47
29
  }
48
30
 
49
- return {};
31
+ return {
32
+ Identifier(node) {
33
+ if (!isInsideReactComponent(node)) {
34
+ return;
35
+ }
36
+
37
+ if (nodeIsType({ node })) {
38
+ return;
39
+ }
40
+
41
+ if (
42
+ nodeTypeIs.not.effect({ node, context }) &&
43
+ nodeTypeIs.not.event({ node, context })
44
+ ) {
45
+ return;
46
+ }
47
+
48
+ if (isInsideEffectorHook({ node, context })) {
49
+ return;
50
+ }
51
+
52
+ context.report({
53
+ node,
54
+ messageId: "useEventNeeded",
55
+ data: {
56
+ unitName: node.name,
57
+ },
58
+ });
59
+ },
60
+ };
50
61
  },
51
62
  };
52
63
 
53
- function isInsideUseEventCall({ node, context }) {
64
+ function isInsideEffectorHook({ node, context }) {
54
65
  const calleeParentNode = traverseParentByType(node.parent, "CallExpression");
55
66
 
56
67
  if (!calleeParentNode?.callee) return false;
@@ -58,6 +69,6 @@ function isInsideUseEventCall({ node, context }) {
58
69
  return nodeTypeIs.effectorReactHook({
59
70
  node: calleeParentNode.callee,
60
71
  context,
61
- hook: ["useEvent", "useUnit"],
72
+ hook: ["useEvent", "useUnit", "useStore"],
62
73
  });
63
74
  }
@@ -16,7 +16,7 @@ module.exports = {
16
16
  messages: {
17
17
  noForward:
18
18
  "Instead of `forward` you can use `sample`, it is more extendable.",
19
- replaceWithSample: "Repalce `forward` with `sample`.",
19
+ replaceWithSample: "Replace `forward` with `sample`.",
20
20
  },
21
21
  schema: [],
22
22
  hasSuggestions: true,
@@ -16,7 +16,7 @@ module.exports = {
16
16
  messages: {
17
17
  noGuard:
18
18
  "Instead of `guard` you can use `sample`, it is more extendable.",
19
- replaceWithSample: "Repalce `guard` with `sample`.",
19
+ replaceWithSample: "Replace `guard` with `sample`.",
20
20
  },
21
21
  schema: [],
22
22
  hasSuggestions: true,
@@ -21,7 +21,7 @@ module.exports = {
21
21
  "Instead of `forward` with `{{ eventName }}.map` you can use `sample`",
22
22
  overPrepend:
23
23
  "Instead of `forward` with `{{ eventName }}.prepend` you can use `sample`",
24
- replaceWithSample: "Repalce `forward` with `sample`.",
24
+ replaceWithSample: "Replace `forward` with `sample`.",
25
25
  },
26
26
  schema: [],
27
27
  hasSuggestions: true,
@@ -1,6 +1,10 @@
1
1
  function extractImportedFrom({ importMap, nodeMap, node, packageName }) {
2
2
  if (node.source.value === packageName) {
3
3
  for (const s of node.specifiers) {
4
+ if (s.type === "ImportDefaultSpecifier") {
5
+ continue;
6
+ }
7
+
4
8
  importMap.set(s.imported.name, s.local.name);
5
9
  nodeMap?.set(s.imported.name, s);
6
10
  }
@@ -0,0 +1,5 @@
1
+ function nodeIsType({ node }) {
2
+ return node?.parent?.type === "TSTypeReference";
3
+ }
4
+
5
+ module.exports = { nodeIsType };
package/utils/react.js CHANGED
@@ -90,11 +90,41 @@ function isInsideReactComponent(node) {
90
90
  if (isForwardRefCallback(node) || isMemoCallback(node)) {
91
91
  return true;
92
92
  }
93
+
94
+ if (isClass(node) && !isClassComponent(node)) {
95
+ return false;
96
+ }
97
+
98
+ if (isClassComponent(node)) {
99
+ return true;
100
+ }
101
+
93
102
  node = node.parent;
94
103
  }
95
104
  return false;
96
105
  }
97
106
 
107
+ function isClass(node) {
108
+ return node?.type === "ClassDeclaration";
109
+ }
110
+
111
+ function isClassComponent(node) {
112
+ if (!node?.superClass) {
113
+ return false;
114
+ }
115
+ if (node?.superClass?.type === "MemberExpression") {
116
+ return (
117
+ node?.superClass?.object?.name === "React" &&
118
+ /^(Pure)?Component$/.test(node?.superClass?.property?.name)
119
+ );
120
+ }
121
+ if (node?.superClass?.type === "Identifier") {
122
+ return /^(Pure)?Component$/.test(node?.superClass?.name);
123
+ }
124
+
125
+ return false;
126
+ }
127
+
98
128
  function isInsideReactHook(node) {
99
129
  while (node) {
100
130
  const functionName = getFunctionName(node);
@@ -7,8 +7,8 @@ function readExample(dirname, exampleName) {
7
7
  }
8
8
 
9
9
  function getCorrectExamples(dirname, config = {}) {
10
- const { ext = "js", namesOnly = true } = config;
11
- const pattern = `correct-*.${ext}`;
10
+ const { ext, namesOnly = true } = config;
11
+ const pattern = `correct-*.${resolveExtension(ext)}`;
12
12
  const correct = glob.sync(join(dirname, "examples", pattern));
13
13
 
14
14
  let result = correct;
@@ -24,9 +24,27 @@ function getCorrectExamples(dirname, config = {}) {
24
24
  return result;
25
25
  }
26
26
 
27
+ function resolveExtension(ext) {
28
+ const DEFAULT_EXT = "js";
29
+
30
+ if (Array.isArray(ext)) {
31
+ if (ext.length === 0) {
32
+ return DEFAULT_EXT;
33
+ }
34
+
35
+ if (ext.length === 1) {
36
+ return ext[0];
37
+ }
38
+
39
+ return `{${ext.join(",")}}`;
40
+ }
41
+
42
+ return ext ?? DEFAULT_EXT;
43
+ }
44
+
27
45
  function getIncorrectExamples(dirname, config = {}) {
28
- const { ext = "js", namesOnly = true } = config;
29
- const pattern = `incorrect-*.${ext}`;
46
+ const { ext, namesOnly = true } = config;
47
+ const pattern = `incorrect-*.${resolveExtension(ext)}`;
30
48
  const incorrect = glob.sync(join(dirname, "examples", pattern));
31
49
 
32
50
  let result = incorrect;
@@ -88,7 +88,11 @@ function* replaceBySample(
88
88
  })})`
89
89
  );
90
90
 
91
- yield fixer.replaceText(importNodes.get(methodName), "sample");
91
+ const importNode = importNodes.get(methodName);
92
+
93
+ if (!importNodes.has("sample")) {
94
+ yield fixer.insertTextAfter(importNode, ", sample");
95
+ }
92
96
  }
93
97
 
94
98
  module.exports = { replaceForwardBySample, replaceGuardBySample };