eslint-plugin-effector 0.4.2 → 0.6.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 (44) hide show
  1. package/README.md +25 -6
  2. package/config/react.js +5 -0
  3. package/config/recommended.js +2 -0
  4. package/config/scope.js +5 -0
  5. package/index.js +7 -0
  6. package/package.json +4 -1
  7. package/rules/enforce-effect-naming-convention/enforce-effect-naming-convention.js +16 -17
  8. package/rules/enforce-gate-naming-convention/enforce-gate-naming-convention.js +114 -0
  9. package/rules/enforce-store-naming-convention/enforce-store-naming-convention.js +25 -25
  10. package/rules/keep-options-order/config.js +3 -0
  11. package/rules/keep-options-order/keep-options-order.js +107 -0
  12. package/rules/no-ambiguity-target/no-ambiguity-target.js +38 -39
  13. package/rules/no-duplicate-on/no-duplicate-on.js +137 -0
  14. package/rules/no-forward/no-forward.js +77 -0
  15. package/rules/no-getState/no-getState.js +9 -36
  16. package/rules/no-unnecessary-combination/no-unnecessary-combination.js +39 -45
  17. package/rules/no-unnecessary-duplication/no-unnecessary-duplication.js +41 -45
  18. package/rules/no-useless-methods/no-useless-methods.js +56 -57
  19. package/rules/no-watch/no-watch.js +6 -12
  20. package/rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping.js +31 -13
  21. package/rules/strict-effect-handlers/strict-effect-handlers.js +76 -0
  22. package/utils/builders.js +19 -0
  23. package/utils/extract-imported-from.js +10 -0
  24. package/utils/get-corrected-store-name.js +12 -14
  25. package/utils/get-nested-object-name.js +18 -0
  26. package/utils/get-store-name-convention.js +3 -3
  27. package/utils/is.js +30 -0
  28. package/utils/method.js +23 -0
  29. package/utils/naming.js +47 -0
  30. package/utils/node-type-is.js +59 -0
  31. package/utils/replace-by-sample.js +66 -0
  32. package/utils/validate-store-name-convention.js +7 -7
  33. package/rules/enforce-effect-naming-convention/enforce-effect-naming-convention.md +0 -11
  34. package/rules/enforce-store-naming-convention/enforce-store-naming-convention.md +0 -44
  35. package/rules/no-ambiguity-target/no-ambiguity-target.md +0 -12
  36. package/rules/no-getState/no-getState.md +0 -20
  37. package/rules/no-unnecessary-combination/no-unnecessary-combination.md +0 -14
  38. package/rules/no-unnecessary-duplication/no-unnecessary-duplication.md +0 -32
  39. package/rules/no-useless-methods/no-useless-methods.md +0 -14
  40. package/rules/no-watch/no-watch.md +0 -42
  41. package/rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping.md +0 -27
  42. package/rules/tsconfig.json +0 -9
  43. package/utils/extract-imported-from-effector.js +0 -8
  44. package/utils/is-store-name-valid.js +0 -22
@@ -1,10 +1,10 @@
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");
7
5
  const { createLinkToRule } = require("../../utils/create-link-to-rule");
6
+ const { method } = require("../../utils/method");
7
+ const { replaceForwardBySample } = require("../../utils/replace-by-sample");
8
8
 
9
9
  module.exports = {
10
10
  meta: {
@@ -20,24 +20,31 @@ module.exports = {
20
20
  "Instead of `forward` with `{{ eventName }}.map` you can use `sample`",
21
21
  overPrepend:
22
22
  "Instead of `forward` with `{{ eventName }}.prepend` you can use `sample`",
23
+ replaceWithSample: "Repalce `forward` with `sample`.",
23
24
  },
24
25
  schema: [],
26
+ hasSuggestions: true,
25
27
  },
26
28
  create(context) {
29
+ const importNodes = new Map();
27
30
  const importedFromEffector = new Map();
28
31
 
29
32
  return {
30
33
  ImportDeclaration(node) {
31
- extractImportedFromEffector(importedFromEffector, node);
34
+ extractImportedFrom({
35
+ importMap: importedFromEffector,
36
+ nodeMap: importNodes,
37
+ node,
38
+ packageName: "effector",
39
+ });
32
40
  },
33
41
  CallExpression(node) {
34
- const localMethod = importedFromEffector.get("forward");
35
- if (!localMethod) {
36
- return;
37
- }
38
-
39
- const isEffectorMethod = node?.callee?.name === localMethod;
40
- if (!isEffectorMethod) {
42
+ if (
43
+ method.isNot("forward", {
44
+ node,
45
+ importMap: importedFromEffector,
46
+ })
47
+ ) {
41
48
  return;
42
49
  }
43
50
 
@@ -70,14 +77,25 @@ module.exports = {
70
77
  return;
71
78
  }
72
79
 
73
- // console.log(eventName);
74
-
75
80
  context.report({
76
81
  node,
77
82
  messageId,
78
83
  data: {
79
84
  eventName,
80
85
  },
86
+ suggest: [
87
+ {
88
+ messageId: "replaceWithSample",
89
+ *fix(fixer) {
90
+ yield* replaceForwardBySample(forwardConfig, {
91
+ fixer,
92
+ node,
93
+ context,
94
+ importNodes,
95
+ });
96
+ },
97
+ },
98
+ ],
81
99
  });
82
100
  }
83
101
 
@@ -0,0 +1,76 @@
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
+ const functionBody = node.body?.body;
25
+
26
+ if (!Array.isArray(functionBody)) {
27
+ return;
28
+ }
29
+
30
+ const calledNodes = functionBody
31
+ .filter((bodyNode) => bodyNode.expression?.type === "AwaitExpression")
32
+ .map((awaitNode) => ({
33
+ node: awaitNode.expression.argument.callee,
34
+ context,
35
+ }));
36
+
37
+ const hasEffects = calledNodes.some(is.effect);
38
+ const hasRegularAsyncFunctions = calledNodes.some(is.not.effect);
39
+
40
+ const hasError = hasEffects && hasRegularAsyncFunctions;
41
+
42
+ if (!hasError) {
43
+ return;
44
+ }
45
+
46
+ const isEffectHandler = is.effect({
47
+ node: node.parent?.parent,
48
+ context,
49
+ });
50
+
51
+ if (isEffectHandler) {
52
+ const effectName = node.parent?.parent?.id?.name ?? "Unknown";
53
+
54
+ context.report({
55
+ node: node.parent,
56
+ messageId: "mixedCallsInHandler",
57
+ data: { effectName },
58
+ });
59
+ } else {
60
+ const functionName = node.id?.name ?? "Unknown";
61
+
62
+ context.report({
63
+ node,
64
+ messageId: "mixedCallsInFunction",
65
+ data: { functionName },
66
+ });
67
+ }
68
+ }
69
+
70
+ return {
71
+ ArrowFunctionExpression: onEffectHandler,
72
+ FunctionExpression: onEffectHandler,
73
+ FunctionDeclaration: onEffectHandler,
74
+ };
75
+ },
76
+ };
@@ -0,0 +1,19 @@
1
+ const buildObjectInText = {
2
+ fromArrayOfNodes({ properties, context }) {
3
+ const content = properties
4
+ .map((property) => context.getSourceCode().getText(property))
5
+ .join(", ");
6
+
7
+ return `{ ${content} }`;
8
+ },
9
+ fromMapOfNodes({ properties, context }) {
10
+ const content = Object.entries(properties)
11
+ .filter(([_, node]) => Boolean(node))
12
+ .map(([key, node]) => `${key}: ${context.getSourceCode().getText(node)}`)
13
+ .join(", ");
14
+
15
+ return `{ ${content} }`;
16
+ },
17
+ };
18
+
19
+ module.exports = { buildObjectInText };
@@ -0,0 +1,10 @@
1
+ function extractImportedFrom({ importMap, nodeMap, node, packageName }) {
2
+ if (node.source.value === packageName) {
3
+ for (const s of node.specifiers) {
4
+ importMap.set(s.imported.name, s.local.name);
5
+ nodeMap?.set(s.imported.name, s);
6
+ }
7
+ }
8
+ }
9
+
10
+ module.exports = { extractImportedFrom };
@@ -1,23 +1,21 @@
1
1
  const { getStoreNameConvention } = require("./get-store-name-convention");
2
2
 
3
3
  function getCorrectedStoreName(storeName, context) {
4
- const storeNameConvention = getStoreNameConvention(context);
4
+ const storeNameConvention = getStoreNameConvention(context);
5
5
 
6
- // handle edge case
7
- if (storeName.startsWith("$") && storeName.endsWith("$")) {
8
-
9
- if (storeNameConvention === "prefix") {
10
- return `$${storeName.slice(0, -1)}`;
11
- } else {
12
- return `${storeName.slice(1)}$`;
13
- }
6
+ // handle edge case
7
+ if (storeName.startsWith("$") && storeName.endsWith("$")) {
8
+ if (storeNameConvention === "prefix") {
9
+ return `$${storeName.slice(0, -1)}`;
10
+ } else {
11
+ return `${storeName.slice(1)}$`;
14
12
  }
13
+ }
15
14
 
16
- const correctedStoreName = storeNameConvention === "prefix"
17
- ? `$${storeName}`
18
- : `${storeName}$`;
15
+ const correctedStoreName =
16
+ storeNameConvention === "prefix" ? `$${storeName}` : `${storeName}$`;
19
17
 
20
- return correctedStoreName;
18
+ return correctedStoreName;
21
19
  }
22
20
 
23
- module.exports = { getCorrectedStoreName };
21
+ module.exports = { getCorrectedStoreName };
@@ -0,0 +1,18 @@
1
+ function getNestedObjectName(node) {
2
+ let root = node;
3
+ let name = "";
4
+
5
+ while (root.type === "MemberExpression") {
6
+ name = `${root.property.name}.${name}`;
7
+ root = root.object;
8
+ }
9
+
10
+ if (root.type === "Identifier") {
11
+ name = `${root.name}.${name}`;
12
+ }
13
+
14
+ // Remove last dot
15
+ return name.slice(0, -1);
16
+ }
17
+
18
+ module.exports = { getNestedObjectName };
@@ -1,6 +1,6 @@
1
1
  function getStoreNameConvention(context) {
2
- // prefix convention is default
3
- return context.settings.effector?.storeNameConvention || "prefix";
2
+ // prefix convention is default
3
+ return context.settings.effector?.storeNameConvention || "prefix";
4
4
  }
5
5
 
6
- module.exports = { getStoreNameConvention };
6
+ module.exports = { getStoreNameConvention };
package/utils/is.js ADDED
@@ -0,0 +1,30 @@
1
+ const { nodeTypeIs } = require("./node-type-is");
2
+ const { namingOf } = require("./naming");
3
+
4
+ function isSomething({ isValidNaming, isTypeCorrect }) {
5
+ return ({ node, context }) => {
6
+ if (context.parserServices.hasFullTypeInformation) {
7
+ return isTypeCorrect({ node, context });
8
+ }
9
+
10
+ return isValidNaming({ name: node?.name ?? node?.id?.name, context });
11
+ };
12
+ }
13
+
14
+ const isStore = isSomething({
15
+ isTypeCorrect: nodeTypeIs.store,
16
+ isValidNaming: namingOf.store.isValid,
17
+ });
18
+
19
+ const isEffect = isSomething({
20
+ isTypeCorrect: nodeTypeIs.effect,
21
+ isValidNaming: namingOf.effect.isValid,
22
+ });
23
+
24
+ const is = {
25
+ store: (opts) => isStore(opts),
26
+ effect: (opts) => isEffect(opts),
27
+ not: { store: (opts) => !isStore(opts), effect: (opts) => !isEffect(opts) },
28
+ };
29
+
30
+ module.exports = { is };
@@ -0,0 +1,23 @@
1
+ function isSomeMethod(methodName, { node, importMap }) {
2
+ const normalizedMethodNames = Array.isArray(methodName)
3
+ ? methodName
4
+ : [methodName];
5
+
6
+ return normalizedMethodNames.some((method) => {
7
+ const localMethod = importMap.get(method);
8
+ if (!localMethod) {
9
+ return false;
10
+ }
11
+
12
+ const isEffectorMethod = node?.callee?.name === localMethod;
13
+
14
+ return isEffectorMethod;
15
+ });
16
+ }
17
+
18
+ const method = {
19
+ is: (...args) => isSomeMethod(...args),
20
+ isNot: (...args) => !isSomeMethod(...args),
21
+ };
22
+
23
+ module.exports = { method };
@@ -0,0 +1,47 @@
1
+ const { getStoreNameConvention } = require("./get-store-name-convention");
2
+
3
+ function isEffectNameValid({ name }) {
4
+ return Boolean(name?.endsWith("Fx"));
5
+ }
6
+
7
+ function isGateNameValid({ name }) {
8
+ const [firstChar] = name.split("");
9
+
10
+ return Boolean(firstChar?.toUpperCase() === firstChar);
11
+ }
12
+
13
+ function isStoreNameValid({ name, context }) {
14
+ const storeNameConvention = getStoreNameConvention(context);
15
+
16
+ // validate edge case
17
+ if (name?.startsWith("$") && name?.endsWith("$")) {
18
+ return false;
19
+ }
20
+
21
+ if (storeNameConvention === "prefix" && name?.startsWith("$")) {
22
+ return true;
23
+ }
24
+
25
+ if (storeNameConvention === "postfix" && name?.endsWith("$")) {
26
+ return true;
27
+ }
28
+
29
+ return false;
30
+ }
31
+
32
+ const namingOf = {
33
+ effect: {
34
+ isValid: (opts) => isEffectNameValid(opts),
35
+ isInvalid: (opts) => !isEffectNameValid(opts),
36
+ },
37
+ store: {
38
+ isValid: (opts) => isStoreNameValid(opts),
39
+ isInvalid: (opts) => !isStoreNameValid(opts),
40
+ },
41
+ gate: {
42
+ isValid: (opts) => isGateNameValid(opts),
43
+ isInvalid: (opts) => !isGateNameValid(opts),
44
+ },
45
+ };
46
+
47
+ module.exports = { namingOf };
@@ -0,0 +1,59 @@
1
+ function hasType({ node, possibleTypes, context, from }) {
2
+ try {
3
+ const checker = context.parserServices.program.getTypeChecker();
4
+ const originalNode = context.parserServices.esTreeNodeToTSNodeMap.get(node);
5
+ const type = checker.getTypeAtLocation(
6
+ originalNode?.initializer ?? originalNode
7
+ );
8
+
9
+ const symbol = type?.symbol ?? type?.aliasSymbol;
10
+
11
+ return (
12
+ possibleTypes.includes(symbol?.escapedName) &&
13
+ Boolean(symbol?.parent?.escapedName?.includes(from))
14
+ );
15
+ } catch (e) {
16
+ return false;
17
+ }
18
+ }
19
+
20
+ const nodeTypeIs = {
21
+ effect: (opts) =>
22
+ hasType({ ...opts, possibleTypes: ["Effect"], from: "effector" }),
23
+ store: (opts) =>
24
+ hasType({ ...opts, possibleTypes: ["Store"], from: "effector" }),
25
+ event: (opts) =>
26
+ hasType({ ...opts, possibleTypes: ["Event"], from: "effector" }),
27
+ unit: (opts) =>
28
+ hasType({
29
+ ...opts,
30
+ possibleTypes: ["Effect", "Store", "Event"],
31
+ from: "effector",
32
+ }),
33
+ gate: (opts) =>
34
+ hasType({ ...opts, possibleTypes: ["Gate"], from: "effector-react" }),
35
+ not: {
36
+ effect: (opts) =>
37
+ !hasType({
38
+ ...opts,
39
+ possibleTypes: ["Effect"],
40
+ from: "effector",
41
+ }),
42
+ store: (opts) =>
43
+ !hasType({ ...opts, possibleTypes: ["Store"], from: "effector" }),
44
+ event: (opts) =>
45
+ !hasType({ ...opts, possibleTypes: ["Event"], from: "effector" }),
46
+ unit: (opts) =>
47
+ !hasType({
48
+ ...opts,
49
+ possibleTypes: ["Effect", "Store", "Event"],
50
+ from: "effector",
51
+ }),
52
+ gate: (opts) =>
53
+ !hasType({ ...opts, possibleTypes: ["Gate"], from: "effector-react" }),
54
+ },
55
+ };
56
+
57
+ module.exports = {
58
+ nodeTypeIs,
59
+ };
@@ -0,0 +1,66 @@
1
+ const { buildObjectInText } = require("./builders");
2
+
3
+ function* replaceForwardBySample(
4
+ forwardConfig,
5
+ { fixer, node, context, importNodes }
6
+ ) {
7
+ let mapperFunctionNode = null;
8
+
9
+ let clockMapperUsed = false;
10
+ let targetMapperUsed = false;
11
+
12
+ let clockNode = forwardConfig.from.value;
13
+ let targetNode = forwardConfig.to.value;
14
+
15
+ if (
16
+ clockNode.type === "CallExpression" &&
17
+ clockNode?.callee?.property?.name === "map"
18
+ ) {
19
+ mapperFunctionNode = clockNode?.arguments?.[0];
20
+ clockNode = clockNode.callee.object;
21
+ clockMapperUsed = true;
22
+ }
23
+
24
+ if (
25
+ targetNode.type === "CallExpression" &&
26
+ targetNode?.callee?.property?.name === "prepend"
27
+ ) {
28
+ mapperFunctionNode = targetNode?.arguments?.[0];
29
+ targetNode = targetNode.callee.object;
30
+ targetMapperUsed = true;
31
+ }
32
+
33
+ // We cannot apply two mappers in one sample
34
+ // Let's revert mappers and use .map + .prepend
35
+ if (clockMapperUsed && targetMapperUsed) {
36
+ mapperFunctionNode = null;
37
+ clockNode = forwardConfig.from.value;
38
+ targetNode = forwardConfig.to.value;
39
+ }
40
+
41
+ yield* replaceBySample(
42
+ { clockNode, targetNode, mapperFunctionNode },
43
+ { node, fixer, context, importNodes, methodName: "forward" }
44
+ );
45
+ }
46
+
47
+ function* replaceBySample(
48
+ { clockNode, targetNode, mapperFunctionNode },
49
+ { node, fixer, context, importNodes, methodName }
50
+ ) {
51
+ yield fixer.replaceText(
52
+ node,
53
+ `sample(${buildObjectInText.fromMapOfNodes({
54
+ properties: {
55
+ clock: clockNode,
56
+ fn: mapperFunctionNode,
57
+ target: targetNode,
58
+ },
59
+ context,
60
+ })})`
61
+ );
62
+
63
+ yield fixer.replaceText(importNodes.get(methodName), "sample");
64
+ }
65
+
66
+ module.exports = { replaceForwardBySample };
@@ -1,13 +1,13 @@
1
1
  const { getStoreNameConvention } = require("./get-store-name-convention");
2
2
 
3
3
  function validateStoreNameConvention(context) {
4
- const storeNameConvention = getStoreNameConvention(context);
4
+ const storeNameConvention = getStoreNameConvention(context);
5
5
 
6
- if (storeNameConvention !== "prefix" && storeNameConvention !== "postfix") {
7
- throw new Error(
8
- "Invalid Configuration of effector-plugin-eslint/enforce-store-naming-convention. The value should be equal to prefix or postfix."
9
- );
10
- }
6
+ if (storeNameConvention !== "prefix" && storeNameConvention !== "postfix") {
7
+ throw new Error(
8
+ "Invalid Configuration of effector-plugin-eslint/enforce-store-naming-convention. The value should be equal to prefix or postfix."
9
+ );
10
+ }
11
11
  }
12
12
 
13
- module.exports = { validateStoreNameConvention };
13
+ module.exports = { validateStoreNameConvention };
@@ -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,44 +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
- 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 = createStore(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
- ```
@@ -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,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,14 +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({ clock: combine($store1, $store2), filter: $filter });
8
- const badEventOne = guard({ clock: combine($store1, $store2, (store1, store2) => ({x: store1, y: store2})), filter: $filter });
9
-
10
- // 👍 better
11
- const goodEventOne = guard({ clock: [$store1, $store2], filter: $filter });
12
- const goodEventTwo = guard({ clock: ({x: $store1, x: $store2}), filter: $filter });
13
- ```
14
-
@@ -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
- ```