Skip to content

Commit 067717c

Browse files
committed
feat(prefer-svelte-reactivity): ignoring variables encapsulated in classes
1 parent 8fa88f4 commit 067717c

File tree

1 file changed

+72
-17
lines changed

1 file changed

+72
-17
lines changed

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

Lines changed: 72 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ import type { TSESTree } from '@typescript-eslint/types';
44
import { findVariable, isIn } from '../utils/ast-utils.js';
55
import { getSvelteContext } from '../utils/svelte-context.js';
66

7+
type FunctionLike =
8+
| TSESTree.ArrowFunctionExpression
9+
| TSESTree.FunctionDeclaration
10+
| TSESTree.MethodDefinition;
11+
12+
type VariableLike = TSESTree.VariableDeclarator | TSESTree.PropertyDefinition;
13+
714
export default createRule('prefer-svelte-reactivity', {
815
meta: {
916
docs: {
@@ -44,10 +51,7 @@ export default createRule('prefer-svelte-reactivity', {
4451
create(context) {
4552
const ignoreEncapsulatedLocalVariables =
4653
context.options[0]?.ignoreEncapsulatedLocalVariables ?? true;
47-
const returnedVariables: Map<
48-
TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclaration,
49-
TSESTree.VariableDeclarator[]
50-
> = new Map();
54+
const returnedVariables: Map<FunctionLike, VariableLike[]> = new Map();
5155
const exportedVars: TSESTree.Node[] = [];
5256
return {
5357
...(getSvelteContext(context)?.svelteFileType === '.svelte.[js|ts]' && {
@@ -85,18 +89,35 @@ export default createRule('prefer-svelte-reactivity', {
8589
if (enclosingFunction === null) {
8690
return;
8791
}
88-
const variable = findVariable(context, node);
89-
if (
90-
variable === null ||
91-
variable.identifiers.length < 1 ||
92-
variable.identifiers[0].parent.type !== 'VariableDeclarator'
93-
) {
92+
let variableDeclaration = null;
93+
if (node.parent.type === 'MemberExpression') {
94+
const enclosingClassBody = findEnclosingClassBody(node);
95+
for (const classElement of enclosingClassBody?.body ?? []) {
96+
if (
97+
classElement.type === 'PropertyDefinition' &&
98+
classElement.key.type === 'Identifier' &&
99+
node.name === classElement.key.name
100+
) {
101+
variableDeclaration = classElement;
102+
}
103+
}
104+
} else {
105+
const variable = findVariable(context, node);
106+
if (
107+
variable !== null &&
108+
variable.identifiers.length > 0 &&
109+
variable.identifiers[0].parent.type === 'VariableDeclarator'
110+
) {
111+
variableDeclaration = variable.identifiers[0].parent;
112+
}
113+
}
114+
if (variableDeclaration === null) {
94115
return;
95116
}
96117
if (!returnedVariables.has(enclosingFunction)) {
97118
returnedVariables.set(enclosingFunction, []);
98119
}
99-
returnedVariables.get(enclosingFunction)?.push(variable.identifiers[0].parent);
120+
returnedVariables.get(enclosingFunction)?.push(variableDeclaration);
100121
},
101122
'Program:exit'() {
102123
const referenceTracker = new ReferenceTracker(context.sourceCode.scopeManager.globalScope!);
@@ -143,9 +164,12 @@ export default createRule('prefer-svelte-reactivity', {
143164
});
144165
}
145166
}
167+
const enclosingPropertyDefinition = findEnclosingPropertyDefinition(node);
146168
if (
147169
findEnclosingReturn(node) !== null ||
148-
findEnclosingPropertyDefinition(node)?.accessibility === 'public'
170+
(enclosingPropertyDefinition !== null &&
171+
(!ignoreEncapsulatedLocalVariables ||
172+
!isPropertyEncapsulated(enclosingPropertyDefinition, returnedVariables)))
149173
) {
150174
context.report({
151175
messageId,
@@ -207,10 +231,18 @@ function findAncestorOfTypes<T extends string>(
207231
return findAncestorOfTypes(node.parent, types);
208232
}
209233

234+
function findEnclosingClassBody(node: TSESTree.Node): TSESTree.ClassBody | null {
235+
return findAncestorOfTypes(node, ['ClassBody']);
236+
}
237+
210238
function findEnclosingFunction(
211239
node: TSESTree.Node
212240
): TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclaration | null {
213-
return findAncestorOfTypes(node, ['ArrowFunctionExpression', 'FunctionDeclaration']);
241+
return findAncestorOfTypes(node, [
242+
'ArrowFunctionExpression',
243+
'FunctionDeclaration',
244+
'MethodDefinition'
245+
]);
214246
}
215247

216248
function findEnclosingPropertyDefinition(node: TSESTree.Node): TSESTree.PropertyDefinition | null {
@@ -222,10 +254,7 @@ function findEnclosingReturn(node: TSESTree.Node): TSESTree.ReturnStatement | nu
222254
}
223255

224256
function isLocalVarEncapsulated(
225-
returnedVariables: Map<
226-
TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclaration,
227-
TSESTree.VariableDeclarator[]
228-
>,
257+
returnedVariables: Map<FunctionLike, VariableLike[]>,
229258
node: TSESTree.Node
230259
): boolean {
231260
const enclosingFunction = findEnclosingFunction(node);
@@ -237,6 +266,32 @@ function isLocalVarEncapsulated(
237266
);
238267
}
239268

269+
function methodReturnsProperty(
270+
method: TSESTree.MethodDefinition,
271+
property: TSESTree.PropertyDefinition,
272+
returnedVariables: Map<FunctionLike, VariableLike[]>
273+
): boolean {
274+
return returnedVariables.get(method)?.includes(property) ?? false;
275+
}
276+
277+
function isPropertyEncapsulated(
278+
node: TSESTree.PropertyDefinition,
279+
returnedVariables: Map<FunctionLike, VariableLike[]>
280+
): boolean {
281+
if (node.accessibility === 'public') {
282+
return false;
283+
}
284+
for (const classElement of node.parent.body) {
285+
if (
286+
classElement.type === 'MethodDefinition' &&
287+
methodReturnsProperty(classElement, node, returnedVariables)
288+
) {
289+
return false;
290+
}
291+
}
292+
return true;
293+
}
294+
240295
function isDateMutable(referenceTracker: ReferenceTracker, ctorNode: TSESTree.Expression): boolean {
241296
return !referenceTracker
242297
.iteratePropertyReferences(ctorNode, {

0 commit comments

Comments
 (0)