Skip to content

Add no-deprecated-methods rule to prevents usage of deprecated component lifecycle methods #1748

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 39 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
12d1376
Added `no-component-will-receive-props` rule
sergei-startsev Mar 28, 2018
daa076f
`no-component-will-receive-props` => `no-deprecated-methods`, added docs
sergei-startsev Mar 28, 2018
5c7ab5f
Fixed jsdoc comments for `no-deprecated-methods` rule
sergei-startsev Mar 28, 2018
323dba2
Adjusted no-deprecated rule for React 16.3.0
sergei-startsev Mar 29, 2018
c8008e0
Merge remote-tracking branch 'origin/master'
sergei-startsev Mar 29, 2018
69b1317
Fix default-props-match-prop-types type annotations detection with ES…
yannickcr May 21, 2018
31e4f33
Fix require-default-props type annotations detection with ESLint 3
yannickcr May 21, 2018
677e1bd
Fix jsx-sort-props auto fix with ESLint 3
yannickcr May 21, 2018
cb4d21e
Run tests against different ESLint versions on Travis
yannickcr May 21, 2018
589231a
Add SpreadElement support to boolean-prop-naming
yannickcr May 28, 2018
e6e8955
Add SpreadElement support to default-props-match-prop-types
yannickcr May 28, 2018
3762258
Add JSXText support to jsx-child-element-spacing
yannickcr May 28, 2018
c734901
Add JSXText support to jsx-curly-brace-presence
yannickcr May 28, 2018
0a6cb51
Add JSXText support to jsx-indent
yannickcr May 28, 2018
bd6caf0
Add JSXText support to jsx-no-literals
yannickcr May 28, 2018
0a028e4
Add JSXText support to jsx-one-expression-per-line
yannickcr May 28, 2018
ed3370b
Add SpreadElement and JSXText support to no-danger-with-children
yannickcr May 28, 2018
ce1fec7
Add JSXText support to no-unescaped-entities
yannickcr May 28, 2018
b3ed9d6
Add SpreadElement and RestElement support to no-unused-state
yannickcr May 28, 2018
632941c
Add SpreadElement support to require-default-props
yannickcr May 28, 2018
29c248d
Add JSXText support to self-closing-comp
yannickcr May 28, 2018
f2d8729
Add SpreadElement support to sort-prop-types
yannickcr May 28, 2018
9d4e27a
Ignore no-typos test that only works with ESLint 5
yannickcr May 28, 2018
ab6b41a
Remove deprecated experimentalObjectRestSpread option from tests conf…
yannickcr May 29, 2018
0d34198
Update CHANGELOG and bump version
yannickcr Jun 3, 2018
9e83b7c
[Deps] update `has`, `prop-types`, `doctrine`
ljharb Jun 4, 2018
df430ee
[Dev Deps] update `babel-eslint`, `coveralls`, `eslint`, `mocha`
ljharb Jun 4, 2018
ac10288
Update CHANGELOG and bump version
ljharb Jun 4, 2018
7c5b8c4
[Docs] Typo fixes in jsx-no-target-blank
ferhatelmas Jun 4, 2018
d779865
Merge pull request #1805 from ferhatelmas/patch-1
ljharb Jun 4, 2018
fee2d1a
Fix crash in no-unused-prop-types when encountering mixed union and i…
yannickcr Jun 4, 2018
c82746c
Fix crash in no-unused-prop-types when encountering impossible inters…
yannickcr Jun 4, 2018
fcff54e
Allow LHS in destructuring-assignment
alexzherdev Jun 14, 2018
b6e911b
Remove unnecessary babel-eslint
alexzherdev Jun 14, 2018
f924414
Fix static propTypes handling in no-typos
alexzherdev Jun 14, 2018
2a674b0
Merge pull request #1825 from alexzherdev/1728-destructuring-state-as…
ljharb Jun 14, 2018
d68b9e8
Remove unnecessary babel-eslint and fix typo
alexzherdev Jun 15, 2018
48e386d
Merge pull request #1827 from alexzherdev/1677-no-typos-static-proptypes
ljharb Jun 15, 2018
278e76a
Merge remote-tracking branch 'origin/master'
sergei-startsev Jun 17, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ Enable the rules that you would like to use.
* [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-methods](docs/rules/no-deprecated-methods.md): Prevents usage of deprecated component lifecycle methods
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be added to no-deprecated; since that rule already deals with methods, including lifecycle methods.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could please point to an example of life cycle method in no-deprecated? I couldn't find any...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There may not be an existing example, but the rule still deals with any depreciations - otherwise it’d be called no-deprecated-static or something :-)

* [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`
Expand Down
53 changes: 53 additions & 0 deletions docs/rules/no-deprecated-methods.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Prevents usage of deprecated component lifecycle methods (react/no-deprecated-methods)

Warns if you have deprecated methods defined when defining a component that extends `React.Component`, `React.PureComponent` or uses ES5 syntax with `createReactClass`. See [React 16.3 details](https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#gradual-migration-path).
Copy link

@asapach asapach Mar 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

methods defined when defining a component

Phrasing could be improved.


## Rule Details

The following patterns are considered warnings:

```jsx
class Foo extends React.Component {
componentWillReceiveProps() { }
// ...
}

class Foo extends React.PureComponent {
componentWillReceiveProps() { }
// ...
}

var Foo = createReactClass({
componentWillReceiveProps: function() {},
})
```

The following patterns are **not** considered warnings:

```jsx
class Foo {
componentWillReceiveProps() {}
}

var Foo = createReactClassNonReact({
componentWillReceiveProps: function() {}
});
```

## Rule Options

This rule can take options to ignore some deprecated methods.

```js
...
"react/no-deprecated-methods": [<enabled>, {
"componentWillMount": <boolean>,
"componentWillReceiveProps": <boolean>,
"componentWillUpdate": <boolean>,
}]
...
```

* `componentWillMount`: `true` by default.
* `componentWillReceiveProps`: `true` by default.
* `componentWillUpdate`: `true` by default.
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const allRules = {
'no-danger': require('./lib/rules/no-danger'),
'no-danger-with-children': require('./lib/rules/no-danger-with-children'),
'no-deprecated': require('./lib/rules/no-deprecated'),
'no-deprecated-methods': require('./lib/rules/no-deprecated-methods'),
'no-did-mount-set-state': require('./lib/rules/no-did-mount-set-state'),
'no-did-update-set-state': require('./lib/rules/no-did-update-set-state'),
'no-direct-mutation-state': require('./lib/rules/no-direct-mutation-state'),
Expand Down
104 changes: 104 additions & 0 deletions lib/rules/no-deprecated-methods.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* @fileoverview Prevents usage of deprecated component lifecycle methods
*/
'use strict';

const Components = require('../util/Components');
const astUtil = require('../util/ast');
const docsUrl = require('../util/docsUrl');

function errorMessage(node, method) {
return `${node} should not use ${method}.`;
}

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

module.exports = {
meta: {
docs: {
description: 'Prevents usage of deprecated component lifecycle methods',
category: 'Possible Errors',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no-deprecated is in 'Best Practices' category - maybe change it for consistency?

recommended: false,
url: docsUrl('no-deprecated-methods')
},
schema: [{
type: 'object',
properties: {
componentWillMount: {
type: 'boolean'
},
componentWillReceiveProps: {
type: 'boolean'
},
componentWillUpdate: {
type: 'boolean'
}
},
additionalProperties: false
}]
},

create: Components.detect((context, components, utils) => {
const defaults = {
componentWillMount: true,
componentWillReceiveProps: true,
componentWillUpdate: true
};
const configuration = Object.assign({}, defaults, context.options[0] || {});
const methods = Object.keys(configuration).filter(key => configuration[key]);
/**
* Returns deprecated methods if available
* @param {ASTNode} node The AST node being checked.
* @returns {Array} The array of deprecated methods.
*/
function getDeprecatedMethods(node) {
const properties = astUtil.getComponentProperties(node);
return properties
.map(property => astUtil.getPropertyName(property))
.filter(name => methods.indexOf(name) !== -1);
}

/**
* Gets name of node if available
* @param {ASTNode} node The AST node being checked.
* @return {String} The name of the node
*/
function getNodeName(node) {
if (node.id) {
return node.id.name;
} else if (node.parent && node.parent.id) {
return node.parent.id.name;
} else if (node.parent && node.parent.parent && node.parent.parent.id) {
return node.parent.parent.id.name;
}
return '';
}

/**
* Checks for violation of rule
* @param {ASTNode} node The AST node being checked.
*/
function checkForViolation(node) {
if (utils.isES5Component(node) || utils.isES6Component(node)) {
const deprecatedMethods = getDeprecatedMethods(node);
if (deprecatedMethods && deprecatedMethods.length) {
const className = getNodeName(node);
deprecatedMethods.forEach(method => {
context.report({
node: node,
message: errorMessage(className, method)
});
});
}
}
}

return {
ClassDeclaration: checkForViolation,
ClassExpression: checkForViolation,
ObjectExpression: checkForViolation
};
})
};
222 changes: 222 additions & 0 deletions tests/lib/rules/no-deprecated-methods.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
/**
* @fileoverview Tests for no-deprecated-methods
*/

'use strict';

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

const rule = require('../../../lib/rules/no-deprecated-methods');
const RuleTester = require('eslint').RuleTester;

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

function errorMessage(node, method) {
return `${node} should not use ${method}.`;
}

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

const ruleTester = new RuleTester();
ruleTester.run('no-deprecated-methods', rule, {
valid: [
{
code: `
var Foo = createReactClass({
render: function() {}
})
`,
parserOptions: parserOptions
},
{
code: `
var Foo = createReactClassNonReact({
componentWillMount: function() {},
componentWillReceiveProps: function() {},
componentWillUpdate: function() {}
});
`,
parserOptions: parserOptions
},
{
code: `
var Foo = {
componentWillMount: function() {},
componentWillReceiveProps: function() {},
componentWillUpdate: function() {}
};
`,
parserOptions: parserOptions
},
{
code: `
class Foo {
componentWillMount() {}
componentWillReceiveProps() {}
componentWillUpdate() {}
}
`,
parserOptions: parserOptions
},
{
code: `
class Foo extends React.Component {
}
`,
parserOptions: parserOptions
},
{
code: `
class Foo extends React.Component {
}
`,
parser: 'babel-eslint',
parserOptions: parserOptions
},
{
code: `
class Foo extends React.PureComponent {
}
`,
parser: 'babel-eslint',
parserOptions: parserOptions
},
{
code: `
function Foo() {
return class Bar extends React.Component {
};
}
`,
parserOptions: parserOptions
},
{
code: `
function Foo() {
return <div>test</div>
}
`,
parserOptions: parserOptions
},
{
code: `
class Foo extends React.Component {
componentWillMount() {}
componentWillReceiveProps() {}
componentWillUpdate() {}
}
`,
options: [{
componentWillMount: false,
componentWillReceiveProps: false,
componentWillUpdate: false
}],
parserOptions: parserOptions
}
],

invalid: [
{
code: `
function Foo() {
return class Bar extends React.PureComponent {
componentWillMount() {}
componentWillReceiveProps() {}
componentWillUpdate() {}
};
}
`,
errors: [
{message: errorMessage('Bar', 'componentWillMount')},
{message: errorMessage('Bar', 'componentWillReceiveProps')},
{message: errorMessage('Bar', 'componentWillUpdate')}
],
parserOptions: parserOptions
},
{
code: `
var Foo = createReactClass({
componentWillMount: function() {},
componentWillReceiveProps: function() {},
componentWillUpdate: function() {}
})
`,
errors: [
{message: errorMessage('Foo', 'componentWillMount')},
{message: errorMessage('Foo', 'componentWillReceiveProps')},
{message: errorMessage('Foo', 'componentWillUpdate')}
],
parserOptions: parserOptions
},
{
code: `
class Foo extends React.PureComponent {
componentWillMount() {}
componentWillReceiveProps() {}
componentWillUpdate() {}
}
`,
errors: [
{message: errorMessage('Foo', 'componentWillMount')},
{message: errorMessage('Foo', 'componentWillReceiveProps')},
{message: errorMessage('Foo', 'componentWillUpdate')}
],
parserOptions: parserOptions
},
{
code: `
class Foo extends PureComponent {
componentWillMount() {}
componentWillReceiveProps() {}
componentWillUpdate() {}
}
`,
errors: [
{message: errorMessage('Foo', 'componentWillMount')},
{message: errorMessage('Foo', 'componentWillReceiveProps')},
{message: errorMessage('Foo', 'componentWillUpdate')}
],
parserOptions: parserOptions
},
{
code: `
class Foo extends React.Component {
componentWillMount() {}
componentWillReceiveProps() {}
componentWillUpdate() {}
}
`,
errors: [
{message: errorMessage('Foo', 'componentWillMount')},
{message: errorMessage('Foo', 'componentWillReceiveProps')},
{message: errorMessage('Foo', 'componentWillUpdate')}
],
parserOptions: parserOptions
},
{
code: `
class Foo extends Component {
componentWillMount() {}
componentWillReceiveProps() {}
componentWillUpdate() {}
}
`,
errors: [
{message: errorMessage('Foo', 'componentWillMount')},
{message: errorMessage('Foo', 'componentWillReceiveProps')},
{message: errorMessage('Foo', 'componentWillUpdate')}
],
parserOptions: parserOptions
}
]
});