From fe63e88b2bba2f29600fe28bc84eaade1abb11b8 Mon Sep 17 00:00:00 2001 From: Bob Fanger Date: Sat, 22 Jun 2024 19:45:15 +0200 Subject: [PATCH] feat: signal-prefer-let rule --- README.md | 1 + docs/rules.md | 1 + docs/rules/signal-prefer-let.md | 50 ++++++++++++++++ .../eslint-plugin-svelte/src/rule-types.ts | 5 ++ .../src/rules/signal-prefer-let.ts | 57 +++++++++++++++++++ .../eslint-plugin-svelte/src/utils/rules.ts | 2 + .../invalid/test01-errors.yaml | 22 +++++++ .../invalid/test01-input.svelte | 5 ++ .../valid/test01-input.svelte | 3 + .../tests/src/rules/signal-prefer-let.ts | 12 ++++ 10 files changed, 158 insertions(+) create mode 100644 docs/rules/signal-prefer-let.md create mode 100644 packages/eslint-plugin-svelte/src/rules/signal-prefer-let.ts create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/signal-prefer-let/invalid/test01-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/signal-prefer-let/invalid/test01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/signal-prefer-let/valid/test01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/src/rules/signal-prefer-let.ts diff --git a/README.md b/README.md index 9a6c25600..9db665639 100644 --- a/README.md +++ b/README.md @@ -430,6 +430,7 @@ These rules relate to better ways of doing things to help you avoid problems: | [svelte/require-event-dispatcher-types](https://sveltejs.github.io/eslint-plugin-svelte/rules/require-event-dispatcher-types/) | require type parameters for `createEventDispatcher` | | | [svelte/require-optimized-style-attribute](https://sveltejs.github.io/eslint-plugin-svelte/rules/require-optimized-style-attribute/) | require style attributes that can be optimized | | | [svelte/require-stores-init](https://sveltejs.github.io/eslint-plugin-svelte/rules/require-stores-init/) | require initial value in store | | +| [svelte/signal-prefer-let](https://sveltejs.github.io/eslint-plugin-svelte/rules/signal-prefer-let/) | use let instead of const for signals values | :bulb: | | [svelte/valid-each-key](https://sveltejs.github.io/eslint-plugin-svelte/rules/valid-each-key/) | enforce keys to use variables defined in the `{#each}` block | | ## Stylistic Issues diff --git a/docs/rules.md b/docs/rules.md index 7f115da5c..f28e6881e 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -67,6 +67,7 @@ These rules relate to better ways of doing things to help you avoid problems: | [svelte/require-event-dispatcher-types](./rules/require-event-dispatcher-types.md) | require type parameters for `createEventDispatcher` | | | [svelte/require-optimized-style-attribute](./rules/require-optimized-style-attribute.md) | require style attributes that can be optimized | | | [svelte/require-stores-init](./rules/require-stores-init.md) | require initial value in store | | +| [svelte/signal-prefer-let](./rules/signal-prefer-let.md) | use let instead of const for signals values | :bulb: | | [svelte/valid-each-key](./rules/valid-each-key.md) | enforce keys to use variables defined in the `{#each}` block | | ## Stylistic Issues diff --git a/docs/rules/signal-prefer-let.md b/docs/rules/signal-prefer-let.md new file mode 100644 index 000000000..67e27087a --- /dev/null +++ b/docs/rules/signal-prefer-let.md @@ -0,0 +1,50 @@ +--- +pageClass: 'rule-details' +sidebarDepth: 0 +title: 'svelte/signal-prefer-let' +description: 'use let instead of const for signals values' +--- + +# svelte/signal-prefer-let + +> use let instead of const for signals values + +- :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 signal is assigned to a const. +In JavaScript `const` are defined as immutable references which cannot be reassigned. +Signals are by definition changing and are reassigned by Svelte's reactivity system. + + + + + +```svelte + +``` + + + +## :wrench: Options + +Nothing + +## :mag: Implementation + +- [Rule source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/src/rules/signal-prefer-let.ts) +- [Test source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/tests/src/rules/signal-prefer-let.ts) diff --git a/packages/eslint-plugin-svelte/src/rule-types.ts b/packages/eslint-plugin-svelte/src/rule-types.ts index 77d3c4ca4..3c1882aca 100644 --- a/packages/eslint-plugin-svelte/src/rule-types.ts +++ b/packages/eslint-plugin-svelte/src/rule-types.ts @@ -299,6 +299,11 @@ export interface RuleOptions { * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/shorthand-directive/ */ 'svelte/shorthand-directive'?: Linter.RuleEntry + /** + * use let instead of const for signals values + * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/signal-prefer-let/ + */ + 'svelte/signal-prefer-let'?: Linter.RuleEntry<[]> /** * enforce order of attributes * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/sort-attributes/ diff --git a/packages/eslint-plugin-svelte/src/rules/signal-prefer-let.ts b/packages/eslint-plugin-svelte/src/rules/signal-prefer-let.ts new file mode 100644 index 000000000..a0ff7c449 --- /dev/null +++ b/packages/eslint-plugin-svelte/src/rules/signal-prefer-let.ts @@ -0,0 +1,57 @@ +import type { TSESTree } from '@typescript-eslint/types'; +import { createRule } from '../utils'; + +export default createRule('signal-prefer-let', { + meta: { + docs: { + description: 'use let instead of const for signals values', + category: 'Best Practices', + recommended: false + }, + schema: [], + messages: { + useLet: "const is used for a signal value. Use 'let' instead.", + replaceConst: "Replace 'const' with 'let'" + }, + type: 'suggestion', + hasSuggestions: true + }, + create(context) { + function preferLet(node: TSESTree.VariableDeclaration) { + if (node.kind !== 'const') { + return; + } + context.report({ + node, + messageId: 'useLet', + suggest: [ + { + messageId: 'replaceConst', + fix: (fixer) => fixer.replaceTextRange([node.range[0], node.range[0] + 5], 'let') + } + ] + }); + } + + return { + 'VariableDeclaration > VariableDeclarator > CallExpression > Identifier'( + node: TSESTree.Identifier + ) { + if (['$props', '$derived', '$state'].includes(node.name)) { + preferLet(node.parent.parent?.parent as TSESTree.VariableDeclaration); + } + }, + 'VariableDeclaration > VariableDeclarator > CallExpression > MemberExpression > Identifier'( + node: TSESTree.Identifier + ) { + if ( + node.name === 'by' && + ((node.parent as TSESTree.MemberExpression).object as TSESTree.Identifier).name === + '$derived' + ) { + preferLet(node.parent.parent?.parent?.parent as TSESTree.VariableDeclaration); + } + } + }; + } +}); diff --git a/packages/eslint-plugin-svelte/src/utils/rules.ts b/packages/eslint-plugin-svelte/src/utils/rules.ts index af0dd20e6..653214bb1 100644 --- a/packages/eslint-plugin-svelte/src/utils/rules.ts +++ b/packages/eslint-plugin-svelte/src/utils/rules.ts @@ -59,6 +59,7 @@ import requireStoreReactiveAccess from '../rules/require-store-reactive-access'; import requireStoresInit from '../rules/require-stores-init'; import shorthandAttribute from '../rules/shorthand-attribute'; import shorthandDirective from '../rules/shorthand-directive'; +import signalPreferLet from '../rules/signal-prefer-let'; import sortAttributes from '../rules/sort-attributes'; import spacedHtmlComment from '../rules/spaced-html-comment'; import system from '../rules/system'; @@ -124,6 +125,7 @@ export const rules = [ requireStoresInit, shorthandAttribute, shorthandDirective, + signalPreferLet, sortAttributes, spacedHtmlComment, system, diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/signal-prefer-let/invalid/test01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/signal-prefer-let/invalid/test01-errors.yaml new file mode 100644 index 000000000..fd7a539c1 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/signal-prefer-let/invalid/test01-errors.yaml @@ -0,0 +1,22 @@ +- message: "const is used for a signal value. Use 'let' instead." + line: 2 + column: 2 + suggestions: + - desc: "Replace 'const' with 'let'" + output: | + +- message: "const is used for a signal value. Use 'let' instead." + line: 4 + column: 2 + suggestions: + - desc: "Replace 'const' with 'let'" + output: | + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/signal-prefer-let/invalid/test01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/signal-prefer-let/invalid/test01-input.svelte new file mode 100644 index 000000000..8371c26be --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/signal-prefer-let/invalid/test01-input.svelte @@ -0,0 +1,5 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/signal-prefer-let/valid/test01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/signal-prefer-let/valid/test01-input.svelte new file mode 100644 index 000000000..720186465 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/signal-prefer-let/valid/test01-input.svelte @@ -0,0 +1,3 @@ + diff --git a/packages/eslint-plugin-svelte/tests/src/rules/signal-prefer-let.ts b/packages/eslint-plugin-svelte/tests/src/rules/signal-prefer-let.ts new file mode 100644 index 000000000..589f43e6e --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/src/rules/signal-prefer-let.ts @@ -0,0 +1,12 @@ +import { RuleTester } from '../../utils/eslint-compat'; +import rule from '../../../src/rules/signal-prefer-let'; +import { loadTestCases } from '../../utils/utils'; + +const tester = new RuleTester({ + languageOptions: { + ecmaVersion: 2020, + sourceType: 'module' + } +}); + +tester.run('signal-prefer-let', rule as any, loadTestCases('signal-prefer-let'));