Skip to content

Commit 78b844e

Browse files
committed
Merge remote-tracking branch 'vuejs/master' into patch-10-prop-specificity
2 parents 34f8b18 + 1ea1396 commit 78b844e

12 files changed

+793
-9
lines changed
Lines changed: 60 additions & 0 deletions
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
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Enforces component's data property to be a function (no-shared-component-data)
2+
3+
When using the data property on a component (i.e. anywhere except on `new Vue`), the value must be a function that returns an object.
4+
5+
## :book: Rule Details
6+
7+
When the value of `data` is an object, it’s shared across all instances of a component.
8+
9+
:-1: Examples of **incorrect** code for this rule:
10+
11+
```js
12+
Vue.component('some-comp', {
13+
data: {
14+
foo: 'bar'
15+
}
16+
})
17+
```
18+
19+
:+1: Examples of **correct** code for this rule:
20+
21+
```js
22+
Vue.component('some-comp', {
23+
data: function () {
24+
return {
25+
foo: 'bar'
26+
}
27+
}
28+
})
29+
```
30+
31+
## :wrench: Options
32+
33+
Nothing.

lib/rules/name-property-casing.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ function create (context) {
5555
// Public
5656
// ----------------------------------------------------------------------
5757

58-
return utils.executeOnVueComponent(context, (obj) => {
58+
return utils.executeOnVue(context, (obj) => {
5959
const node = obj.properties
6060
.filter(item => (
6161
item.type === 'Property' &&
Lines changed: 157 additions & 0 deletions
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.executeOnVue(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-shared-component-data.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* @fileoverview Enforces component's data property to be a function.
3+
* @author Armano
4+
*/
5+
'use strict'
6+
7+
const utils = require('../utils')
8+
9+
function create (context) {
10+
// ----------------------------------------------------------------------
11+
// Public
12+
// ----------------------------------------------------------------------
13+
14+
return utils.executeOnVueComponent(context, (obj) => {
15+
obj.properties
16+
.filter(p =>
17+
p.type === 'Property' &&
18+
p.key.type === 'Identifier' &&
19+
p.key.name === 'data' &&
20+
p.value.type !== 'FunctionExpression' &&
21+
p.value.type !== 'Identifier'
22+
)
23+
.forEach(cp => {
24+
context.report({
25+
node: cp.value,
26+
message: '`data` property in component must be a function'
27+
})
28+
})
29+
})
30+
}
31+
32+
// ------------------------------------------------------------------------------
33+
// Rule Definition
34+
// ------------------------------------------------------------------------------
35+
36+
module.exports = {
37+
meta: {
38+
docs: {
39+
description: "Enforces component's data property to be a function.",
40+
category: 'Possible Errors',
41+
recommended: false
42+
},
43+
fixable: null, // or "code" or "whitespace"
44+
schema: [
45+
// fill in your schema
46+
]
47+
},
48+
49+
create
50+
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,13 @@ function create (context) {
3333
}
3434
}
3535
},
36-
utils.executeOnVueComponent(context, (obj) => {
36+
utils.executeOnVue(context, (obj) => {
3737
const computedProperties = utils.getComputedProperties(obj)
3838

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
) {

lib/rules/order-in-components.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ function create (context) {
8080
const extendedOrder = order.map(property => groups[property] || property)
8181
const orderMap = getOrderMap(extendedOrder)
8282

83-
return utils.executeOnVueComponent(context, (obj) => {
83+
return utils.executeOnVue(context, (obj) => {
8484
checkOrder(obj.properties, orderMap, context)
8585
})
8686
}

lib/rules/return-in-computed-property.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ function create (context) {
6161
}
6262
}
6363
},
64-
utils.executeOnVueComponent(context, properties => {
64+
utils.executeOnVue(context, properties => {
6565
const computedProperties = utils.getComputedProperties(properties)
6666

6767
computedProperties.forEach(cp => {

lib/utils/index.js

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,25 @@ module.exports = {
405405
node.arguments[0].type === 'ObjectExpression'
406406
},
407407

408+
executeOnVue (context, cb) {
409+
return Object.assign(
410+
this.executeOnVueComponent(context, cb),
411+
this.executeOnVueInstance(context, cb)
412+
)
413+
},
414+
415+
executeOnVueInstance (context, cb) {
416+
const _this = this
417+
418+
return {
419+
'NewExpression:exit' (node) {
420+
// new Vue({})
421+
if (!_this.isVueInstance(node)) return
422+
cb(node.arguments[0])
423+
}
424+
}
425+
},
426+
408427
executeOnVueComponent (context, cb) {
409428
const filePath = context.getFilename()
410429
const _this = this
@@ -419,11 +438,6 @@ module.exports = {
419438
// Vue.component('xxx', {}) || component('xxx', {})
420439
if (!_this.isVueComponent(node)) return
421440
cb(node.arguments.slice(-1)[0])
422-
},
423-
'NewExpression:exit' (node) {
424-
// new Vue({})
425-
if (!_this.isVueInstance(node)) return
426-
cb(node.arguments[0])
427441
}
428442
}
429443
}

0 commit comments

Comments
 (0)