diff --git a/docs/rules/no-direct-mutation-state.md b/docs/rules/no-direct-mutation-state.md index 3a8c8bdd05..c2bd487f63 100644 --- a/docs/rules/no-direct-mutation-state.md +++ b/docs/rules/no-direct-mutation-state.md @@ -58,3 +58,18 @@ class Hello extends React.Component { } } ``` + +## Rule Options + +This rule can take one argument to ignore some specific props during validation. + +```js +... +"react/no-direct-mutation-state": [, { + constructors: +}] +... +``` + +* `enabled`: for enabling the rule. 0=off, 1=warn, 2=error. Defaults to 0. +* `constructors`: optional array of methods name to check like constructor diff --git a/lib/rules/no-direct-mutation-state.js b/lib/rules/no-direct-mutation-state.js index 120a296b5e..6d2bcd7a5e 100644 --- a/lib/rules/no-direct-mutation-state.js +++ b/lib/rules/no-direct-mutation-state.js @@ -17,10 +17,26 @@ module.exports = { description: 'Prevent direct mutation of this.state', category: 'Possible Errors', recommended: true - } + }, + + schema: [{ + type: 'object', + properties: { + constructors: { + type: 'array', + items: { + type: 'string' + } + } + }, + additionalProperties: false + }] }, create: Components.detect((context, components, utils) => { + const configuration = context.options[0] || {}; + const ctorsMethod = configuration.constructors || []; + /** * Checks if the component is valid * @param {Object} component The component to process @@ -30,6 +46,24 @@ module.exports = { return Boolean(component && !component.mutateSetState); } + /** + * Checks if the method is ignored + * @param {String} name Name of the method to check. + * @returns {Boolean} True if the method is ignored, false if not. + */ + function isCtorMethod(name) { + return ctorsMethod.indexOf(name) !== -1; + } + + /** + * Checks if the node is constructor or ignored + * @param {ASTNode} node The AST node being checked. + * @returns {Boolean} True if the method is is constructor or ignored, false if not. + */ + function isConstructor(node) { + return node.kind === 'constructor' || (node.kind === 'method' && isCtorMethod(node.key.name)); + } + /** * Reports undeclared proptypes for a given component * @param {Object} component The component to process @@ -48,13 +82,13 @@ module.exports = { // -------------------------------------------------------------------------- // Public // -------------------------------------------------------------------------- - let inConstructor = false; + let inConstructorOrIgnored = false; let inCallExpression = false; return { MethodDefinition(node) { - if (node.kind === 'constructor') { - inConstructor = true; + if (isConstructor(node)) { + inConstructorOrIgnored = true; } }, @@ -64,7 +98,7 @@ module.exports = { AssignmentExpression(node) { let item; - if ((inConstructor && !inCallExpression) || !node.left || !node.left.object) { + if ((inConstructorOrIgnored && !inCallExpression) || !node.left || !node.left.object) { return; } item = node.left; @@ -90,8 +124,8 @@ module.exports = { }, 'MethodDefinition:exit': function (node) { - if (node.kind === 'constructor') { - inConstructor = false; + if (isConstructor(node)) { + inConstructorOrIgnored = false; } }, diff --git a/tests/lib/rules/no-direct-mutation-state.js b/tests/lib/rules/no-direct-mutation-state.js index 6886125cc5..0f974517c5 100644 --- a/tests/lib/rules/no-direct-mutation-state.js +++ b/tests/lib/rules/no-direct-mutation-state.js @@ -69,6 +69,17 @@ ruleTester.run('no-direct-mutation-state', rule, { ' }', '}' ].join('\n') + }, { + code: [ + 'class Hello extends React.Component {', + ' myMethod() {', + ' this.state = {' + + ' boo: "bar" ' + + ' };' + + ' }', + '}' + ].join('\n'), + options: [{constructors: ['myMethod']}] }], invalid: [{ @@ -154,6 +165,20 @@ ruleTester.run('no-direct-mutation-state', rule, { errors: [{ message: 'Do not mutate state directly. Use setState().' }] + }, { + code: [ + 'class Hello extends React.Component {', + ' myMethod(props) {', + ' doSomethingAsync(() => {', + ' this.state = "bad";', + ' });', + ' }', + '}' + ].join('\n'), + options: [{constructors: ['myMethod']}], + errors: [{ + message: 'Do not mutate state directly. Use setState().' + }] }, { code: [ 'class Hello extends React.Component {',