Skip to content

Commit b5e2c63

Browse files
committed
feat(prefer-svelte-reactivity): ignoring variables encapsulated in classes
1 parent fd80bf7 commit b5e2c63

File tree

1 file changed

+84
-23
lines changed

1 file changed

+84
-23
lines changed

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

Lines changed: 84 additions & 23 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!);
@@ -135,17 +156,25 @@ export default createRule('prefer-svelte-reactivity', {
135156
});
136157
}
137158
}
138-
for (const returnedVar of Array.from(returnedVariables.values()).flat()) {
139-
if (isIn(node, returnedVar)) {
140-
context.report({
141-
messageId,
142-
node
143-
});
159+
for (const [fn, fnReturnVars] of returnedVariables.entries()) {
160+
if (fn.type === 'MethodDefinition') {
161+
continue;
162+
}
163+
for (const returnedVar of fnReturnVars) {
164+
if (isIn(node, returnedVar)) {
165+
context.report({
166+
messageId,
167+
node
168+
});
169+
}
144170
}
145171
}
172+
const enclosingPropertyDefinition = findEnclosingPropertyDefinition(node);
146173
if (
147174
findEnclosingReturn(node) !== null ||
148-
findEnclosingPropertyDefinition(node)?.accessibility === 'public'
175+
(enclosingPropertyDefinition !== null &&
176+
(!ignoreEncapsulatedLocalVariables ||
177+
!isPropertyEncapsulated(enclosingPropertyDefinition, returnedVariables)))
149178
) {
150179
context.report({
151180
messageId,
@@ -207,10 +236,18 @@ function findAncestorOfTypes<T extends string>(
207236
return findAncestorOfTypes(node.parent, types);
208237
}
209238

239+
function findEnclosingClassBody(node: TSESTree.Node): TSESTree.ClassBody | null {
240+
return findAncestorOfTypes(node, ['ClassBody']);
241+
}
242+
210243
function findEnclosingFunction(
211244
node: TSESTree.Node
212245
): TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclaration | null {
213-
return findAncestorOfTypes(node, ['ArrowFunctionExpression', 'FunctionDeclaration']);
246+
return findAncestorOfTypes(node, [
247+
'ArrowFunctionExpression',
248+
'FunctionDeclaration',
249+
'MethodDefinition'
250+
]);
214251
}
215252

216253
function findEnclosingPropertyDefinition(node: TSESTree.Node): TSESTree.PropertyDefinition | null {
@@ -222,10 +259,7 @@ function findEnclosingReturn(node: TSESTree.Node): TSESTree.ReturnStatement | nu
222259
}
223260

224261
function isLocalVarEncapsulated(
225-
returnedVariables: Map<
226-
TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclaration,
227-
TSESTree.VariableDeclarator[]
228-
>,
262+
returnedVariables: Map<FunctionLike, VariableLike[]>,
229263
node: TSESTree.Node
230264
): boolean {
231265
const enclosingFunction = findEnclosingFunction(node);
@@ -237,6 +271,33 @@ function isLocalVarEncapsulated(
237271
);
238272
}
239273

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

0 commit comments

Comments
 (0)