-
-
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 6 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,127 @@ | ||
/** | ||
* @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 => { | ||
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. in case when there is only computed: {
foo: {
set () {}
}
} returned value is going to be return [
{
key: '....',
value: undefined
}
] And code when its used has to check for that case or we should filter it here 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. Good point, thanks @armano2 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 i think its better to check it here cus we are going propably to add rule about checking if computed propery has |
||
const key = cp.key.name | ||
let value | ||
|
||
if (cp.value.type === 'FunctionExpression') { | ||
value = cp.value.body | ||
} else if (cp.value.type === 'ObjectExpression') { | ||
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 } | ||
}) | ||
} | ||
|
||
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