Skip to content

Commit f5775b7

Browse files
committed
feat: signal-prefer-let rule
1 parent f8f377f commit f5775b7

File tree

12 files changed

+154
-0
lines changed

12 files changed

+154
-0
lines changed

.changeset/proud-donuts-tickle.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'eslint-plugin-svelte': minor
3+
---
4+
5+
New svelte/signal-prefer-let rule

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,7 @@ These rules relate to better ways of doing things to help you avoid problems:
430430
| [svelte/require-event-dispatcher-types](https://sveltejs.github.io/eslint-plugin-svelte/rules/require-event-dispatcher-types/) | require type parameters for `createEventDispatcher` | |
431431
| [svelte/require-optimized-style-attribute](https://sveltejs.github.io/eslint-plugin-svelte/rules/require-optimized-style-attribute/) | require style attributes that can be optimized | |
432432
| [svelte/require-stores-init](https://sveltejs.github.io/eslint-plugin-svelte/rules/require-stores-init/) | require initial value in store | |
433+
| [svelte/signal-prefer-let](https://sveltejs.github.io/eslint-plugin-svelte/rules/signal-prefer-let/) | use let instead of const for signals values | :wrench: |
433434
| [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 | |
434435

435436
## Stylistic Issues

docs/rules.md

+1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ These rules relate to better ways of doing things to help you avoid problems:
6767
| [svelte/require-event-dispatcher-types](./rules/require-event-dispatcher-types.md) | require type parameters for `createEventDispatcher` | |
6868
| [svelte/require-optimized-style-attribute](./rules/require-optimized-style-attribute.md) | require style attributes that can be optimized | |
6969
| [svelte/require-stores-init](./rules/require-stores-init.md) | require initial value in store | |
70+
| [svelte/signal-prefer-let](./rules/signal-prefer-let.md) | use let instead of const for signals values | :wrench: |
7071
| [svelte/valid-each-key](./rules/valid-each-key.md) | enforce keys to use variables defined in the `{#each}` block | |
7172

7273
## Stylistic Issues

docs/rules/signal-prefer-let.md

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
pageClass: 'rule-details'
3+
sidebarDepth: 0
4+
title: 'svelte/signal-prefer-let'
5+
description: 'use let instead of const for signals values'
6+
---
7+
8+
# svelte/signal-prefer-let
9+
10+
> use let instead of const for signals values
11+
12+
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> **_This rule has not been released yet._** </badge>
13+
- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.
14+
15+
## :book: Rule Details
16+
17+
This rule reports whenever a signal is assigned to a const.
18+
In JavaScript `const` are defined as immutable references which cannot be reassigned.
19+
Signals are by definition changing and are reassigned by Svelte's reactivity system.
20+
21+
<ESLintCodeBlock fix>
22+
23+
<!--eslint-skip-->
24+
25+
```svelte
26+
<script>
27+
/* eslint svelte/signal-prefer-let: "error" */
28+
29+
/* ✓ GOOD */
30+
let { value } = $props();
31+
32+
let doubled = $derived(value * 2);
33+
34+
/* ✗ BAD */
35+
const { value } = $props();
36+
37+
const doubled = $derived(value * 2);
38+
</script>
39+
```
40+
41+
</ESLintCodeBlock>
42+
43+
## :wrench: Options
44+
45+
Nothing
46+
47+
## :mag: Implementation
48+
49+
- [Rule source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/src/rules/signal-prefer-let.ts)
50+
- [Test source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/tests/src/rules/signal-prefer-let.ts)

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

+5
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,11 @@ export interface RuleOptions {
299299
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/shorthand-directive/
300300
*/
301301
'svelte/shorthand-directive'?: Linter.RuleEntry<SvelteShorthandDirective>
302+
/**
303+
* use let instead of const for signals values
304+
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/signal-prefer-let/
305+
*/
306+
'svelte/signal-prefer-let'?: Linter.RuleEntry<[]>
302307
/**
303308
* enforce order of attributes
304309
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/sort-attributes/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import type { TSESTree } from '@typescript-eslint/types';
2+
import { createRule } from '../utils';
3+
4+
export default createRule('signal-prefer-let', {
5+
meta: {
6+
docs: {
7+
description: 'use let instead of const for signals values',
8+
category: 'Best Practices',
9+
recommended: false
10+
},
11+
schema: [],
12+
messages: {
13+
useLet: "const is used for a signal value. Use 'let' instead."
14+
},
15+
type: 'suggestion',
16+
fixable: 'code'
17+
},
18+
create(context) {
19+
function preferLet(node: TSESTree.VariableDeclaration) {
20+
if (node.kind !== 'const') {
21+
return;
22+
}
23+
context.report({
24+
node,
25+
messageId: 'useLet',
26+
fix: (fixer) => fixer.replaceTextRange([node.range[0], node.range[0] + 5], 'let')
27+
});
28+
}
29+
30+
return {
31+
'VariableDeclaration > VariableDeclarator > CallExpression > Identifier'(
32+
node: TSESTree.Identifier
33+
) {
34+
if (['$props', '$derived', '$state'].includes(node.name)) {
35+
preferLet(node.parent.parent?.parent as TSESTree.VariableDeclaration);
36+
}
37+
},
38+
'VariableDeclaration > VariableDeclarator > CallExpression > MemberExpression > Identifier'(
39+
node: TSESTree.Identifier
40+
) {
41+
if (
42+
node.name === 'by' &&
43+
((node.parent as TSESTree.MemberExpression).object as TSESTree.Identifier).name ===
44+
'$derived'
45+
) {
46+
preferLet(node.parent.parent?.parent?.parent as TSESTree.VariableDeclaration);
47+
}
48+
}
49+
};
50+
}
51+
});

packages/eslint-plugin-svelte/src/utils/rules.ts

+2
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import requireStoreReactiveAccess from '../rules/require-store-reactive-access';
5959
import requireStoresInit from '../rules/require-stores-init';
6060
import shorthandAttribute from '../rules/shorthand-attribute';
6161
import shorthandDirective from '../rules/shorthand-directive';
62+
import signalPreferLet from '../rules/signal-prefer-let';
6263
import sortAttributes from '../rules/sort-attributes';
6364
import spacedHtmlComment from '../rules/spaced-html-comment';
6465
import system from '../rules/system';
@@ -124,6 +125,7 @@ export const rules = [
124125
requireStoresInit,
125126
shorthandAttribute,
126127
shorthandDirective,
128+
signalPreferLet,
127129
sortAttributes,
128130
spacedHtmlComment,
129131
system,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
- message: "const is used for a signal value. Use 'let' instead."
2+
line: 2
3+
column: 2
4+
- message: "const is used for a signal value. Use 'let' instead."
5+
line: 3
6+
column: 2
7+
- message: "const is used for a signal value. Use 'let' instead."
8+
line: 4
9+
column: 2
10+
- message: "const is used for a signal value. Use 'let' instead."
11+
line: 5
12+
column: 2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<script>
2+
const { prop } = $props();
3+
const state = $state();
4+
const derived = $derived(state + 1);
5+
const derivedBy = $derived.by(() => prop + state);
6+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<script>
2+
let { prop } = $props();
3+
let state = $state();
4+
let derived = $derived(state + 1);
5+
let derivedBy = $derived.by(() => prop + state);
6+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<script>
2+
let { value } = $props();
3+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { RuleTester } from '../../utils/eslint-compat';
2+
import rule from '../../../src/rules/signal-prefer-let';
3+
import { loadTestCases } from '../../utils/utils';
4+
5+
const tester = new RuleTester({
6+
languageOptions: {
7+
ecmaVersion: 2020,
8+
sourceType: 'module'
9+
}
10+
});
11+
12+
tester.run('signal-prefer-let', rule as any, loadTestCases('signal-prefer-let'));

0 commit comments

Comments
 (0)