Skip to content

Commit 5bb0f1d

Browse files
committed
feat(prefer-svelte-reactivity): ignoring variables encapsulated in functions
1 parent a114e28 commit 5bb0f1d

File tree

4 files changed

+55
-3
lines changed

4 files changed

+55
-3
lines changed

.changeset/long-ghosts-prove.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'eslint-plugin-svelte': minor
3+
---
4+
5+
feat(prefer-svelte-reactivity): ignoring variables encapsulated in functions

docs/rules/prefer-svelte-reactivity.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,18 @@ export default e;
105105

106106
## :wrench: Options
107107

108-
Nothing.
108+
```json
109+
{
110+
"svelte/prefer-svelte-reactivity": [
111+
"error",
112+
{
113+
"ignoreEncapsulatedLocalVariables": true
114+
}
115+
]
116+
}
117+
```
118+
119+
- `ignoreEncapsulatedLocalVariables` ... Whether to ignore variables that are defined inside a function and aren't returned, thus being encapsulated in the function. Default `true`.
109120

110121
## :books: Further Reading
111122

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ export interface RuleOptions {
320320
* disallow using mutable instances of built-in classes where a reactive alternative is provided by svelte/reactivity
321321
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/prefer-svelte-reactivity/
322322
*/
323-
'svelte/prefer-svelte-reactivity'?: Linter.RuleEntry<[]>
323+
'svelte/prefer-svelte-reactivity'?: Linter.RuleEntry<SveltePreferSvelteReactivity>
324324
/**
325325
* Prefer using writable $derived instead of $state and $effect
326326
* @see https://sveltejs.github.io/eslint-plugin-svelte/rules/prefer-writable-derived/
@@ -580,6 +580,10 @@ type SveltePreferConst = []|[{
580580
excludedRunes?: string[]
581581
[k: string]: unknown | undefined
582582
}]
583+
// ----- svelte/prefer-svelte-reactivity -----
584+
type SveltePreferSvelteReactivity = []|[{
585+
ignoreEncapsulatedLocalVariables?: boolean
586+
}]
583587
// ----- svelte/require-event-prefix -----
584588
type SvelteRequireEventPrefix = []|[{
585589
checkAsyncFunctions?: boolean

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

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,18 @@ export default createRule('prefer-svelte-reactivity', {
1212
category: 'Possible Errors',
1313
recommended: true
1414
},
15-
schema: [],
15+
schema: [
16+
{
17+
type: 'object',
18+
properties: {
19+
ignoreEncapsulatedLocalVariables: {
20+
type: 'boolean',
21+
default: true
22+
}
23+
},
24+
additionalProperties: false
25+
}
26+
],
1627
messages: {
1728
mutableDateUsed:
1829
'Found a mutable instance of the built-in Date class. Use SvelteDate instead.',
@@ -31,6 +42,8 @@ export default createRule('prefer-svelte-reactivity', {
3142
]
3243
},
3344
create(context) {
45+
const ignoreEncapsulatedLocalVariables =
46+
context.options[0]?.ignoreEncapsulatedLocalVariables ?? true;
3447
const returnedVariables: Map<
3548
TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclaration,
3649
TSESTree.VariableDeclarator[]
@@ -136,6 +149,9 @@ export default createRule('prefer-svelte-reactivity', {
136149
node
137150
});
138151
}
152+
if (ignoreEncapsulatedLocalVariables && isEncapsulated(returnedVariables, node)) {
153+
continue;
154+
}
139155
if (path[0] === 'Date' && isDateMutable(referenceTracker, node as TSESTree.Expression)) {
140156
context.report({
141157
messageId: 'mutableDateUsed',
@@ -198,6 +214,22 @@ function findEnclosingFunction(
198214
return findAncestorOfTypes(node, ['ArrowFunctionExpression', 'FunctionDeclaration']);
199215
}
200216

217+
function isEncapsulated(
218+
returnedVariables: Map<
219+
TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclaration,
220+
TSESTree.VariableDeclarator[]
221+
>,
222+
node: TSESTree.Node
223+
): boolean {
224+
const enclosingFunction = findEnclosingFunction(node);
225+
if (enclosingFunction === null) {
226+
return false;
227+
}
228+
return (
229+
returnedVariables.get(enclosingFunction)?.some((variable) => isIn(node, variable)) !== true
230+
);
231+
}
232+
201233
function isDateMutable(referenceTracker: ReferenceTracker, ctorNode: TSESTree.Expression): boolean {
202234
return !referenceTracker
203235
.iteratePropertyReferences(ctorNode, {

0 commit comments

Comments
 (0)