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 @@