Skip to content

Commit 6d875d5

Browse files
committed
feat(prefer-svelte-reactivity): reporting returned variables
1 parent 44ed800 commit 6d875d5

File tree

2 files changed

+68
-0
lines changed

2 files changed

+68
-0
lines changed

.changeset/lovely-moments-kneel.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): reporting returned variables

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

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ export default createRule('prefer-svelte-reactivity', {
3131
]
3232
},
3333
create(context) {
34+
const returnedVariables: Map<
35+
TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclaration,
36+
TSESTree.VariableDeclarator[]
37+
> = new Map();
3438
const exportedVars: TSESTree.Node[] = [];
3539
return {
3640
...(getSvelteContext(context)?.svelteFileType === '.svelte.[js|ts]' && {
@@ -59,6 +63,28 @@ export default createRule('prefer-svelte-reactivity', {
5963
}
6064
}
6165
}),
66+
Identifier(node) {
67+
const enclosingReturn = findEnclosingReturn(node);
68+
if (enclosingReturn === null) {
69+
return;
70+
}
71+
const enclosingFunction = findEnclosingFunction(enclosingReturn);
72+
if (enclosingFunction === null) {
73+
return;
74+
}
75+
const variable = findVariable(context, node);
76+
if (
77+
variable === null ||
78+
variable.identifiers.length < 1 ||
79+
variable.identifiers[0].parent.type !== 'VariableDeclarator'
80+
) {
81+
return;
82+
}
83+
if (!returnedVariables.has(enclosingFunction)) {
84+
returnedVariables.set(enclosingFunction, []);
85+
}
86+
returnedVariables.get(enclosingFunction)?.push(variable.identifiers[0].parent);
87+
},
6288
'Program:exit'() {
6389
const referenceTracker = new ReferenceTracker(context.sourceCode.scopeManager.globalScope!);
6490
for (const { node, path } of referenceTracker.iterateGlobalReferences({
@@ -96,6 +122,20 @@ export default createRule('prefer-svelte-reactivity', {
96122
});
97123
}
98124
}
125+
for (const returnedVar of Array.from(returnedVariables.values()).flat()) {
126+
if (isIn(node, returnedVar)) {
127+
context.report({
128+
messageId,
129+
node
130+
});
131+
}
132+
}
133+
if (findEnclosingReturn(node) !== null) {
134+
context.report({
135+
messageId,
136+
node
137+
});
138+
}
99139
if (path[0] === 'Date' && isDateMutable(referenceTracker, node as TSESTree.Expression)) {
100140
context.report({
101141
messageId: 'mutableDateUsed',
@@ -135,6 +175,29 @@ export default createRule('prefer-svelte-reactivity', {
135175
}
136176
});
137177

178+
function findAncestorOfTypes<T extends string>(
179+
node: TSESTree.Node,
180+
types: string[]
181+
): (TSESTree.Node & { type: T }) | null {
182+
if (types.includes(node.type)) {
183+
return node as TSESTree.Node & { type: T };
184+
}
185+
if (node.parent === undefined || node.parent === null) {
186+
return null;
187+
}
188+
return findAncestorOfTypes(node.parent, types);
189+
}
190+
191+
function findEnclosingReturn(node: TSESTree.Node): TSESTree.ReturnStatement | null {
192+
return findAncestorOfTypes(node, ['ReturnStatement']);
193+
}
194+
195+
function findEnclosingFunction(
196+
node: TSESTree.Node
197+
): TSESTree.ArrowFunctionExpression | TSESTree.FunctionDeclaration | null {
198+
return findAncestorOfTypes(node, ['ArrowFunctionExpression', 'FunctionDeclaration']);
199+
}
200+
138201
function isDateMutable(referenceTracker: ReferenceTracker, ctorNode: TSESTree.Expression): boolean {
139202
return !referenceTracker
140203
.iteratePropertyReferences(ctorNode, {

0 commit comments

Comments
 (0)