@@ -6,7 +6,9 @@ import type { RuleContext } from "../types"
6
6
import { getScope } from "../utils/ast-utils"
7
7
import { traverseNodes } from "svelte-eslint-parser"
8
8
9
- /** */
9
+ /**
10
+ * Get usage of `tick`
11
+ */
10
12
function extractTickReferences (
11
13
context : RuleContext ,
12
14
) : { node : TSESTree . CallExpression ; name : string } [ ] {
@@ -27,7 +29,9 @@ function extractTickReferences(
27
29
} )
28
30
}
29
31
30
- /** */
32
+ /**
33
+ * Get usage of `setTimeout`, `setInterval`, `queueMicrotask`
34
+ */
31
35
function extractTaskReferences (
32
36
context : RuleContext ,
33
37
) : { node : TSESTree . CallExpression ; name : string } [ ] {
@@ -45,11 +49,13 @@ function extractTaskReferences(
45
49
} )
46
50
}
47
51
48
- /** */
52
+ /**
53
+ * If `node` is inside of `maybeAncestorNode`, return true.
54
+ */
49
55
function isChildNode (
50
56
maybeAncestorNode : TSESTree . Node | AST . SvelteNode ,
51
57
node : TSESTree . Node ,
52
- ) {
58
+ ) : boolean {
53
59
let parent = node . parent
54
60
while ( parent ) {
55
61
if ( parent === maybeAncestorNode ) return true
@@ -58,16 +64,27 @@ function isChildNode(
58
64
return false
59
65
}
60
66
61
- /** */
67
+ /**
68
+ * Return true if `node` is a function call.
69
+ */
62
70
function isFunctionCall ( node : TSESTree . Node ) : boolean {
63
71
if ( node . type !== "Identifier" ) return false
64
72
const { parent } = node
65
73
if ( parent ?. type !== "CallExpression" ) return false
66
74
return parent . callee . type === "Identifier" && parent . callee . name === node . name
67
75
}
68
76
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 {
71
88
const { parent } = node
72
89
if ( parent ?. type !== "MemberExpression" ) return true
73
90
if (
@@ -82,13 +99,15 @@ function isObjectNode(node: TSESTree.Identifier): boolean {
82
99
: parent . object . name === node . name
83
100
}
84
101
85
- /** */
102
+ /**
103
+ * Return true if `node` is a reactive variable.
104
+ */
86
105
function isReactiveVariableNode (
87
106
context : RuleContext ,
88
107
node : TSESTree . Node ,
89
108
) : node is TSESTree . Identifier {
90
109
if ( node . type !== "Identifier" ) return false
91
- if ( ! isObjectNode ( node ) || isFunctionCall ( node ) ) return false
110
+ if ( ! isVariableNode ( node ) || isFunctionCall ( node ) ) return false
92
111
93
112
// Variable name starts with `$` means Svelte store.
94
113
if ( node . name . startsWith ( "$" ) ) return true
@@ -107,8 +126,12 @@ function isReactiveVariableNode(
107
126
} )
108
127
}
109
128
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 {
112
135
const { parent } = node
113
136
if ( parent ?. type === "AssignmentExpression" ) {
114
137
return parent . left . type === "Identifier" && parent . left . name === node . name
@@ -122,8 +145,10 @@ function isNodeUseForAssign(node: TSESTree.Identifier): boolean {
122
145
)
123
146
}
124
147
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 {
127
152
if ( ! getDeclarationBody ( node ) ) return false
128
153
const { parent } = node
129
154
if (
@@ -137,7 +162,9 @@ function isPromiseThenOrCatch(node: TSESTree.Node): boolean {
137
162
return [ "then" , "catch" ] . includes ( property . name )
138
163
}
139
164
140
- /** */
165
+ /**
166
+ * Get all tracked reactive variables.
167
+ */
141
168
function getTrackedVariableNodes (
142
169
context : RuleContext ,
143
170
ast : AST . SvelteReactiveStatement ,
@@ -201,7 +228,9 @@ function getFunctionDeclarationNode(
201
228
traverseNodes ( parent , {
202
229
// eslint-disable-next-line no-loop-func -- ignore
203
230
enterNode ( node ) {
204
- declaration = getDeclarationBody ( node , functionCall . name )
231
+ if ( ! declaration ) {
232
+ declaration = getDeclarationBody ( node , functionCall . name )
233
+ }
205
234
} ,
206
235
leaveNode ( ) {
207
236
/* noop */
@@ -223,10 +252,29 @@ function getFunctionDeclarationNode(
223
252
return declaration
224
253
}
225
254
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
+
226
274
/** */
227
275
function doLint (
228
276
context : RuleContext ,
229
- node : TSESTree . Node ,
277
+ ast : TSESTree . Node ,
230
278
callFuncIdentifiers : TSESTree . Identifier [ ] ,
231
279
tickCallExpressions : { node : TSESTree . CallExpression ; name : string } [ ] ,
232
280
taskReferences : {
@@ -238,10 +286,10 @@ function doLint(
238
286
) {
239
287
let isSameMicroTask = pIsSameTask
240
288
241
- traverseNodes ( node , {
289
+ traverseNodes ( ast , {
242
290
enterNode ( node ) {
243
291
// Promise.then() or Promise.catch() is called.
244
- if ( isPromiseThenOrCatch ( node ) ) {
292
+ if ( isPromiseThenOrCatchBody ( node ) ) {
245
293
isSameMicroTask = false
246
294
}
247
295
@@ -284,7 +332,7 @@ function doLint(
284
332
if (
285
333
isReactiveVariableNode ( context , node ) &&
286
334
reactiveVariableNames . includes ( node . name ) &&
287
- isNodeUseForAssign ( node )
335
+ isNodeForAssign ( node )
288
336
) {
289
337
context . report ( {
290
338
node,
@@ -305,13 +353,18 @@ function doLint(
305
353
}
306
354
} ,
307
355
leaveNode ( node ) {
308
- // After `await` statement runs on a different microtask.
309
356
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
+ }
311
364
}
312
365
313
366
// Promise.then() or Promise.catch() is called.
314
- if ( isPromiseThenOrCatch ( node ) ) {
367
+ if ( isPromiseThenOrCatchBody ( node ) ) {
315
368
isSameMicroTask = true
316
369
}
317
370
0 commit comments