Skip to content

Commit d60b044

Browse files
committed
chore: refactor
1 parent f585181 commit d60b044

File tree

2 files changed

+91
-22
lines changed

2 files changed

+91
-22
lines changed

src/rules/infinite-reactive-loop.ts

+75-22
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import type { RuleContext } from "../types"
66
import { getScope } from "../utils/ast-utils"
77
import { traverseNodes } from "svelte-eslint-parser"
88

9-
/** */
9+
/**
10+
* Get usage of `tick`
11+
*/
1012
function extractTickReferences(
1113
context: RuleContext,
1214
): { node: TSESTree.CallExpression; name: string }[] {
@@ -27,7 +29,9 @@ function extractTickReferences(
2729
})
2830
}
2931

30-
/** */
32+
/**
33+
* Get usage of `setTimeout`, `setInterval`, `queueMicrotask`
34+
*/
3135
function extractTaskReferences(
3236
context: RuleContext,
3337
): { node: TSESTree.CallExpression; name: string }[] {
@@ -45,11 +49,13 @@ function extractTaskReferences(
4549
})
4650
}
4751

48-
/** */
52+
/**
53+
* If `node` is inside of `maybeAncestorNode`, return true.
54+
*/
4955
function isChildNode(
5056
maybeAncestorNode: TSESTree.Node | AST.SvelteNode,
5157
node: TSESTree.Node,
52-
) {
58+
): boolean {
5359
let parent = node.parent
5460
while (parent) {
5561
if (parent === maybeAncestorNode) return true
@@ -58,16 +64,27 @@ function isChildNode(
5864
return false
5965
}
6066

61-
/** */
67+
/**
68+
* Return true if `node` is a function call.
69+
*/
6270
function isFunctionCall(node: TSESTree.Node): boolean {
6371
if (node.type !== "Identifier") return false
6472
const { parent } = node
6573
if (parent?.type !== "CallExpression") return false
6674
return parent.callee.type === "Identifier" && parent.callee.name === node.name
6775
}
6876

69-
/** */
70-
function isObjectNode(node: TSESTree.Identifier): boolean {
77+
/**
78+
* Return true if `node` is a variable.
79+
*
80+
* e.g. foo.bar
81+
* If node is `foo`, return true.
82+
* If node is `bar`, return false.
83+
*
84+
* e.g. let baz = 1
85+
* If node is `baz`, return true.
86+
*/
87+
function isVariableNode(node: TSESTree.Identifier): boolean {
7188
const { parent } = node
7289
if (parent?.type !== "MemberExpression") return true
7390
if (
@@ -82,13 +99,15 @@ function isObjectNode(node: TSESTree.Identifier): boolean {
8299
: parent.object.name === node.name
83100
}
84101

85-
/** */
102+
/**
103+
* Return true if `node` is a reactive variable.
104+
*/
86105
function isReactiveVariableNode(
87106
context: RuleContext,
88107
node: TSESTree.Node,
89108
): node is TSESTree.Identifier {
90109
if (node.type !== "Identifier") return false
91-
if (!isObjectNode(node) || isFunctionCall(node)) return false
110+
if (!isVariableNode(node) || isFunctionCall(node)) return false
92111

93112
// Variable name starts with `$` means Svelte store.
94113
if (node.name.startsWith("$")) return true
@@ -107,8 +126,12 @@ function isReactiveVariableNode(
107126
})
108127
}
109128

110-
/** */
111-
function isNodeUseForAssign(node: TSESTree.Identifier): boolean {
129+
/**
130+
* e.g. foo.bar = baz + 1
131+
* If node is `foo`, return true.
132+
* Otherwise, return false.
133+
*/
134+
function isNodeForAssign(node: TSESTree.Identifier): boolean {
112135
const { parent } = node
113136
if (parent?.type === "AssignmentExpression") {
114137
return parent.left.type === "Identifier" && parent.left.name === node.name
@@ -122,8 +145,10 @@ function isNodeUseForAssign(node: TSESTree.Identifier): boolean {
122145
)
123146
}
124147

125-
/** */
126-
function isPromiseThenOrCatch(node: TSESTree.Node): boolean {
148+
/**
149+
* Return true if `node` is inside of `then` or `catch`.
150+
*/
151+
function isPromiseThenOrCatchBody(node: TSESTree.Node): boolean {
127152
if (!getDeclarationBody(node)) return false
128153
const { parent } = node
129154
if (
@@ -137,7 +162,9 @@ function isPromiseThenOrCatch(node: TSESTree.Node): boolean {
137162
return ["then", "catch"].includes(property.name)
138163
}
139164

140-
/** */
165+
/**
166+
* Get all tracked reactive variables.
167+
*/
141168
function getTrackedVariableNodes(
142169
context: RuleContext,
143170
ast: AST.SvelteReactiveStatement,
@@ -201,7 +228,9 @@ function getFunctionDeclarationNode(
201228
traverseNodes(parent, {
202229
// eslint-disable-next-line no-loop-func -- ignore
203230
enterNode(node) {
204-
declaration = getDeclarationBody(node, functionCall.name)
231+
if (!declaration) {
232+
declaration = getDeclarationBody(node, functionCall.name)
233+
}
205234
},
206235
leaveNode() {
207236
/* noop */
@@ -223,10 +252,29 @@ function getFunctionDeclarationNode(
223252
return declaration
224253
}
225254

255+
/** */
256+
function isInsideOfFunction(node: TSESTree.Node) {
257+
let parent: TSESTree.Node | AST.SvelteReactiveStatement | null = node
258+
while (parent) {
259+
parent = parent.parent as TSESTree.Node | AST.SvelteReactiveStatement | null
260+
if (!parent) break
261+
if (parent.type === "FunctionDeclaration" && parent.async) return true
262+
if (
263+
parent.type === "VariableDeclarator" &&
264+
(parent.init?.type === "FunctionExpression" ||
265+
parent.init?.type === "ArrowFunctionExpression") &&
266+
parent.init?.async
267+
) {
268+
return true
269+
}
270+
}
271+
return false
272+
}
273+
226274
/** */
227275
function doLint(
228276
context: RuleContext,
229-
node: TSESTree.Node,
277+
ast: TSESTree.Node,
230278
callFuncIdentifiers: TSESTree.Identifier[],
231279
tickCallExpressions: { node: TSESTree.CallExpression; name: string }[],
232280
taskReferences: {
@@ -238,10 +286,10 @@ function doLint(
238286
) {
239287
let isSameMicroTask = pIsSameTask
240288

241-
traverseNodes(node, {
289+
traverseNodes(ast, {
242290
enterNode(node) {
243291
// Promise.then() or Promise.catch() is called.
244-
if (isPromiseThenOrCatch(node)) {
292+
if (isPromiseThenOrCatchBody(node)) {
245293
isSameMicroTask = false
246294
}
247295

@@ -284,7 +332,7 @@ function doLint(
284332
if (
285333
isReactiveVariableNode(context, node) &&
286334
reactiveVariableNames.includes(node.name) &&
287-
isNodeUseForAssign(node)
335+
isNodeForAssign(node)
288336
) {
289337
context.report({
290338
node,
@@ -305,13 +353,18 @@ function doLint(
305353
}
306354
},
307355
leaveNode(node) {
308-
// After `await` statement runs on a different microtask.
309356
if (node.type === "AwaitExpression") {
310-
isSameMicroTask = false
357+
if ((ast.parent?.type as string) === "SvelteReactiveStatement") {
358+
if (!isInsideOfFunction(node)) {
359+
isSameMicroTask = false
360+
}
361+
} else {
362+
isSameMicroTask = false
363+
}
311364
}
312365

313366
// Promise.then() or Promise.catch() is called.
314-
if (isPromiseThenOrCatch(node)) {
367+
if (isPromiseThenOrCatchBody(node)) {
315368
isSameMicroTask = true
316369
}
317370

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<script>
2+
let obj = { a: 0 }
3+
4+
const doSomething = async () => {
5+
obj.a += 1
6+
await fetch()
7+
}
8+
9+
$: (async () => {
10+
const doSomething = async () => {
11+
await fetch()
12+
}
13+
obj.a += 1
14+
await doSomething()
15+
})()
16+
</script>

0 commit comments

Comments
 (0)