diff --git a/README.md b/README.md index f0a95ec47..9cbc2bde5 100644 --- a/README.md +++ b/README.md @@ -282,6 +282,7 @@ These rules relate to better ways of doing things to help you avoid problems: |:--------|:------------|:---| | [svelte/button-has-type](https://ota-meshi.github.io/eslint-plugin-svelte/rules/button-has-type/) | disallow usage of button without an explicit type attribute | | | [svelte/no-at-debug-tags](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-at-debug-tags/) | disallow the use of `{@debug}` | :star: | +| [svelte/no-reactive-functions](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-reactive-functions/) | It's not necessary to define functions in reactive statements | :bulb: | | [svelte/no-reactive-literals](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-reactive-literals/) | Don't assign literal values in reactive statements | :bulb: | | [svelte/no-unused-svelte-ignore](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-unused-svelte-ignore/) | disallow unused svelte-ignore comments | :star: | | [svelte/no-useless-mustaches](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-useless-mustaches/) | disallow unnecessary mustache interpolations | :wrench: | diff --git a/docs/rules.md b/docs/rules.md index 407aa65d5..8145ffbee 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -42,6 +42,7 @@ These rules relate to better ways of doing things to help you avoid problems: |:--------|:------------|:---| | [svelte/button-has-type](./rules/button-has-type.md) | disallow usage of button without an explicit type attribute | | | [svelte/no-at-debug-tags](./rules/no-at-debug-tags.md) | disallow the use of `{@debug}` | :star: | +| [svelte/no-reactive-functions](./rules/no-reactive-functions.md) | It's not necessary to define functions in reactive statements | :bulb: | | [svelte/no-reactive-literals](./rules/no-reactive-literals.md) | Don't assign literal values in reactive statements | :bulb: | | [svelte/no-unused-svelte-ignore](./rules/no-unused-svelte-ignore.md) | disallow unused svelte-ignore comments | :star: | | [svelte/no-useless-mustaches](./rules/no-useless-mustaches.md) | disallow unnecessary mustache interpolations | :wrench: | diff --git a/docs/rules/no-reactive-functions.md b/docs/rules/no-reactive-functions.md new file mode 100644 index 000000000..c25c758e0 --- /dev/null +++ b/docs/rules/no-reactive-functions.md @@ -0,0 +1,52 @@ +--- +pageClass: "rule-details" +sidebarDepth: 0 +title: "svelte/no-reactive-functions" +description: "It's not necessary to define functions in reactive statements" +--- + +# svelte/no-reactive-functions + +> It's not necessary to define functions in reactive statements + +- :exclamation: **_This rule has not been released yet._** +- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions). + +## :book: Rule Details + +This rule reports whenever a function is defined in a reactive statement. This isn't necessary, as each time the function is executed it'll already have access to the latest values necessary. Redefining the function in the reactive statement is just a waste of CPU cycles. + + + + + +```svelte + +``` + + + +## :wrench: Options + +Nothing + +## :heart: Compatibility + +This rule was taken from [@tivac/eslint-plugin-svelte]. +This rule is compatible with `@tivac/svelte/reactive-functions` rule. + +[@tivac/eslint-plugin-svelte]: https://github.com/tivac/eslint-plugin-svelte/ +## :mag: Implementation + +- [Rule source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/src/rules/no-reactive-functions.ts) +- [Test source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/tests/src/rules/no-reactive-functions.ts) diff --git a/src/rules/no-reactive-functions.ts b/src/rules/no-reactive-functions.ts new file mode 100644 index 000000000..4b8c77951 --- /dev/null +++ b/src/rules/no-reactive-functions.ts @@ -0,0 +1,66 @@ +import type { TSESTree } from "@typescript-eslint/types" +import type { AST } from "svelte-eslint-parser" +import { createRule } from "../utils" + +export default createRule("no-reactive-functions", { + meta: { + docs: { + description: + "It's not necessary to define functions in reactive statements", + category: "Best Practices", + recommended: false, + }, + hasSuggestions: true, + schema: [], + messages: { + noReactiveFns: `Do not create functions inside reactive statements unless absolutely necessary.`, + fixReactiveFns: `Move the function out of the reactive statement`, + }, + type: "suggestion", // "problem", or "layout", + }, + create(context) { + return { + // $: foo = () => { ... } + [`SvelteReactiveStatement > ExpressionStatement > AssignmentExpression > :function`]( + node: TSESTree.ArrowFunctionExpression, + ) { + // Move upwards to include the entire label + const parent = node.parent?.parent?.parent + + if (!parent) { + return false + } + + const source = context.getSourceCode() + + return context.report({ + node: parent, + loc: parent.loc, + messageId: "noReactiveFns", + suggest: [ + { + messageId: "fixReactiveFns", + fix(fixer) { + const tokens = source.getFirstTokens(parent, { + includeComments: false, + count: 3, + }) + + const noExtraSpace = source.isSpaceBetweenTokens( + tokens[1] as AST.Token, + tokens[2] as AST.Token, + ) + + // Replace the entire reactive label with "const" + return fixer.replaceTextRange( + [tokens[0].range[0], tokens[1].range[1]], + noExtraSpace ? "const" : "const ", + ) + }, + }, + ], + }) + }, + } + }, +}) diff --git a/src/utils/rules.ts b/src/utils/rules.ts index 4874d321f..791b05025 100644 --- a/src/utils/rules.ts +++ b/src/utils/rules.ts @@ -16,6 +16,7 @@ import noExtraReactiveCurlies from "../rules/no-extra-reactive-curlies" import noInnerDeclarations from "../rules/no-inner-declarations" import noNotFunctionHandler from "../rules/no-not-function-handler" import noObjectInTextMustaches from "../rules/no-object-in-text-mustaches" +import noReactiveFunctions from "../rules/no-reactive-functions" import noReactiveLiterals from "../rules/no-reactive-literals" import noShorthandStylePropertyOverrides from "../rules/no-shorthand-style-property-overrides" import noSpacesAroundEqualSignsInAttribute from "../rules/no-spaces-around-equal-signs-in-attribute" @@ -51,6 +52,7 @@ export const rules = [ noInnerDeclarations, noNotFunctionHandler, noObjectInTextMustaches, + noReactiveFunctions, noReactiveLiterals, noShorthandStylePropertyOverrides, noSpacesAroundEqualSignsInAttribute, diff --git a/tests/fixtures/rules/no-reactive-functions/invalid/test01-errors.json b/tests/fixtures/rules/no-reactive-functions/invalid/test01-errors.json new file mode 100644 index 000000000..924be5e56 --- /dev/null +++ b/tests/fixtures/rules/no-reactive-functions/invalid/test01-errors.json @@ -0,0 +1,38 @@ +[ + { + "message": "Do not create functions inside reactive statements unless absolutely necessary.", + "line": 3, + "column": 5, + "suggestions": [ + { + "desc": "Move the function out of the reactive statement", + "messageId": "fixReactiveFns", + "output": "\n\n" + } + ] + }, + { + "message": "Do not create functions inside reactive statements unless absolutely necessary.", + "line": 4, + "column": 5, + "suggestions": [ + { + "desc": "Move the function out of the reactive statement", + "messageId": "fixReactiveFns", + "output": "\n\n" + } + ] + }, + { + "message": "Do not create functions inside reactive statements unless absolutely necessary.", + "line": 5, + "column": 5, + "suggestions": [ + { + "desc": "Move the function out of the reactive statement", + "messageId": "fixReactiveFns", + "output": "\n\n" + } + ] + } +] diff --git a/tests/fixtures/rules/no-reactive-functions/invalid/test01-input.svelte b/tests/fixtures/rules/no-reactive-functions/invalid/test01-input.svelte new file mode 100644 index 000000000..b08cd8648 --- /dev/null +++ b/tests/fixtures/rules/no-reactive-functions/invalid/test01-input.svelte @@ -0,0 +1,6 @@ + + diff --git a/tests/fixtures/rules/no-reactive-functions/valid/test01-input.svelte b/tests/fixtures/rules/no-reactive-functions/valid/test01-input.svelte new file mode 100644 index 000000000..1088d2b02 --- /dev/null +++ b/tests/fixtures/rules/no-reactive-functions/valid/test01-input.svelte @@ -0,0 +1,5 @@ + + diff --git a/tests/src/rules/no-reactive-functions.ts b/tests/src/rules/no-reactive-functions.ts new file mode 100644 index 000000000..20033743d --- /dev/null +++ b/tests/src/rules/no-reactive-functions.ts @@ -0,0 +1,16 @@ +import { RuleTester } from "eslint" +import rule from "../../../src/rules/no-reactive-functions" +import { loadTestCases } from "../../utils/utils" + +const tester = new RuleTester({ + parserOptions: { + ecmaVersion: 2020, + sourceType: "module", + }, +}) + +tester.run( + "no-reactive-functions", + rule as any, + loadTestCases("no-reactive-functions"), +)