Skip to content

Commit 1645a9e

Browse files
authored
fix: false positives for mutable member in svelte/no-immutable-reactive-statements rule (#581)
1 parent 98986a1 commit 1645a9e

File tree

4 files changed

+70
-19
lines changed

4 files changed

+70
-19
lines changed

.changeset/hip-donkeys-collect.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-svelte": patch
3+
---
4+
5+
fix: false positives for mutable member in `svelte/no-immutable-reactive-statements` rule

.prettierignore

+1
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ build
44
/lib
55
.npmrc
66
/tests/fixtures/rules/indent/valid/
7+
.changeset

src/rules/no-immutable-reactive-statements.ts

+54-19
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { AST } from 'svelte-eslint-parser';
22
import { createRule } from '../utils';
33
import type { Scope, Variable, Reference, Definition } from '@typescript-eslint/scope-manager';
4+
import type { TSESTree } from '@typescript-eslint/types';
45

56
export default createRule('no-immutable-reactive-statements', {
67
meta: {
@@ -55,45 +56,79 @@ export default createRule('no-immutable-reactive-statements', {
5556
// Global variables are assumed to be immutable.
5657
return true;
5758
}
58-
const isMutable = variable.defs.some((def) => {
59-
if (def.type === 'Variable') {
60-
const parent = def.parent;
61-
if (parent.kind === 'const') {
62-
return false;
63-
}
64-
const pp = parent.parent;
65-
if (pp && pp.type === 'ExportNamedDeclaration' && pp.declaration === parent) {
66-
// Props
67-
return true;
68-
}
69-
return hasWrite(variable);
70-
}
59+
const isMutableDefine = variable.defs.some((def) => {
7160
if (def.type === 'ImportBinding') {
7261
return false;
7362
}
74-
7563
if (def.node.type === 'AssignmentExpression') {
7664
// Reactive values
7765
return true;
7866
}
67+
if (def.type === 'Variable') {
68+
const parent = def.parent;
69+
if (parent.kind === 'const') {
70+
if (
71+
def.node.init &&
72+
(def.node.init.type === 'FunctionExpression' ||
73+
def.node.init.type === 'ArrowFunctionExpression' ||
74+
def.node.init.type === 'Literal')
75+
) {
76+
return false;
77+
}
78+
} else {
79+
const pp = parent.parent;
80+
if (pp && pp.type === 'ExportNamedDeclaration' && pp.declaration === parent) {
81+
// Props
82+
return true;
83+
}
84+
}
85+
return hasWrite(variable);
86+
}
7987
return false;
8088
});
81-
cacheMutableVariable.set(variable, isMutable);
82-
return isMutable;
89+
cacheMutableVariable.set(variable, isMutableDefine);
90+
return isMutableDefine;
8391
}
8492

8593
/** Checks whether the given variable has a write or reactive store reference or not. */
8694
function hasWrite(variable: Variable) {
8795
const defIds = variable.defs.map((def: Definition) => def.name);
88-
return variable.references.some(
89-
(reference) =>
96+
for (const reference of variable.references) {
97+
if (
9098
reference.isWrite() &&
9199
!defIds.some(
92100
(defId) =>
93101
defId.range[0] <= reference.identifier.range[0] &&
94102
reference.identifier.range[1] <= defId.range[1]
95103
)
96-
);
104+
) {
105+
return true;
106+
}
107+
if (isMutableMember(reference.identifier)) {
108+
return true;
109+
}
110+
}
111+
return false;
112+
113+
function isMutableMember(
114+
expr: TSESTree.Identifier | TSESTree.JSXIdentifier | TSESTree.MemberExpression
115+
): boolean {
116+
if (expr.type === 'JSXIdentifier') return false;
117+
const parent = expr.parent;
118+
if (parent.type === 'AssignmentExpression') {
119+
return parent.left === expr;
120+
}
121+
if (parent.type === 'UpdateExpression') {
122+
return parent.argument === expr;
123+
}
124+
if (parent.type === 'UnaryExpression') {
125+
return parent.operator === 'delete' && parent.argument === expr;
126+
}
127+
if (parent.type === 'MemberExpression') {
128+
return parent.object === expr && isMutableMember(parent);
129+
}
130+
return false;
131+
}
97132
}
98133

99134
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<script>
2+
const array = [1];
3+
const object = { b: 1 };
4+
$: a = array[0];
5+
$: b = object.b;
6+
</script>
7+
8+
{a}{b}
9+
<button on:click={() => (array[0] = 2)}>Foo</button>
10+
<button on:click={() => (object.b = 2)}>Foo</button>

0 commit comments

Comments
 (0)