Skip to content

Commit 5ebdf71

Browse files
armano2michalsnik
authored andcommitted
Add no-async-in-computed-properties rule (#72)
* Add `no-async-in-computed-properties` rule. * Remove yield/generator from this rule. * Remove yield from example code
1 parent 29d1cb6 commit 5ebdf71

5 files changed

+564
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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+
tim () {
25+
setTimeout(() => { }, 0)
26+
},
27+
inter () {
28+
setInterval(() => { }, 0)
29+
},
30+
anim () {
31+
requestAnimationFrame(() => {})
32+
}
33+
}
34+
}
35+
```
36+
37+
:+1: Examples of **correct** code for this rule:
38+
39+
```js
40+
export default {
41+
computed: {
42+
foo () {
43+
var bar = 0
44+
try {
45+
bar = bar / this.a
46+
} catch (e) {
47+
return 0
48+
} finally {
49+
return bar
50+
}
51+
}
52+
}
53+
}
54+
```
55+
56+
## :wrench: Options
57+
58+
Nothing.
59+
60+
[vue-async-computed]: https://github.com/foxbenjaminfox/vue-async-computed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
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+
async: 'async function declaration',
65+
new: 'Promise object',
66+
timed: 'timed function'
67+
}
68+
69+
function onFunctionEnter (node) {
70+
if (node.async) {
71+
forbiddenNodes.push({
72+
node: node,
73+
type: 'async'
74+
})
75+
}
76+
}
77+
78+
return Object.assign({},
79+
{
80+
FunctionDeclaration: onFunctionEnter,
81+
82+
FunctionExpression: onFunctionEnter,
83+
84+
ArrowFunctionExpression: onFunctionEnter,
85+
86+
NewExpression (node) {
87+
if (node.callee.name === 'Promise') {
88+
forbiddenNodes.push({
89+
node: node,
90+
type: 'new'
91+
})
92+
}
93+
},
94+
95+
CallExpression (node) {
96+
if (isPromise(node)) {
97+
forbiddenNodes.push({
98+
node: node,
99+
type: 'promise'
100+
})
101+
}
102+
if (isTimedFunction(node)) {
103+
forbiddenNodes.push({
104+
node: node,
105+
type: 'timed'
106+
})
107+
}
108+
},
109+
110+
AwaitExpression (node) {
111+
forbiddenNodes.push({
112+
node: node,
113+
type: 'await'
114+
})
115+
}
116+
},
117+
utils.executeOnVueComponent(context, (obj) => {
118+
const computedProperties = utils.getComputedProperties(obj)
119+
120+
computedProperties.forEach(cp => {
121+
forbiddenNodes.forEach(el => {
122+
if (
123+
cp.value &&
124+
el.node.loc.start.line >= cp.value.loc.start.line &&
125+
el.node.loc.end.line <= cp.value.loc.end.line
126+
) {
127+
context.report({
128+
node: el.node,
129+
message: 'Unexpected {{expressionName}} in "{{propertyName}}" computed property.',
130+
data: {
131+
expressionName: expressionTypes[el.type],
132+
propertyName: cp.key
133+
}
134+
})
135+
}
136+
})
137+
})
138+
})
139+
)
140+
}
141+
142+
// ------------------------------------------------------------------------------
143+
// Rule Definition
144+
// ------------------------------------------------------------------------------
145+
146+
module.exports = {
147+
create,
148+
meta: {
149+
docs: {
150+
description: 'Check if there are no asynchronous actions inside computed properties.',
151+
category: 'Best Practices',
152+
recommended: false
153+
},
154+
fixable: null,
155+
schema: []
156+
}
157+
}

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)