Skip to content

Commit df1647f

Browse files
feat: add excludedRunes option to the prefer-const rule (#1064)
Co-authored-by: Yosuke Ota <[email protected]>
1 parent f1c7846 commit df1647f

File tree

17 files changed

+122
-12
lines changed

17 files changed

+122
-12
lines changed

.changeset/sixty-cars-fail.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'eslint-plugin-svelte': minor
3+
---
4+
5+
feat: add `excludedRunes` option to the `prefer-const` rule

docs/rules/prefer-const.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ since: 'v3.0.0-next.6'
1414

1515
## :book: Rule Details
1616

17-
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.
17+
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.
1818

1919
<!--eslint-skip-->
2020

@@ -46,7 +46,8 @@ This rule reports the same as the base ESLint `prefer-const` rule, except that i
4646
"error",
4747
{
4848
"destructuring": "any",
49-
"ignoreReadonly": true
49+
"ignoreReadonly": true,
50+
"excludedRunes": ["$props", "$derived"]
5051
}
5152
]
5253
}
@@ -56,6 +57,7 @@ This rule reports the same as the base ESLint `prefer-const` rule, except that i
5657
- `any` (default): if any variables in destructuring should be const, this rule warns for those variables.
5758
- `all`: if all variables in destructuring should be const, this rule warns the variables. Otherwise, ignores them.
5859
- `ignoreReadonly`: If `true`, this rule will ignore variables that are read between the declaration and the _first_ assignment.
60+
- `excludedRunes`: An array of rune names that should be ignored. Even if a rune is declared with `let`, it will still be ignored.
5961

6062
## :books: Further Reading
6163

packages/eslint-plugin-svelte/src/rule-types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,7 @@ type SveltePreferClassDirective = []|[{
530530
type SveltePreferConst = []|[{
531531
destructuring?: ("any" | "all")
532532
ignoreReadBeforeAssign?: boolean
533+
excludedRunes?: string[]
533534
}]
534535
// ----- svelte/shorthand-attribute -----
535536
type SvelteShorthandAttribute = []|[{

packages/eslint-plugin-svelte/src/rules/prefer-const.ts

+24-9
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ function findDeclarationCallee(node: TSESTree.Expression) {
2121
* Determines if a declaration should be skipped in the const preference analysis.
2222
* Specifically checks for Svelte's state management utilities ($props, $derived).
2323
*/
24-
function shouldSkipDeclaration(declaration: TSESTree.Expression | null) {
24+
function shouldSkipDeclaration(declaration: TSESTree.Expression | null, excludedRunes: string[]) {
2525
if (!declaration) {
2626
return false;
2727
}
@@ -31,19 +31,15 @@ function shouldSkipDeclaration(declaration: TSESTree.Expression | null) {
3131
return false;
3232
}
3333

34-
if (callee.type === 'Identifier' && ['$props', '$derived'].includes(callee.name)) {
34+
if (callee.type === 'Identifier' && excludedRunes.includes(callee.name)) {
3535
return true;
3636
}
3737

3838
if (callee.type !== 'MemberExpression' || callee.object.type !== 'Identifier') {
3939
return false;
4040
}
4141

42-
if (
43-
callee.object.name === '$derived' &&
44-
callee.property.type === 'Identifier' &&
45-
callee.property.name === 'by'
46-
) {
42+
if (excludedRunes.includes(callee.object.name)) {
4743
return true;
4844
}
4945

@@ -58,16 +54,35 @@ export default createRule('prefer-const', {
5854
category: 'Best Practices',
5955
recommended: false,
6056
extensionRule: 'prefer-const'
61-
}
57+
},
58+
schema: [
59+
{
60+
type: 'object',
61+
properties: {
62+
destructuring: { enum: ['any', 'all'] },
63+
ignoreReadBeforeAssign: { type: 'boolean' },
64+
excludedRunes: {
65+
type: 'array',
66+
items: {
67+
type: 'string'
68+
}
69+
}
70+
},
71+
additionalProperties: false
72+
}
73+
]
6274
},
6375
create(context) {
76+
const config = context.options[0] ?? {};
77+
const excludedRunes = config.excludedRunes ?? ['$props', '$derived'];
78+
6479
return defineWrapperListener(coreRule, context, {
6580
createListenerProxy(coreListener) {
6681
return {
6782
...coreListener,
6883
VariableDeclaration(node) {
6984
for (const decl of node.declarations) {
70-
if (shouldSkipDeclaration(decl.init)) {
85+
if (shouldSkipDeclaration(decl.init, excludedRunes)) {
7186
return;
7287
}
7388
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"options": [{ "excludedRunes": [] }]
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
- message: "'prop1' is never reassigned. Use 'const' instead."
2+
line: 2
3+
column: 8
4+
suggestions: null
5+
- message: "'prop2' is never reassigned. Use 'const' instead."
6+
line: 2
7+
column: 15
8+
suggestions: null
9+
- message: "'zero' is never reassigned. Use 'const' instead."
10+
line: 3
11+
column: 6
12+
suggestions: null
13+
- message: "'derived' is never reassigned. Use 'const' instead."
14+
line: 4
15+
column: 6
16+
suggestions: null
17+
- message: "'derivedBy' is never reassigned. Use 'const' instead."
18+
line: 5
19+
column: 6
20+
suggestions: null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<script>
2+
let { prop1, prop2 } = $props();
3+
let zero = $state(0);
4+
let derived = $derived(zero * 2);
5+
let derivedBy = $derived.by(calc());
6+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<script>
2+
const { prop1, prop2 } = $props();
3+
const zero = $state(0);
4+
const derived = $derived(zero * 2);
5+
const derivedBy = $derived.by(calc());
6+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"options": [{ "excludedRunes": ["$state"] }]
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
- message: "'prop1' is never reassigned. Use 'const' instead."
2+
line: 2
3+
column: 8
4+
suggestions: null
5+
- message: "'prop2' is never reassigned. Use 'const' instead."
6+
line: 2
7+
column: 15
8+
suggestions: null
9+
- message: "'derived' is never reassigned. Use 'const' instead."
10+
line: 4
11+
column: 6
12+
suggestions: null
13+
- message: "'derivedBy' is never reassigned. Use 'const' instead."
14+
line: 5
15+
column: 6
16+
suggestions: null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<script>
2+
let { prop1, prop2 } = $props();
3+
let zero = $state(0);
4+
let derived = $derived(zero * 2);
5+
let derivedBy = $derived.by(calc());
6+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<script>
2+
const { prop1, prop2 } = $props();
3+
let zero = $state(0);
4+
const derived = $derived(zero * 2);
5+
const derivedBy = $derived.by(calc());
6+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"options": [{ "excludedRunes": [] }]
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<script>
2+
const { prop1, prop2 } = $props();
3+
const zero = $state(0);
4+
const derived = $derived(zero * 2);
5+
const derivedBy = $derived.by(calc());
6+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"options": [{ "excludedRunes": ["$props", "$derived", "$state"] }]
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<script>
2+
let { prop1, prop2 } = $props();
3+
let zero = $state(0);
4+
let derived = $derived(zero * 2);
5+
let derivedBy = $derived.by(calc());
6+
</script>
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
<script>
2-
const a = {};
2+
let { prop1, prop2 } = $props();
3+
const zero = $state(0);
4+
let derived = $derived(zero * 2);
5+
let derivedBy = $derived.by(calc());
36
</script>

0 commit comments

Comments
 (0)