Skip to content

Commit 61cb289

Browse files
committed
feat(prefer-svelte-reactivity): tracking variables through function calls
1 parent 7f87fa9 commit 61cb289

File tree

1 file changed

+50
-13
lines changed

1 file changed

+50
-13
lines changed

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

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export default createRule('prefer-svelte-reactivity', {
5151
create(context) {
5252
const ignoreEncapsulatedLocalVariables =
5353
context.options[0]?.ignoreEncapsulatedLocalVariables ?? true;
54+
const returnedFunctionCalls: Map<FunctionLike, TSESTree.MethodDefinition[]> = new Map();
5455
const returnedVariables: Map<FunctionLike, VariableLike[]> = new Map();
5556
const exportedVars: TSESTree.Node[] = [];
5657
return {
@@ -81,6 +82,29 @@ export default createRule('prefer-svelte-reactivity', {
8182
}
8283
}),
8384
Identifier(node) {
85+
function recordVariable(enclosingFunction: FunctionLike, variable: VariableLike): void {
86+
if (variable === null) {
87+
return;
88+
}
89+
if (!returnedVariables.has(enclosingFunction)) {
90+
returnedVariables.set(enclosingFunction, []);
91+
}
92+
returnedVariables.get(enclosingFunction)?.push(variable);
93+
}
94+
95+
function recordFunctionCall(
96+
enclosingFunction: FunctionLike,
97+
functionCall: TSESTree.MethodDefinition
98+
): void {
99+
if (functionCall === null) {
100+
return;
101+
}
102+
if (!returnedFunctionCalls.has(enclosingFunction)) {
103+
returnedFunctionCalls.set(enclosingFunction, []);
104+
}
105+
returnedFunctionCalls.get(enclosingFunction)?.push(functionCall);
106+
}
107+
84108
const enclosingReturn = findEnclosingReturn(node);
85109
if (enclosingReturn === null) {
86110
return;
@@ -89,7 +113,6 @@ export default createRule('prefer-svelte-reactivity', {
89113
if (enclosingFunction === null) {
90114
return;
91115
}
92-
let variableDeclaration = null;
93116
if (node.parent.type === 'MemberExpression') {
94117
const enclosingClassBody = findEnclosingClassBody(node);
95118
for (const classElement of enclosingClassBody?.body ?? []) {
@@ -98,7 +121,14 @@ export default createRule('prefer-svelte-reactivity', {
98121
classElement.key.type === 'Identifier' &&
99122
node.name === classElement.key.name
100123
) {
101-
variableDeclaration = classElement;
124+
recordVariable(enclosingFunction, classElement);
125+
}
126+
if (
127+
classElement.type === 'MethodDefinition' &&
128+
classElement.key.type === 'Identifier' &&
129+
node.name === classElement.key.name
130+
) {
131+
recordFunctionCall(enclosingFunction, classElement);
102132
}
103133
}
104134
} else {
@@ -108,16 +138,9 @@ export default createRule('prefer-svelte-reactivity', {
108138
variable.identifiers.length > 0 &&
109139
variable.identifiers[0].parent.type === 'VariableDeclarator'
110140
) {
111-
variableDeclaration = variable.identifiers[0].parent;
141+
recordVariable(enclosingFunction, variable.identifiers[0].parent);
112142
}
113143
}
114-
if (variableDeclaration === null) {
115-
return;
116-
}
117-
if (!returnedVariables.has(enclosingFunction)) {
118-
returnedVariables.set(enclosingFunction, []);
119-
}
120-
returnedVariables.get(enclosingFunction)?.push(variableDeclaration);
121144
},
122145
'Program:exit'() {
123146
const referenceTracker = new ReferenceTracker(context.sourceCode.scopeManager.globalScope!);
@@ -174,7 +197,11 @@ export default createRule('prefer-svelte-reactivity', {
174197
findEnclosingReturn(node) !== null ||
175198
(enclosingPropertyDefinition !== null &&
176199
(!ignoreEncapsulatedLocalVariables ||
177-
!isPropertyEncapsulated(enclosingPropertyDefinition, returnedVariables)))
200+
!isPropertyEncapsulated(
201+
enclosingPropertyDefinition,
202+
returnedFunctionCalls,
203+
returnedVariables
204+
)))
178205
) {
179206
context.report({
180207
messageId,
@@ -274,13 +301,23 @@ function isLocalVarEncapsulated(
274301
function methodReturnsProperty(
275302
method: TSESTree.MethodDefinition,
276303
property: TSESTree.PropertyDefinition,
304+
returnedFunctionCalls: Map<FunctionLike, TSESTree.MethodDefinition[]>,
277305
returnedVariables: Map<FunctionLike, VariableLike[]>
278306
): boolean {
279-
return returnedVariables.get(method)?.includes(property) ?? false;
307+
return (
308+
(returnedVariables.get(method)?.includes(property) ?? false) ||
309+
(returnedFunctionCalls
310+
.get(method)
311+
?.some((calledFn) =>
312+
methodReturnsProperty(calledFn, property, returnedFunctionCalls, returnedVariables)
313+
) ??
314+
false)
315+
);
280316
}
281317

282318
function isPropertyEncapsulated(
283319
node: TSESTree.PropertyDefinition,
320+
returnedFunctionCalls: Map<FunctionLike, TSESTree.MethodDefinition[]>,
284321
returnedVariables: Map<FunctionLike, VariableLike[]>
285322
): boolean {
286323
if (node.accessibility === 'public') {
@@ -290,7 +327,7 @@ function isPropertyEncapsulated(
290327
if (
291328
classElement.type === 'MethodDefinition' &&
292329
classElement.accessibility === 'public' &&
293-
methodReturnsProperty(classElement, node, returnedVariables)
330+
methodReturnsProperty(classElement, node, returnedFunctionCalls, returnedVariables)
294331
) {
295332
return false;
296333
}

0 commit comments

Comments
 (0)