eslint-plugin-effector 0.1.2 → 0.3.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 (42) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +5 -2
  4. package/config/recommended.js +3 -0
  5. package/index.js +4 -1
  6. package/package.json +9 -3
  7. package/rules/enforce-effect-naming-convention/enforce-effect-naming-convention.js +1 -1
  8. package/rules/enforce-effect-naming-convention/enforce-effect-naming-convention.md +9 -1
  9. package/rules/enforce-store-naming-convention/enforce-store-naming-convention.js +44 -21
  10. package/rules/enforce-store-naming-convention/enforce-store-naming-convention.md +43 -2
  11. package/rules/no-ambiguity-target/no-ambiguity-target.js +71 -0
  12. package/rules/no-ambiguity-target/no-ambiguity-target.md +12 -0
  13. package/rules/no-getState/no-getState.js +6 -13
  14. package/rules/no-getState/no-getState.md +18 -1
  15. package/rules/no-unnecessary-duplication/no-unnecessary-duplication.js +7 -2
  16. package/rules/no-unnecessary-duplication/no-unnecessary-duplication.md +5 -5
  17. package/rules/no-useless-methods/no-useless-methods.js +82 -0
  18. package/rules/no-useless-methods/no-useless-methods.md +14 -0
  19. package/rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping.js +95 -0
  20. package/rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping.md +27 -0
  21. package/utils/are-nodes-same-in-text.js +22 -0
  22. package/utils/get-corrected-store-name.js +23 -0
  23. package/utils/get-store-name-convention.js +6 -0
  24. package/utils/is-store-name-valid.js +22 -0
  25. package/utils/traverse-nested-object-node.js +9 -0
  26. package/utils/traverse-parent-by-type.js +13 -0
  27. package/utils/validate-store-name-convention.js +13 -0
  28. package/coverage/lcov-report/base.css +0 -224
  29. package/coverage/lcov-report/block-navigation.js +0 -79
  30. package/coverage/lcov-report/enforce-effect-naming-convention/enforce-effect-naming-convention.js.html +0 -482
  31. package/coverage/lcov-report/enforce-effect-naming-convention/index.html +0 -111
  32. package/coverage/lcov-report/enforce-store-naming-convention/enforce-store-naming-convention.js.html +0 -560
  33. package/coverage/lcov-report/enforce-store-naming-convention/index.html +0 -111
  34. package/coverage/lcov-report/favicon.png +0 -0
  35. package/coverage/lcov-report/index.html +0 -141
  36. package/coverage/lcov-report/no-getState/index.html +0 -111
  37. package/coverage/lcov-report/no-getState/no-getState.js.html +0 -326
  38. package/coverage/lcov-report/prettify.css +0 -1
  39. package/coverage/lcov-report/prettify.js +0 -2
  40. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  41. package/coverage/lcov-report/sorter.js +0 -170
  42. package/coverage/lcov.info +0 -256
package/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ ## v0.3.0
4
+
5
+ - Add new rule: `no-useless-methods` ([PR #41](https://github.com/effector/eslint-plugin/pull/41))
6
+ - Add new rule: `no-ambiguity-target` ([PR #42](https://github.com/effector/eslint-plugin/pull/42))
7
+ - Add possibility to configure store's naming convention — suffix of prefix ([PR #37](https://github.com/effector/eslint-plugin/pull/37) by @ilyaryabchinski)
8
+
9
+ ## v0.2.0
10
+
11
+ - Add tests against Effector 22
12
+ - Specify supported Node.JS versions
13
+ - Add new rule: `prefer-sample-over-forward-with-mapping` ([PR #34](https://github.com/igorkamyshev/eslint-plugin-effector/pull/34))
14
+
15
+ ## v0.1.4
16
+
17
+ - Exclude test-coverage report from npm-package
18
+ - Fixed SyntaxError in `no-unnecessary-duplication` suggestions ([PR #28](https://github.com/igorkamyshev/eslint-plugin-effector/pull/28))
19
+
20
+ ## v0.1.3
21
+
22
+ - Fixed false-positive in `no-unnecessary-duplication` with composite `clock`/`source` ([PR #22](https://github.com/igorkamyshev/eslint-plugin-effector/pull/22))
23
+ - Fixed TypeError in `enforce-store-naming-convention` ([PR #25](https://github.com/igorkamyshev/eslint-plugin-effector/pull/25))
24
+ - Fixed false-positive in `enforce-effect-naming-convention` with `combine` ([PR #26](https://github.com/igorkamyshev/eslint-plugin-effector/pull/26))
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Igor Kamyshev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  Enforcing best practices for [Effector](http://effector.dev/)
4
4
 
5
- > This plugin can use TypeScript for more precise results, but JavaScript is supported too.
5
+ > This plugin uses TypeScript for more precise results, but JavaScript is supported too.
6
6
 
7
7
  ## Installation
8
8
 
9
- You'll first need to install [ESLint](http://eslint.org):
9
+ First, install [ESLint](http://eslint.org):
10
10
 
11
11
  ```
12
12
  $ yarn add -D eslint
@@ -45,3 +45,6 @@ To configure individual rules:
45
45
  - [effector/enforce-effect-naming-convention](/rules/enforce-effect-naming-convention/enforce-effect-naming-convention.md)
46
46
  - [effector/no-getState](/rules/no-getState/no-getState.md)
47
47
  - [effector/no-unnecessary-duplication](/rules/no-unnecessary-duplication/no-unnecessary-duplication.md)
48
+ - [effector/no-useless-methods](/rules/no-useless-methods/no-useless-methods.md)
49
+ - [effector/no-ambiguity-target](/rules/no-ambiguity-target/no-ambiguity-target.md)
50
+ - [effector/prefer-sample-over-forward-with-mapping](/rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping.md)
@@ -3,6 +3,9 @@ module.exports = {
3
3
  "effector/enforce-store-naming-convention": "error",
4
4
  "effector/enforce-effect-naming-convention": "error",
5
5
  "effector/no-getState": "error",
6
+ "effector/no-useless-methods": "error",
6
7
  "effector/no-unnecessary-duplication": "warn",
8
+ "effector/prefer-sample-over-forward-with-mapping": "warn",
9
+ "effector/no-ambiguity-target": "warn",
7
10
  },
8
11
  };
package/index.js CHANGED
@@ -4,8 +4,11 @@ module.exports = {
4
4
  "enforce-effect-naming-convention": require("./rules/enforce-effect-naming-convention/enforce-effect-naming-convention"),
5
5
  "no-getState": require("./rules/no-getState/no-getState"),
6
6
  "no-unnecessary-duplication": require("./rules/no-unnecessary-duplication/no-unnecessary-duplication"),
7
+ "prefer-sample-over-forward-with-mapping": require("./rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping"),
8
+ "no-useless-methods": require("./rules/no-useless-methods/no-useless-methods"),
9
+ "no-ambiguity-target": require("./rules/no-ambiguity-target/no-ambiguity-target"),
7
10
  },
8
11
  configs: {
9
12
  recommended: require("./config/recommended"),
10
- },
13
+ }
11
14
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-effector",
3
- "version": "0.1.2",
3
+ "version": "0.3.0",
4
4
  "description": "Enforcing best practices for Effector",
5
5
  "main": "index.js",
6
6
  "author": "Igor Kamyshev <igor@kamyshev.me>",
@@ -11,10 +11,13 @@
11
11
  "publishConfig": {
12
12
  "access": "public"
13
13
  },
14
+ "engines": {
15
+ "node": "^14 || ^16"
16
+ },
14
17
  "devDependencies": {
15
18
  "@typescript-eslint/experimental-utils": "^4.29.3",
16
19
  "@typescript-eslint/parser": "^4.29.3",
17
- "effector": "^21.8.12",
20
+ "effector": "^22.0.0",
18
21
  "eslint": "^7.32.0",
19
22
  "glob": "^7.1.7",
20
23
  "jest": "^27.1.0",
@@ -23,5 +26,8 @@
23
26
  "peerDependencies": {
24
27
  "effector": "*",
25
28
  "eslint": "*"
29
+ },
30
+ "dependencies": {
31
+ "prettier": "^2.3.2"
26
32
  }
27
- }
33
+ }
@@ -55,7 +55,7 @@ module.exports = {
55
55
  },
56
56
  CallExpression(node) {
57
57
  // Effect creation with method
58
- const EFFECT_CREATION_METHODS = ["createEffect", "attach", "combine"];
58
+ const EFFECT_CREATION_METHODS = ["createEffect", "attach"];
59
59
  for (const method of EFFECT_CREATION_METHODS) {
60
60
  const localMethod = importedFromEffector.get(method);
61
61
  if (!localMethod) {
@@ -1,3 +1,11 @@
1
- # Enforces naming convention for Effector-effects (`effector/enforce-effect-naming-convention`)
1
+ # effector/enforce-effect-naming-convention
2
2
 
3
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,25 +1,32 @@
1
1
  const {
2
2
  extractImportedFromEffector,
3
3
  } = require("../../utils/extract-imported-from-effector");
4
+ const { isStoreNameValid } = require("../../utils/is-store-name-valid");
5
+ const { validateStoreNameConvention } = require("../../utils/validate-store-name-convention");
6
+ const { getStoreNameConvention } = require("../../utils/get-store-name-convention");
7
+ const { getCorrectedStoreName } = require("../../utils/get-corrected-store-name");
4
8
 
5
9
  module.exports = {
6
10
  meta: {
7
11
  type: "problem",
8
12
  docs: {
9
13
  description:
10
- "Enforce $ as a prefix for any store created by Effector methods",
14
+ "Enforce $ as a prefix or postfix for any store created by Effector methods",
11
15
  category: "Naming",
12
16
  recommended: true,
13
17
  },
14
18
  messages: {
15
19
  invalidName:
16
- 'Store "{{ storeName }}" should be named with prefix, rename it to "${{ storeName }}"',
17
- renameStore: 'Rename "{{ storeName }}" to "${{ storeName }}"',
20
+ 'Store "{{ storeName }}" should be named with {{ storeNameConvention }}, rename it to "{{ correctedStoreName }}"',
21
+ renameStore: 'Rename "{{ storeName }}" to "{{ correctedStoreName }}"',
18
22
  },
19
23
  schema: [],
20
24
  },
21
25
  create(context) {
22
- const parserServices = context.parserServices;
26
+ const { parserServices } = context;
27
+
28
+ validateStoreNameConvention(context);
29
+
23
30
  // TypeScript-way
24
31
  if (parserServices.hasFullTypeInformation) {
25
32
  return {
@@ -29,8 +36,8 @@ module.exports = {
29
36
  const type = checker.getTypeAtLocation(originalNode.initializer);
30
37
 
31
38
  const isEffectorStore =
32
- type?.symbol?.escapedName === "Store" &&
33
- type?.symbol?.parent?.escapedName?.includes("effector");
39
+ type?.symbol?.escapedName === "Store" &&
40
+ type?.symbol?.parent?.escapedName?.includes("effector");
34
41
 
35
42
  if (!isEffectorStore) {
36
43
  return;
@@ -38,11 +45,15 @@ module.exports = {
38
45
 
39
46
  const storeName = node.id.name;
40
47
 
41
- if (storeName?.startsWith("$")) {
48
+ if (isStoreNameValid(storeName, context)) {
42
49
  return;
43
50
  }
44
51
 
45
- reportStoreNameConventionViolation({ context, node, storeName });
52
+ reportStoreNameConventionViolation({
53
+ context,
54
+ node,
55
+ storeName
56
+ });
46
57
  },
47
58
  };
48
59
  }
@@ -68,46 +79,50 @@ module.exports = {
68
79
  }
69
80
 
70
81
  const resultSavedInVariable =
71
- node.parent.type === "VariableDeclarator";
82
+ node.parent.type === "VariableDeclarator";
72
83
  if (!resultSavedInVariable) {
73
84
  continue;
74
85
  }
75
86
 
76
87
  const storeName = node.parent.id.name;
77
- if (storeName.startsWith("$")) {
88
+
89
+ if (isStoreNameValid(storeName, context)) {
78
90
  continue;
79
91
  }
80
92
 
81
93
  reportStoreNameConventionViolation({
82
94
  context,
83
95
  node: node.parent,
84
- storeName,
96
+ storeName
85
97
  });
86
98
  return;
87
99
  }
88
100
 
89
101
  // Store creation with .map
90
102
  if (node.callee?.property?.name === "map") {
91
- const objectIsEffectorStore = node.callee.object.name.startsWith("$");
92
- if (!objectIsEffectorStore) {
103
+ const storeNameCreatedFromMap = node.callee?.object?.name;
104
+
105
+ if (!isStoreNameValid(storeNameCreatedFromMap, context)) {
93
106
  return;
94
107
  }
95
108
 
96
109
  const resultSavedInVariable =
97
- node.parent.type === "VariableDeclarator";
110
+ node.parent.type === "VariableDeclarator";
98
111
  if (!resultSavedInVariable) {
99
112
  return;
100
113
  }
101
114
 
102
115
  const storeName = node.parent.id.name;
103
- if (storeName.startsWith("$")) {
116
+
117
+ if (isStoreNameValid(storeName, context)) {
104
118
  return;
105
119
  }
106
120
 
121
+
107
122
  reportStoreNameConventionViolation({
108
123
  context,
109
124
  node: node.parent,
110
- storeName,
125
+ storeName
111
126
  });
112
127
  return;
113
128
  }
@@ -115,23 +130,24 @@ module.exports = {
115
130
  // Store creation in domain
116
131
  const STORE_IN_DOMAIN_CREATION_METHODS = ["createStore", "store"];
117
132
  if (
118
- STORE_IN_DOMAIN_CREATION_METHODS.includes(node.callee?.property?.name)
133
+ STORE_IN_DOMAIN_CREATION_METHODS.includes(node.callee?.property?.name)
119
134
  ) {
120
135
  const resultSavedInVariable =
121
- node.parent.type === "VariableDeclarator";
136
+ node.parent.type === "VariableDeclarator";
122
137
  if (!resultSavedInVariable) {
123
138
  return;
124
139
  }
125
140
 
126
141
  const storeName = node.parent.id.name;
127
- if (storeName.startsWith("$")) {
142
+
143
+ if (isStoreNameValid(storeName, context)) {
128
144
  return;
129
145
  }
130
146
 
131
147
  reportStoreNameConventionViolation({
132
148
  context,
133
149
  node: node.parent,
134
- storeName,
150
+ storeName
135
151
  });
136
152
  return;
137
153
  }
@@ -141,20 +157,27 @@ module.exports = {
141
157
  };
142
158
 
143
159
  function reportStoreNameConventionViolation({ context, node, storeName }) {
160
+
161
+ const storeNameConvention = getStoreNameConvention(context);
162
+ const correctedStoreName = getCorrectedStoreName(storeName, context);
163
+
144
164
  context.report({
145
165
  node,
146
166
  messageId: "invalidName",
147
167
  data: {
148
168
  storeName,
169
+ correctedStoreName,
170
+ storeNameConvention
149
171
  },
150
172
  suggest: [
151
173
  {
152
174
  messageId: "renameStore",
153
175
  data: { storeName },
154
176
  fix(fixer) {
155
- return fixer.insertTextBeforeRange(node.range, "$");
177
+ return fixer.replaceTextRange(node.id.range, correctedStoreName);
156
178
  },
157
179
  },
158
180
  ],
159
181
  });
160
182
  }
183
+
@@ -1,3 +1,44 @@
1
- # Enforces naming convention for Effector-stores a codebase (`effector/enforce-store-naming-convention`)
1
+ # effector/enforce-store-naming-convention
2
2
 
3
- Enforcing naming conventions helps keep the codebase consistent, and reduces overhead when thinking about how to name a variable with store. Your stores should be distingueshed by a prefix $. For example, `$name` is a store,`name` is not.
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
+ When configured as:
7
+ ```js
8
+ module.exports = {
9
+ rules: {
10
+ "effector/enforce-store-naming-convention": "error",
11
+ },
12
+ };
13
+ ```
14
+ Prefix convention will be enforced:
15
+ ```ts
16
+ // 👍 nice name
17
+ const $name = createStore(null);
18
+
19
+ // 👎 bad name
20
+ const name = createStrore(null);
21
+ ```
22
+ ## Postfix convention
23
+
24
+ When configured as:
25
+ ```js
26
+ module.exports = {
27
+ rules: {
28
+ "effector/enforce-store-naming-convention": "error",
29
+ },
30
+ settings: {
31
+ effector: {
32
+ storeNameConvention: "postfix"
33
+ }
34
+ }
35
+ };
36
+ ```
37
+ Postfix convention will be enforced:
38
+ ```ts
39
+ // 👍 nice name
40
+ const name$ = createStore(null);
41
+
42
+ // 👎 bad name
43
+ const name = createStrore(null);
44
+ ```
@@ -0,0 +1,71 @@
1
+ const {
2
+ extractImportedFromEffector,
3
+ } = require("../../utils/extract-imported-from-effector");
4
+ const { traverseParentByType } = require("../../utils/traverse-parent-by-type");
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
+ },
14
+ messages: {
15
+ ambiguityTarget:
16
+ "Method `{{ methodName }}` returns `target` and assigns the result to a variable. Consider removing one of them.",
17
+ },
18
+ schema: [],
19
+ },
20
+ create(context) {
21
+ const importedFromEffector = new Map();
22
+
23
+ return {
24
+ ImportDeclaration(node) {
25
+ extractImportedFromEffector(importedFromEffector, node);
26
+ },
27
+ CallExpression(node) {
28
+ const POSSIBLE_USELESS_METHODS = ["sample", "guard"];
29
+ for (const method of POSSIBLE_USELESS_METHODS) {
30
+ const localMethod = importedFromEffector.get(method);
31
+ if (!localMethod) {
32
+ continue;
33
+ }
34
+
35
+ const isEffectorMethod = node?.callee?.name === localMethod;
36
+ if (!isEffectorMethod) {
37
+ continue;
38
+ }
39
+
40
+ const configHasTarget = node?.arguments?.[0]?.properties?.some(
41
+ (prop) => prop?.key.name === "target"
42
+ );
43
+ if (!configHasTarget) {
44
+ continue;
45
+ }
46
+
47
+ const resultAssignedInVariable = traverseParentByType(
48
+ node,
49
+ "VariableDeclarator"
50
+ );
51
+ const resultPartOfChain = traverseParentByType(
52
+ node,
53
+ "ObjectExpression"
54
+ );
55
+
56
+ if (resultAssignedInVariable || resultPartOfChain) {
57
+ context.report({
58
+ node,
59
+ messageId: "ambiguityTarget",
60
+ data: {
61
+ methodName: node?.callee?.name,
62
+ },
63
+ });
64
+
65
+ continue;
66
+ }
67
+ }
68
+ },
69
+ };
70
+ },
71
+ };
@@ -0,0 +1,12 @@
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,6 +1,7 @@
1
1
  const {
2
- extractImportedFromEffector,
3
- } = require("../../utils/extract-imported-from-effector");
2
+ traverseNestedObjectNode,
3
+ } = require("../../utils/traverse-nested-object-node");
4
+ const { isStoreNameValid } = require("../../utils/is-store-name-valid");
4
5
 
5
6
  module.exports = {
6
7
  meta: {
@@ -17,7 +18,7 @@ module.exports = {
17
18
  schema: [],
18
19
  },
19
20
  create(context) {
20
- const parserServices = context.parserServices;
21
+ const { parserServices } = context;
21
22
 
22
23
  return {
23
24
  CallExpression(node) {
@@ -26,7 +27,7 @@ module.exports = {
26
27
  return;
27
28
  }
28
29
 
29
- const object = traverseNestedObject(node.callee?.object);
30
+ const object = traverseNestedObjectNode(node.callee?.object);
30
31
  const objectName = object?.name;
31
32
 
32
33
  if (!objectName) {
@@ -51,7 +52,7 @@ module.exports = {
51
52
  }
52
53
  // JavaScript-way
53
54
  else {
54
- const isEffectorStore = objectName.startsWith("$");
55
+ const isEffectorStore = isStoreNameValid(objectName, context);
55
56
  if (!isEffectorStore) {
56
57
  return;
57
58
  }
@@ -72,11 +73,3 @@ function reportGetStateCall({ context, node, storeName }) {
72
73
  },
73
74
  });
74
75
  }
75
-
76
- function traverseNestedObject(node) {
77
- if (node.type === "MemberExpression") {
78
- return traverseNestedObject(node.property);
79
- }
80
-
81
- return node;
82
- }
@@ -1,3 +1,20 @@
1
- # Forbids `.getState` calls on any Effector store (`effector/no-getState`)
1
+ # effector/no-getState
2
2
 
3
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,6 +1,7 @@
1
1
  const {
2
2
  extractImportedFromEffector,
3
3
  } = require("../../utils/extract-imported-from-effector");
4
+ const { areNodesSameInText } = require("../../utils/are-nodes-same-in-text");
4
5
 
5
6
  module.exports = {
6
7
  meta: {
@@ -20,6 +21,8 @@ module.exports = {
20
21
  },
21
22
  create(context) {
22
23
  const importedFromEffector = new Map();
24
+ const sourceCode = context.getSourceCode();
25
+
23
26
  return {
24
27
  ImportDeclaration(node) {
25
28
  extractImportedFromEffector(importedFromEffector, node);
@@ -49,8 +52,10 @@ module.exports = {
49
52
  return;
50
53
  }
51
54
 
52
- const sameSourceAndClock =
53
- params?.source?.value?.name === params?.clock?.value?.name;
55
+ const sameSourceAndClock = areNodesSameInText({
56
+ context,
57
+ nodes: [params.source?.value, params.clock?.value],
58
+ });
54
59
  if (!sameSourceAndClock) {
55
60
  return;
56
61
  }
@@ -1,4 +1,4 @@
1
- # Forbids same `clock` and `source` inside method config (`effector/no-unnecessary-duplication`)
1
+ # effector/no-unnecessary-duplication
2
2
 
3
3
  Same `clock`/`source` in `sample` and `guard` don't make sense, any of these fields can be omitted in this case.
4
4
 
@@ -6,7 +6,7 @@ Same `clock`/`source` in `sample` and `guard` don't make sense, any of these fie
6
6
  const $data = createStore(null);
7
7
 
8
8
  // 👎 can be simplified
9
- const target = sample({
9
+ const target1 = sample({
10
10
  source: $data,
11
11
  clock: $data,
12
12
  fn(data) {
@@ -15,15 +15,15 @@ const target = sample({
15
15
  });
16
16
 
17
17
  // 👍 better
18
- const target = sample({
18
+ const target2 = sample({
19
19
  source: $data,
20
20
  fn(data) {
21
21
  return data.length;
22
22
  },
23
23
  });
24
24
 
25
- // 👍 also possible
26
- const target = sample({
25
+ // 👍 also nice solution
26
+ const target3 = sample({
27
27
  clock: $data,
28
28
  fn(data) {
29
29
  return data.length;