diff --git a/.changeset/sixty-cars-fail.md b/.changeset/sixty-cars-fail.md new file mode 100644 index 000000000..b98c40f6d --- /dev/null +++ b/.changeset/sixty-cars-fail.md @@ -0,0 +1,5 @@ +--- +'eslint-plugin-svelte': minor +--- + +feat: add `excludedRunes` option to the `prefer-const` rule diff --git a/docs/rules/prefer-const.md b/docs/rules/prefer-const.md index a6ad021e0..3afe130af 100644 --- a/docs/rules/prefer-const.md +++ b/docs/rules/prefer-const.md @@ -14,7 +14,7 @@ since: 'v3.0.0-next.6' ## :book: Rule Details -This rule reports the same as the base ESLint `prefer-const` rule, except that ignores Svelte reactive values such as `$derived` and `$props`. If this rule is active, make sure to disable the base `prefer-const` rule, as it will conflict with this rule. +This rule reports the same as the base ESLint `prefer-const` rule, except that ignores Svelte reactive values such as `$derived` and `$props` as default. If this rule is active, make sure to disable the base `prefer-const` rule, as it will conflict with this rule. @@ -46,7 +46,8 @@ This rule reports the same as the base ESLint `prefer-const` rule, except that i "error", { "destructuring": "any", - "ignoreReadonly": true + "ignoreReadonly": true, + "excludedRunes": ["$props", "$state"] } ] } @@ -56,6 +57,7 @@ This rule reports the same as the base ESLint `prefer-const` rule, except that i - `any` (default): if any variables in destructuring should be const, this rule warns for those variables. - `all`: if all variables in destructuring should be const, this rule warns the variables. Otherwise, ignores them. - `ignoreReadonly`: If `true`, this rule will ignore variables that are read between the declaration and the _first_ assignment. +- `excludedRunes`: An array of rune names that should be ignored. Even if a rune is declared with `let`, it will still be ignored. ## :books: Further Reading diff --git a/packages/eslint-plugin-svelte/src/rule-types.ts b/packages/eslint-plugin-svelte/src/rule-types.ts index 30f15c1c2..5836fa0d1 100644 --- a/packages/eslint-plugin-svelte/src/rule-types.ts +++ b/packages/eslint-plugin-svelte/src/rule-types.ts @@ -525,6 +525,7 @@ type SveltePreferClassDirective = []|[{ type SveltePreferConst = []|[{ destructuring?: ("any" | "all") ignoreReadBeforeAssign?: boolean + excludedRunes?: string[] }] // ----- svelte/shorthand-attribute ----- type SvelteShorthandAttribute = []|[{ diff --git a/packages/eslint-plugin-svelte/src/rules/prefer-const.ts b/packages/eslint-plugin-svelte/src/rules/prefer-const.ts index 0d079b1e1..842bbc924 100644 --- a/packages/eslint-plugin-svelte/src/rules/prefer-const.ts +++ b/packages/eslint-plugin-svelte/src/rules/prefer-const.ts @@ -21,7 +21,7 @@ function findDeclarationCallee(node: TSESTree.Expression) { * Determines if a declaration should be skipped in the const preference analysis. * Specifically checks for Svelte's state management utilities ($props, $derived). */ -function shouldSkipDeclaration(declaration: TSESTree.Expression | null) { +function shouldSkipDeclaration(declaration: TSESTree.Expression | null, excludedRunes: string[]) { if (!declaration) { return false; } @@ -31,7 +31,7 @@ function shouldSkipDeclaration(declaration: TSESTree.Expression | null) { return false; } - if (callee.type === 'Identifier' && ['$props', '$derived'].includes(callee.name)) { + if (callee.type === 'Identifier' && excludedRunes.includes(callee.name)) { return true; } @@ -39,11 +39,7 @@ function shouldSkipDeclaration(declaration: TSESTree.Expression | null) { return false; } - if ( - callee.object.name === '$derived' && - callee.property.type === 'Identifier' && - callee.property.name === 'by' - ) { + if (excludedRunes.includes(callee.object.name)) { return true; } @@ -58,16 +54,35 @@ export default createRule('prefer-const', { category: 'Best Practices', recommended: false, extensionRule: 'prefer-const' - } + }, + schema: [ + { + type: 'object', + properties: { + destructuring: { enum: ['any', 'all'] }, + ignoreReadBeforeAssign: { type: 'boolean' }, + excludedRunes: { + type: 'array', + items: { + type: 'string' + } + } + }, + additionalProperties: false + } + ] }, create(context) { + const config = context.options[0] ?? {}; + const excludedRunes = config.excludedRunes ?? ['$props', '$derived']; + return defineWrapperListener(coreRule, context, { createListenerProxy(coreListener) { return { ...coreListener, VariableDeclaration(node) { for (const decl of node.declarations) { - if (shouldSkipDeclaration(decl.init)) { + if (shouldSkipDeclaration(decl.init, excludedRunes)) { return; } } diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/invalid/option1/_config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/invalid/option1/_config.json new file mode 100644 index 000000000..0751a9652 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/invalid/option1/_config.json @@ -0,0 +1,3 @@ +{ + "options": [{ "excludedRunes": [] }] +} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/invalid/option1/test01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/invalid/option1/test01-errors.yaml new file mode 100644 index 000000000..03d2d8e3d --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/invalid/option1/test01-errors.yaml @@ -0,0 +1,20 @@ +- message: "'prop1' is never reassigned. Use 'const' instead." + line: 2 + column: 8 + suggestions: null +- message: "'prop2' is never reassigned. Use 'const' instead." + line: 2 + column: 15 + suggestions: null +- message: "'zero' is never reassigned. Use 'const' instead." + line: 3 + column: 6 + suggestions: null +- message: "'derived' is never reassigned. Use 'const' instead." + line: 4 + column: 6 + suggestions: null +- message: "'derivedBy' is never reassigned. Use 'const' instead." + line: 5 + column: 6 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/invalid/option1/test01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/invalid/option1/test01-input.svelte new file mode 100644 index 000000000..97f28d4a2 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/invalid/option1/test01-input.svelte @@ -0,0 +1,6 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/invalid/option1/test01-output.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/invalid/option1/test01-output.svelte new file mode 100644 index 000000000..dd1b99c5e --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/invalid/option1/test01-output.svelte @@ -0,0 +1,6 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/invalid/option2/_config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/invalid/option2/_config.json new file mode 100644 index 000000000..9021ae6a7 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/invalid/option2/_config.json @@ -0,0 +1,3 @@ +{ + "options": [{ "excludedRunes": ["$state"] }] +} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/invalid/option2/test01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/invalid/option2/test01-errors.yaml new file mode 100644 index 000000000..b784981e8 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/invalid/option2/test01-errors.yaml @@ -0,0 +1,16 @@ +- message: "'prop1' is never reassigned. Use 'const' instead." + line: 2 + column: 8 + suggestions: null +- message: "'prop2' is never reassigned. Use 'const' instead." + line: 2 + column: 15 + suggestions: null +- message: "'derived' is never reassigned. Use 'const' instead." + line: 4 + column: 6 + suggestions: null +- message: "'derivedBy' is never reassigned. Use 'const' instead." + line: 5 + column: 6 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/invalid/option2/test01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/invalid/option2/test01-input.svelte new file mode 100644 index 000000000..97f28d4a2 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/invalid/option2/test01-input.svelte @@ -0,0 +1,6 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/invalid/option2/test01-output.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/invalid/option2/test01-output.svelte new file mode 100644 index 000000000..2e25c28b6 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/invalid/option2/test01-output.svelte @@ -0,0 +1,6 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/valid/option1/_config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/valid/option1/_config.json new file mode 100644 index 000000000..0751a9652 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/valid/option1/_config.json @@ -0,0 +1,3 @@ +{ + "options": [{ "excludedRunes": [] }] +} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/valid/option1/test01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/valid/option1/test01-input.svelte new file mode 100644 index 000000000..dd1b99c5e --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/valid/option1/test01-input.svelte @@ -0,0 +1,6 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/valid/option2/_config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/valid/option2/_config.json new file mode 100644 index 000000000..03a86e4c3 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/valid/option2/_config.json @@ -0,0 +1,3 @@ +{ + "options": [{ "excludedRunes": ["$props", "$derived", "$state"] }] +} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/valid/option2/test01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/valid/option2/test01-input.svelte new file mode 100644 index 000000000..97f28d4a2 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/valid/option2/test01-input.svelte @@ -0,0 +1,6 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/valid/test01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/valid/test01-input.svelte index 5c768eb34..33ac25397 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/valid/test01-input.svelte +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/prefer-const/valid/test01-input.svelte @@ -1,3 +1,6 @@