Skip to content

Commit

Permalink
no-mutation-props #1113
Browse files Browse the repository at this point in the history
  • Loading branch information
ianschmitz authored and joeybaker committed Sep 7, 2017
1 parent f1e86b5 commit 63eaf60
Show file tree
Hide file tree
Showing 5 changed files with 257 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ Finally, enable all of the rules that you would like to use. Use [our preset](#
* [react/no-is-mounted](docs/rules/no-is-mounted.md): Prevent usage of `isMounted`
* [react/no-multi-comp](docs/rules/no-multi-comp.md): Prevent multiple component definition per file
* [react/no-redundant-should-component-update](docs/rules/no-redundant-should-component-update.md): Prevent usage of `shouldComponentUpdate` when extending React.PureComponent
* [react/no-mutation-props](docs/rules/no-mutation-props.md): Prevent mutation of `this.props`
* [react/no-render-return-value](docs/rules/no-render-return-value.md): Prevent usage of the return value of `React.render`
* [react/no-set-state](docs/rules/no-set-state.md): Prevent usage of `setState`
* [react/no-typos](docs/rules/no-typos.md): Prevent common casing typos
Expand Down
19 changes: 19 additions & 0 deletions docs/rules/no-mutation-props.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Prevent mutation of this.props (no-mutation-props)

NEVER mutate `this.props`, as all React components must act like pure functions with respect to their props.
Treat `this.props` as if it were immutable. More info available at [https://facebook.github.io/react/docs/components-and-props.html#props-are-read-only](https://facebook.github.io/react/docs/components-and-props.html#props-are-read-only)

## Rule Details

This rule is aimed to forbid the use of mutating `this.props`.

The following patterns are considered warnings:

```jsx
var Hello = React.createClass({
render: function() {
this.props.name = this.props.name.toUpperCase();
return <div>Hello {this.props.name}</div>;
}
});
```
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const allRules = {
'no-unused-prop-types': require('./lib/rules/no-unused-prop-types'),
'no-unused-state': require('./lib/rules/no-unused-state'),
'no-will-update-set-state': require('./lib/rules/no-will-update-set-state'),
'no-mutation-props': require('./lib/rules/no-mutation-props'),
'prefer-es6-class': require('./lib/rules/prefer-es6-class'),
'prefer-stateless-function': require('./lib/rules/prefer-stateless-function'),
'prop-types': require('./lib/rules/prop-types'),
Expand Down Expand Up @@ -123,6 +124,7 @@ module.exports = {
'react/no-danger-with-children': 2,
'react/no-deprecated': 2,
'react/no-direct-mutation-state': 2,
'react/no-mutation-props': 2,
'react/no-find-dom-node': 2,
'react/no-is-mounted': 2,
'react/no-render-return-value': 2,
Expand Down
89 changes: 89 additions & 0 deletions lib/rules/no-mutation-props.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* @fileoverview Prevent direct mutation of this.props
* @author Ian Schmitz
*/
'use strict';

var has = require('has');
var Components = require('../util/Components');

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------

module.exports = {
meta: {
docs: {
description: 'Prevent direct mutation of this.props',
category: 'Possible Errors',
recommended: true
}
},

create: Components.detect(function (context, components, utils) {

/**
* Checks if the component is valid
* @param {Object} component The component to process
* @returns {Boolean} True if the component is valid, false if not.
*/
function isValid(component) {
return Boolean(component && !component.mutateProps);
}

/**
* Reports this.props mutations for a given component
* @param {Object} component The component to process
*/
function reportMutations(component) {
var mutation;
for (var i = 0, j = component.mutations.length; i < j; i++) {
mutation = component.mutations[i];
context.report({
node: mutation,
message: 'Do not mutate props.'
});
}
}

// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------

return {
AssignmentExpression: function (node) {
var item;

if (!node.left || !node.left.object || !node.left.object.object) {
return;
}

item = node.left.object;
while (item.object.property) {
item = item.object;
}

if (item.object.type === 'ThisExpression' && item.property.name === 'props') {
var component = components.get(utils.getParentComponent());
var mutations = component && component.mutations || [];
mutations.push(node.left.object);
components.set(node, {
mutateProps: true,
mutations: mutations
});
}
},

'Program:exit': function () {
var list = components.list();
for (var component in list) {
if (!has(list, component) || isValid(list[component])) {
continue;
}
reportMutations(list[component]);
}
}
};

})
};
146 changes: 146 additions & 0 deletions tests/lib/rules/no-mutation-props.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/**
* @fileoverview Prevent mutation of this.props
* @author Ian Schmitz
*/
'use strict';

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------

var rule = require('../../../lib/rules/no-mutation-props');
var RuleTester = require('eslint').RuleTester;

var parserOptions = {
ecmaVersion: 6,
ecmaFeatures: {
jsx: true
}
};

require('babel-eslint');

// ------------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------------

var ruleTester = new RuleTester();
ruleTester.run('no-mutation-props', rule, {

valid: [{
code: [
'var Hello = React.createClass({',
' render: function() {',
' return <div>Hello {this.props.name}</div>;',
' }',
'});'
].join('\n'),
parserOptions: parserOptions
}, {
code: [
'var Hello = React.createClass({',
' render: function() {',
' var obj = {props: {}};',
' obj.props.name = "foo";',
' return <div>Hello {obj.props.name}</div>;',
' }',
'});'
].join('\n'),
parserOptions: parserOptions
}, {
code: [
'var Hello = "foo";',
'module.exports = {};'
].join('\n'),
parserOptions: parserOptions
}, {
code: [
'class Hello {',
' getFoo() {',
' this.props.foo = \'bar\'',
' return this.props.foo;',
' }',
'}'
].join('\n'),
parserOptions: parserOptions
}],

invalid: [{
code: [
'var Hello = React.createClass({',
' render: function() {',
' this.props.foo = "bar"',
' return <div>Hello {this.props.name}</div>;',
' }',
'});'
].join('\n'),
parserOptions: parserOptions,
errors: [{
message: 'Do not mutate props.'
}]
}, {
code: [
'var Hello = React.createClass({',
' render: function() {',
' this.props.person.name= "bar"',
' return <div>Hello {this.props.name}</div>;',
' }',
'});'
].join('\n'),
parserOptions: parserOptions,
errors: [{
message: 'Do not mutate props.'
}]
}, {
code: [
'var Hello = React.createClass({',
' render: function() {',
' this.props.person.name.first = "bar"',
' return <div>Hello</div>;',
' }',
'});'
].join('\n'),
parserOptions: parserOptions,
errors: [{
message: 'Do not mutate props.'
}]
}, {
code: [
'var Hello = React.createClass({',
' render: function() {',
' this.props.person.name.first = "bar"',
' this.props.person.name.last = "baz"',
' return <div>Hello</div>;',
' }',
'});'
].join('\n'),
parserOptions: parserOptions,
errors: [{
message: 'Do not mutate props.',
line: 3,
column: 5
}, {
message: 'Do not mutate props.',
line: 4,
column: 5
}]
}
/**
* Would be nice to prevent this too
, {
code: [
'var Hello = React.createClass({',
' render: function() {',
' var that = this;',
' that.props.person.name.first = "bar"',
' return <div>Hello</div>;',
' }',
'});'
].join('\n'),
parserOptions: parserOptions,
errors: [{
message: 'Do not mutate props.'
}]
}*/
]
});

0 comments on commit 63eaf60

Please sign in to comment.