Skip to content

Commit 747ab5b

Browse files
authored
Update vue/no-watch-after-await rule to support <script setup> (#1541)
1 parent 994d74e commit 747ab5b

File tree

2 files changed

+115
-25
lines changed

2 files changed

+115
-25
lines changed

Diff for: lib/rules/no-watch-after-await.js

+58-25
Original file line numberDiff line numberDiff line change
@@ -57,20 +57,31 @@ module.exports = {
5757
/** @param {RuleContext} context */
5858
create(context) {
5959
const watchCallNodes = new Set()
60-
/** @type {Map<FunctionExpression | ArrowFunctionExpression | FunctionDeclaration, { setupProperty: Property, afterAwait: boolean }>} */
61-
const setupFunctions = new Map()
60+
/**
61+
* @typedef {object} SetupScopeData
62+
* @property {boolean} afterAwait
63+
* @property {[number,number]} range
64+
*/
65+
/** @type {Map<FunctionExpression | ArrowFunctionExpression | FunctionDeclaration | Program, SetupScopeData>} */
66+
const setupScopes = new Map()
6267

6368
/**
6469
* @typedef {object} ScopeStack
6570
* @property {ScopeStack | null} upper
66-
* @property {FunctionExpression | ArrowFunctionExpression | FunctionDeclaration} functionNode
71+
* @property {FunctionExpression | ArrowFunctionExpression | FunctionDeclaration | Program} scopeNode
6772
*/
6873
/** @type {ScopeStack | null} */
6974
let scopeStack = null
7075

71-
return Object.assign(
76+
return utils.compositingVisitors(
7277
{
73-
Program() {
78+
/** @param {Program} node */
79+
Program(node) {
80+
scopeStack = {
81+
upper: scopeStack,
82+
scopeNode: node
83+
}
84+
7485
const tracker = new ReferenceTracker(context.getScope())
7586
const traceMap = {
7687
vue: {
@@ -87,39 +98,39 @@ module.exports = {
8798
for (const { node } of tracker.iterateEsmReferences(traceMap)) {
8899
watchCallNodes.add(node)
89100
}
90-
}
91-
},
92-
utils.defineVueVisitor(context, {
101+
},
93102
/** @param {FunctionExpression | ArrowFunctionExpression | FunctionDeclaration} node */
94103
':function'(node) {
95104
scopeStack = {
96105
upper: scopeStack,
97-
functionNode: node
106+
scopeNode: node
98107
}
99108
},
100-
onSetupFunctionEnter(node) {
101-
setupFunctions.set(node, {
102-
setupProperty: node.parent,
103-
afterAwait: false
104-
})
109+
':function:exit'() {
110+
scopeStack = scopeStack && scopeStack.upper
105111
},
106-
AwaitExpression() {
112+
/** @param {AwaitExpression} node */
113+
AwaitExpression(node) {
107114
if (!scopeStack) {
108115
return
109116
}
110-
const setupFunctionData = setupFunctions.get(scopeStack.functionNode)
111-
if (!setupFunctionData) {
117+
const setupScope = setupScopes.get(scopeStack.scopeNode)
118+
if (!setupScope || !utils.inRange(setupScope.range, node)) {
112119
return
113120
}
114-
setupFunctionData.afterAwait = true
121+
setupScope.afterAwait = true
115122
},
116123
/** @param {CallExpression} node */
117124
CallExpression(node) {
118125
if (!scopeStack) {
119126
return
120127
}
121-
const setupFunctionData = setupFunctions.get(scopeStack.functionNode)
122-
if (!setupFunctionData || !setupFunctionData.afterAwait) {
128+
const setupScope = setupScopes.get(scopeStack.scopeNode)
129+
if (
130+
!setupScope ||
131+
!setupScope.afterAwait ||
132+
!utils.inRange(setupScope.range, node)
133+
) {
123134
return
124135
}
125136

@@ -129,12 +140,34 @@ module.exports = {
129140
messageId: 'forbidden'
130141
})
131142
}
143+
}
144+
},
145+
(() => {
146+
const scriptSetup = utils.getScriptSetupElement(context)
147+
if (!scriptSetup) {
148+
return {}
149+
}
150+
return {
151+
/**
152+
* @param {Program} node
153+
*/
154+
Program(node) {
155+
setupScopes.set(node, {
156+
afterAwait: false,
157+
range: scriptSetup.range
158+
})
159+
}
160+
}
161+
})(),
162+
utils.defineVueVisitor(context, {
163+
onSetupFunctionEnter(node) {
164+
setupScopes.set(node, {
165+
afterAwait: false,
166+
range: node.range
167+
})
132168
},
133-
/** @param {FunctionExpression | ArrowFunctionExpression | FunctionDeclaration} node */
134-
':function:exit'(node) {
135-
scopeStack = scopeStack && scopeStack.upper
136-
137-
setupFunctions.delete(node)
169+
onSetupFunctionExit(node) {
170+
setupScopes.delete(node)
138171
}
139172
})
140173
)

Diff for: tests/lib/rules/no-watch-after-await.js

+57
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,43 @@ tester.run('no-watch-after-await', rule, {
118118
}
119119
</script>
120120
`
121+
},
122+
{
123+
filename: 'test.vue',
124+
code: `
125+
<script setup>
126+
import {watchEffect} from 'vue'
127+
watchEffect(() => { /* ... */ })
128+
await doSomething()
129+
</script>
130+
`,
131+
parserOptions: { ecmaVersion: 2022 }
132+
},
133+
{
134+
filename: 'test.vue',
135+
code: `
136+
<script setup>
137+
await doSomething()
138+
</script>
139+
<script>
140+
import {watchEffect} from 'vue'
141+
watchEffect(() => { /* ... */ }) // not error
142+
</script>
143+
`,
144+
parserOptions: { ecmaVersion: 2022 }
145+
},
146+
{
147+
filename: 'test.vue',
148+
code: `
149+
<script setup>
150+
</script>
151+
<script>
152+
import {watchEffect} from 'vue'
153+
await doSomething()
154+
watchEffect(() => { /* ... */ }) // not error
155+
</script>
156+
`,
157+
parserOptions: { ecmaVersion: 2022 }
121158
}
122159
],
123160
invalid: [
@@ -199,6 +236,26 @@ tester.run('no-watch-after-await', rule, {
199236
line: 12
200237
}
201238
]
239+
},
240+
{
241+
filename: 'test.vue',
242+
code: `
243+
<script setup>
244+
import {watch} from 'vue'
245+
watch(foo, () => { /* ... */ })
246+
247+
await doSomething()
248+
249+
watch(foo, () => { /* ... */ })
250+
</script>
251+
`,
252+
parserOptions: { ecmaVersion: 2022 },
253+
errors: [
254+
{
255+
message: 'The `watch` after `await` expression are forbidden.',
256+
line: 8
257+
}
258+
]
202259
}
203260
]
204261
})

0 commit comments

Comments
 (0)