eslint-plugin-effector 0.3.1 → 0.5.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 (39) hide show
  1. package/README.md +40 -9
  2. package/config/react.js +5 -0
  3. package/config/recommended.js +3 -0
  4. package/config/scope.js +5 -0
  5. package/index.js +8 -1
  6. package/package.json +13 -15
  7. package/rules/enforce-effect-naming-convention/enforce-effect-naming-convention.js +19 -17
  8. package/rules/enforce-gate-naming-convention/enforce-gate-naming-convention.js +114 -0
  9. package/rules/enforce-gate-naming-convention/enforce-gate-naming-convention.md +11 -0
  10. package/rules/enforce-store-naming-convention/enforce-store-naming-convention.js +46 -40
  11. package/rules/enforce-store-naming-convention/enforce-store-naming-convention.md +12 -4
  12. package/rules/no-ambiguity-target/no-ambiguity-target.js +8 -4
  13. package/rules/no-duplicate-on/no-duplicate-on.js +128 -0
  14. package/rules/no-duplicate-on/no-duplicate-on.md +16 -0
  15. package/rules/no-getState/no-getState.js +11 -36
  16. package/rules/no-unnecessary-combination/no-unnecessary-combination.js +96 -0
  17. package/rules/no-unnecessary-combination/no-unnecessary-combination.md +25 -0
  18. package/rules/no-unnecessary-duplication/no-unnecessary-duplication.js +9 -5
  19. package/rules/no-useless-methods/no-useless-methods.js +18 -4
  20. package/rules/no-watch/no-watch.js +53 -0
  21. package/rules/no-watch/no-watch.md +42 -0
  22. package/rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping.js +8 -4
  23. package/rules/strict-effect-handlers/strict-effect-handlers.js +74 -0
  24. package/rules/strict-effect-handlers/strict-effect-handlers.md +38 -0
  25. package/rules/tsconfig.json +2 -1
  26. package/utils/create-link-to-rule.js +5 -0
  27. package/utils/extract-imported-from.js +9 -0
  28. package/utils/get-corrected-store-name.js +12 -14
  29. package/utils/get-nested-object-name.js +18 -0
  30. package/utils/get-store-name-convention.js +3 -3
  31. package/utils/is.js +30 -0
  32. package/utils/naming.js +47 -0
  33. package/utils/node-type-is.js +55 -0
  34. package/utils/validate-store-name-convention.js +7 -7
  35. package/.github/workflows/ci.yml +0 -43
  36. package/CHANGELOG.md +0 -28
  37. package/jest.config.js +0 -7
  38. package/utils/extract-imported-from-effector.js +0 -8
  39. package/utils/is-store-name-valid.js +0 -22
@@ -0,0 +1,128 @@
1
+ const { createLinkToRule } = require("../../utils/create-link-to-rule");
2
+ const { getNestedObjectName } = require("../../utils/get-nested-object-name");
3
+ const { is } = require("../../utils/is");
4
+
5
+ module.exports = {
6
+ meta: {
7
+ type: "problem",
8
+ docs: {
9
+ description: "Forbids duplicate `.on` calls on store",
10
+ category: "Quality",
11
+ recommended: true,
12
+ url: createLinkToRule("no-duplicate-on"),
13
+ },
14
+ messages: {
15
+ duplicateOn:
16
+ "Method `.on` is called on store `{{ storeName }}` more than once for {{ unitName }}.",
17
+ },
18
+ schema: [],
19
+ },
20
+ create(context) {
21
+ const usedOns = new Map();
22
+
23
+ function isEventUsedInStoreOn(scope, storeName, unitName) {
24
+ const usedOnsOnScope = usedOns.get(scope);
25
+
26
+ if (!usedOnsOnScope) {
27
+ return false;
28
+ }
29
+
30
+ const usedUnits = usedOnsOnScope.get(storeName);
31
+
32
+ if (!usedUnits) {
33
+ return false;
34
+ }
35
+
36
+ return usedUnits.has(unitName);
37
+ }
38
+
39
+ function markUnitAsUsedInStoreOn(scope, storeName, unitNames) {
40
+ let usedOnsOnScope = usedOns.get(scope);
41
+
42
+ if (!usedOnsOnScope) {
43
+ usedOnsOnScope = new Map();
44
+ usedOns.set(scope, usedOnsOnScope);
45
+ }
46
+
47
+ let usedUnits = usedOnsOnScope.get(storeName);
48
+
49
+ if (!usedUnits) {
50
+ usedUnits = new Set();
51
+ usedOnsOnScope.set(storeName, usedUnits);
52
+ }
53
+
54
+ usedUnits.add(...unitNames);
55
+ }
56
+
57
+ return {
58
+ 'CallExpression[callee.property.name="on"]'(node) {
59
+ const storeObject = getNestedCallee(node) ?? getAssignedVariable(node);
60
+ const storeName = getStoreName(storeObject);
61
+
62
+ if (!is.store({ context, node: storeObject })) {
63
+ return;
64
+ }
65
+
66
+ const triggerObjects = normalizePossibleArrayNode(node.arguments[0]);
67
+ const unitNames = triggerObjects.map(getNestedObjectName);
68
+
69
+ const scope = context.getScope();
70
+
71
+ for (const unitName of unitNames) {
72
+ const unitAlreadyUsed = isEventUsedInStoreOn(
73
+ scope,
74
+ storeName,
75
+ unitName
76
+ );
77
+
78
+ if (unitAlreadyUsed) {
79
+ context.report({
80
+ node,
81
+ messageId: "duplicateOn",
82
+ data: {
83
+ storeName,
84
+ unitName,
85
+ },
86
+ });
87
+
88
+ return;
89
+ }
90
+ }
91
+
92
+ markUnitAsUsedInStoreOn(scope, storeName, unitNames);
93
+ },
94
+ };
95
+ },
96
+ };
97
+
98
+ function normalizePossibleArrayNode(node) {
99
+ if (node.type === "ArrayExpression") {
100
+ return node.elements;
101
+ }
102
+
103
+ return [node];
104
+ }
105
+
106
+ function getNestedCallee(node) {
107
+ const { callee } = node;
108
+
109
+ if (callee.object?.type === "CallExpression") {
110
+ return getNestedCallee(callee.object);
111
+ }
112
+
113
+ return callee.object;
114
+ }
115
+
116
+ function getAssignedVariable(node) {
117
+ const { parent } = node;
118
+
119
+ if (parent.type === "VariableDeclarator") {
120
+ return parent;
121
+ }
122
+
123
+ return getAssignedVariable(parent);
124
+ }
125
+
126
+ function getStoreName(node) {
127
+ return node.name ?? node.id.name;
128
+ }
@@ -0,0 +1,16 @@
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,7 +1,8 @@
1
1
  const {
2
2
  traverseNestedObjectNode,
3
3
  } = require("../../utils/traverse-nested-object-node");
4
- const { isStoreNameValid } = require("../../utils/is-store-name-valid");
4
+ const { createLinkToRule } = require("../../utils/create-link-to-rule");
5
+ const { is } = require("../../utils/is");
5
6
 
6
7
  module.exports = {
7
8
  meta: {
@@ -10,6 +11,7 @@ module.exports = {
10
11
  description: "Forbids `.getState` calls on any Effector store",
11
12
  category: "Quality",
12
13
  recommended: true,
14
+ url: createLinkToRule("no-getState"),
13
15
  },
14
16
  messages: {
15
17
  abusiveCall:
@@ -18,47 +20,20 @@ module.exports = {
18
20
  schema: [],
19
21
  },
20
22
  create(context) {
21
- const { parserServices } = context;
22
-
23
23
  return {
24
- CallExpression(node) {
25
- const methodName = node.callee?.property?.name;
26
- if (methodName !== "getState") {
27
- return;
28
- }
24
+ 'CallExpression[callee.property.name="getState"]'(node) {
25
+ const storeNode = traverseNestedObjectNode(node.callee?.object);
29
26
 
30
- const object = traverseNestedObjectNode(node.callee?.object);
31
- const objectName = object?.name;
27
+ const isEffectorStore = is.store({
28
+ context,
29
+ node: storeNode,
30
+ });
32
31
 
33
- if (!objectName) {
32
+ if (!isEffectorStore) {
34
33
  return;
35
34
  }
36
35
 
37
- // TypeScript-way
38
- if (parserServices.hasFullTypeInformation) {
39
- const checker = parserServices.program.getTypeChecker();
40
- const originalNode = parserServices.esTreeNodeToTSNodeMap.get(object);
41
- const type = checker.getTypeAtLocation(originalNode);
42
-
43
- const isEffectorStore =
44
- type?.symbol?.escapedName === "Store" &&
45
- type?.symbol?.parent?.escapedName?.includes("effector");
46
-
47
- if (!isEffectorStore) {
48
- return;
49
- }
50
-
51
- reportGetStateCall({ context, node, storeName: objectName });
52
- }
53
- // JavaScript-way
54
- else {
55
- const isEffectorStore = isStoreNameValid(objectName, context);
56
- if (!isEffectorStore) {
57
- return;
58
- }
59
-
60
- reportGetStateCall({ context, node, storeName: objectName });
61
- }
36
+ reportGetStateCall({ context, node, storeName: storeNode.name });
62
37
  },
63
38
  };
64
39
  },
@@ -0,0 +1,96 @@
1
+ const { extractImportedFrom } = require("../../utils/extract-imported-from");
2
+ const { createLinkToRule } = require("../../utils/create-link-to-rule");
3
+
4
+ module.exports = {
5
+ meta: {
6
+ type: "problem",
7
+ docs: {
8
+ description:
9
+ "Forbids unnecessary combinations in `clock`, `source` and `forward`",
10
+ category: "Quality",
11
+ recommended: true,
12
+ url: createLinkToRule("no-unnecessary-combination"),
13
+ },
14
+ messages: {
15
+ unnecessaryCombination:
16
+ "Method {{ methodName }} is used under the hood, you can omit it.",
17
+ },
18
+ schema: [],
19
+ },
20
+ create(context) {
21
+ const importedFromEffector = new Map();
22
+
23
+ return {
24
+ ImportDeclaration(node) {
25
+ extractImportedFrom({
26
+ importMap: importedFromEffector,
27
+ node,
28
+ packageName: "effector",
29
+ });
30
+ },
31
+ CallExpression(node) {
32
+ const METHODS_WITH_POSSIBLE_UNNECESSARY_COMBINATION = [
33
+ "sample",
34
+ "guard",
35
+ "forward",
36
+ ];
37
+
38
+ const CONFIG_ARG_PROPERTIES = ["source", "clock", "from"];
39
+
40
+ function toLocalMethod(method) {
41
+ return importedFromEffector.get(method);
42
+ }
43
+
44
+ const UNNECESSARY_METHODS = {
45
+ source: ["combine", "merge"].map(toLocalMethod).filter(Boolean),
46
+ clock: ["merge"].map(toLocalMethod).filter(Boolean),
47
+ from: ["merge"].map(toLocalMethod).filter(Boolean),
48
+ };
49
+
50
+ for (const method of METHODS_WITH_POSSIBLE_UNNECESSARY_COMBINATION) {
51
+ const localMethod = importedFromEffector.get(method);
52
+ if (!localMethod) {
53
+ continue;
54
+ }
55
+
56
+ const isEffectorMethod = node?.callee?.name === localMethod;
57
+ if (!isEffectorMethod) {
58
+ continue;
59
+ }
60
+
61
+ const candidates =
62
+ node?.arguments?.[0]?.properties?.filter((n) =>
63
+ CONFIG_ARG_PROPERTIES.includes(n.key.name)
64
+ ) ?? [];
65
+
66
+ if (candidates.length === 0) {
67
+ continue;
68
+ }
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);
81
+
82
+ if (!UnnecessaryMethodIsEffectorMethod) {
83
+ continue;
84
+ }
85
+
86
+ context.report({
87
+ node: candidate?.value,
88
+ messageId: "unnecessaryCombination",
89
+ data: { methodName: candidateName },
90
+ });
91
+ }
92
+ }
93
+ },
94
+ };
95
+ },
96
+ };
@@ -0,0 +1,25 @@
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,7 +1,6 @@
1
- const {
2
- extractImportedFromEffector,
3
- } = require("../../utils/extract-imported-from-effector");
1
+ const { extractImportedFrom } = require("../../utils/extract-imported-from");
4
2
  const { areNodesSameInText } = require("../../utils/are-nodes-same-in-text");
3
+ const { createLinkToRule } = require("../../utils/create-link-to-rule");
5
4
 
6
5
  module.exports = {
7
6
  meta: {
@@ -10,6 +9,7 @@ module.exports = {
10
9
  description: "Forbids unnecessary duplication in `clock` and `source`",
11
10
  category: "Quality",
12
11
  recommended: true,
12
+ url: createLinkToRule("no-unnecessary-duplication"),
13
13
  },
14
14
  messages: {
15
15
  unnecessaryDuplication:
@@ -18,14 +18,18 @@ module.exports = {
18
18
  removeSource: "Remove `source`",
19
19
  },
20
20
  schema: [],
21
+ hasSuggestions: true,
21
22
  },
22
23
  create(context) {
23
24
  const importedFromEffector = new Map();
24
- const sourceCode = context.getSourceCode();
25
25
 
26
26
  return {
27
27
  ImportDeclaration(node) {
28
- extractImportedFromEffector(importedFromEffector, node);
28
+ extractImportedFrom({
29
+ importMap: importedFromEffector,
30
+ node,
31
+ packageName: "effector",
32
+ });
29
33
  },
30
34
  CallExpression(node) {
31
35
  const METHODS_WITH_POSSIBLE_DUPLCATION = ["sample", "guard"];
@@ -1,7 +1,6 @@
1
- const {
2
- extractImportedFromEffector,
3
- } = require("../../utils/extract-imported-from-effector");
1
+ const { extractImportedFrom } = require("../../utils/extract-imported-from");
4
2
  const { traverseParentByType } = require("../../utils/traverse-parent-by-type");
3
+ const { createLinkToRule } = require("../../utils/create-link-to-rule");
5
4
 
6
5
  module.exports = {
7
6
  meta: {
@@ -10,6 +9,7 @@ module.exports = {
10
9
  description: "Forbids useless calls of `sample` and `guard`",
11
10
  category: "Quality",
12
11
  recommended: true,
12
+ url: createLinkToRule("no-useless-methods"),
13
13
  },
14
14
  messages: {
15
15
  uselessMethod:
@@ -22,7 +22,11 @@ module.exports = {
22
22
 
23
23
  return {
24
24
  ImportDeclaration(node) {
25
- extractImportedFromEffector(importedFromEffector, node);
25
+ extractImportedFrom({
26
+ importMap: importedFromEffector,
27
+ node,
28
+ packageName: "effector",
29
+ });
26
30
  },
27
31
  CallExpression(node) {
28
32
  const POSSIBLE_USELESS_METHODS = ["sample", "guard"];
@@ -68,6 +72,16 @@ module.exports = {
68
72
  continue;
69
73
  }
70
74
 
75
+ const resultIsWatched = node?.parent?.property?.name === "watch";
76
+ if (resultIsWatched) {
77
+ continue;
78
+ }
79
+
80
+ const resultIsArgument = node?.parent?.type === "CallExpression";
81
+ if (resultIsArgument) {
82
+ continue;
83
+ }
84
+
71
85
  context.report({
72
86
  node,
73
87
  messageId: "uselessMethod",
@@ -0,0 +1,53 @@
1
+ const {
2
+ traverseNestedObjectNode,
3
+ } = require("../../utils/traverse-nested-object-node");
4
+ const { createLinkToRule } = require("../../utils/create-link-to-rule");
5
+ const { nodeTypeIs } = require("../../utils/node-type-is");
6
+
7
+ module.exports = {
8
+ meta: {
9
+ type: "suggestion",
10
+ docs: {
11
+ description: "Avoid `.watch` calls on any Effector unit or operator",
12
+ category: "Quality",
13
+ recommended: true,
14
+ url: createLinkToRule("no-watch"),
15
+ },
16
+ messages: {
17
+ abusiveCall:
18
+ "Method `.watch` leads to imperative code. Try to replace it with operators (`sample`, `guard`, etc) or use the `target` parameter of the operators.",
19
+ },
20
+ schema: [],
21
+ },
22
+ create(context) {
23
+ const { parserServices } = context;
24
+ if (!parserServices.hasFullTypeInformation) {
25
+ // JavaScript-way https://github.com/effector/eslint-plugin/issues/48#issuecomment-931107829
26
+ return {};
27
+ }
28
+
29
+ return {
30
+ 'CallExpression[callee.property.name="watch"]'(node) {
31
+ const object = traverseNestedObjectNode(node.callee?.object);
32
+
33
+ const isEffectorUnit = nodeTypeIs.unit({
34
+ node: object,
35
+ context,
36
+ });
37
+
38
+ if (!isEffectorUnit) {
39
+ return;
40
+ }
41
+
42
+ reportWatchCall({ context, node });
43
+ },
44
+ };
45
+ },
46
+ };
47
+
48
+ function reportWatchCall({ context, node }) {
49
+ context.report({
50
+ node,
51
+ messageId: "abusiveCall",
52
+ });
53
+ }
@@ -0,0 +1,42 @@
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,9 +1,8 @@
1
- const {
2
- extractImportedFromEffector,
3
- } = require("../../utils/extract-imported-from-effector");
1
+ const { extractImportedFrom } = require("../../utils/extract-imported-from");
4
2
  const {
5
3
  traverseNestedObjectNode,
6
4
  } = require("../../utils/traverse-nested-object-node");
5
+ const { createLinkToRule } = require("../../utils/create-link-to-rule");
7
6
 
8
7
  module.exports = {
9
8
  meta: {
@@ -12,6 +11,7 @@ module.exports = {
12
11
  description: "Prefer `sample` over `forward` with `.map`/`.prepend`",
13
12
  category: "Quality",
14
13
  recommended: true,
14
+ url: createLinkToRule("prefer-sample-over-forward-with-mapping"),
15
15
  },
16
16
  messages: {
17
17
  overMap:
@@ -26,7 +26,11 @@ module.exports = {
26
26
 
27
27
  return {
28
28
  ImportDeclaration(node) {
29
- extractImportedFromEffector(importedFromEffector, node);
29
+ extractImportedFrom({
30
+ importMap: importedFromEffector,
31
+ node,
32
+ packageName: "effector",
33
+ });
30
34
  },
31
35
  CallExpression(node) {
32
36
  const localMethod = importedFromEffector.get("forward");
@@ -0,0 +1,74 @@
1
+ const { createLinkToRule } = require("../../utils/create-link-to-rule");
2
+ const { is } = require("../../utils/is");
3
+
4
+ module.exports = {
5
+ meta: {
6
+ type: "problem",
7
+ docs: {
8
+ description:
9
+ "Forbids mix of async functions and effects calls in effect handlers.",
10
+ category: "Quality",
11
+ recommended: true,
12
+ url: createLinkToRule("strict-effect-handlers"),
13
+ },
14
+ messages: {
15
+ mixedCallsInHandler:
16
+ "Handler of effect `{{ effectName }}` can lead to scope loosing in Fork API.",
17
+ mixedCallsInFunction:
18
+ "Function `{{ functionName }}` can lead to scope loosing in Fork API.",
19
+ },
20
+ schema: [],
21
+ },
22
+ create(context) {
23
+ function onEffectHandler(node) {
24
+ if (!node.body?.body) {
25
+ return;
26
+ }
27
+
28
+ const calledNodes = node.body.body
29
+ .filter((bodyNode) => bodyNode.expression?.type === "AwaitExpression")
30
+ .map((awaitNode) => ({
31
+ node: awaitNode.expression.argument.callee,
32
+ context,
33
+ }));
34
+
35
+ const hasEffects = calledNodes.some(is.effect);
36
+ const hasRegularAsyncFunctions = calledNodes.some(is.not.effect);
37
+
38
+ const hasError = hasEffects && hasRegularAsyncFunctions;
39
+
40
+ if (!hasError) {
41
+ return;
42
+ }
43
+
44
+ const isEffectHandler = is.effect({
45
+ node: node.parent?.parent,
46
+ context,
47
+ });
48
+
49
+ if (isEffectHandler) {
50
+ const effectName = node.parent?.parent?.id?.name ?? "Unknown";
51
+
52
+ context.report({
53
+ node: node.parent,
54
+ messageId: "mixedCallsInHandler",
55
+ data: { effectName },
56
+ });
57
+ } else {
58
+ const functionName = node.id?.name ?? "Unknown";
59
+
60
+ context.report({
61
+ node,
62
+ messageId: "mixedCallsInFunction",
63
+ data: { functionName },
64
+ });
65
+ }
66
+ }
67
+
68
+ return {
69
+ ArrowFunctionExpression: onEffectHandler,
70
+ FunctionExpression: onEffectHandler,
71
+ FunctionDeclaration: onEffectHandler,
72
+ };
73
+ },
74
+ };
@@ -0,0 +1,38 @@
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,9 +1,10 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "isolatedModules": true,
4
+ "esModuleInterop": true,
4
5
  "module": "commonjs",
5
6
  "lib": ["es2017", "es2019"],
6
7
  "baseUrl": "./"
7
8
  },
8
- "include": ["./**/examples/*.ts"]
9
+ "include": ["./**/examples/**/*.ts"]
9
10
  }
@@ -0,0 +1,5 @@
1
+ function createLinkToRule(name) {
2
+ return `https://github.com/effector/eslint-plugin/blob/master/rules/${name}/${name}.md`;
3
+ }
4
+
5
+ module.exports = { createLinkToRule };