Skip to content

Commit b5b6347

Browse files
authored
Add composition api's computed function support to vue/no-async-in-computed-properties refs #1393 (#1398)
* Add composition api's computed function support to vue/no-async-in-computed-properties refs #1393 * rename getComputedGetterBody to getGetterBodyFromComputedFunction * add testcase without return * use array instead of set
1 parent 1b75e28 commit b5b6347

File tree

4 files changed

+455
-52
lines changed

4 files changed

+455
-52
lines changed

docs/rules/no-async-in-computed-properties.md

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,21 @@
22
pageClass: rule-details
33
sidebarDepth: 0
44
title: vue/no-async-in-computed-properties
5-
description: disallow asynchronous actions in computed properties
5+
description: disallow asynchronous actions in computed properties and functions
66
since: v3.8.0
77
---
88
# vue/no-async-in-computed-properties
99

10-
> disallow asynchronous actions in computed properties
10+
> disallow asynchronous actions in computed properties and functions
1111
1212
- :gear: This rule is included in all of `"plugin:vue/vue3-essential"`, `"plugin:vue/essential"`, `"plugin:vue/vue3-strongly-recommended"`, `"plugin:vue/strongly-recommended"`, `"plugin:vue/vue3-recommended"` and `"plugin:vue/recommended"`.
1313

14-
Computed properties should be synchronous. Asynchronous actions inside them may not work as expected and can lead to an unexpected behaviour, that's why you should avoid them.
14+
Computed properties and functions should be synchronous. Asynchronous actions inside them may not work as expected and can lead to an unexpected behaviour, that's why you should avoid them.
1515
If you need async computed properties you might want to consider using additional plugin [vue-async-computed]
1616

1717
## :book: Rule Details
1818

19-
This rule is aimed at preventing asynchronous methods from being called in computed properties.
19+
This rule is aimed at preventing asynchronous methods from being called in computed properties and functions.
2020

2121
<eslint-code-block :rules="{'vue/no-async-in-computed-properties': ['error']}">
2222

@@ -62,6 +62,47 @@ export default {
6262

6363
</eslint-code-block>
6464

65+
<eslint-code-block :rules="{'vue/no-async-in-computed-properties': ['error']}">
66+
67+
```vue
68+
<script>
69+
import {computed} from 'vue'
70+
export default {
71+
setup() {
72+
/* ✓ GOOD */
73+
const foo = computed(() => {
74+
var bar = 0
75+
try {
76+
bar = bar / this.a
77+
} catch (e) {
78+
return 0
79+
} finally {
80+
return bar
81+
}
82+
})
83+
84+
/* ✗ BAD */
85+
const pro = computed(() => Promise.all([new Promise((resolve, reject) => {})]))
86+
const foo1 = computed(async () => await someFunc())
87+
const bar = computed(() => {
88+
return fetch(url).then(response => {})
89+
})
90+
const tim = computed(() => {
91+
setTimeout(() => { }, 0)
92+
})
93+
const inter = computed(() => {
94+
setInterval(() => { }, 0)
95+
})
96+
const anim = computed(() => {
97+
requestAnimationFrame(() => {})
98+
})
99+
}
100+
}
101+
</script>
102+
```
103+
104+
</eslint-code-block>
105+
65106
## :wrench: Options
66107

67108
Nothing.

lib/rules/no-async-in-computed-properties.js

Lines changed: 91 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* @author Armano
44
*/
55
'use strict'
6-
6+
const { ReferenceTracker } = require('eslint-utils')
77
const utils = require('../utils')
88

99
/**
@@ -77,13 +77,16 @@ module.exports = {
7777
},
7878
/** @param {RuleContext} context */
7979
create(context) {
80+
/** @type {Map<ObjectExpression, ComponentComputedProperty[]>} */
81+
const computedPropertiesMap = new Map()
82+
/** @type {Array<FunctionExpression | ArrowFunctionExpression>} */
83+
const computedFunctionNodes = []
84+
8085
/**
8186
* @typedef {object} ScopeStack
8287
* @property {ScopeStack | null} upper
8388
* @property {BlockStatement | Expression} body
8489
*/
85-
/** @type {Map<ObjectExpression, ComponentComputedProperty[]>} */
86-
const computedPropertiesMap = new Map()
8790
/** @type {ScopeStack | null} */
8891
let scopeStack = null
8992

@@ -139,63 +142,103 @@ module.exports = {
139142
})
140143
}
141144
})
142-
}
143-
return utils.defineVueVisitor(context, {
144-
onVueObjectEnter(node) {
145-
computedPropertiesMap.set(node, utils.getComputedProperties(node))
146-
},
147-
':function': onFunctionEnter,
148-
':function:exit': onFunctionExit,
149145

150-
NewExpression(node, { node: vueNode }) {
151-
if (!scopeStack) {
152-
return
153-
}
146+
computedFunctionNodes.forEach((c) => {
154147
if (
155-
node.callee.type === 'Identifier' &&
156-
node.callee.name === 'Promise'
148+
node.loc.start.line >= c.loc.start.line &&
149+
node.loc.end.line <= c.loc.end.line &&
150+
targetBody === c.body
157151
) {
158-
verify(
152+
context.report({
159153
node,
160-
scopeStack.body,
161-
'new',
162-
computedPropertiesMap.get(vueNode)
163-
)
154+
message: 'Unexpected {{expressionName}} in computed function.',
155+
data: {
156+
expressionName: expressionTypes[type]
157+
}
158+
})
164159
}
165-
},
160+
})
161+
}
162+
return Object.assign(
163+
{
164+
Program() {
165+
const tracker = new ReferenceTracker(context.getScope())
166+
const traceMap = utils.createCompositionApiTraceMap({
167+
[ReferenceTracker.ESM]: true,
168+
computed: {
169+
[ReferenceTracker.CALL]: true
170+
}
171+
})
172+
173+
for (const { node } of tracker.iterateEsmReferences(traceMap)) {
174+
if (node.type !== 'CallExpression') {
175+
continue
176+
}
166177

167-
CallExpression(node, { node: vueNode }) {
168-
if (!scopeStack) {
169-
return
178+
const getter = utils.getGetterBodyFromComputedFunction(node)
179+
if (getter) {
180+
computedFunctionNodes.push(getter)
181+
}
182+
}
170183
}
171-
if (isPromise(node)) {
172-
verify(
173-
node,
174-
scopeStack.body,
175-
'promise',
176-
computedPropertiesMap.get(vueNode)
177-
)
178-
} else if (isTimedFunction(node)) {
184+
},
185+
utils.defineVueVisitor(context, {
186+
onVueObjectEnter(node) {
187+
computedPropertiesMap.set(node, utils.getComputedProperties(node))
188+
},
189+
':function': onFunctionEnter,
190+
':function:exit': onFunctionExit,
191+
192+
NewExpression(node, { node: vueNode }) {
193+
if (!scopeStack) {
194+
return
195+
}
196+
if (
197+
node.callee.type === 'Identifier' &&
198+
node.callee.name === 'Promise'
199+
) {
200+
verify(
201+
node,
202+
scopeStack.body,
203+
'new',
204+
computedPropertiesMap.get(vueNode)
205+
)
206+
}
207+
},
208+
209+
CallExpression(node, { node: vueNode }) {
210+
if (!scopeStack) {
211+
return
212+
}
213+
if (isPromise(node)) {
214+
verify(
215+
node,
216+
scopeStack.body,
217+
'promise',
218+
computedPropertiesMap.get(vueNode)
219+
)
220+
} else if (isTimedFunction(node)) {
221+
verify(
222+
node,
223+
scopeStack.body,
224+
'timed',
225+
computedPropertiesMap.get(vueNode)
226+
)
227+
}
228+
},
229+
230+
AwaitExpression(node, { node: vueNode }) {
231+
if (!scopeStack) {
232+
return
233+
}
179234
verify(
180235
node,
181236
scopeStack.body,
182-
'timed',
237+
'await',
183238
computedPropertiesMap.get(vueNode)
184239
)
185240
}
186-
},
187-
188-
AwaitExpression(node, { node: vueNode }) {
189-
if (!scopeStack) {
190-
return
191-
}
192-
verify(
193-
node,
194-
scopeStack.body,
195-
'await',
196-
computedPropertiesMap.get(vueNode)
197-
)
198-
}
199-
})
241+
})
242+
)
200243
}
201244
}

lib/utils/index.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,44 @@ module.exports = {
860860
})
861861
},
862862

863+
/**
864+
* Get getter body from computed function
865+
* @param {CallExpression} callExpression call of computed function
866+
* @return {FunctionExpression | ArrowFunctionExpression | null} getter function
867+
*/
868+
getGetterBodyFromComputedFunction(callExpression) {
869+
if (callExpression.arguments.length <= 0) {
870+
return null
871+
}
872+
873+
const arg = callExpression.arguments[0]
874+
875+
if (
876+
arg.type === 'FunctionExpression' ||
877+
arg.type === 'ArrowFunctionExpression'
878+
) {
879+
return arg
880+
}
881+
882+
if (arg.type === 'ObjectExpression') {
883+
const getProperty = arg.properties.find(
884+
/**
885+
* @param {ESNode} p
886+
* @returns { p is (Property & { value: FunctionExpression | ArrowFunctionExpression }) }
887+
*/
888+
(p) =>
889+
p.type === 'Property' &&
890+
p.key.type === 'Identifier' &&
891+
p.key.name === 'get' &&
892+
(p.value.type === 'FunctionExpression' ||
893+
p.value.type === 'ArrowFunctionExpression')
894+
)
895+
return getProperty ? getProperty.value : null
896+
}
897+
898+
return null
899+
},
900+
863901
isVueFile,
864902

865903
/**

0 commit comments

Comments
 (0)