Skip to content

Commit 17229ae

Browse files
committed
add allowReassign option
1 parent 1640da2 commit 17229ae

File tree

8 files changed

+104
-4
lines changed

8 files changed

+104
-4
lines changed

Diff for: docs/rules/no-unnecessary-state-wrap.md

+20-1
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,15 @@ Therefore, wrapping them with `$state` is unnecessary and can lead to confusion.
6565
"svelte/no-unnecessary-state-wrap": [
6666
"error",
6767
{
68-
"additionalReactiveClasses": []
68+
"additionalReactiveClasses": [],
69+
"allowReassign": false
6970
}
7071
]
7172
}
7273
```
7374

7475
- `additionalReactiveClasses` ... An array of class names that should also be considered reactive. This is useful when you have custom classes that are inherently reactive. Default is `[]`.
76+
- `allowReassign` ... If `true`, allows `$state` wrapping of reactive classes when the variable is reassigned. Default is `false`.
7577

7678
### Examples with Options
7779

@@ -90,6 +92,23 @@ Therefore, wrapping them with `$state` is unnecessary and can lead to confusion.
9092
</script>
9193
```
9294

95+
#### `allowReassign`
96+
97+
```svelte
98+
<script>
99+
/* eslint svelte/no-unnecessary-state-wrap: ["error", { "allowReassign": true }] */
100+
import { SvelteSet } from 'svelte/reactivity';
101+
102+
// ✓ GOOD
103+
let set1 = $state(new SvelteSet());
104+
set1 = new SvelteSet([1, 2, 3]); // Variable is reassigned
105+
106+
// ✗ BAD
107+
const set2 = $state(new SvelteSet()); // const cannot be reassigned
108+
let set3 = $state(new SvelteSet()); // Variable is never reassigned
109+
</script>
110+
```
111+
93112
## :mag: Implementation
94113

95114
- [Rule source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/packages/eslint-plugin-svelte/src/rules/no-unnecessary-state-wrap.ts)

Diff for: packages/eslint-plugin-svelte/src/rule-types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,7 @@ type SvelteNoUnknownStyleDirectiveProperty = []|[{
516516
// ----- svelte/no-unnecessary-state-wrap -----
517517
type SvelteNoUnnecessaryStateWrap = []|[{
518518
additionalReactiveClasses?: string[]
519+
allowReassign?: boolean
519520
}]
520521
// ----- svelte/no-unused-class-name -----
521522
type SvelteNoUnusedClassName = []|[{

Diff for: packages/eslint-plugin-svelte/src/rules/no-unnecessary-state-wrap.ts

+21-3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ export default createRule('no-unnecessary-state-wrap', {
2929
type: 'string'
3030
},
3131
uniqueItems: true
32+
},
33+
allowReassign: {
34+
type: 'boolean'
3235
}
3336
},
3437
additionalProperties: false
@@ -50,6 +53,7 @@ export default createRule('no-unnecessary-state-wrap', {
5053
create(context) {
5154
const options = context.options[0] ?? {};
5255
const additionalReactiveClasses = options.additionalReactiveClasses ?? [];
56+
const allowReassign = options.allowReassign ?? false;
5357

5458
const referenceTracker = new ReferenceTracker(getSourceCode(context).scopeManager.globalScope!);
5559
const traceMap: Record<string, Record<string, boolean>> = {};
@@ -75,11 +79,25 @@ export default createRule('no-unnecessary-state-wrap', {
7579
};
7680
});
7781

82+
function isReassigned(identifier: TSESTree.Identifier): boolean {
83+
const variable = getSourceCode(context).scopeManager.getDeclaredVariables(
84+
identifier.parent
85+
)[0];
86+
return variable.references.some((ref) => {
87+
return ref.isWrite() && ref.identifier !== identifier;
88+
});
89+
}
90+
7891
function reportUnnecessaryStateWrap(
7992
stateNode: TSESTree.Node,
8093
targetNode: TSESTree.Node,
81-
className: string
94+
className: string,
95+
identifier?: TSESTree.Identifier
8296
) {
97+
if (allowReassign && identifier && isReassigned(identifier)) {
98+
return;
99+
}
100+
83101
context.report({
84102
node: targetNode,
85103
messageId: 'unnecessaryStateWrap',
@@ -112,7 +130,7 @@ export default createRule('no-unnecessary-state-wrap', {
112130
if (additionalReactiveClasses.includes(name)) {
113131
const parent = node.parent;
114132
if (parent?.type === 'VariableDeclarator' && parent.id.type === 'Identifier') {
115-
reportUnnecessaryStateWrap(node, arg, name);
133+
reportUnnecessaryStateWrap(node, arg, name, parent.id);
116134
}
117135
}
118136
}
@@ -128,7 +146,7 @@ export default createRule('no-unnecessary-state-wrap', {
128146
) {
129147
const parent = node.parent.parent;
130148
if (parent?.type === 'VariableDeclarator' && parent.id.type === 'Identifier') {
131-
reportUnnecessaryStateWrap(node.parent, node, name);
149+
reportUnnecessaryStateWrap(node.parent, node, name, parent.id);
132150
}
133151
}
134152
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"options": [
3+
{
4+
"allowReassign": true
5+
}
6+
]
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
- message: SvelteSet is already reactive, $state wrapping is unnecessary.
2+
line: 6
3+
column: 21
4+
suggestions:
5+
- desc: Remove unnecessary $state wrapping
6+
messageId: suggestRemoveStateWrap
7+
output: |
8+
<script>
9+
import { SvelteSet, SvelteMap } from 'svelte/reactivity';
10+
11+
// These should be reported as unnecessary $state wrapping
12+
// even with allowReassign: true because they are not reassigned
13+
const set = new SvelteSet();
14+
let map = $state(new SvelteMap());
15+
</script>
16+
- message: SvelteMap is already reactive, $state wrapping is unnecessary.
17+
line: 7
18+
column: 19
19+
suggestions:
20+
- desc: Remove unnecessary $state wrapping
21+
messageId: suggestRemoveStateWrap
22+
output: |
23+
<script>
24+
import { SvelteSet, SvelteMap } from 'svelte/reactivity';
25+
26+
// These should be reported as unnecessary $state wrapping
27+
// even with allowReassign: true because they are not reassigned
28+
const set = $state(new SvelteSet());
29+
let map = new SvelteMap();
30+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script>
2+
import { SvelteSet, SvelteMap } from 'svelte/reactivity';
3+
4+
// These should be reported as unnecessary $state wrapping
5+
// even with allowReassign: true because they are not reassigned
6+
const set = $state(new SvelteSet());
7+
let map = $state(new SvelteMap());
8+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"options": [
3+
{
4+
"allowReassign": true
5+
}
6+
]
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<script>
2+
import { SvelteSet, SvelteMap } from 'svelte/reactivity';
3+
4+
// These should be allowed when allowReassign is true and variables are reassigned
5+
let set = $state(new SvelteSet());
6+
set = new SvelteSet([1, 2, 3]);
7+
8+
let map = $state(new SvelteMap());
9+
map = new SvelteMap([['key', 'value']]);
10+
</script>

0 commit comments

Comments
 (0)