Skip to content

Commit

Permalink
Handle UpdateExpressions in no-direct-mutation-state
Browse files Browse the repository at this point in the history
This rule should also catch increment & decrement operators.
  • Loading branch information
zpao committed Aug 21, 2017
1 parent 162b92b commit aafafc4
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 10 deletions.
59 changes: 49 additions & 10 deletions lib/rules/no-direct-mutation-state.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,36 @@ module.exports = {
}
}

/**
* Walks throughs the MemberExpression to the top-most property.
* @param {Object} node The node to process
* @returns {Object} The outer-most MemberExpression
*/
function getOuterMemberExpression(node) {
while (node.object && node.object.property) {
node = node.object;
}
return node;
}

/**
* Determine if this MemberExpression is for `this.state`
* @param {Object} node The node to process
* @returns {Boolean}
*/
function isStateMemberExpression(node) {
return node.object.type === 'ThisExpression' && node.property.name === 'state';
}

/**
* Determine if we should currently ignore assignments in this component.
* @param {?Object} component The component to process
* @returns {Boolean} True if we should skip assignment checks.
*/
function shouldIgnoreComponent(component) {
return !component || (component.inConstructor && !component.inCallExpression);
}

// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
Expand All @@ -64,19 +94,12 @@ module.exports = {
},

AssignmentExpression(node) {
let item;
const component = components.get(utils.getParentComponent());
if (!component || (component.inConstructor && !component.inCallExpression) || !node.left || !node.left.object) {
if (shouldIgnoreComponent(component) || !node.left || !node.left.object) {
return;
}
item = node.left;
while (item.object && item.object.property) {
item = item.object;
}
if (
item.object.type === 'ThisExpression' &&
item.property.name === 'state'
) {
const item = getOuterMemberExpression(node.left);
if (isStateMemberExpression(item)) {
const mutations = (component && component.mutations) || [];
mutations.push(node.left.object);
components.set(node, {
Expand All @@ -86,6 +109,22 @@ module.exports = {
}
},

UpdateExpression(node) {
const component = components.get(utils.getParentComponent());
if (shouldIgnoreComponent(component) || node.argument.type !== 'MemberExpression') {
return;
}
const item = getOuterMemberExpression(node.argument);
if (isStateMemberExpression(item)) {
const mutations = (component && component.mutations) || [];
mutations.push(item);
components.set(node, {
mutateSetState: true,
mutations
});
}
},

'CallExpression:exit': function(node) {
components.set(node, {
inCallExpression: false
Expand Down
20 changes: 20 additions & 0 deletions tests/lib/rules/no-direct-mutation-state.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ ruleTester.run('no-direct-mutation-state', rule, {
' }',
'}'
].join('\n')
}, {
code: [
'class Hello extends React.Component {',
' constructor() {',
' this.state.foo = 1;',
' }',
'}'
].join('\n')
}, {
code: `
class OneComponent extends Component {
Expand Down Expand Up @@ -97,6 +105,18 @@ ruleTester.run('no-direct-mutation-state', rule, {
errors: [{
message: 'Do not mutate state directly. Use setState().'
}]
}, {
code: [
'var Hello = createReactClass({',
' render: function() {',
' this.state.foo++;',
' return <div>Hello {this.props.name}</div>;',
' }',
'});'
].join('\n'),
errors: [{
message: 'Do not mutate state directly. Use setState().'
}]
}, {
code: [
'var Hello = createReactClass({',
Expand Down

0 comments on commit aafafc4

Please sign in to comment.