-
-
Notifications
You must be signed in to change notification settings - Fork 683
Add no-side-effects-in-computed-properties
rule
#69
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 10 commits
6b175fd
34fb5da
20deabd
774a169
2d5201f
01b2b40
99b2599
be7dfa0
f218407
c5a61a9
5e6330a
53aaa37
c036ba9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# Do not introduce side effects in computed properties (no-side-effects-in-computed-properties) | ||
|
||
It is considered a very bad practice to introduce side effects inside computed properties. It makes the code not predictable and hard to understand. | ||
|
||
|
||
## Rule Details | ||
|
||
Examples of **incorrect** code for this rule: | ||
|
||
```js | ||
|
||
export default { | ||
computed: { | ||
fullName() { | ||
this.firstName = 'lorem' // <- side effect | ||
return `${this.firstName} ${this.lastName}` | ||
}, | ||
somethingReversed() { | ||
return this.something.reverse() // <- side effect | ||
} | ||
} | ||
} | ||
|
||
``` | ||
|
||
Examples of **correct** code for this rule: | ||
|
||
```js | ||
|
||
export default { | ||
computed: { | ||
fullName() { | ||
return `${this.firstName} ${this.lastName}` | ||
}, | ||
somethingReversed() { | ||
return this.something.slice(0).reverse() | ||
} | ||
} | ||
} | ||
|
||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
/** | ||
* @fileoverview Don't introduce side effects in computed properties | ||
* @author Michał Sajnóg | ||
*/ | ||
'use strict' | ||
|
||
const utils = require('../utils') | ||
|
||
function parseMemberExpression (node) { | ||
const members = [] | ||
let memberExpression | ||
|
||
if (node.type === 'MemberExpression') { | ||
memberExpression = node | ||
|
||
while (memberExpression.type === 'MemberExpression') { | ||
if (memberExpression.property.type === 'Identifier') { | ||
members.push(memberExpression.property.name) | ||
} | ||
memberExpression = memberExpression.object | ||
} | ||
|
||
if (memberExpression.type === 'ThisExpression') { | ||
members.push('this') | ||
} else if (memberExpression.type === 'Identifier') { | ||
members.push(memberExpression.name) | ||
} | ||
} | ||
|
||
return members.reverse() | ||
} | ||
|
||
function getComputedProperties (componentProperties) { | ||
const computedPropertiesNode = componentProperties | ||
.filter(p => | ||
p.key.type === 'Identifier' && | ||
p.key.name === 'computed' && | ||
p.value.type === 'ObjectExpression' | ||
)[0] | ||
|
||
if (!computedPropertiesNode) { return [] } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unit test never enters this |
||
|
||
const computedProperties = computedPropertiesNode.value.properties | ||
|
||
return computedProperties | ||
.map(cp => { | ||
const key = cp.key.name | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @michalsnik
example: export default {
computed: {
...mapGetters({})
}
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Very good point, thank you @armano2 for pointing that! |
||
let value | ||
|
||
if (cp.value.type === 'FunctionExpression') { | ||
value = cp.value.body | ||
} else if (cp.value.type === 'ObjectExpression') { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. coverage from unit test never enters this part |
||
value = cp.value.properties | ||
.filter(p => | ||
p.key.type === 'Identifier' && | ||
p.key.name === 'get' && | ||
p.value.type === 'FunctionExpression' | ||
) | ||
.map(p => p.value.body)[0] | ||
} | ||
|
||
return { key, value } | ||
}) | ||
.filter(cp => cp.value) | ||
} | ||
|
||
function create (context) { | ||
const forbiddenNodes = [] | ||
|
||
return Object.assign({}, | ||
{ | ||
// this.xxx <=|+=|-=> | ||
'AssignmentExpression > MemberExpression' (node) { | ||
if (parseMemberExpression(node)[0] === 'this') { | ||
forbiddenNodes.push(node) | ||
} | ||
}, | ||
// this.xxx <++|--> | ||
'UpdateExpression > MemberExpression' (node) { | ||
if (parseMemberExpression(node)[0] === 'this') { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. all tests are for |
||
forbiddenNodes.push(node) | ||
} | ||
}, | ||
// this.xxx.func() | ||
'CallExpression' (node) { | ||
const code = context.getSourceCode().getText(node) | ||
const MUTATION_REGEX = /(this.)((?!(concat|slice|map|filter)\().)*((push|pop|shift|unshift|reverse|splice|sort|copyWithin|fill)\()/g | ||
|
||
if (MUTATION_REGEX.test(code)) { | ||
forbiddenNodes.push(node) | ||
} | ||
} | ||
}, | ||
utils.executeOnVueComponent(context, (properties) => { | ||
const computedProperties = getComputedProperties(properties) | ||
|
||
computedProperties.forEach(cp => { | ||
forbiddenNodes.forEach(node => { | ||
if ( | ||
node.loc.start.line >= cp.value.loc.start.line && | ||
node.loc.end.line <= cp.value.loc.end.line | ||
) { | ||
context.report({ | ||
node: node, | ||
message: `Unexpected side effect in "${cp.key}" computed property.` | ||
}) | ||
} | ||
}) | ||
}) | ||
}) | ||
) | ||
} | ||
|
||
// ------------------------------------------------------------------------------ | ||
// Rule Definition | ||
// ------------------------------------------------------------------------------ | ||
|
||
module.exports = { | ||
create, | ||
meta: { | ||
docs: { | ||
description: 'Don\'t introduce side effects in computed properties', | ||
category: 'Best Practices', | ||
recommended: false | ||
}, | ||
fixable: null, | ||
schema: [] | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unit test never enters here