From a2b15b221a2cc078820b538e92618a41b25b17c5 Mon Sep 17 00:00:00 2001 From: Alex Chernyshev Date: Wed, 13 Apr 2022 22:48:32 +0300 Subject: [PATCH] Add rule `no-inline-units` --- config/recommended.js | 1 + index.js | 1 + rules/no-forward/no-forward.js | 2 +- rules/no-inline-units/no-inline-units.js | 84 +++++++++++++++++++ rules/no-inline-units/no-inline-units.md | 13 +++ rules/no-inline-units/no-inline-units.test.js | 83 ++++++++++++++++++ 6 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 rules/no-inline-units/no-inline-units.js create mode 100644 rules/no-inline-units/no-inline-units.md create mode 100644 rules/no-inline-units/no-inline-units.test.js diff --git a/config/recommended.js b/config/recommended.js index 8f2f4a8..5abbb75 100644 --- a/config/recommended.js +++ b/config/recommended.js @@ -11,5 +11,6 @@ module.exports = { "effector/no-unnecessary-combination": "warn", "effector/no-duplicate-on": "error", "effector/keep-options-order": "warn", + "effector/no-inline-units": "error", }, }; diff --git a/index.js b/index.js index 2f74f5e..3796d9f 100644 --- a/index.js +++ b/index.js @@ -15,6 +15,7 @@ module.exports = { "keep-options-order": require("./rules/keep-options-order/keep-options-order"), "no-forward": require("./rules/no-forward/no-forward"), "no-guard": require("./rules/no-guard/no-guard"), + "no-inline-units": require("./rules/no-inline-units/no-inline-units"), }, configs: { recommended: require("./config/recommended"), diff --git a/rules/no-forward/no-forward.js b/rules/no-forward/no-forward.js index 4d77518..af328fc 100644 --- a/rules/no-forward/no-forward.js +++ b/rules/no-forward/no-forward.js @@ -16,7 +16,7 @@ module.exports = { messages: { noForward: "Instead of `forward` you can use `sample`, it is more extendable.", - replaceWithSample: "Repalce `forward` with `sample`.", + replaceWithSample: "Replace `forward` with `sample`.", }, schema: [], hasSuggestions: true, diff --git a/rules/no-inline-units/no-inline-units.js b/rules/no-inline-units/no-inline-units.js new file mode 100644 index 0000000..1de7de8 --- /dev/null +++ b/rules/no-inline-units/no-inline-units.js @@ -0,0 +1,84 @@ +const { extractImportedFrom } = require("../../utils/extract-imported-from"); +const { createLinkToRule } = require("../../utils/create-link-to-rule"); +const { method } = require("../../utils/method"); + +const METHODS = [ + "forward", + "sample", + "guard", + "attach", + "merge", + "combine", + "createApi", +]; + +const UNIT_CREATORS = ["createStore", "createEvent", "createEffect"]; + +module.exports = { + meta: { + type: "problem", + docs: { + description: "Forbids to use inline units in methods", + category: "Quality", + recommended: true, + url: createLinkToRule("no-inline-units"), + }, + messages: { + noInlineUnits: + 'Declare inline method "{{ inlineMethodName }}" in a variable', + }, + schema: [], + }, + create(context) { + const importNodes = new Map(); + const importedFromEffector = new Map(); + + return { + ImportDeclaration(node) { + extractImportedFrom({ + importMap: importedFromEffector, + nodeMap: importNodes, + node, + packageName: "effector", + }); + }, + CallExpression(node) { + const isEffectorMethod = method.is(METHODS, { + node, + importMap: importedFromEffector, + }); + + if (!isEffectorMethod) { + return; + } + + const configNode = node?.arguments?.[0]; + const optionsNodes = configNode?.properties; + let inlineMethodName; + + const hasInlineUnits = optionsNodes?.some((optionNode) => { + const isFound = method.is(UNIT_CREATORS, { + node: optionNode?.value, + importMap: importedFromEffector, + }); + + if (isFound) { + inlineMethodName = optionNode?.value?.callee?.name; + } + + return isFound; + }); + + if (!hasInlineUnits) { + return; + } + + context.report({ + node, + messageId: "noInlineUnits", + data: { inlineMethodName }, + }); + }, + }; + }, +}; diff --git a/rules/no-inline-units/no-inline-units.md b/rules/no-inline-units/no-inline-units.md new file mode 100644 index 0000000..df8d3d2 --- /dev/null +++ b/rules/no-inline-units/no-inline-units.md @@ -0,0 +1,13 @@ +# effector/no-inline-units + +Disallows to use inline units in methods + +```ts +// 👎 Bad +sample({ source: $somestore, target: createEffect() }); + +// 👍 Good +const effectFx = createEffect(); + +sample({ source: $somestore, target: effectFx }); +``` diff --git a/rules/no-inline-units/no-inline-units.test.js b/rules/no-inline-units/no-inline-units.test.js new file mode 100644 index 0000000..124a624 --- /dev/null +++ b/rules/no-inline-units/no-inline-units.test.js @@ -0,0 +1,83 @@ +const { RuleTester } = require("eslint"); + +const rule = require("./no-inline-units"); + +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 2020, + sourceType: "module", + }, +}); + +ruleTester.run("effector/no-inline-units.test", rule, { + valid: [ + ` + import { sample } from 'effector'; + sample({ clock: event, target: effectFx }); + `, + ` + import { sample } from 'someLibrary'; + sample({ + clock: event, + source: $shouldExecute, + filter: (shouldExecute) => shouldExecute, + target: effectFx + }); + `, + ` + import { sample, attach } from 'effector'; + sample({ + clock: event, + target: attach({ + effect: originalFx, + mapParams: params => { + return { wrapped: params } + }, + }) + }); + `, + ].map((code) => ({ code })), + invalid: [ + ` + import { forward, createEffect } from 'effector'; + forward({ from: eventOne, to: createEffect() }); + `, + ` + import { forward, createStore } from 'effector'; + forward({ from: eventOne, to: createStore(null) }); + `, + ` + import { forward, createEvent } from 'effector'; + forward({ from: eventOne, to: createEvent() }); + `, + ` + import { attach, createEffect } from 'effector'; + attach({ + effect: createEffect(), + mapParams: params => { + return {wrapped: params} + }, + }); + `, + ` + import { sample, attach, createEffect } from 'effector'; + sample({ + clock: event, + target: attach({ + effect: createEffect(), + mapParams: params => { + return {wrapped: params} + }, + }) + }); + `, + ].map((code) => ({ + code, + errors: [ + { + messageId: "noInlineUnits", + type: "CallExpression", + }, + ], + })), +});