From 3266ab604011f91e027c7a2e1152d3a893b282cc Mon Sep 17 00:00:00 2001 From: Benjamin Stepp Date: Wed, 22 Feb 2017 18:47:50 -0600 Subject: [PATCH 01/10] prefer-stateless-function w/ decorators Currently prefer-stateless-function warns when using a decorated class over a stateless function. The decorator syntax only works with classes, so it makes sense not to warn in this case. Fixes #1034 --- docs/rules/prefer-stateless-function.md | 1 + lib/rules/prefer-stateless-function.js | 15 +++++++++ tests/lib/rules/prefer-stateless-function.js | 34 ++++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/docs/rules/prefer-stateless-function.md b/docs/rules/prefer-stateless-function.md index 0eaf88d92a..d136f614c1 100644 --- a/docs/rules/prefer-stateless-function.md +++ b/docs/rules/prefer-stateless-function.md @@ -10,6 +10,7 @@ This rule will check your class based React components for * instance property other than `this.props` and `this.context` * extension of `React.PureComponent` (if the `ignorePureComponents` flag is true) * presence of `ref` attribute in JSX +* the use of decorators * `render` method that return anything but JSX: `undefined`, `null`, etc. (only in React <15.0.0, see [shared settings](https://github.com/yannickcr/eslint-plugin-react/blob/master/README.md#configuration) for React version configuration) If none of these elements are found, the rule will warn you to write this component as a pure function. diff --git a/lib/rules/prefer-stateless-function.js b/lib/rules/prefer-stateless-function.js index 9cf896a616..4403273d9c 100644 --- a/lib/rules/prefer-stateless-function.js +++ b/lib/rules/prefer-stateless-function.js @@ -286,11 +286,25 @@ module.exports = { }); } + /** + * Mark a ClassDeclaration as having used decorators + * @param {ASTNode} node The AST node being checked. + */ + function markDecoratorsAsUsed(node) { + components.set(node, { + useDecorators: true + }); + } + return { ClassDeclaration: function (node) { if (ignorePureComponents && utils.isPureComponent(node)) { markSCUAsDeclared(node); } + + if (node.decorators && node.decorators.length) { + markDecoratorsAsUsed(node); + } }, // Mark `this` destructuring as a usage of `this` @@ -378,6 +392,7 @@ module.exports = { list[component].useRef || list[component].invalidReturn || list[component].hasChildContextTypes || + list[component].useDecorators || (!utils.isES5Component(list[component].node) && !utils.isES6Component(list[component].node)) ) { continue; diff --git a/tests/lib/rules/prefer-stateless-function.js b/tests/lib/rules/prefer-stateless-function.js index 34e4601673..a7d8681728 100644 --- a/tests/lib/rules/prefer-stateless-function.js +++ b/tests/lib/rules/prefer-stateless-function.js @@ -268,6 +268,40 @@ ruleTester.run('prefer-stateless-function', rule, { '};' ].join('\n'), parser: 'babel-eslint' + }, { + // Uses a decorator + code: [ + '@foo', + 'class Foo extends React.Component {', + ' render() {', + ' return
{this.props.foo}
;', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint' + }, { + // Uses a called decorator + code: [ + '@foo("bar")', + 'class Foo extends React.Component {', + ' render() {', + ' return
{this.props.foo}
;', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint' + }, { + // Uses multiple decorators + code: [ + '@foo', + '@bar()', + 'class Foo extends React.Component {', + ' render() {', + ' return
{this.props.foo}
;', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint' } ], From 557322a0e5323879bf152e60b5ecec398c8c5f6d Mon Sep 17 00:00:00 2001 From: Yann Pringault Date: Thu, 9 Mar 2017 11:40:50 +0100 Subject: [PATCH 02/10] Fix nextProps false positive in no-unused-prop-types --- lib/rules/no-unused-prop-types.js | 31 ++++++++++++++++++++++--- tests/lib/rules/no-unused-prop-types.js | 16 +++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/lib/rules/no-unused-prop-types.js b/lib/rules/no-unused-prop-types.js index be9054934c..744f211c96 100644 --- a/lib/rules/no-unused-prop-types.js +++ b/lib/rules/no-unused-prop-types.js @@ -18,6 +18,7 @@ var annotations = require('../util/annotations'); // ------------------------------------------------------------------------------ var DIRECT_PROPS_REGEX = /^props\s*(\.|\[)/; +var DIRECT_NEXT_PROPS_REGEX = /^nextProps\s*(\.|\[)/; // ------------------------------------------------------------------------------ // Rule Definition @@ -77,6 +78,24 @@ module.exports = { return value; } + /** + * Check if we are in a class constructor + * @return {boolean} true if we are in a class constructor, false if not + **/ + function inComponentWillReceiveProps() { + var scope = context.getScope(); + while (scope) { + if ( + scope.block && scope.block.parent && + scope.block.parent.key && scope.block.parent.key.name === 'componentWillReceiveProps' + ) { + return true; + } + scope = scope.upper; + } + return false; + } + /** * Checks if we are using a prop * @param {ASTNode} node The AST node being checked. @@ -88,7 +107,8 @@ module.exports = { node.object.type === 'ThisExpression' && node.property.name === 'props' ); var isStatelessFunctionUsage = node.object.name === 'props'; - return isClassUsage || isStatelessFunctionUsage; + var isNextPropsUsage = node.object.name === 'nextProps' && inComponentWillReceiveProps(); + return isClassUsage || isStatelessFunctionUsage || isNextPropsUsage; } /** @@ -491,12 +511,17 @@ module.exports = { */ function getPropertyName(node) { var isDirectProp = DIRECT_PROPS_REGEX.test(sourceCode.getText(node)); + var isDirectNextProp = DIRECT_NEXT_PROPS_REGEX.test(sourceCode.getText(node)); var isInClassComponent = utils.getParentES6Component() || utils.getParentES5Component(); var isNotInConstructor = !inConstructor(node); - if (isDirectProp && isInClassComponent && isNotInConstructor) { + var isNotInComponentWillReceiveProps = !inComponentWillReceiveProps(); + if ((isDirectProp || isDirectNextProp) + && isInClassComponent + && isNotInConstructor + && isNotInComponentWillReceiveProps) { return void 0; } - if (!isDirectProp) { + if (!isDirectProp && !isDirectNextProp) { node = node.parent; } var property = node.property; diff --git a/tests/lib/rules/no-unused-prop-types.js b/tests/lib/rules/no-unused-prop-types.js index 02a6032273..8611e10d70 100644 --- a/tests/lib/rules/no-unused-prop-types.js +++ b/tests/lib/rules/no-unused-prop-types.js @@ -1461,6 +1461,22 @@ ruleTester.run('no-unused-prop-types', rule, { '};' ].join('\n'), parserOptions: parserOptions + }, { + code: [ + 'class Hello extends Component {', + ' componentWillReceiveProps (nextProps) {', + ' if (nextProps.foo) {', + ' doSomething(this.props.bar);', + ' }', + ' }', + '}', + + 'Hello.propTypes = {', + ' foo: PropTypes.bool,', + ' bar: PropTypes.bool', + '};' + ].join('\n'), + parserOptions: parserOptions } ], From 4e8e86a9c94f17f0b4ff76d1726284a919f61395 Mon Sep 17 00:00:00 2001 From: David Reid Date: Wed, 22 Mar 2017 20:47:42 -0700 Subject: [PATCH 03/10] Fix ignorePureComponents when using class expressions. --- lib/rules/prefer-stateless-function.js | 14 ++++++++------ tests/lib/rules/prefer-stateless-function.js | 13 +++++++++++++ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/lib/rules/prefer-stateless-function.js b/lib/rules/prefer-stateless-function.js index 9cf896a616..a481cafd75 100644 --- a/lib/rules/prefer-stateless-function.js +++ b/lib/rules/prefer-stateless-function.js @@ -286,12 +286,15 @@ module.exports = { }); } + function visitClass(node) { + if (ignorePureComponents && utils.isPureComponent(node)) { + markSCUAsDeclared(node); + } + } + return { - ClassDeclaration: function (node) { - if (ignorePureComponents && utils.isPureComponent(node)) { - markSCUAsDeclared(node); - } - }, + ClassDeclaration: visitClass, + ClassExpression: visitClass, // Mark `this` destructuring as a usage of `this` VariableDeclarator: function(node) { @@ -386,7 +389,6 @@ module.exports = { if (list[component].hasSCU && list[component].usePropsOrContext) { continue; } - context.report({ node: list[component].node, message: 'Component should be written as a pure function' diff --git a/tests/lib/rules/prefer-stateless-function.js b/tests/lib/rules/prefer-stateless-function.js index 34e4601673..5cb63b2a85 100644 --- a/tests/lib/rules/prefer-stateless-function.js +++ b/tests/lib/rules/prefer-stateless-function.js @@ -64,6 +64,19 @@ ruleTester.run('prefer-stateless-function', rule, { options: [{ ignorePureComponents: true }] + }, { + // Extends from PureComponent in an expression context. + code: [ + 'const Foo = class extends React.PureComponent {', + ' render() {', + ' return
{this.props.foo}
;', + ' }', + '};' + ].join('\n'), + parserOptions: parserOptions, + options: [{ + ignorePureComponents: true + }] }, { // Has a lifecyle method code: [ From f35c03c7a830f67213cdcb63c6f25e257cc51f2d Mon Sep 17 00:00:00 2001 From: David Reid Date: Tue, 28 Mar 2017 09:27:20 -0700 Subject: [PATCH 04/10] Fix indentation in no-unused-prop-types example. --- docs/rules/no-unused-prop-types.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/no-unused-prop-types.md b/docs/rules/no-unused-prop-types.md index 6a4014e649..5fa3698d92 100644 --- a/docs/rules/no-unused-prop-types.md +++ b/docs/rules/no-unused-prop-types.md @@ -20,7 +20,7 @@ var Hello = React.createClass({ propTypes: { firstname: React.PropTypes.string.isRequired, middlename: React.PropTypes.string.isRequired, // middlename is never used below - lastname: React.PropTypes.string.isRequired + lastname: React.PropTypes.string.isRequired }, render: function() { return
Hello {this.props.firstname} {this.props.lastname}
; From 877e6dce413ea9c54fa3cec96a791bb14a2b7f48 Mon Sep 17 00:00:00 2001 From: William Holloway Date: Wed, 5 Apr 2017 14:17:48 -0700 Subject: [PATCH 05/10] Add no-will-update-set-state rule --- README.md | 1 + docs/rules/no-will-update-set-state.md | 90 ++++++++ index.js | 1 + lib/rules/no-will-update-set-state.js | 66 ++++++ tests/lib/rules/no-will-update-set-state.js | 238 ++++++++++++++++++++ 5 files changed, 396 insertions(+) create mode 100644 docs/rules/no-will-update-set-state.md create mode 100644 lib/rules/no-will-update-set-state.js create mode 100644 tests/lib/rules/no-will-update-set-state.js diff --git a/README.md b/README.md index 06f97b7b75..0bbbed59e2 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,7 @@ Finally, enable all of the rules that you would like to use. Use [our preset](# * [react/no-unescaped-entities](docs/rules/no-unescaped-entities.md): Prevent invalid characters from appearing in markup * [react/no-unknown-property](docs/rules/no-unknown-property.md): Prevent usage of unknown DOM property (fixable) * [react/no-unused-prop-types](docs/rules/no-unused-prop-types.md): Prevent definitions of unused prop types +* [react/no-will-update-set-state](docs/rules/no-will-update-set-state.md): Prevent usage of `setState` in `componentWillUpdate` * [react/prefer-es6-class](docs/rules/prefer-es6-class.md): Enforce ES5 or ES6 class for React Components * [react/prefer-stateless-function](docs/rules/prefer-stateless-function.md): Enforce stateless React Components to be written as a pure function * [react/prop-types](docs/rules/prop-types.md): Prevent missing props validation in a React component definition diff --git a/docs/rules/no-will-update-set-state.md b/docs/rules/no-will-update-set-state.md new file mode 100644 index 0000000000..e3dddee237 --- /dev/null +++ b/docs/rules/no-will-update-set-state.md @@ -0,0 +1,90 @@ +# Prevent usage of setState in componentWillUpdate (no-will-update-set-state) + +Updating the state during the componentWillUpdate step can lead to indeterminate component state and is not allowed. + +## Rule Details + +The following patterns are considered warnings: + +```jsx +var Hello = React.createClass({ + componentWillUpdate: function() { + this.setState({ + name: this.props.name.toUpperCase() + }); + }, + render: function() { + return
Hello {this.state.name}
; + } +}); +``` + +The following patterns are not considered warnings: + +```jsx +var Hello = React.createClass({ + componentWillUpdate: function() { + this.props.prepareHandler(); + }, + render: function() { + return
Hello {this.props.name}
; + } +}); +``` + +```jsx +var Hello = React.createClass({ + componentWillUpdate: function() { + this.prepareHandler(function callback(newName) { + this.setState({ + name: newName + }); + }); + }, + render: function() { + return
Hello {this.props.name}
; + } +}); +``` + +## Rule Options + +```js +... +"no-will-update-set-state": [, ] +... +``` + +### `disallow-in-func` mode + +By default this rule forbids any call to `this.setState` in `componentWillUpdate` outside of functions. The `disallow-in-func` mode makes this rule more strict by disallowing calls to `this.setState` even within functions. + +The following patterns are considered warnings: + +```jsx +var Hello = React.createClass({ + componentDidUpdate: function() { + this.setState({ + name: this.props.name.toUpperCase() + }); + }, + render: function() { + return
Hello {this.state.name}
; + } +}); +``` + +```jsx +var Hello = React.createClass({ + componentDidUpdate: function() { + this.prepareHandler(function callback(newName) { + this.setState({ + name: newName + }); + }); + }, + render: function() { + return
Hello {this.state.name}
; + } +}); +``` diff --git a/index.js b/index.js index 914e4433f3..9fd15e15bf 100644 --- a/index.js +++ b/index.js @@ -20,6 +20,7 @@ var allRules = { 'no-did-update-set-state': require('./lib/rules/no-did-update-set-state'), 'no-render-return-value': require('./lib/rules/no-render-return-value'), 'no-unescaped-entities': require('./lib/rules/no-unescaped-entities'), + 'no-will-update-set-state': require('./lib/rules/no-will-update-set-state'), 'react-in-jsx-scope': require('./lib/rules/react-in-jsx-scope'), 'jsx-uses-vars': require('./lib/rules/jsx-uses-vars'), 'jsx-handler-names': require('./lib/rules/jsx-handler-names'), diff --git a/lib/rules/no-will-update-set-state.js b/lib/rules/no-will-update-set-state.js new file mode 100644 index 0000000000..e0da8aa7ef --- /dev/null +++ b/lib/rules/no-will-update-set-state.js @@ -0,0 +1,66 @@ +/** + * @fileoverview Prevent usage of setState in componentWillUpdate + * @author Yannick Croissant + */ +'use strict'; + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + docs: { + description: 'Prevent usage of setState in componentWillUpdate', + category: 'Best Practices', + recommended: false + }, + + schema: [{ + enum: ['disallow-in-func'] + }] + }, + + create: function(context) { + + var mode = context.options[0] || 'allow-in-func'; + + // -------------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------------- + + return { + + CallExpression: function(node) { + var callee = node.callee; + if ( + callee.type !== 'MemberExpression' || + callee.object.type !== 'ThisExpression' || + callee.property.name !== 'setState' + ) { + return; + } + var ancestors = context.getAncestors(callee).reverse(); + var depth = 0; + for (var i = 0, j = ancestors.length; i < j; i++) { + if (/Function(Expression|Declaration)$/.test(ancestors[i].type)) { + depth++; + } + if ( + (ancestors[i].type !== 'Property' && ancestors[i].type !== 'MethodDefinition') || + ancestors[i].key.name !== 'componentWillUpdate' || + (mode !== 'disallow-in-func' && depth > 1) + ) { + continue; + } + context.report({ + node: callee, + message: 'Do not use setState in componentWillUpdate' + }); + break; + } + } + }; + + } +}; diff --git a/tests/lib/rules/no-will-update-set-state.js b/tests/lib/rules/no-will-update-set-state.js new file mode 100644 index 0000000000..292f32249c --- /dev/null +++ b/tests/lib/rules/no-will-update-set-state.js @@ -0,0 +1,238 @@ +/** + * @fileoverview Prevent usage of setState in componentWillUpdate + * @author Yannick Croissant + */ +'use strict'; + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +var rule = require('../../../lib/rules/no-will-update-set-state'); +var RuleTester = require('eslint').RuleTester; + +var parserOptions = { + ecmaVersion: 6, + ecmaFeatures: { + jsx: true + } +}; + +require('babel-eslint'); + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +var ruleTester = new RuleTester(); +ruleTester.run('no-will-update-set-state', rule, { + + valid: [{ + code: [ + 'var Hello = React.createClass({', + ' render: function() {', + ' return
Hello {this.props.name}
;', + ' }', + '});' + ].join('\n'), + parserOptions: parserOptions + }, { + code: [ + 'var Hello = React.createClass({', + ' componentWillUpdate: function() {}', + '});' + ].join('\n'), + parserOptions: parserOptions + }, { + code: [ + 'var Hello = React.createClass({', + ' componentWillUpdate: function() {', + ' someNonMemberFunction(arg);', + ' this.someHandler = this.setState;', + ' }', + '});' + ].join('\n'), + parserOptions: parserOptions + }, { + code: [ + 'var Hello = React.createClass({', + ' componentWillUpdate: function() {', + ' someClass.onSomeEvent(function(data) {', + ' this.setState({', + ' data: data', + ' });', + ' })', + ' }', + '});' + ].join('\n'), + parserOptions: parserOptions + }, { + code: [ + 'var Hello = React.createClass({', + ' componentWillUpdate: function() {', + ' function handleEvent(data) {', + ' this.setState({', + ' data: data', + ' });', + ' }', + ' someClass.onSomeEvent(handleEvent)', + ' }', + '});' + ].join('\n'), + parser: 'babel-eslint', + parserOptions: parserOptions + }], + + invalid: [{ + code: [ + 'var Hello = React.createClass({', + ' componentWillUpdate: function() {', + ' this.setState({', + ' data: data', + ' });', + ' }', + '});' + ].join('\n'), + parserOptions: parserOptions, + errors: [{ + message: 'Do not use setState in componentWillUpdate' + }] + }, { + code: [ + 'class Hello extends React.Component {', + ' componentWillUpdate() {', + ' this.setState({', + ' data: data', + ' });', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint', + errors: [{ + message: 'Do not use setState in componentWillUpdate' + }] + }, { + code: [ + 'var Hello = React.createClass({', + ' componentWillUpdate: function() {', + ' this.setState({', + ' data: data', + ' });', + ' }', + '});' + ].join('\n'), + options: ['disallow-in-func'], + parserOptions: parserOptions, + errors: [{ + message: 'Do not use setState in componentWillUpdate' + }] + }, { + code: [ + 'class Hello extends React.Component {', + ' componentWillUpdate() {', + ' this.setState({', + ' data: data', + ' });', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint', + options: ['disallow-in-func'], + errors: [{ + message: 'Do not use setState in componentWillUpdate' + }] + }, { + code: [ + 'var Hello = React.createClass({', + ' componentWillUpdate: function() {', + ' someClass.onSomeEvent(function(data) {', + ' this.setState({', + ' data: data', + ' });', + ' })', + ' }', + '});' + ].join('\n'), + parserOptions: parserOptions, + options: ['disallow-in-func'], + errors: [{ + message: 'Do not use setState in componentWillUpdate' + }] + }, { + code: [ + 'class Hello extends React.Component {', + ' componentWillUpdate() {', + ' someClass.onSomeEvent(function(data) {', + ' this.setState({', + ' data: data', + ' });', + ' })', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint', + options: ['disallow-in-func'], + errors: [{ + message: 'Do not use setState in componentWillUpdate' + }] + }, { + code: [ + 'var Hello = React.createClass({', + ' componentWillUpdate: function() {', + ' if (true) {', + ' this.setState({', + ' data: data', + ' });', + ' }', + ' }', + '});' + ].join('\n'), + parserOptions: parserOptions, + errors: [{ + message: 'Do not use setState in componentWillUpdate' + }] + }, { + code: [ + 'class Hello extends React.Component {', + ' componentWillUpdate() {', + ' if (true) {', + ' this.setState({', + ' data: data', + ' });', + ' }', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint', + errors: [{ + message: 'Do not use setState in componentWillUpdate' + }] + }, { + code: [ + 'var Hello = React.createClass({', + ' componentWillUpdate: function() {', + ' someClass.onSomeEvent((data) => this.setState({data: data}));', + ' }', + '});' + ].join('\n'), + parser: 'babel-eslint', + parserOptions: parserOptions, + options: ['disallow-in-func'], + errors: [{ + message: 'Do not use setState in componentWillUpdate' + }] + }, { + code: [ + 'class Hello extends React.Component {', + ' componentWillUpdate() {', + ' someClass.onSomeEvent((data) => this.setState({data: data}));', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint', + options: ['disallow-in-func'], + errors: [{ + message: 'Do not use setState in componentWillUpdate' + }] + }] +}); From 58519aef5cce8d1c96af3d795dca59d661de0ae3 Mon Sep 17 00:00:00 2001 From: William Holloway Date: Thu, 6 Apr 2017 14:42:18 -0700 Subject: [PATCH 06/10] Factor out no--set-state rule code to a utility --- lib/rules/no-did-mount-set-state.js | 61 +---------------------- lib/rules/no-did-update-set-state.js | 61 +---------------------- lib/rules/no-will-update-set-state.js | 61 +---------------------- lib/util/makeNoMethodSetStateRule.js | 70 +++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 177 deletions(-) create mode 100644 lib/util/makeNoMethodSetStateRule.js diff --git a/lib/rules/no-did-mount-set-state.js b/lib/rules/no-did-mount-set-state.js index 648f9ec32e..353d26febf 100644 --- a/lib/rules/no-did-mount-set-state.js +++ b/lib/rules/no-did-mount-set-state.js @@ -4,63 +4,6 @@ */ 'use strict'; -// ------------------------------------------------------------------------------ -// Rule Definition -// ------------------------------------------------------------------------------ +var makeNoMethodSetStateRule = require('../util/makeNoSetStateRule'); -module.exports = { - meta: { - docs: { - description: 'Prevent usage of setState in componentDidMount', - category: 'Best Practices', - recommended: false - }, - - schema: [{ - enum: ['disallow-in-func'] - }] - }, - - create: function(context) { - - var mode = context.options[0] || 'allow-in-func'; - - // -------------------------------------------------------------------------- - // Public - // -------------------------------------------------------------------------- - - return { - - CallExpression: function(node) { - var callee = node.callee; - if ( - callee.type !== 'MemberExpression' || - callee.object.type !== 'ThisExpression' || - callee.property.name !== 'setState' - ) { - return; - } - var ancestors = context.getAncestors(callee).reverse(); - var depth = 0; - for (var i = 0, j = ancestors.length; i < j; i++) { - if (/Function(Expression|Declaration)$/.test(ancestors[i].type)) { - depth++; - } - if ( - (ancestors[i].type !== 'Property' && ancestors[i].type !== 'MethodDefinition') || - ancestors[i].key.name !== 'componentDidMount' || - (mode !== 'disallow-in-func' && depth > 1) - ) { - continue; - } - context.report({ - node: callee, - message: 'Do not use setState in componentDidMount' - }); - break; - } - } - }; - - } -}; +module.exports = makeNoMethodSetStateRule('componentDidMount'); diff --git a/lib/rules/no-did-update-set-state.js b/lib/rules/no-did-update-set-state.js index 53f096e3f1..9bee29677c 100644 --- a/lib/rules/no-did-update-set-state.js +++ b/lib/rules/no-did-update-set-state.js @@ -4,63 +4,6 @@ */ 'use strict'; -// ------------------------------------------------------------------------------ -// Rule Definition -// ------------------------------------------------------------------------------ +var makeNoMethodSetStateRule = require('../util/makeNoSetStateRule'); -module.exports = { - meta: { - docs: { - description: 'Prevent usage of setState in componentDidUpdate', - category: 'Best Practices', - recommended: false - }, - - schema: [{ - enum: ['disallow-in-func'] - }] - }, - - create: function(context) { - - var mode = context.options[0] || 'allow-in-func'; - - // -------------------------------------------------------------------------- - // Public - // -------------------------------------------------------------------------- - - return { - - CallExpression: function(node) { - var callee = node.callee; - if ( - callee.type !== 'MemberExpression' || - callee.object.type !== 'ThisExpression' || - callee.property.name !== 'setState' - ) { - return; - } - var ancestors = context.getAncestors(callee).reverse(); - var depth = 0; - for (var i = 0, j = ancestors.length; i < j; i++) { - if (/Function(Expression|Declaration)$/.test(ancestors[i].type)) { - depth++; - } - if ( - (ancestors[i].type !== 'Property' && ancestors[i].type !== 'MethodDefinition') || - ancestors[i].key.name !== 'componentDidUpdate' || - (mode !== 'disallow-in-func' && depth > 1) - ) { - continue; - } - context.report({ - node: callee, - message: 'Do not use setState in componentDidUpdate' - }); - break; - } - } - }; - - } -}; +module.exports = makeNoMethodSetStateRule('componentDidUpdate'); diff --git a/lib/rules/no-will-update-set-state.js b/lib/rules/no-will-update-set-state.js index e0da8aa7ef..7ca3edac41 100644 --- a/lib/rules/no-will-update-set-state.js +++ b/lib/rules/no-will-update-set-state.js @@ -4,63 +4,6 @@ */ 'use strict'; -// ------------------------------------------------------------------------------ -// Rule Definition -// ------------------------------------------------------------------------------ +var makeNoMethodSetStateRule = require('../util/makeNoSetStateRule'); -module.exports = { - meta: { - docs: { - description: 'Prevent usage of setState in componentWillUpdate', - category: 'Best Practices', - recommended: false - }, - - schema: [{ - enum: ['disallow-in-func'] - }] - }, - - create: function(context) { - - var mode = context.options[0] || 'allow-in-func'; - - // -------------------------------------------------------------------------- - // Public - // -------------------------------------------------------------------------- - - return { - - CallExpression: function(node) { - var callee = node.callee; - if ( - callee.type !== 'MemberExpression' || - callee.object.type !== 'ThisExpression' || - callee.property.name !== 'setState' - ) { - return; - } - var ancestors = context.getAncestors(callee).reverse(); - var depth = 0; - for (var i = 0, j = ancestors.length; i < j; i++) { - if (/Function(Expression|Declaration)$/.test(ancestors[i].type)) { - depth++; - } - if ( - (ancestors[i].type !== 'Property' && ancestors[i].type !== 'MethodDefinition') || - ancestors[i].key.name !== 'componentWillUpdate' || - (mode !== 'disallow-in-func' && depth > 1) - ) { - continue; - } - context.report({ - node: callee, - message: 'Do not use setState in componentWillUpdate' - }); - break; - } - } - }; - - } -}; +module.exports = makeNoMethodSetStateRule('componentWillUpdate'); diff --git a/lib/util/makeNoMethodSetStateRule.js b/lib/util/makeNoMethodSetStateRule.js new file mode 100644 index 0000000000..dc4c2c191f --- /dev/null +++ b/lib/util/makeNoMethodSetStateRule.js @@ -0,0 +1,70 @@ +/** + * @fileoverview Prevent usage of setState in lifecycle methods + * @author Yannick Croissant + */ +'use strict'; + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +function makeNoMethodSetStateRule(methodName) { + return { + meta: { + docs: { + description: 'Prevent usage of setState in ' + methodName, + category: 'Best Practices', + recommended: false + }, + + schema: [{ + enum: ['disallow-in-func'] + }] + }, + + create: function(context) { + + var mode = context.options[0] || 'allow-in-func'; + + // -------------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------------- + + return { + + CallExpression: function(node) { + var callee = node.callee; + if ( + callee.type !== 'MemberExpression' || + callee.object.type !== 'ThisExpression' || + callee.property.name !== 'setState' + ) { + return; + } + var ancestors = context.getAncestors(callee).reverse(); + var depth = 0; + for (var i = 0, j = ancestors.length; i < j; i++) { + if (/Function(Expression|Declaration)$/.test(ancestors[i].type)) { + depth++; + } + if ( + (ancestors[i].type !== 'Property' && ancestors[i].type !== 'MethodDefinition') || + ancestors[i].key.name !== methodName || + (mode !== 'disallow-in-func' && depth > 1) + ) { + continue; + } + context.report({ + node: callee, + message: 'Do not use setState in ' + methodName + }); + break; + } + } + }; + + } + }; +} + +module.exports = makeNoMethodSetStateRule; From 4405523fe1b8cfe51cb67e41f5587852562f0588 Mon Sep 17 00:00:00 2001 From: William Holloway Date: Thu, 6 Apr 2017 14:43:48 -0700 Subject: [PATCH 07/10] Fix util filename in require --- lib/rules/no-did-mount-set-state.js | 2 +- lib/rules/no-did-update-set-state.js | 2 +- lib/rules/no-will-update-set-state.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/rules/no-did-mount-set-state.js b/lib/rules/no-did-mount-set-state.js index 353d26febf..dd6422a602 100644 --- a/lib/rules/no-did-mount-set-state.js +++ b/lib/rules/no-did-mount-set-state.js @@ -4,6 +4,6 @@ */ 'use strict'; -var makeNoMethodSetStateRule = require('../util/makeNoSetStateRule'); +var makeNoMethodSetStateRule = require('../util/makeNoMethodSetStateRule'); module.exports = makeNoMethodSetStateRule('componentDidMount'); diff --git a/lib/rules/no-did-update-set-state.js b/lib/rules/no-did-update-set-state.js index 9bee29677c..75f42e5270 100644 --- a/lib/rules/no-did-update-set-state.js +++ b/lib/rules/no-did-update-set-state.js @@ -4,6 +4,6 @@ */ 'use strict'; -var makeNoMethodSetStateRule = require('../util/makeNoSetStateRule'); +var makeNoMethodSetStateRule = require('../util/makeNoMethodSetStateRule'); module.exports = makeNoMethodSetStateRule('componentDidUpdate'); diff --git a/lib/rules/no-will-update-set-state.js b/lib/rules/no-will-update-set-state.js index 7ca3edac41..8f73df7525 100644 --- a/lib/rules/no-will-update-set-state.js +++ b/lib/rules/no-will-update-set-state.js @@ -4,6 +4,6 @@ */ 'use strict'; -var makeNoMethodSetStateRule = require('../util/makeNoSetStateRule'); +var makeNoMethodSetStateRule = require('../util/makeNoMethodSetStateRule'); module.exports = makeNoMethodSetStateRule('componentWillUpdate'); From 5f893e5fe8ba64aff1a29e5b45055d08be29b66e Mon Sep 17 00:00:00 2001 From: Adam Lancaster Date: Wed, 12 Apr 2017 13:08:32 +0100 Subject: [PATCH 08/10] corrects all the .eslintrc examples in the docs directory --- docs/rules/display-name.md | 2 +- docs/rules/forbid-component-props.md | 2 +- docs/rules/forbid-elements.md | 2 +- docs/rules/jsx-closing-bracket-location.md | 6 +++--- docs/rules/jsx-curly-spacing.md | 6 +++--- docs/rules/jsx-equals-spacing.md | 2 +- docs/rules/jsx-handler-names.md | 2 +- docs/rules/jsx-indent-props.md | 2 +- docs/rules/jsx-max-props-per-line.md | 2 +- docs/rules/jsx-no-bind.md | 2 +- docs/rules/jsx-no-duplicate-props.md | 2 +- docs/rules/jsx-pascal-case.md | 2 +- docs/rules/jsx-sort-props.md | 2 +- docs/rules/no-did-mount-set-state.md | 2 +- docs/rules/no-did-update-set-state.md | 2 +- docs/rules/no-multi-comp.md | 2 +- docs/rules/no-unknown-property.md | 2 +- docs/rules/no-will-update-set-state.md | 2 +- docs/rules/prefer-es6-class.md | 2 +- docs/rules/prefer-stateless-function.md | 2 +- docs/rules/require-optimization.md | 4 ++-- docs/rules/self-closing-comp.md | 2 +- docs/rules/sort-prop-types.md | 2 +- 23 files changed, 28 insertions(+), 28 deletions(-) diff --git a/docs/rules/display-name.md b/docs/rules/display-name.md index 49cfcf3c9d..de56ba1615 100644 --- a/docs/rules/display-name.md +++ b/docs/rules/display-name.md @@ -29,7 +29,7 @@ var Hello = React.createClass({ ```js ... -"display-name": [, { "ignoreTranspilerName": }] +"react/display-name": [, { "ignoreTranspilerName": }] ... ``` diff --git a/docs/rules/forbid-component-props.md b/docs/rules/forbid-component-props.md index 924cbe6aaf..737f58ecf2 100644 --- a/docs/rules/forbid-component-props.md +++ b/docs/rules/forbid-component-props.md @@ -35,7 +35,7 @@ The following patterns are not considered warnings: ```js ... -"forbid-component-props": [, { "forbid": [] }] +"react/forbid-component-props": [, { "forbid": [] }] ... ``` diff --git a/docs/rules/forbid-elements.md b/docs/rules/forbid-elements.md index 16c4c62ddd..9be67e56ed 100644 --- a/docs/rules/forbid-elements.md +++ b/docs/rules/forbid-elements.md @@ -10,7 +10,7 @@ This rule checks all JSX elements and `React.createElement` calls and verifies t ```js ... -"forbid-elements": [, { "forbid": [] }] +"react/forbid-elements": [, { "forbid": [] }] ... ``` diff --git a/docs/rules/jsx-closing-bracket-location.md b/docs/rules/jsx-closing-bracket-location.md index 2181b9a408..67d3dd5687 100644 --- a/docs/rules/jsx-closing-bracket-location.md +++ b/docs/rules/jsx-closing-bracket-location.md @@ -39,14 +39,14 @@ There are two ways to configure this rule. The first form is a string shortcut corresponding to the `location` values specified below. If omitted, it defaults to `"tag-aligned"`. ```js -"jsx-closing-bracket-location": // -> [, "tag-aligned"] -"jsx-closing-bracket-location": [, ""] +"react/jsx-closing-bracket-location": // -> [, "tag-aligned"] +"react/jsx-closing-bracket-location": [, ""] ``` The second form allows you to distinguish between non-empty and self-closing tags. Both properties are optional, and both default to `"tag-aligned"`. You can also disable the rule for one particular type of tag by setting the value to `false`. ```js -"jsx-closing-bracket-location": [, { +"react/jsx-closing-bracket-location": [, { "nonEmpty": "" || false, "selfClosing": "" || false }] diff --git a/docs/rules/jsx-curly-spacing.md b/docs/rules/jsx-curly-spacing.md index 3f5b2761f1..6dd65d4731 100644 --- a/docs/rules/jsx-curly-spacing.md +++ b/docs/rules/jsx-curly-spacing.md @@ -20,7 +20,7 @@ There are two main options for the rule: Depending on your coding conventions, you can choose either option by specifying it in your configuration: ```json -"jsx-curly-spacing": [2, "always"] +"react/jsx-curly-spacing": [2, "always"] ``` #### never @@ -68,7 +68,7 @@ The following patterns are not warnings: By default, braces spanning multiple lines are allowed with either setting. If you want to disallow them you can specify an additional `allowMultiline` property with the value `false`: ```json -"jsx-curly-spacing": [2, "never", {"allowMultiline": false}] +"react/jsx-curly-spacing": [2, "never", {"allowMultiline": false}] ``` When `"never"` is used and `allowMultiline` is `false`, the following patterns are considered warnings: @@ -112,7 +112,7 @@ The following patterns are not warnings: You can specify an additional `spacing` property that is an object with the following possible values: ```json -"jsx-curly-spacing": [2, "always", {"spacing": { +"react/jsx-curly-spacing": [2, "always", {"spacing": { "objectLiterals": "never" }}] ``` diff --git a/docs/rules/jsx-equals-spacing.md b/docs/rules/jsx-equals-spacing.md index a07ea54aa6..fd66cf6a91 100644 --- a/docs/rules/jsx-equals-spacing.md +++ b/docs/rules/jsx-equals-spacing.md @@ -18,7 +18,7 @@ There are two options for the rule: Depending on your coding conventions, you can choose either option by specifying it in your configuration: ```json -"jsx-equals-spacing": [2, "always"] +"react/jsx-equals-spacing": [2, "always"] ``` #### never diff --git a/docs/rules/jsx-handler-names.md b/docs/rules/jsx-handler-names.md index ef6aa192ae..12dcdcbde9 100644 --- a/docs/rules/jsx-handler-names.md +++ b/docs/rules/jsx-handler-names.md @@ -28,7 +28,7 @@ The following patterns are not considered warnings: ```js ... -"jsx-handler-names": [, { +"react/jsx-handler-names": [, { "eventHandlerPrefix": , "eventHandlerPropPrefix": }] diff --git a/docs/rules/jsx-indent-props.md b/docs/rules/jsx-indent-props.md index af2ea453ac..ee6d77d94a 100644 --- a/docs/rules/jsx-indent-props.md +++ b/docs/rules/jsx-indent-props.md @@ -33,7 +33,7 @@ It takes an option as the second parameter which can be `"tab"` for tab-based in ```js ... -"jsx-indent-props": [, 'tab'|] +"react/jsx-indent-props": [, 'tab'|] ... ``` diff --git a/docs/rules/jsx-max-props-per-line.md b/docs/rules/jsx-max-props-per-line.md index 490b50e0f1..4baaf90e14 100644 --- a/docs/rules/jsx-max-props-per-line.md +++ b/docs/rules/jsx-max-props-per-line.md @@ -35,7 +35,7 @@ The following patterns are not considered warnings: ```js ... -"jsx-max-props-per-line": [, { "maximum": , "when": }] +"react/jsx-max-props-per-line": [, { "maximum": , "when": }] ... ``` diff --git a/docs/rules/jsx-no-bind.md b/docs/rules/jsx-no-bind.md index 3baa143c47..ef8258b212 100644 --- a/docs/rules/jsx-no-bind.md +++ b/docs/rules/jsx-no-bind.md @@ -21,7 +21,7 @@ The following patterns are not considered warnings: ## Rule Options ```js -"jsx-no-bind": [, { +"react/jsx-no-bind": [, { "ignoreRefs": || false, "allowArrowFunctions": || false, "allowBind": || false diff --git a/docs/rules/jsx-no-duplicate-props.md b/docs/rules/jsx-no-duplicate-props.md index 729ad39f57..52e4dd7dd2 100644 --- a/docs/rules/jsx-no-duplicate-props.md +++ b/docs/rules/jsx-no-duplicate-props.md @@ -20,7 +20,7 @@ The following patterns are not considered warnings: ```js ... -"jsx-no-duplicate-props": [, { "ignoreCase": }] +"react/jsx-no-duplicate-props": [, { "ignoreCase": }] ... ``` diff --git a/docs/rules/jsx-pascal-case.md b/docs/rules/jsx-pascal-case.md index 077378018a..08dcb3a996 100644 --- a/docs/rules/jsx-pascal-case.md +++ b/docs/rules/jsx-pascal-case.md @@ -40,7 +40,7 @@ The following patterns are not considered warnings: ```js ... -"jsx-pascal-case": [, { allowAllCaps: , ignore: }] +"react/jsx-pascal-case": [, { allowAllCaps: , ignore: }] ... ``` diff --git a/docs/rules/jsx-sort-props.md b/docs/rules/jsx-sort-props.md index 81d1a7bfdf..7429d5deb9 100644 --- a/docs/rules/jsx-sort-props.md +++ b/docs/rules/jsx-sort-props.md @@ -23,7 +23,7 @@ The following patterns are considered okay and do not cause warnings: ```js ... -"jsx-sort-props": [, { +"react/jsx-sort-props": [, { "callbacksLast": , "shorthandFirst": , "shorthandLast": , diff --git a/docs/rules/no-did-mount-set-state.md b/docs/rules/no-did-mount-set-state.md index ce6f78b9a8..45d243054a 100644 --- a/docs/rules/no-did-mount-set-state.md +++ b/docs/rules/no-did-mount-set-state.md @@ -53,7 +53,7 @@ var Hello = React.createClass({ ```js ... -"no-did-mount-set-state": [, ] +"react/no-did-mount-set-state": [, ] ... ``` diff --git a/docs/rules/no-did-update-set-state.md b/docs/rules/no-did-update-set-state.md index fb40e34637..74d7af0b50 100644 --- a/docs/rules/no-did-update-set-state.md +++ b/docs/rules/no-did-update-set-state.md @@ -51,7 +51,7 @@ var Hello = React.createClass({ ```js ... -"no-did-update-set-state": [, ] +"react/no-did-update-set-state": [, ] ... ``` diff --git a/docs/rules/no-multi-comp.md b/docs/rules/no-multi-comp.md index 6122dc602c..a5303004dc 100644 --- a/docs/rules/no-multi-comp.md +++ b/docs/rules/no-multi-comp.md @@ -36,7 +36,7 @@ var HelloJohn = React.createClass({ ```js ... -"no-multi-comp": [, { "ignoreStateless": }] +"react/no-multi-comp": [, { "ignoreStateless": }] ... ``` diff --git a/docs/rules/no-unknown-property.md b/docs/rules/no-unknown-property.md index 772898d78d..6e54499242 100644 --- a/docs/rules/no-unknown-property.md +++ b/docs/rules/no-unknown-property.md @@ -26,7 +26,7 @@ var Hello =
Hello World
; ```js ... -"no-unknown-property": [, { ignore: }] +"react/no-unknown-property": [, { ignore: }] ... ``` diff --git a/docs/rules/no-will-update-set-state.md b/docs/rules/no-will-update-set-state.md index e3dddee237..fca0ac2c0b 100644 --- a/docs/rules/no-will-update-set-state.md +++ b/docs/rules/no-will-update-set-state.md @@ -51,7 +51,7 @@ var Hello = React.createClass({ ```js ... -"no-will-update-set-state": [, ] +"react/no-will-update-set-state": [, ] ... ``` diff --git a/docs/rules/prefer-es6-class.md b/docs/rules/prefer-es6-class.md index 371272feeb..47d7c1b30f 100644 --- a/docs/rules/prefer-es6-class.md +++ b/docs/rules/prefer-es6-class.md @@ -6,7 +6,7 @@ React offers you two way to create traditional components: using the ES5 `React. ```js ... -"prefer-es6-class": [, ] +"react/prefer-es6-class": [, ] ... ``` diff --git a/docs/rules/prefer-stateless-function.md b/docs/rules/prefer-stateless-function.md index 0eaf88d92a..da76b7bd98 100644 --- a/docs/rules/prefer-stateless-function.md +++ b/docs/rules/prefer-stateless-function.md @@ -54,7 +54,7 @@ class Foo extends React.Component { ```js ... -"prefer-stateless-function": [, { "ignorePureComponents": }] +"react/prefer-stateless-function": [, { "ignorePureComponents": }] ... ``` diff --git a/docs/rules/require-optimization.md b/docs/rules/require-optimization.md index 4758e7a574..69bebda6f8 100644 --- a/docs/rules/require-optimization.md +++ b/docs/rules/require-optimization.md @@ -52,7 +52,7 @@ React.createClass({ ```js ... -"require-optimization": [, { allowDecorators: [] }] +"react/require-optimization": [, { allowDecorators: [] }] ... ``` @@ -76,6 +76,6 @@ class Hello extends React.Component {} ```js ... -"require-optimization": [2, {allowDecorators: ['customDecorators']}] +"react/require-optimization": [2, {allowDecorators: ['customDecorators']}] ... ``` diff --git a/docs/rules/self-closing-comp.md b/docs/rules/self-closing-comp.md index 156c9cbdca..2913c5ef3c 100644 --- a/docs/rules/self-closing-comp.md +++ b/docs/rules/self-closing-comp.md @@ -32,7 +32,7 @@ The rule can take one argument to select types of tags, which should be self-clo ```js ... -"self-closing-comp": ["error", { +"react/self-closing-comp": ["error", { "component": true, "html": true }] diff --git a/docs/rules/sort-prop-types.md b/docs/rules/sort-prop-types.md index e569af8847..f8210b9160 100644 --- a/docs/rules/sort-prop-types.md +++ b/docs/rules/sort-prop-types.md @@ -76,7 +76,7 @@ class Component extends React.Component { ```js ... -"sort-prop-types": [, { +"react/sort-prop-types": [, { "callbacksLast": , "ignoreCase": , "requiredFirst": , From cdfc5083b1254e92a9e68625bec93078b1ae14f4 Mon Sep 17 00:00:00 2001 From: Matthew Herbst Date: Wed, 12 Apr 2017 18:17:26 -0700 Subject: [PATCH 09/10] Link fix: ReactDOM docs were broken out This fixes a link regarding ReactDOM.render. Previously these docs were part of the Top Level API docs for React, but have since been split into their own documentation. --- docs/rules/no-render-return-value.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/no-render-return-value.md b/docs/rules/no-render-return-value.md index a0575c259c..12ef6c767e 100644 --- a/docs/rules/no-render-return-value.md +++ b/docs/rules/no-render-return-value.md @@ -2,7 +2,7 @@ > `ReactDOM.render()` currently returns a reference to the root `ReactComponent` instance. However, using this return value is legacy and should be avoided because future versions of React may render components asynchronously in some cases. If you need a reference to the root `ReactComponent` instance, the preferred solution is to attach a [callback ref](http://facebook.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute) to the root element. -Source: [React Top-Level API documentation](http://facebook.github.io/react/docs/top-level-api.html#reactdom.render) +Source: [ReactDOM documentation](https://facebook.github.io/react/docs/react-dom.html#render) ## Rule Details From 0fac8d95404e53f8cf437b6c3a9943f727839dac Mon Sep 17 00:00:00 2001 From: David Reid Date: Tue, 18 Apr 2017 15:25:20 -0700 Subject: [PATCH 10/10] Update prefer-stateless-function.js Remove trailing spaces. --- lib/rules/prefer-stateless-function.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/prefer-stateless-function.js b/lib/rules/prefer-stateless-function.js index e6369ac125..f9a989bb1b 100644 --- a/lib/rules/prefer-stateless-function.js +++ b/lib/rules/prefer-stateless-function.js @@ -305,7 +305,7 @@ module.exports = { markDecoratorsAsUsed(node); } } - + return { ClassDeclaration: visitClass, ClassExpression: visitClass,