Skip to content

Commit ee5ea4b

Browse files
authored
Update vue/no-lifecycle-after-await rule to support <script setup> (#1539)
1 parent 774056c commit ee5ea4b

File tree

4 files changed

+146
-27
lines changed

4 files changed

+146
-27
lines changed

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

+61-26
Original file line numberDiff line numberDiff line change
@@ -41,26 +41,34 @@ module.exports = {
4141
/** @param {RuleContext} context */
4242
create(context) {
4343
/**
44-
* @typedef {object} SetupFunctionData
45-
* @property {Property} setupProperty
44+
* @typedef {object} SetupScopeData
4645
* @property {boolean} afterAwait
46+
* @property {[number,number]} range
4747
*/
4848
/**
4949
* @typedef {object} ScopeStack
5050
* @property {ScopeStack | null} upper
51-
* @property {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression} functionNode
51+
* @property {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression | Program} scopeNode
5252
*/
5353
/** @type {Set<ESNode>} */
5454
const lifecycleHookCallNodes = new Set()
55-
/** @type {Map<FunctionDeclaration | FunctionExpression | ArrowFunctionExpression, SetupFunctionData>} */
56-
const setupFunctions = new Map()
55+
/** @type {Map<FunctionDeclaration | FunctionExpression | ArrowFunctionExpression | Program, SetupScopeData>} */
56+
const setupScopes = new Map()
5757

5858
/** @type {ScopeStack | null} */
5959
let scopeStack = null
6060

61-
return Object.assign(
61+
return utils.compositingVisitors(
6262
{
63-
Program() {
63+
/**
64+
* @param {Program} node
65+
*/
66+
Program(node) {
67+
scopeStack = {
68+
upper: scopeStack,
69+
scopeNode: node
70+
}
71+
6472
const tracker = new ReferenceTracker(context.getScope())
6573
const traceMap = {
6674
/** @type {TraceMap} */
@@ -77,37 +85,41 @@ module.exports = {
7785
for (const { node } of tracker.iterateEsmReferences(traceMap)) {
7886
lifecycleHookCallNodes.add(node)
7987
}
80-
}
81-
},
82-
utils.defineVueVisitor(context, {
88+
},
89+
/**
90+
* @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node
91+
*/
8392
':function'(node) {
8493
scopeStack = {
8594
upper: scopeStack,
86-
functionNode: node
95+
scopeNode: node
8796
}
8897
},
89-
onSetupFunctionEnter(node) {
90-
setupFunctions.set(node, {
91-
setupProperty: node.parent,
92-
afterAwait: false
93-
})
98+
':function:exit'() {
99+
scopeStack = scopeStack && scopeStack.upper
94100
},
95-
AwaitExpression() {
101+
/** @param {AwaitExpression} node */
102+
AwaitExpression(node) {
96103
if (!scopeStack) {
97104
return
98105
}
99-
const setupFunctionData = setupFunctions.get(scopeStack.functionNode)
100-
if (!setupFunctionData) {
106+
const setupScope = setupScopes.get(scopeStack.scopeNode)
107+
if (!setupScope || !utils.inRange(setupScope.range, node)) {
101108
return
102109
}
103-
setupFunctionData.afterAwait = true
110+
setupScope.afterAwait = true
104111
},
112+
/** @param {CallExpression} node */
105113
CallExpression(node) {
106114
if (!scopeStack) {
107115
return
108116
}
109-
const setupFunctionData = setupFunctions.get(scopeStack.functionNode)
110-
if (!setupFunctionData || !setupFunctionData.afterAwait) {
117+
const setupScope = setupScopes.get(scopeStack.scopeNode)
118+
if (
119+
!setupScope ||
120+
!setupScope.afterAwait ||
121+
!utils.inRange(setupScope.range, node)
122+
) {
111123
return
112124
}
113125

@@ -121,11 +133,34 @@ module.exports = {
121133
messageId: 'forbidden'
122134
})
123135
}
136+
}
137+
},
138+
(() => {
139+
const scriptSetup = utils.getScriptSetupElement(context)
140+
if (!scriptSetup) {
141+
return {}
142+
}
143+
return {
144+
/**
145+
* @param {Program} node
146+
*/
147+
Program(node) {
148+
setupScopes.set(node, {
149+
afterAwait: false,
150+
range: scriptSetup.range
151+
})
152+
}
153+
}
154+
})(),
155+
utils.defineVueVisitor(context, {
156+
onSetupFunctionEnter(node) {
157+
setupScopes.set(node, {
158+
afterAwait: false,
159+
range: node.range
160+
})
124161
},
125-
':function:exit'(node) {
126-
scopeStack = scopeStack && scopeStack.upper
127-
128-
setupFunctions.delete(node)
162+
onSetupFunctionExit(node) {
163+
setupScopes.delete(node)
129164
}
130165
})
131166
)

Diff for: lib/utils/index.js

+26-1
Original file line numberDiff line numberDiff line change
@@ -1003,7 +1003,12 @@ module.exports = {
10031003
vueStack = vueStack.parent
10041004
}
10051005
}
1006-
if (visitor.onSetupFunctionEnter || visitor.onRenderFunctionEnter) {
1006+
if (
1007+
visitor.onSetupFunctionEnter ||
1008+
visitor.onSetupFunctionExit ||
1009+
visitor.onRenderFunctionEnter
1010+
) {
1011+
const setups = new Set()
10071012
/** @param { (FunctionExpression | ArrowFunctionExpression) & { parent: Property } } node */
10081013
vueVisitor[
10091014
'Property[value.type=/^(Arrow)?FunctionExpression$/] > :function'
@@ -1014,6 +1019,7 @@ module.exports = {
10141019
const name = getStaticPropertyName(prop)
10151020
if (name === 'setup') {
10161021
callVisitor('onSetupFunctionEnter', node)
1022+
setups.add(node)
10171023
} else if (name === 'render') {
10181024
callVisitor('onRenderFunctionEnter', node)
10191025
}
@@ -1023,6 +1029,17 @@ module.exports = {
10231029
node
10241030
)
10251031
}
1032+
if (visitor.onSetupFunctionExit) {
1033+
/** @param { (FunctionExpression | ArrowFunctionExpression) & { parent: Property } } node */
1034+
vueVisitor[
1035+
'Property[value.type=/^(Arrow)?FunctionExpression$/] > :function:exit'
1036+
] = (node) => {
1037+
if (setups.has(node)) {
1038+
callVisitor('onSetupFunctionExit', node)
1039+
setups.delete(node)
1040+
}
1041+
}
1042+
}
10261043
}
10271044

10281045
return vueVisitor
@@ -1511,6 +1528,14 @@ module.exports = {
15111528
}
15121529
return dp[alen][blen]
15131530
},
1531+
/**
1532+
* Checks whether the target node is within the given range.
1533+
* @param { [number, number] } range
1534+
* @param {ASTNode} target
1535+
*/
1536+
inRange(range, target) {
1537+
return range[0] <= target.range[0] && target.range[1] <= range[1]
1538+
},
15141539
/**
15151540
* Checks whether the given node is Property.
15161541
*/

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

+55
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,43 @@ tester.run('no-lifecycle-after-await', rule, {
106106
}
107107
</script>
108108
`
109+
},
110+
{
111+
filename: 'test.vue',
112+
code: `
113+
<script setup>
114+
import {onMounted} from 'vue'
115+
onMounted(() => { /* ... */ })
116+
await doSomething()
117+
</script>
118+
`,
119+
parserOptions: { ecmaVersion: 2022 }
120+
},
121+
{
122+
filename: 'test.vue',
123+
code: `
124+
<script setup>
125+
await doSomething()
126+
</script>
127+
<script>
128+
import {onMounted} from 'vue'
129+
onMounted(() => { /* ... */ }) // not error
130+
</script>
131+
`,
132+
parserOptions: { ecmaVersion: 2022 }
133+
},
134+
{
135+
filename: 'test.vue',
136+
code: `
137+
<script setup>
138+
</script>
139+
<script>
140+
import {onMounted} from 'vue'
141+
await doSomething()
142+
onMounted(() => { /* ... */ }) // not error
143+
</script>
144+
`,
145+
parserOptions: { ecmaVersion: 2022 }
109146
}
110147
],
111148
invalid: [
@@ -224,6 +261,24 @@ tester.run('no-lifecycle-after-await', rule, {
224261
messageId: 'forbidden'
225262
}
226263
]
264+
},
265+
{
266+
filename: 'test.vue',
267+
code: `
268+
<script setup>
269+
import {onMounted} from 'vue'
270+
await doSomething()
271+
272+
onMounted(() => { /* ... */ }) // error
273+
</script>
274+
`,
275+
parserOptions: { ecmaVersion: 2022 },
276+
errors: [
277+
{
278+
messageId: 'forbidden',
279+
line: 6
280+
}
281+
]
227282
}
228283
]
229284
})

Diff for: typings/eslint-plugin-vue/util-types/utils.ts

+4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ export interface VueVisitor extends VueVisitorBase {
1919
node: (FunctionExpression | ArrowFunctionExpression) & { parent: Property },
2020
obj: VueObjectData
2121
): void
22+
onSetupFunctionExit?(
23+
node: (FunctionExpression | ArrowFunctionExpression) & { parent: Property },
24+
obj: VueObjectData
25+
): void
2226
onRenderFunctionEnter?(
2327
node: (FunctionExpression | ArrowFunctionExpression) & { parent: Property },
2428
obj: VueObjectData

0 commit comments

Comments
 (0)