eslint-plugin-effector 0.5.2 → 0.7.1

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 (39) 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/utils/builders.js +19 -0
  20. package/utils/extract-config.js +13 -0
  21. package/utils/extract-imported-from.js +2 -1
  22. package/utils/method.js +23 -0
  23. package/utils/node-type-is.js +16 -0
  24. package/utils/react.js +184 -0
  25. package/utils/read-example.js +38 -1
  26. package/utils/replace-by-sample.js +94 -0
  27. package/rules/enforce-effect-naming-convention/enforce-effect-naming-convention.md +0 -11
  28. package/rules/enforce-gate-naming-convention/enforce-gate-naming-convention.md +0 -11
  29. package/rules/enforce-store-naming-convention/enforce-store-naming-convention.md +0 -52
  30. package/rules/no-ambiguity-target/no-ambiguity-target.md +0 -12
  31. package/rules/no-duplicate-on/no-duplicate-on.md +0 -16
  32. package/rules/no-getState/no-getState.md +0 -20
  33. package/rules/no-unnecessary-combination/no-unnecessary-combination.md +0 -25
  34. package/rules/no-unnecessary-duplication/no-unnecessary-duplication.md +0 -32
  35. package/rules/no-useless-methods/no-useless-methods.md +0 -14
  36. package/rules/no-watch/no-watch.md +0 -42
  37. package/rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping.md +0 -27
  38. package/rules/strict-effect-handlers/strict-effect-handlers.md +0 -38
  39. package/rules/tsconfig.json +0 -10
@@ -0,0 +1,94 @@
1
+ const { buildObjectInText } = require("./builders");
2
+
3
+ function* replaceGuardBySample(
4
+ guardConfig,
5
+ { fixer, node, context, importNodes }
6
+ ) {
7
+ let mapperFunctionNode = null;
8
+
9
+ let clockNode = guardConfig.clock?.value;
10
+ let targetNode = guardConfig.target?.value;
11
+ let sourceNode = guardConfig.source?.value;
12
+ let filterNode = guardConfig.filter?.value;
13
+
14
+ if (
15
+ targetNode?.type === "CallExpression" &&
16
+ targetNode?.callee?.property?.name === "prepend"
17
+ ) {
18
+ mapperFunctionNode = targetNode?.arguments?.[0];
19
+ targetNode = targetNode.callee.object;
20
+ targetMapperUsed = true;
21
+ }
22
+
23
+ yield* replaceBySample(
24
+ { clockNode, sourceNode, filterNode, mapperFunctionNode, targetNode },
25
+ { node, fixer, context, importNodes, methodName: "guard" }
26
+ );
27
+ }
28
+
29
+ function* replaceForwardBySample(
30
+ forwardConfig,
31
+ { fixer, node, context, importNodes }
32
+ ) {
33
+ let mapperFunctionNode = null;
34
+
35
+ let clockMapperUsed = false;
36
+ let targetMapperUsed = false;
37
+
38
+ let clockNode = forwardConfig.from.value;
39
+ let targetNode = forwardConfig.to.value;
40
+
41
+ if (
42
+ clockNode?.type === "CallExpression" &&
43
+ clockNode?.callee?.property?.name === "map"
44
+ ) {
45
+ mapperFunctionNode = clockNode?.arguments?.[0];
46
+ clockNode = clockNode.callee.object;
47
+ clockMapperUsed = true;
48
+ }
49
+
50
+ if (
51
+ targetNode?.type === "CallExpression" &&
52
+ targetNode?.callee?.property?.name === "prepend"
53
+ ) {
54
+ mapperFunctionNode = targetNode?.arguments?.[0];
55
+ targetNode = targetNode.callee.object;
56
+ targetMapperUsed = true;
57
+ }
58
+
59
+ // We cannot apply two mappers in one sample
60
+ // Let's revert mappers and use .map + .prepend
61
+ if (clockMapperUsed && targetMapperUsed) {
62
+ mapperFunctionNode = null;
63
+ clockNode = forwardConfig.from.value;
64
+ targetNode = forwardConfig.to.value;
65
+ }
66
+
67
+ yield* replaceBySample(
68
+ { clockNode, mapperFunctionNode, targetNode },
69
+ { node, fixer, context, importNodes, methodName: "forward" }
70
+ );
71
+ }
72
+
73
+ function* replaceBySample(
74
+ { clockNode, sourceNode, filterNode, mapperFunctionNode, targetNode },
75
+ { node, fixer, context, importNodes, methodName }
76
+ ) {
77
+ yield fixer.replaceText(
78
+ node,
79
+ `sample(${buildObjectInText.fromMapOfNodes({
80
+ properties: {
81
+ clock: clockNode,
82
+ source: sourceNode,
83
+ filter: filterNode,
84
+ fn: mapperFunctionNode,
85
+ target: targetNode,
86
+ },
87
+ context,
88
+ })})`
89
+ );
90
+
91
+ yield fixer.replaceText(importNodes.get(methodName), "sample");
92
+ }
93
+
94
+ module.exports = { replaceForwardBySample, replaceGuardBySample };
@@ -1,11 +0,0 @@
1
- # effector/enforce-effect-naming-convention
2
-
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,11 +0,0 @@
1
- # effector/enforce-gate-naming-convention
2
-
3
- Enforcing naming conventions helps keep the codebase consistent, and reduces overhead when thinking about how to name a variable with gate. Every gate is a react-component, so it should be named as regular react-compoent.
4
-
5
- ```ts
6
- // 👍 nice name
7
- const MyFavoritePageGate = createGate();
8
-
9
- // 👎 bad name
10
- const otherFavoritePageGate = createGate();
11
- ```
@@ -1,52 +0,0 @@
1
- # effector/enforce-store-naming-convention
2
-
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
-
7
- When configured as:
8
-
9
- ```js
10
- module.exports = {
11
- rules: {
12
- "effector/enforce-store-naming-convention": "error",
13
- },
14
- };
15
- ```
16
-
17
- Prefix convention will be enforced:
18
-
19
- ```ts
20
- // 👍 nice name
21
- const $name = createStore(null);
22
-
23
- // 👎 bad name
24
- const name = createStore(null);
25
- ```
26
-
27
- ## Postfix convention
28
-
29
- When configured as:
30
-
31
- ```js
32
- module.exports = {
33
- rules: {
34
- "effector/enforce-store-naming-convention": "error",
35
- },
36
- settings: {
37
- effector: {
38
- storeNameConvention: "postfix",
39
- },
40
- },
41
- };
42
- ```
43
-
44
- Postfix convention will be enforced:
45
-
46
- ```ts
47
- // 👍 nice name
48
- const name$ = createStore(null);
49
-
50
- // 👎 bad name
51
- const name = createStrore(null);
52
- ```
@@ -1,12 +0,0 @@
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,16 +0,0 @@
1
- # effector/no-duplicate-on
2
-
3
- Disallows duplcates `on`-handlers on particular store.
4
-
5
- ```ts
6
- const increment = createEvent();
7
-
8
- // 👍 all explicitly
9
- const $goodCounter = createStore(0).on(increment, (counter) => counter + 1);
10
-
11
- // 👎 so, which handler should we choose?
12
- // it's better to remove one of them
13
- const $badCounter = createStore(0)
14
- .on(increment, (counter) => counter + 1)
15
- .on(increment, (counter) => counter + 2);
16
- ```
@@ -1,20 +0,0 @@
1
- # effector/no-getState
2
-
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,25 +0,0 @@
1
- # effector/no-unnecessary-combination
2
-
3
- Call of `combine`/`merge` in `clock`/`source` is unnecessary. It can be omitted from source code.
4
-
5
- ```ts
6
- // 👎 can be simplified
7
- const badEventOne = guard({
8
- clock: combine($store1, $store2),
9
- filter: $filter,
10
- });
11
- const badEventOne = guard({
12
- clock: combine($store1, $store2, (store1, store2) => ({
13
- x: store1,
14
- y: store2,
15
- })),
16
- filter: $filter,
17
- });
18
-
19
- // 👍 better
20
- const goodEventOne = guard({ clock: [$store1, $store2], filter: $filter });
21
- const goodEventTwo = guard({
22
- clock: { x: $store1, x: $store2 },
23
- filter: $filter,
24
- });
25
- ```
@@ -1,32 +0,0 @@
1
- # effector/no-unnecessary-duplication
2
-
3
- Same `clock`/`source` in `sample` and `guard` don't make sense, any of these fields can be omitted in this case.
4
-
5
- ```ts
6
- const $data = createStore(null);
7
-
8
- // 👎 can be simplified
9
- const target1 = sample({
10
- source: $data,
11
- clock: $data,
12
- fn(data) {
13
- return data.length;
14
- },
15
- });
16
-
17
- // 👍 better
18
- const target2 = sample({
19
- source: $data,
20
- fn(data) {
21
- return data.length;
22
- },
23
- });
24
-
25
- // 👍 also nice solution
26
- const target3 = sample({
27
- clock: $data,
28
- fn(data) {
29
- return data.length;
30
- },
31
- });
32
- ```
@@ -1,14 +0,0 @@
1
- # effector/no-useless-methods
2
-
3
- Call of `gaurd`/`sample` without `target` or variable assignment is useless. It can be omitted from source code.
4
-
5
- ```ts
6
- // 👎 can be omitted
7
- guard({ clock: trigger, filter: Boolean });
8
-
9
- // 👍 makes sense
10
- const target1 = guard({ clock: trigger, filter: Boolean });
11
-
12
- // 👍 make sense too
13
- guard({ clock: trigger, filter: Boolean, target: target2 });
14
- ```
@@ -1,42 +0,0 @@
1
- # effector/no-watch
2
-
3
- Method `.watch` leads to imperative code. Try replacing it with operators (`forward`, `sample`, etc) or use the `target` parameter of the operators.
4
-
5
- > Caution! This rule only works on projects using TypeScript.
6
-
7
- ```ts
8
- const myFx = createEffect();
9
- const myEvent = createEvent();
10
- const $awesome = createStore();
11
-
12
- // 👍 good solutions
13
- forward({
14
- from: myFx.finally,
15
- to: myEvent,
16
- });
17
-
18
- guard({
19
- clock: myEvent,
20
- filter: Boolean,
21
- target: myFx,
22
- });
23
-
24
- sample({
25
- from: $awesome.updates,
26
- fn: identity,
27
- to: myEvent,
28
- });
29
-
30
- // 👎 bad solutions
31
- myFx.finally.watch(myEvent);
32
-
33
- myEvent.watch((payload) => {
34
- if (Boolean(payload)) {
35
- myFx(payload);
36
- }
37
- });
38
-
39
- $awesome.updates.watch((data) => {
40
- myEvent(identity(data));
41
- });
42
- ```
@@ -1,27 +0,0 @@
1
- # effector/prefer-sample-over-forward-with-mapping
2
-
3
- Prefer `sample` over `forward` with `.map`/`.prepend`.
4
-
5
- ```js
6
- const eventOne = createEvent();
7
- const eventTwo = createEvent();
8
-
9
- // 👎 looks weird
10
- forward({
11
- from: eventOne.map((items) => items.length),
12
- to: eventTwo,
13
- });
14
-
15
- // 👎 weird too
16
- forward({
17
- from: eventOne,
18
- to: eventTwo.prepend((items) => items.length),
19
- });
20
-
21
- // 👍 better
22
- sample({
23
- source: eventOne,
24
- fn: (items) => items.length,
25
- target: eventTwo,
26
- });
27
- ```
@@ -1,38 +0,0 @@
1
- # effector/strict-effect-handlers
2
-
3
- [Related documentation](https://effector.dev/docs/api/effector/scope#imperative-effects-calls-with-scope)
4
-
5
- When effect calls another effects then it should call only effects, not common async functions and effect calls should have await:
6
-
7
- ```ts
8
- // 👍 effect without inner effects:
9
- const delayFx = createEffect(async () => {
10
- await new Promise((rs) => setTimeout(rs, 80));
11
- });
12
- ```
13
-
14
- ```ts
15
- const authUserFx = createEffect();
16
- const sendMessageFx = createEffect();
17
-
18
- // 👍 effect with inner effects
19
- const sendWithAuthFx = createEffect(async () => {
20
- await authUserFx();
21
- await delayFx();
22
- await sendMessageFx();
23
- });
24
- ```
25
-
26
- ```ts
27
- // 👎 effect with inner effects and common async functions
28
-
29
- const sendWithAuthFx = createEffect(async () => {
30
- await authUserFx();
31
- //WRONG! wrap that in effect
32
- await new Promise((rs) => setTimeout(rs, 80));
33
- //context lost
34
- await sendMessageFx();
35
- });
36
- ```
37
-
38
- So, any effect might either call another effects or perform some async computations but not both.
@@ -1,10 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "isolatedModules": true,
4
- "esModuleInterop": true,
5
- "module": "commonjs",
6
- "lib": ["es2017", "es2019"],
7
- "baseUrl": "./"
8
- },
9
- "include": ["./**/examples/**/*.ts"]
10
- }