Skip to content

Commit

Permalink
Add no-deprecated rule
Browse files Browse the repository at this point in the history
  • Loading branch information
yannickcr committed Dec 14, 2015
1 parent ad9d78e commit c3e3be7
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 10 deletions.
12 changes: 2 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ Finally, enable all of the rules that you would like to use.
"react/jsx-uses-react": 1,
"react/jsx-uses-vars": 1,
"react/no-danger": 1,
"react/no-deprecated": 1,
"react/no-did-mount-set-state": 1,
"react/no-did-update-set-state": 1,
"react/no-direct-mutation-state": 1,
Expand Down Expand Up @@ -105,6 +106,7 @@ Finally, enable all of the rules that you would like to use.
* [jsx-uses-react](docs/rules/jsx-uses-react.md): Prevent React to be incorrectly marked as unused
* [jsx-uses-vars](docs/rules/jsx-uses-vars.md): Prevent variables used in JSX to be incorrectly marked as unused
* [no-danger](docs/rules/no-danger.md): Prevent usage of dangerous JSX properties
* [no-deprecated](docs/rules/no-deprecated.md): Prevent usage of deprecated methods
* [no-did-mount-set-state](docs/rules/no-did-mount-set-state.md): Prevent usage of `setState` in `componentDidMount`
* [no-did-update-set-state](docs/rules/no-did-update-set-state.md): Prevent usage of `setState` in `componentDidUpdate`
* [no-direct-mutation-state](docs/rules/no-direct-mutation-state.md): Prevent direct mutation of `this.state`
Expand All @@ -119,16 +121,6 @@ Finally, enable all of the rules that you would like to use.
* [sort-comp](docs/rules/sort-comp.md): Enforce component methods order
* [wrap-multilines](docs/rules/wrap-multilines.md): Prevent missing parentheses around multilines JSX

## To Do

* no-deprecated: Prevent usage of deprecated methods ([React 0.12 Updated API](http://facebook.github.io/react/blog/2014/10/28/react-v0.12.html#new-terminology-amp-updated-apis))
* no-classic: Prevent usage of "classic" methods ([#2700](https://github.com/facebook/react/pull/2700))
* [Implement relevant rules from David Chang's React Style Guide](https://reactjsnews.com/react-style-guide-patterns-i-like)
* [Implement relevant rules from John Cobb's best practices and conventions](http://web-design-weekly.com/2015/01/29/opinionated-guide-react-js-best-practices-conventions/)
* [Implement relevant rules from Alexander Early's tips and best practices](http://aeflash.com/2015-02/react-tips-and-best-practices.html)

[Any rule idea is welcome !](https://github.com/yannickcr/eslint-plugin-react/issues)

# License

ESLint-plugin-React is licensed under the [MIT License](http://www.opensource.org/licenses/mit-license.php).
Expand Down
38 changes: 38 additions & 0 deletions docs/rules/no-deprecated.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Prevent usage of deprecated methods (no-deprecated)

Several methods are deprecated between React versions. This rule will warn you if you try to use a deprecated method.

## Rule Details

The following patterns are considered warnings:

```js
React.render(<MyComponent />, root);

React.unmountComponentAtNode(root);

React.findDOMNode(this.refs.foo);

React.renderToString(<MyComponent />);

React.renderToStaticMarkup(<MyComponent />);
```

The following patterns are not considered warnings:

```js
ReactDOM.render(<MyComponent />, root);

// When [1, {"react": "0.13.0"}]
ReactDOM.findDOMNode(this.refs.foo);
```

## Rule Options

By default this rule will warn to every methods marked as deprecated. You can limit it to an older React version if you are not using the latest one:

```js
"rules": {
"react/no-deprecated": [1, {"react": "0.12.0"}] // Will warn for every deprecated methods in React 0.12.0 and below
}
```
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module.exports = {
'self-closing-comp': require('./lib/rules/self-closing-comp'),
'no-danger': require('./lib/rules/no-danger'),
'no-set-state': require('./lib/rules/no-set-state'),
'no-deprecated': require('./lib/rules/no-deprecated'),
'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'),
'react-in-jsx-scope': require('./lib/rules/react-in-jsx-scope'),
Expand Down Expand Up @@ -43,6 +44,7 @@ module.exports = {
'display-name': 0,
'wrap-multilines': 0,
'self-closing-comp': 0,
'no-deprecated': 0,
'no-danger': 0,
'no-set-state': 0,
'no-did-mount-set-state': 0,
Expand Down
101 changes: 101 additions & 0 deletions lib/rules/no-deprecated.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* @fileoverview Prevent usage of deprecated methods
* @author Yannick Croissant
* @author Scott Feeney
*/
'use strict';

// ------------------------------------------------------------------------------
// Constants
// ------------------------------------------------------------------------------

var DEPRECATED_MESSAGE = '{{oldMethod}} is deprecated since React {{version}}{{newMethod}}';

var DEPRECATED = {
MemberExpression: {
// 0.12.0
'React.renderComponent': ['0.12.0', 'React.render'],
'React.renderComponentToString': ['0.12.0', 'React.renderToString'],
'React.renderComponentToStaticMarkup': ['0.12.0', 'React.renderToStaticMarkup'],
'React.isValidComponent': ['0.12.0', 'React.isValidElement'],
'React.PropTypes.component': ['0.12.0', 'React.PropTypes.element'],
'React.PropTypes.renderable': ['0.12.0', 'React.PropTypes.node'],
'React.isValidClass': ['0.12.0'],
'this.transferPropsTo': ['0.12.0', 'spread operator ({...})'],
// 0.13.0
'React.addons.classSet': ['0.13.0', 'the npm module classnames'],
'React.addons.cloneWithProps': ['0.13.0', 'React.cloneElement'],
// 0.14.0
'React.render': ['0.14.0', 'ReactDOM.render'],
'React.unmountComponentAtNode': ['0.14.0', 'ReactDOM.unmountComponentAtNode'],
'React.findDOMNode': ['0.14.0', 'ReactDOM.findDOMNode'],
'React.renderToString': ['0.14.0', 'ReactDOMServer.renderToString'],
'React.renderToStaticMarkup': ['0.14.0', 'ReactDOMServer.renderToStaticMarkup']
}
};

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

module.exports = function(context) {

// Validate React version passed in options
// (append the patch version if missing, allowing shorthands like 0.12 for React 0.12.0)
var optVer = context.options[0] ? context.options[0].react : '999.999.999';
optVer = /^[0-9]+\.[0-9]+$/.test(optVer) ? optVer + '.0' : optVer;
optVer = optVer.split('.').map(function(part) {
return Number(part);
});

function checkVersion(methodVer) {
methodVer = methodVer.split('.').map(function(part) {
return Number(part);
});
var higherMajor = methodVer[0] < optVer[0];
var higherMinor = methodVer[0] === optVer[0] && methodVer[1] < optVer[1];
var higherOrEqualPatch = methodVer[0] === optVer[0] && methodVer[1] === optVer[1] && methodVer[2] <= optVer[2];

return higherMajor || higherMinor || higherOrEqualPatch;
}

function isDeprecated(type, method) {
return (
DEPRECATED[type] &&
DEPRECATED[type][method] &&
checkVersion(DEPRECATED[type][method][0])
);
}

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

return {

MemberExpression: function(node) {
var method = context.getSource(node);
if (!isDeprecated(node.type, method)) {
return;
}
context.report(node, DEPRECATED_MESSAGE, {
oldMethod: method,
version: DEPRECATED[node.type][method][0],
newMethod: DEPRECATED[node.type][method][1] ? ', use ' + DEPRECATED[node.type][method][1] + ' instead' : ''
});
}

};

};

module.exports.schema = [{
type: 'object',
properties: {
react: {
type: 'string',
pattern: '^[0-9]+\.[0-9]+(\.[0-9]+)?$'
}
},
additionalProperties: false
}];
87 changes: 87 additions & 0 deletions tests/lib/rules/no-deprecated.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* @fileoverview Prevent usage of deprecated methods
* @author Yannick Croissant
* @author Scott Feeney
*/
'use strict';

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

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

require('babel-eslint');

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

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

valid: [
// Not deprecated
'var MyClass = React.createClass({});',
'var element = React.createElement(\'p\', {}, null);',
'var clone = React.cloneElement(element);',
'ReactDOM.render(element, container);',
'ReactDOM.unmountComponentAtNode(container);',
'ReactDOM.findDOMNode(instance);',
'ReactDOMServer.renderToString(element);',
'ReactDOMServer.renderToStaticMarkup(element);',
// Deprecated in a later version
{code: 'React.renderComponent()', options: [{react: '0.11.0'}]}
],

invalid: [{
code: 'React.renderComponent()',
options: [{react: '0.12.0'}],
errors: [{
message: 'React.renderComponent is deprecated since React 0.12.0, use React.render instead'
}]
}, {
code: 'this.transferPropsTo()',
errors: [{
message: 'this.transferPropsTo is deprecated since React 0.12.0, use spread operator ({...}) 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'
)
}]
}]

});

0 comments on commit c3e3be7

Please sign in to comment.