Skip to content

Commit c0cdedd

Browse files
committed
Add no-async-in-computed-properties rule.
1 parent 1d00dd6 commit c0cdedd

5 files changed

+608
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Check if there are no asynchronous actions inside computed properties (no-async-in-computed-properties)
2+
3+
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.
4+
If you need async computed properties you might want to consider using additional plugin [vue-async-computed]
5+
6+
## :book: Rule Details
7+
8+
This rule is aimed at preventing asynchronous methods from being called in computed properties.
9+
10+
:-1: Examples of **incorrect** code for this rule:
11+
12+
```js
13+
export default {
14+
computed: {
15+
pro () {
16+
return Promise.all([new Promise((resolve, reject) => {})])
17+
},
18+
foo: async function () {
19+
return await someFunc()
20+
},
21+
bar () {
22+
return fetch(url).then(response => {})
23+
},
24+
yiel: function* () {
25+
yield 1
26+
yield* g1()
27+
},
28+
tim () {
29+
setTimeout(() => { }, 0)
30+
},
31+
inter () {
32+
setInterval(() => { }, 0)
33+
},
34+
anim () {
35+
requestAnimationFrame(() => {})
36+
}
37+
}
38+
}
39+
```
40+
41+
:+1: Examples of **correct** code for this rule:
42+
43+
```js
44+
export default {
45+
computed: {
46+
foo () {
47+
var bar = 0
48+
try {
49+
bar = bar / this.a
50+
} catch (e) {
51+
return 0
52+
} finally {
53+
return bar
54+
}
55+
}
56+
}
57+
}
58+
```
59+
60+
## :wrench: Options
61+
62+
Nothing.
63+
64+
[vue-async-computed]: https://github.com/foxbenjaminfox/vue-async-computed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/**
2+
* @fileoverview Check if there are no asynchronous actions inside computed properties.
3+
* @author Armano
4+
*/
5+
'use strict'
6+
7+
const utils = require('../utils')
8+
9+
const PROMISE_FUNCTIONS = [
10+
'then',
11+
'catch',
12+
'finally'
13+
]
14+
15+
const PROMISE_METHODS = [
16+
'all',
17+
'race',
18+
'reject',
19+
'resolve'
20+
]
21+
22+
const TIMED_FUNCTIONS = [
23+
'setTimeout',
24+
'setInterval',
25+
'setImmediate',
26+
'requestAnimationFrame'
27+
]
28+
29+
function isTimedFunction (node) {
30+
return (
31+
node.type === 'CallExpression' &&
32+
node.callee.type === 'Identifier' &&
33+
TIMED_FUNCTIONS.indexOf(node.callee.name) !== -1
34+
) || (
35+
node.type === 'CallExpression' &&
36+
node.callee.type === 'MemberExpression' &&
37+
node.callee.object.type === 'Identifier' &&
38+
node.callee.object.name === 'window' && (
39+
TIMED_FUNCTIONS.indexOf(node.callee.property.name) !== -1
40+
)
41+
) && node.arguments.length
42+
}
43+
44+
function isPromise (node) {
45+
if (node.type === 'CallExpression' && node.callee.type === 'MemberExpression') {
46+
return ( // hello.PROMISE_FUNCTION()
47+
node.callee.property.type === 'Identifier' &&
48+
PROMISE_FUNCTIONS.indexOf(node.callee.property.name) !== -1
49+
) || ( // Promise.PROMISE_METHOD()
50+
node.callee.object.type === 'Identifier' &&
51+
node.callee.object.name === 'Promise' &&
52+
PROMISE_METHODS.indexOf(node.callee.property.name) !== -1
53+
)
54+
}
55+
return false
56+
}
57+
58+
function create (context) {
59+
const forbiddenNodes = []
60+
61+
const expressionTypes = {
62+
promise: 'asynchronous action',
63+
await: 'await operator',
64+
yield: 'yield keyword',
65+
generator: 'generator function expression',
66+
async: 'async function declaration',
67+
new: 'Promise object',
68+
timed: 'timed function'
69+
}
70+
71+
function onFunctionEnter (node) {
72+
if (node.async) {
73+
forbiddenNodes.push({
74+
node: node,
75+
type: 'async'
76+
})
77+
}
78+
if (node.generator) {
79+
forbiddenNodes.push({
80+
node: node,
81+
type: 'generator'
82+
})
83+
}
84+
}
85+
86+
return Object.assign({},
87+
{
88+
FunctionDeclaration: onFunctionEnter,
89+
90+
FunctionExpression: onFunctionEnter,
91+
92+
ArrowFunctionExpression: onFunctionEnter,
93+
94+
NewExpression (node) {
95+
if (node.callee.name === 'Promise') {
96+
forbiddenNodes.push({
97+
node: node,
98+
type: 'new'
99+
})
100+
}
101+
},
102+
103+
CallExpression (node) {
104+
if (isPromise(node)) {
105+
forbiddenNodes.push({
106+
node: node,
107+
type: 'promise'
108+
})
109+
}
110+
if (isTimedFunction(node)) {
111+
forbiddenNodes.push({
112+
node: node,
113+
type: 'timed'
114+
})
115+
}
116+
},
117+
118+
YieldExpression (node) {
119+
// await nodes are YieldExpression's with babel-eslint < 7.0.0
120+
forbiddenNodes.push({
121+
node: node,
122+
type: 'yield'
123+
})
124+
},
125+
126+
AwaitExpression (node) {
127+
forbiddenNodes.push({
128+
node: node,
129+
type: 'await'
130+
})
131+
}
132+
},
133+
utils.executeOnVueComponent(context, (obj) => {
134+
const computedProperties = utils.getComputedProperties(obj)
135+
136+
computedProperties.forEach(cp => {
137+
forbiddenNodes.forEach(el => {
138+
if (
139+
cp.value &&
140+
el.node.loc.start.line >= cp.value.loc.start.line &&
141+
el.node.loc.end.line <= cp.value.loc.end.line
142+
) {
143+
context.report({
144+
node: el.node,
145+
message: 'Unexpected {{expressionName}} in "{{propertyName}}" computed property.',
146+
data: {
147+
expressionName: expressionTypes[el.type],
148+
propertyName: cp.key
149+
}
150+
})
151+
}
152+
})
153+
})
154+
})
155+
)
156+
}
157+
158+
// ------------------------------------------------------------------------------
159+
// Rule Definition
160+
// ------------------------------------------------------------------------------
161+
162+
module.exports = {
163+
create,
164+
meta: {
165+
docs: {
166+
description: 'Check if there are no asynchronous actions inside computed properties.',
167+
category: 'Best Practices',
168+
recommended: false
169+
},
170+
fixable: null,
171+
schema: []
172+
}
173+
}

lib/rules/no-side-effects-in-computed-properties.js

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ function create (context) {
3939
computedProperties.forEach(cp => {
4040
forbiddenNodes.forEach(node => {
4141
if (
42+
cp.value &&
4243
node.loc.start.line >= cp.value.loc.start.line &&
4344
node.loc.end.line <= cp.value.loc.end.line
4445
) {

0 commit comments

Comments
 (0)