diff --git a/README.md b/README.md index c621fb75aa..c6b0889da5 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ Enable the rules that you would like to use. * [react/no-children-prop](docs/rules/no-children-prop.md): Prevent passing children as props * [react/no-danger](docs/rules/no-danger.md): Prevent usage of dangerous JSX properties * [react/no-danger-with-children](docs/rules/no-danger-with-children.md): Prevent problem with children and props.dangerouslySetInnerHTML -* [react/no-deprecated](docs/rules/no-deprecated.md): Prevent usage of deprecated methods +* [react/no-deprecated](docs/rules/no-deprecated.md): Prevent usage of deprecated methods, including component lifecyle methods * [react/no-did-mount-set-state](docs/rules/no-did-mount-set-state.md): Prevent usage of `setState` in `componentDidMount` * [react/no-did-update-set-state](docs/rules/no-did-update-set-state.md): Prevent usage of `setState` in `componentDidUpdate` * [react/no-direct-mutation-state](docs/rules/no-direct-mutation-state.md): Prevent direct mutation of `this.state` diff --git a/docs/rules/no-deprecated.md b/docs/rules/no-deprecated.md index 77cf0ff7bb..63076fed6e 100644 --- a/docs/rules/no-deprecated.md +++ b/docs/rules/no-deprecated.md @@ -27,6 +27,27 @@ const propTypes = { React.DOM.div(); import React, { PropTypes } from 'react'; + +class Foo extends React.Component { + componentWillMount() { } + componentWillReceiveProps() { } + componentWillUpdate() { } + // ... +} + +class Foo extends React.PureComponent { + componentWillMount() { } + componentWillReceiveProps() { } + componentWillUpdate() { } + // ... +} + +var Foo = createReactClass({ + componentWillMount: function() {}, + componentWillReceiveProps: function() {}, + componentWillUpdate: function() {}, + // ... +}) ``` The following patterns are **not** considered warnings: @@ -38,4 +59,10 @@ ReactDOM.render(, root); ReactDOM.findDOMNode(this.refs.foo); import { PropTypes } from 'prop-types'; + +class Foo { + componentWillMount() { } + componentWillReceiveProps() { } + componentWillUpdate() { } +} ``` diff --git a/lib/rules/no-deprecated.js b/lib/rules/no-deprecated.js index 6e8c88e265..6072290f73 100644 --- a/lib/rules/no-deprecated.js +++ b/lib/rules/no-deprecated.js @@ -2,14 +2,17 @@ * @fileoverview Prevent usage of deprecated methods * @author Yannick Croissant * @author Scott Feeney + * @author Sergei Startsev */ 'use strict'; const has = require('has'); +const Components = require('../util/Components'); +const astUtil = require('../util/ast'); +const docsUrl = require('../util/docsUrl'); const pragmaUtil = require('../util/pragma'); const versionUtil = require('../util/version'); -const docsUrl = require('../util/docsUrl'); // ------------------------------------------------------------------------------ // Constants @@ -20,7 +23,7 @@ const MODULES = { 'react-addons-perf': ['ReactPerf', 'Perf'] }; -const DEPRECATED_MESSAGE = '{{oldMethod}} is deprecated since React {{version}}{{newMethod}}'; +const DEPRECATED_MESSAGE = '{{oldMethod}} is deprecated since React {{version}}{{newMethod}}{{refs}}'; // ------------------------------------------------------------------------------ // Rule Definition @@ -37,7 +40,7 @@ module.exports = { schema: [] }, - create: function(context) { + create: Components.detect((context, components, utils) => { const sourceCode = context.getSourceCode(); const pragma = pragmaUtil.getFromContext(context); @@ -73,6 +76,22 @@ module.exports = { deprecated[`${pragma}.PropTypes`] = ['15.5.0', 'the npm module prop-types']; // 15.6.0 deprecated[`${pragma}.DOM`] = ['15.6.0', 'the npm module react-dom-factories']; + // 16.3.0 + deprecated.componentWillMount = [ + '16.3.0', + 'UNSAFE_componentWillMount', + 'https://reactjs.org/docs/react-component.html#unsafe_componentwillmount' + ]; + deprecated.componentWillReceiveProps = [ + '16.3.0', + 'UNSAFE_componentWillReceiveProps', + 'https://reactjs.org/docs/react-component.html#unsafe_componentwillreceiveprops' + ]; + deprecated.componentWillUpdate = [ + '16.3.0', + 'UNSAFE_componentWillUpdate', + 'https://reactjs.org/docs/react-component.html#unsafe_componentwillupdate' + ]; return deprecated; } @@ -91,13 +110,17 @@ module.exports = { return; } const deprecated = getDeprecated(); + const version = deprecated[method][0]; + const newMethod = deprecated[method][1]; + const refs = deprecated[method][2]; context.report({ node: node, message: DEPRECATED_MESSAGE, data: { oldMethod: method, - version: deprecated[method][0], - newMethod: deprecated[method][1] ? `, use ${deprecated[method][1]} instead` : '' + version, + newMethod: newMethod ? `, use ${newMethod} instead` : '', + refs: refs ? `, see ${refs}` : '' } }); } @@ -119,6 +142,27 @@ module.exports = { return moduleName; } + /** + * Returns life cycle methods if available + * @param {ASTNode} node The AST node being checked. + * @returns {Array} The array of methods. + */ + function getLifeCycleMethods(node) { + const properties = astUtil.getComponentProperties(node); + return properties.map(property => astUtil.getPropertyName(property)); + } + + /** + * Checks life cycle methods + * @param {ASTNode} node The AST node being checked. + */ + function checkLifeCycleMethods(node) { + if (utils.isES5Component(node) || utils.isES6Component(node)) { + const methods = getLifeCycleMethods(node); + methods.forEach(method => checkDeprecation(node, method)); + } + } + // -------------------------------------------------------------------------- // Public // -------------------------------------------------------------------------- @@ -145,10 +189,10 @@ module.exports = { VariableDeclarator: function(node) { const reactModuleName = getReactModuleName(node); const isRequire = node.init && node.init.callee && node.init.callee.name === 'require'; - const isReactRequire = - node.init && node.init.arguments && - node.init.arguments.length && typeof MODULES[node.init.arguments[0].value] !== 'undefined' - ; + const isReactRequire = node.init + && node.init.arguments + && node.init.arguments.length + && typeof MODULES[node.init.arguments[0].value] !== 'undefined'; const isDestructuring = node.id && node.id.type === 'ObjectPattern'; if ( @@ -160,8 +204,11 @@ module.exports = { node.id.properties.forEach(property => { checkDeprecation(node, `${reactModuleName || pragma}.${property.key.name}`); }); - } + }, + ClassDeclaration: checkLifeCycleMethods, + ClassExpression: checkLifeCycleMethods, + ObjectExpression: checkLifeCycleMethods }; - } + }) }; diff --git a/tests/lib/rules/no-deprecated.js b/tests/lib/rules/no-deprecated.js index 9ddcd6b906..b1b5630c33 100644 --- a/tests/lib/rules/no-deprecated.js +++ b/tests/lib/rules/no-deprecated.js @@ -2,6 +2,7 @@ * @fileoverview Prevent usage of deprecated methods * @author Yannick Croissant * @author Scott Feeney + * @author Sergei Startsev */ 'use strict'; @@ -23,6 +24,12 @@ const parserOptions = { require('babel-eslint'); +function errorMessage(oldMethod, version, newMethod, refs) { + newMethod = newMethod ? `, use ${newMethod} instead` : ''; + refs = refs ? `, see ${refs}` : ''; + return `${oldMethod} is deprecated since React ${version}${newMethod}${refs}`; +} + // ------------------------------------------------------------------------------ // Tests // ------------------------------------------------------------------------------ @@ -39,151 +46,339 @@ ruleTester.run('no-deprecated', rule, { 'ReactDOM.findDOMNode(instance);', 'ReactDOMServer.renderToString(element);', 'ReactDOMServer.renderToStaticMarkup(element);', + { + code: ` + var Foo = createReactClass({ + render: function() {} + }) + ` + }, + // Non-React + { + code: ` + var Foo = createReactClassNonReact({ + componentWillMount: function() {}, + componentWillReceiveProps: function() {}, + componentWillUpdate: function() {} + }); + ` + }, + { + code: ` + var Foo = { + componentWillMount: function() {}, + componentWillReceiveProps: function() {}, + componentWillUpdate: function() {} + }; + ` + }, + { + code: ` + class Foo { + componentWillMount() {} + componentWillReceiveProps() {} + componentWillUpdate() {} + } + ` + }, // Deprecated in a later version {code: 'React.renderComponent()', settings: {react: {version: '0.11.0'}}}, {code: 'React.createClass()', settings: {react: {version: '15.4.0'}}}, - {code: 'PropTypes', settings: {react: {version: '15.4.0'}}} + {code: 'PropTypes', settings: {react: {version: '15.4.0'}}}, + { + code: ` + class Foo extends React.Component { + componentWillMount() {} + componentWillReceiveProps() {} + componentWillUpdate() {} + } + `, + settings: {react: {version: '16.2.0'}} + } ], - invalid: [{ - code: 'React.renderComponent()', - settings: {react: {version: '0.12.0'}}, - errors: [{ - message: 'React.renderComponent is deprecated since React 0.12.0, use React.render instead' - }] - }, { - code: 'Foo.renderComponent()', - settings: {react: {pragma: 'Foo', version: '0.12.0'}}, - errors: [{ - message: 'Foo.renderComponent is deprecated since React 0.12.0, use Foo.render instead' - }] - }, { - code: '/** @jsx Foo */ Foo.renderComponent()', - settings: {react: {version: '0.12.0'}}, - errors: [{ - message: 'Foo.renderComponent is deprecated since React 0.12.0, use Foo.render instead' - }] - }, { - code: 'this.transferPropsTo()', - errors: [{ - message: 'this.transferPropsTo is deprecated since React 0.12.0, use spread operator ({...}) instead' - }] - }, { - code: 'React.addons.TestUtils', - errors: [{ - message: 'React.addons.TestUtils is deprecated since React 15.5.0, use ReactDOM.TestUtils instead' - }] - }, { - code: 'React.addons.classSet()', - errors: [{ - message: 'React.addons.classSet is deprecated since React 0.13.0, use the npm module classnames instead' - }] - }, { - code: 'React.render(element, container);', - errors: [{ - message: 'React.render is deprecated since React 0.14.0, use ReactDOM.render instead' - }] - }, { - code: 'React.unmountComponentAtNode(container);', - errors: [{ - message: ( - 'React.unmountComponentAtNode is deprecated since React 0.14.0, ' + - 'use ReactDOM.unmountComponentAtNode instead' - ) - }] - }, { - code: 'React.findDOMNode(instance);', - errors: [{ - message: 'React.findDOMNode is deprecated since React 0.14.0, use ReactDOM.findDOMNode instead' - }] - }, { - code: 'React.renderToString(element);', - errors: [{ - message: 'React.renderToString is deprecated since React 0.14.0, use ReactDOMServer.renderToString instead' - }] - }, { - code: 'React.renderToStaticMarkup(element);', - errors: [{ - message: ( - 'React.renderToStaticMarkup is deprecated since React 0.14.0, ' + - 'use ReactDOMServer.renderToStaticMarkup instead' - ) - }] - }, { - code: 'React.createClass({});', - errors: [{ - message: 'React.createClass is deprecated since React 15.5.0, use the npm module create-react-class instead' - }] - }, { - code: 'Foo.createClass({});', - settings: {react: {pragma: 'Foo'}}, - errors: [{ - message: 'Foo.createClass is deprecated since React 15.5.0, use the npm module create-react-class instead' - }] - }, { - code: 'React.PropTypes', - errors: [{ - message: 'React.PropTypes is deprecated since React 15.5.0, use the npm module prop-types instead' - }] - }, { - code: 'var {createClass} = require(\'react\');', - parser: 'babel-eslint', - errors: [{ - message: 'React.createClass is deprecated since React 15.5.0, use the npm module create-react-class instead' - }] - }, { - code: 'var {createClass, PropTypes} = require(\'react\');', - parser: 'babel-eslint', - errors: [{ - message: 'React.createClass is deprecated since React 15.5.0, use the npm module create-react-class instead' - }, { - message: 'React.PropTypes is deprecated since React 15.5.0, use the npm module prop-types instead' - }] - }, { - code: 'import {createClass} from \'react\';', - parser: 'babel-eslint', - errors: [{ - message: 'React.createClass is deprecated since React 15.5.0, use the npm module create-react-class instead' - }] - }, { - code: 'import {createClass, PropTypes} from \'react\';', - parser: 'babel-eslint', - errors: [{ - message: 'React.createClass is deprecated since React 15.5.0, use the npm module create-react-class instead' - }, { - message: 'React.PropTypes is deprecated since React 15.5.0, use the npm module prop-types instead' - }] - }, { - code: ` + invalid: [ + { + code: 'React.renderComponent()', + errors: [{message: errorMessage('React.renderComponent', '0.12.0', 'React.render')}] + }, + { + code: 'Foo.renderComponent()', + settings: {react: {pragma: 'Foo'}}, + errors: [{message: errorMessage('Foo.renderComponent', '0.12.0', 'Foo.render')}] + }, + { + code: '/** @jsx Foo */ Foo.renderComponent()', + errors: [{message: errorMessage('Foo.renderComponent', '0.12.0', 'Foo.render')}] + }, + { + code: 'this.transferPropsTo()', + errors: [{message: errorMessage('this.transferPropsTo', '0.12.0', 'spread operator ({...})')}] + }, + { + code: 'React.addons.TestUtils', + errors: [{message: errorMessage('React.addons.TestUtils', '15.5.0', 'ReactDOM.TestUtils')}] + }, + { + code: 'React.addons.classSet()', + errors: [{message: errorMessage('React.addons.classSet', '0.13.0', 'the npm module classnames')}] + }, + { + code: 'React.render(element, container);', + errors: [{message: errorMessage('React.render', '0.14.0', 'ReactDOM.render')}] + }, + { + code: 'React.unmountComponentAtNode(container);', + errors: [{message: errorMessage('React.unmountComponentAtNode', '0.14.0', 'ReactDOM.unmountComponentAtNode')}] + }, + { + code: 'React.findDOMNode(instance);', + errors: [{message: errorMessage('React.findDOMNode', '0.14.0', 'ReactDOM.findDOMNode')}] + }, + { + code: 'React.renderToString(element);', + errors: [{message: errorMessage('React.renderToString', '0.14.0', 'ReactDOMServer.renderToString')}] + }, + { + code: 'React.renderToStaticMarkup(element);', + errors: [{message: errorMessage('React.renderToStaticMarkup', '0.14.0', 'ReactDOMServer.renderToStaticMarkup')}] + }, + { + code: 'React.createClass({});', + errors: [{message: errorMessage('React.createClass', '15.5.0', 'the npm module create-react-class')}] + }, + { + code: 'Foo.createClass({});', + settings: {react: {pragma: 'Foo'}}, + errors: [{message: errorMessage('Foo.createClass', '15.5.0', 'the npm module create-react-class')}] + }, + { + code: 'React.PropTypes', + errors: [{message: errorMessage('React.PropTypes', '15.5.0', 'the npm module prop-types')}] + }, + { + code: 'var {createClass} = require(\'react\');', + parser: 'babel-eslint', + errors: [{message: errorMessage('React.createClass', '15.5.0', 'the npm module create-react-class')}] + }, + { + code: 'var {createClass, PropTypes} = require(\'react\');', + parser: 'babel-eslint', + errors: [ + {message: errorMessage('React.createClass', '15.5.0', 'the npm module create-react-class')}, + {message: errorMessage('React.PropTypes', '15.5.0', 'the npm module prop-types')} + ] + }, + { + code: 'import {createClass} from \'react\';', + parser: 'babel-eslint', + errors: [{message: errorMessage('React.createClass', '15.5.0', 'the npm module create-react-class')}] + }, + { + code: 'import {createClass, PropTypes} from \'react\';', + parser: 'babel-eslint', + errors: [ + {message: errorMessage('React.createClass', '15.5.0', 'the npm module create-react-class')}, + {message: errorMessage('React.PropTypes', '15.5.0', 'the npm module prop-types')} + ] + }, + { + code: ` import React from 'react'; const {createClass, PropTypes} = React; `, - parser: 'babel-eslint', - errors: [{ - message: 'React.createClass is deprecated since React 15.5.0, use the npm module create-react-class instead' - }, { - message: 'React.PropTypes is deprecated since React 15.5.0, use the npm module prop-types instead' - }] - }, { - code: 'import {printDOM} from \'react-addons-perf\';', - parser: 'babel-eslint', - errors: [{ - message: 'ReactPerf.printDOM is deprecated since React 15.0.0, use ReactPerf.printOperations instead' - }] - }, { - code: ` - import ReactPerf from 'react-addons-perf'; - const {printDOM} = ReactPerf; - `, - parser: 'babel-eslint', - errors: [{ - message: 'ReactPerf.printDOM is deprecated since React 15.0.0, use ReactPerf.printOperations instead' - }] - }, - { - code: 'React.DOM.div', - errors: [{ - message: 'React.DOM is deprecated since React 15.6.0, use the npm module react-dom-factories instead' - }] - }] + parser: 'babel-eslint', + errors: [ + {message: errorMessage('React.createClass', '15.5.0', 'the npm module create-react-class')}, + {message: errorMessage('React.PropTypes', '15.5.0', 'the npm module prop-types')} + ] + }, + { + code: 'import {printDOM} from \'react-addons-perf\';', + parser: 'babel-eslint', + errors: [{message: errorMessage('ReactPerf.printDOM', '15.0.0', 'ReactPerf.printOperations')}] + }, + { + code: ` + import ReactPerf from 'react-addons-perf'; + const {printDOM} = ReactPerf; + `, + parser: 'babel-eslint', + errors: [{message: errorMessage('ReactPerf.printDOM', '15.0.0', 'ReactPerf.printOperations')}] + }, + { + code: 'React.DOM.div', + errors: [{message: errorMessage('React.DOM', '15.6.0', 'the npm module react-dom-factories')}] + }, + { + code: ` + class Bar extends React.PureComponent { + componentWillMount() {} + componentWillReceiveProps() {} + componentWillUpdate() {} + }; + `, + errors: [ + { + message: errorMessage( + 'componentWillMount', '16.3.0', 'UNSAFE_componentWillMount', + 'https://reactjs.org/docs/react-component.html#unsafe_componentwillmount' + ) + }, + { + message: errorMessage( + 'componentWillReceiveProps', '16.3.0', 'UNSAFE_componentWillReceiveProps', + 'https://reactjs.org/docs/react-component.html#unsafe_componentwillreceiveprops' + ) + }, + { + message: errorMessage('componentWillUpdate', '16.3.0', 'UNSAFE_componentWillUpdate', + 'https://reactjs.org/docs/react-component.html#unsafe_componentwillupdate' + ) + } + ] + }, + { + code: ` + function Foo() { + return class Bar extends React.PureComponent { + componentWillMount() {} + componentWillReceiveProps() {} + componentWillUpdate() {} + }; + } + `, + errors: [ + { + message: errorMessage( + 'componentWillMount', '16.3.0', 'UNSAFE_componentWillMount', + 'https://reactjs.org/docs/react-component.html#unsafe_componentwillmount' + ) + }, + { + message: errorMessage( + 'componentWillReceiveProps', '16.3.0', 'UNSAFE_componentWillReceiveProps', + 'https://reactjs.org/docs/react-component.html#unsafe_componentwillreceiveprops' + ) + }, + { + message: errorMessage('componentWillUpdate', '16.3.0', 'UNSAFE_componentWillUpdate', + 'https://reactjs.org/docs/react-component.html#unsafe_componentwillupdate' + ) + } + ] + }, + { + code: ` + class Bar extends PureComponent { + componentWillMount() {} + componentWillReceiveProps() {} + componentWillUpdate() {} + }; + `, + errors: [ + { + message: errorMessage( + 'componentWillMount', '16.3.0', 'UNSAFE_componentWillMount', + 'https://reactjs.org/docs/react-component.html#unsafe_componentwillmount' + ) + }, + { + message: errorMessage( + 'componentWillReceiveProps', '16.3.0', 'UNSAFE_componentWillReceiveProps', + 'https://reactjs.org/docs/react-component.html#unsafe_componentwillreceiveprops' + ) + }, + { + message: errorMessage('componentWillUpdate', '16.3.0', 'UNSAFE_componentWillUpdate', + 'https://reactjs.org/docs/react-component.html#unsafe_componentwillupdate' + ) + } + ] + }, + { + code: ` + class Foo extends React.Component { + componentWillMount() {} + componentWillReceiveProps() {} + componentWillUpdate() {} + } + `, + errors: [ + { + message: errorMessage( + 'componentWillMount', '16.3.0', 'UNSAFE_componentWillMount', + 'https://reactjs.org/docs/react-component.html#unsafe_componentwillmount' + ) + }, + { + message: errorMessage( + 'componentWillReceiveProps', '16.3.0', 'UNSAFE_componentWillReceiveProps', + 'https://reactjs.org/docs/react-component.html#unsafe_componentwillreceiveprops' + ) + }, + { + message: errorMessage('componentWillUpdate', '16.3.0', 'UNSAFE_componentWillUpdate', + 'https://reactjs.org/docs/react-component.html#unsafe_componentwillupdate' + ) + } + ] + }, + { + code: ` + class Foo extends Component { + componentWillMount() {} + componentWillReceiveProps() {} + componentWillUpdate() {} + } + `, + errors: [ + { + message: errorMessage( + 'componentWillMount', '16.3.0', 'UNSAFE_componentWillMount', + 'https://reactjs.org/docs/react-component.html#unsafe_componentwillmount' + ) + }, + { + message: errorMessage( + 'componentWillReceiveProps', '16.3.0', 'UNSAFE_componentWillReceiveProps', + 'https://reactjs.org/docs/react-component.html#unsafe_componentwillreceiveprops' + ) + }, + { + message: errorMessage('componentWillUpdate', '16.3.0', 'UNSAFE_componentWillUpdate', + 'https://reactjs.org/docs/react-component.html#unsafe_componentwillupdate' + ) + } + ] + }, + { + code: ` + var Foo = createReactClass({ + componentWillMount: function() {}, + componentWillReceiveProps: function() {}, + componentWillUpdate: function() {} + }) + `, + errors: [ + { + message: errorMessage( + 'componentWillMount', '16.3.0', 'UNSAFE_componentWillMount', + 'https://reactjs.org/docs/react-component.html#unsafe_componentwillmount' + ) + }, + { + message: errorMessage( + 'componentWillReceiveProps', '16.3.0', 'UNSAFE_componentWillReceiveProps', + 'https://reactjs.org/docs/react-component.html#unsafe_componentwillreceiveprops' + ) + }, + { + message: errorMessage('componentWillUpdate', '16.3.0', 'UNSAFE_componentWillUpdate', + 'https://reactjs.org/docs/react-component.html#unsafe_componentwillupdate' + ) + } + ] + } + ] });