eslint-plugin-effector 0.16.0 → 0.17.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 (53) hide show
  1. package/README.md +24 -37
  2. package/dist/index.cjs +1259 -0
  3. package/dist/index.d.cts +177 -0
  4. package/dist/index.d.mts +178 -0
  5. package/dist/index.mjs +1233 -0
  6. package/package.json +71 -17
  7. package/.nvmrc +0 -1
  8. package/config/future.js +0 -7
  9. package/config/patronum.js +0 -5
  10. package/config/react.js +0 -7
  11. package/config/recommended.js +0 -15
  12. package/config/scope.js +0 -6
  13. package/index.js +0 -31
  14. package/rules/enforce-effect-naming-convention/enforce-effect-naming-convention.js +0 -143
  15. package/rules/enforce-gate-naming-convention/enforce-gate-naming-convention.js +0 -122
  16. package/rules/enforce-store-naming-convention/enforce-store-naming-convention.js +0 -205
  17. package/rules/keep-options-order/config.js +0 -3
  18. package/rules/keep-options-order/keep-options-order.js +0 -107
  19. package/rules/mandatory-scope-binding/mandatory-scope-binding.js +0 -81
  20. package/rules/no-ambiguity-target/no-ambiguity-target.js +0 -74
  21. package/rules/no-duplicate-clock-or-source-array-values/no-duplicate-clock-or-source-array-values.js +0 -124
  22. package/rules/no-duplicate-on/no-duplicate-on.js +0 -137
  23. package/rules/no-forward/no-forward.js +0 -73
  24. package/rules/no-getState/no-getState.js +0 -50
  25. package/rules/no-guard/no-guard.js +0 -78
  26. package/rules/no-patronum-debug/no-patronum-debug.js +0 -133
  27. package/rules/no-unnecessary-combination/no-unnecessary-combination.js +0 -88
  28. package/rules/no-unnecessary-duplication/no-unnecessary-duplication.js +0 -115
  29. package/rules/no-useless-methods/no-useless-methods.js +0 -93
  30. package/rules/no-watch/no-watch.js +0 -61
  31. package/rules/prefer-sample-over-forward-with-mapping/prefer-sample-over-forward-with-mapping.js +0 -111
  32. package/rules/prefer-useUnit/prefer-useUnit.js +0 -56
  33. package/rules/require-pickup-in-persist/require-pickup-in-persist.js +0 -47
  34. package/rules/strict-effect-handlers/strict-effect-handlers.js +0 -76
  35. package/utils/are-nodes-same-in-text.js +0 -22
  36. package/utils/builders.js +0 -19
  37. package/utils/create-link-to-rule.js +0 -5
  38. package/utils/extract-config.js +0 -26
  39. package/utils/extract-imported-from.js +0 -18
  40. package/utils/get-corrected-store-name.js +0 -45
  41. package/utils/get-nested-object-name.js +0 -18
  42. package/utils/get-store-name-convention.js +0 -6
  43. package/utils/is.js +0 -39
  44. package/utils/method.js +0 -23
  45. package/utils/naming.js +0 -47
  46. package/utils/node-is-type.js +0 -5
  47. package/utils/node-type-is.js +0 -106
  48. package/utils/react.js +0 -214
  49. package/utils/read-example.js +0 -63
  50. package/utils/replace-by-sample.js +0 -98
  51. package/utils/traverse-nested-object-node.js +0 -9
  52. package/utils/traverse-parent-by-type.js +0 -15
  53. package/utils/validate-store-name-convention.js +0 -13
@@ -1,56 +0,0 @@
1
- const { createLinkToRule } = require("../../utils/create-link-to-rule");
2
- const { extractImportedFrom } = require("../../utils/extract-imported-from");
3
-
4
- module.exports = {
5
- meta: {
6
- type: "problem",
7
- docs: {
8
- description:
9
- "Suggests to replace old hooks `useStore`/`useEvent` by the new one `useUnit`",
10
- category: "Quality",
11
- recommended: true,
12
- url: createLinkToRule("prefer-useUnit"),
13
- },
14
- messages: {
15
- useUnitNeeded: "`{{ hookName }}` could be replaced by `useUnit`",
16
- },
17
- schema: [],
18
- },
19
- create(context) {
20
- const importedFromEffectorReact = new Map();
21
-
22
- return {
23
- ImportDeclaration(node) {
24
- extractImportedFrom({
25
- importMap: importedFromEffectorReact,
26
- packageName: "effector-react",
27
- node,
28
- });
29
- },
30
- CallExpression(node) {
31
- const OLD_HOOKS = ["useStore", "useEvent"];
32
- const NEW_HOOK = ["useUnit"];
33
-
34
- for (const oldHookName of OLD_HOOKS) {
35
- const localOldHookName = importedFromEffectorReact.get(oldHookName);
36
- if (!localOldHookName) {
37
- continue;
38
- }
39
-
40
- const isOldHook = node.callee.name === localOldHookName;
41
- if (!isOldHook) {
42
- continue;
43
- }
44
-
45
- context.report({
46
- node,
47
- messageId: "useUnitNeeded",
48
- data: {
49
- hookName: oldHookName,
50
- },
51
- });
52
- }
53
- },
54
- };
55
- },
56
- };
@@ -1,47 +0,0 @@
1
- const { createLinkToRule } = require("../../utils/create-link-to-rule");
2
-
3
- module.exports = {
4
- meta: {
5
- type: "problem",
6
- docs: {
7
- category: "Quality",
8
- url: createLinkToRule("require-pickup-in-persist"),
9
- },
10
- messages: {
11
- pickupMissing:
12
- "This `persist` call does not specify a `pickup` event that is required for scoped usage of `effector-storage`.",
13
- },
14
- schema: [],
15
- },
16
- create(context) {
17
- const pickupImports = new Set();
18
-
19
- /**
20
- * Finds `effector-storage` packages, scoped and unscoped, including
21
- * contents of these packages. See examples for a full list.
22
- */
23
- const PACKAGE_NAME = /^@?effector-storage(\u002F[\w-]+)*$/;
24
-
25
- const declarationSelector = `ImportDeclaration[source.value=${PACKAGE_NAME}]`;
26
- const persistImportSelector = `ImportSpecifier[imported.name="persist"]`;
27
-
28
- const configSelector = `[arguments.length=1][arguments.0.type="ObjectExpression"]`;
29
- const callSelector = `[callee.type="Identifier"]`;
30
-
31
- return {
32
- [`${declarationSelector} > ${persistImportSelector}`](node) {
33
- pickupImports.add(node.local.name);
34
- },
35
- [`CallExpression${configSelector}${callSelector}`](node) {
36
- if (!pickupImports.has(node.callee.name)) return;
37
-
38
- const config = node.arguments[0];
39
-
40
- if (config.properties.some((prop) => prop.key?.name === "pickup"))
41
- return;
42
-
43
- context.report({ node, messageId: "pickupMissing" });
44
- },
45
- };
46
- },
47
- };
@@ -1,76 +0,0 @@
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
- };
@@ -1,22 +0,0 @@
1
- const prettier = require("prettier");
2
-
3
- function areNodesSameInText({ context, nodes }) {
4
- const texts = nodes.map((node) => {
5
- let sourceText = context.getSourceCode().getText(node);
6
-
7
- const shouldBeWrapped =
8
- sourceText.startsWith("{") && sourceText.endsWith("}");
9
-
10
- if (shouldBeWrapped) {
11
- sourceText = `(${sourceText})`;
12
- }
13
-
14
- return prettier.format(sourceText, {
15
- parser: "babel-ts",
16
- });
17
- });
18
-
19
- return texts.every((text) => text === texts[0]);
20
- }
21
-
22
- module.exports = { areNodesSameInText };
package/utils/builders.js DELETED
@@ -1,19 +0,0 @@
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 };
@@ -1,5 +0,0 @@
1
- function createLinkToRule(name) {
2
- return `https://eslint.effector.dev/rules/${name}`;
3
- }
4
-
5
- module.exports = { createLinkToRule };
@@ -1,26 +0,0 @@
1
- function extractConfig(fields, { node }) {
2
- const config = {};
3
-
4
- if (isObject(node.arguments?.[0])) {
5
- extractConfigFromObject(config, fields, node.arguments?.[0]);
6
- } else if (isObject(node.arguments?.[1])) {
7
- extractConfigFromObject(config, fields, node.arguments?.[1]);
8
- if (fields.includes("clock")) {
9
- config.clock = { value: node.arguments?.[0] };
10
- }
11
- }
12
-
13
- return config;
14
- }
15
-
16
- function isObject(node) {
17
- return Boolean(node?.properties);
18
- }
19
-
20
- function extractConfigFromObject(config, fields, node) {
21
- fields.forEach((field) => {
22
- config[field] = node?.properties?.find((n) => n.key?.name === field);
23
- });
24
- }
25
-
26
- module.exports = { extractConfig };
@@ -1,18 +0,0 @@
1
- function extractImportedFrom({ importMap, nodeMap, node, packageName }) {
2
- const normalizePackageName = Array.isArray(packageName)
3
- ? packageName
4
- : [packageName];
5
-
6
- if (normalizePackageName.includes(node.source.value)) {
7
- for (const s of node.specifiers) {
8
- if (s.type === "ImportDefaultSpecifier") {
9
- continue;
10
- }
11
-
12
- importMap.set(s.imported.name, s.local.name);
13
- nodeMap?.set(s.imported.name, s);
14
- }
15
- }
16
- }
17
-
18
- module.exports = { extractImportedFrom };
@@ -1,45 +0,0 @@
1
- const { getStoreNameConvention } = require("./get-store-name-convention");
2
-
3
- function getCorrectedStoreName(storeName, context) {
4
- const storeNameConvention = getStoreNameConvention(context);
5
-
6
- // handle edge case
7
- if (storeName.startsWith("$") && storeName.endsWith("$")) {
8
- const storeNameWithoutConvention = trimByPattern(storeName, "$");
9
- return formatStoreName(storeNameWithoutConvention, storeNameConvention);
10
- }
11
-
12
- const correctedStoreName = formatStoreName(storeName, storeNameConvention);
13
-
14
- return correctedStoreName;
15
- }
16
-
17
- function formatStoreName(storeName, convention) {
18
- return convention === "prefix" ? `$${storeName}` : `${storeName}$`;
19
- }
20
-
21
- function trimByPattern(s, template) {
22
- let l = 0,
23
- r = s.length - 1;
24
-
25
- while (l <= r) {
26
- const head = s[l];
27
- const tail = s[r];
28
-
29
- if (head === template) {
30
- l++;
31
- }
32
-
33
- if (tail === template) {
34
- r--;
35
- }
36
-
37
- if (head !== template && tail !== template) {
38
- return s.slice(l, r + 1);
39
- }
40
- }
41
-
42
- return s;
43
- }
44
-
45
- module.exports = { getCorrectedStoreName };
@@ -1,18 +0,0 @@
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 +0,0 @@
1
- function getStoreNameConvention(context) {
2
- // prefix convention is default
3
- return context.settings.effector?.storeNameConvention || "prefix";
4
- }
5
-
6
- module.exports = { getStoreNameConvention };
package/utils/is.js DELETED
@@ -1,39 +0,0 @@
1
- const { ESLintUtils } = require("@typescript-eslint/utils");
2
-
3
- const { nodeTypeIs } = require("./node-type-is");
4
- const { namingOf } = require("./naming");
5
-
6
- function isSomething({ isValidNaming, isTypeCorrect }) {
7
- return ({ node, context }) => {
8
- let parserServices;
9
- try {
10
- parserServices = ESLintUtils.getParserServices(context);
11
- } catch (e) {
12
- // no types info
13
- }
14
-
15
- if (parserServices?.program) {
16
- return isTypeCorrect({ node, context });
17
- }
18
-
19
- return isValidNaming({ name: node?.name ?? node?.id?.name, context });
20
- };
21
- }
22
-
23
- const isStore = isSomething({
24
- isTypeCorrect: nodeTypeIs.store,
25
- isValidNaming: namingOf.store.isValid,
26
- });
27
-
28
- const isEffect = isSomething({
29
- isTypeCorrect: nodeTypeIs.effect,
30
- isValidNaming: namingOf.effect.isValid,
31
- });
32
-
33
- const is = {
34
- store: (opts) => isStore(opts),
35
- effect: (opts) => isEffect(opts),
36
- not: { store: (opts) => !isStore(opts), effect: (opts) => !isEffect(opts) },
37
- };
38
-
39
- module.exports = { is };
package/utils/method.js DELETED
@@ -1,23 +0,0 @@
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 };
package/utils/naming.js DELETED
@@ -1,47 +0,0 @@
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?.length > 1 && 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 };
@@ -1,5 +0,0 @@
1
- function nodeIsType({ node }) {
2
- return node?.parent?.type === "TSTypeReference";
3
- }
4
-
5
- module.exports = { nodeIsType };
@@ -1,106 +0,0 @@
1
- const { ESLintUtils } = require("@typescript-eslint/utils");
2
-
3
- function hasType({ node, possibleTypes, context, from }) {
4
- try {
5
- const parserServices = ESLintUtils.getParserServices(context);
6
- const checker = parserServices.program.getTypeChecker();
7
- const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node);
8
- const type = checker.getTypeAtLocation(
9
- originalNode?.initializer ?? originalNode
10
- );
11
-
12
- const symbol = type?.symbol ?? type?.aliasSymbol;
13
-
14
- return (
15
- possibleTypes.includes(symbol?.escapedName) &&
16
- Boolean(symbol?.parent?.escapedName?.includes(from))
17
- );
18
- } catch (e) {
19
- return false;
20
- }
21
- }
22
-
23
- const nodeTypeIs = {
24
- effect: (opts) =>
25
- hasType({ ...opts, possibleTypes: ["Effect"], from: "effector" }),
26
- store: (opts) =>
27
- hasType({
28
- ...opts,
29
- possibleTypes: ["Store", "StoreWritable"],
30
- from: "effector",
31
- }),
32
- event: (opts) =>
33
- hasType({
34
- ...opts,
35
- possibleTypes: ["Event", "EventCallable"],
36
- from: "effector",
37
- }),
38
- unit: (opts) =>
39
- hasType({
40
- ...opts,
41
- possibleTypes: [
42
- "Effect",
43
- "Store",
44
- "Event",
45
- "EventCallable",
46
- "StoreWritable",
47
- ],
48
- from: "effector",
49
- }),
50
- gate: (opts) =>
51
- hasType({ ...opts, possibleTypes: ["Gate"], from: "effector-react" }),
52
- effectorReactHook: (opts) =>
53
- hasType({
54
- ...opts,
55
- possibleTypes: opts.hook
56
- ? [].concat(opts.hook)
57
- : [
58
- "useStore",
59
- "useStoreMap",
60
- "useList",
61
- "useEvent",
62
- "useGate",
63
- "useUnit",
64
- ],
65
- from: "effector-react",
66
- }),
67
- not: {
68
- effect: (opts) =>
69
- !hasType({
70
- ...opts,
71
- possibleTypes: ["Effect"],
72
- from: "effector",
73
- }),
74
- store: (opts) =>
75
- !hasType({
76
- ...opts,
77
- possibleTypes: ["Store", "StoreWritable"],
78
- from: "effector",
79
- }),
80
- event: (opts) =>
81
- !hasType({
82
- ...opts,
83
- possibleTypes: ["Event", "EventCallable"],
84
- from: "effector",
85
- }),
86
- unit: (opts) =>
87
- !hasType({
88
- ...opts,
89
- possibleTypes: [
90
- "Effect",
91
- "Store",
92
- "Event",
93
- "EventCallable",
94
- "StoreWritable",
95
- ],
96
- from: "effector",
97
- }),
98
- gate: (opts) =>
99
- !hasType({ ...opts, possibleTypes: ["Gate"], from: "effector-react" }),
100
- effectorReactHook: (opts) => !nodeTypeIs.effectorReactHook(opts),
101
- },
102
- };
103
-
104
- module.exports = {
105
- nodeTypeIs,
106
- };