Skip to content

Commit

Permalink
feat: signal-prefer-let rule
Browse files Browse the repository at this point in the history
  • Loading branch information
bfanger committed Jun 26, 2024
1 parent f8f377f commit fe63e88
Show file tree
Hide file tree
Showing 10 changed files with 158 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
50 changes: 50 additions & 0 deletions docs/rules/signal-prefer-let.md
Original file line number Diff line number Diff line change
@@ -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: <badge text="This rule has not been released yet." vertical="middle" type="error"> **_This rule has not been released yet._** </badge>
- :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.

<ESLintCodeBlock>

<!--eslint-skip-->

```svelte
<script>
/* eslint svelte/signal-prefer-let: "error" */
/* ✓ GOOD */
let { value } = $props();
let doubled = $derived(value * 2);
/* ✗ BAD */
const { value } = $props();
const doubled = $derived(value * 2);
</script>
```

</ESLintCodeBlock>

## :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)
5 changes: 5 additions & 0 deletions packages/eslint-plugin-svelte/src/rule-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,11 @@ export interface RuleOptions {
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/shorthand-directive/
*/
'svelte/shorthand-directive'?: Linter.RuleEntry<SvelteShorthandDirective>
/**
* 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/
Expand Down
57 changes: 57 additions & 0 deletions packages/eslint-plugin-svelte/src/rules/signal-prefer-let.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
};
}
});
2 changes: 2 additions & 0 deletions packages/eslint-plugin-svelte/src/utils/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -124,6 +125,7 @@ export const rules = [
requireStoresInit,
shorthandAttribute,
shorthandDirective,
signalPreferLet,
sortAttributes,
spacedHtmlComment,
system,
Expand Down
Original file line number Diff line number Diff line change
@@ -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: |
<script>
let { value, fn } = $props();
const x = $derived.by(fn);
</script>
- message: "const is used for a signal value. Use 'let' instead."
line: 4
column: 2
suggestions:
- desc: "Replace 'const' with 'let'"
output: |
<script>
const { value, fn } = $props();
let x = $derived.by(fn);
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<script>
const { value, fn } = $props();
const x = $derived.by(fn);
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<script>
let { value } = $props();
</script>
12 changes: 12 additions & 0 deletions packages/eslint-plugin-svelte/tests/src/rules/signal-prefer-let.ts
Original file line number Diff line number Diff line change
@@ -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'));

0 comments on commit fe63e88

Please sign in to comment.