diff --git a/README.md b/README.md index 6061fdd625..b0ea9d6aae 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,7 @@ Finally, enable all of the rules that you would like to use. "react/no-direct-mutation-state": 1, "react/no-multi-comp": 1, "react/no-set-state": 1, + "react/no-string-refs": 1, "react/no-unknown-property": 1, "react/prefer-es6-class": 1, "react/prop-types": 1, @@ -128,6 +129,7 @@ Finally, enable all of the rules that you would like to use. * [no-is-mounted](docs/rules/no-is-mounted.md): Prevent usage of `isMounted` * [no-multi-comp](docs/rules/no-multi-comp.md): Prevent multiple component definition per file * [no-set-state](docs/rules/no-set-state.md): Prevent usage of `setState` +* [no-string-refs](docs/rules/no-string-refs.md): Prevent using string references in `ref` attribute. * [no-unknown-property](docs/rules/no-unknown-property.md): Prevent usage of unknown DOM property * [prefer-es6-class](docs/rules/prefer-es6-class.md): Prefer es6 class instead of createClass for React Components * [prop-types](docs/rules/prop-types.md): Prevent missing props validation in a React component definition diff --git a/docs/rules/no-string-refs.md b/docs/rules/no-string-refs.md new file mode 100644 index 0000000000..97fed0b360 --- /dev/null +++ b/docs/rules/no-string-refs.md @@ -0,0 +1,41 @@ +# Prevent using string references (no-string-refs) + +Currently, two ways are supported by React to refer to components. The first one, providing a string identifier is considered legacy in the official documentation. Referring to components by setting an property on the `this` object in the reference callback is preferred. + +## Rule Details + +Invalid: + +```js +var Hello = React.createClass({ + render: function() { + return
Hello, world.
; + } +}); +``` + +```js +var Hello = React.createClass({ + componentDidMount: function() { + var component = this.refs.hello; + // ...do something with component + }, + render: function() { + return
Hello, world.
; + } +}); +``` + +Valid: + +```js +var Hello = React.createClass({ + componentDidMount: function() { + var component = this.hello; + // ...do something with component + }, + render() { + return
this.hello = c}>Hello, world.
; + } +}); +``` diff --git a/index.js b/index.js index e482777787..59225fd219 100644 --- a/index.js +++ b/index.js @@ -36,7 +36,8 @@ module.exports = { 'no-direct-mutation-state': require('./lib/rules/no-direct-mutation-state'), 'forbid-prop-types': require('./lib/rules/forbid-prop-types'), 'prefer-es6-class': require('./lib/rules/prefer-es6-class'), - 'jsx-key': require('./lib/rules/jsx-key') + 'jsx-key': require('./lib/rules/jsx-key'), + 'no-string-refs': require('./lib/rules/no-string-refs') }, rulesConfig: { 'jsx-uses-react': 0, @@ -73,6 +74,7 @@ module.exports = { 'no-direct-mutation-state': 0, 'forbid-prop-types': 0, 'prefer-es6-class': 0, - 'jsx-key': 0 + 'jsx-key': 0, + 'no-string-refs': 0 } }; diff --git a/lib/rules/no-string-refs.js b/lib/rules/no-string-refs.js new file mode 100644 index 0000000000..e6cd65daba --- /dev/null +++ b/lib/rules/no-string-refs.js @@ -0,0 +1,88 @@ +/** + * @fileoverview Prevent string definitions for references and prevent referencing this.refs + * @author Tom Hastjarjanto + */ +'use strict'; + +var Components = require('../util/Components'); + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = Components.detect(function(context, components, utils) { + /** + * Checks if we are using refs + * @param {ASTNode} node The AST node being checked. + * @returns {Boolean} True if we are using refs, false if not. + */ + function isRefsUsage(node) { + return Boolean( + ( + utils.getParentES6Component() || + utils.getParentES5Component() + ) && + node.object.type === 'ThisExpression' && + node.property.name === 'refs' + ); + } + + /** + * Checks if we are using a ref attribute + * @param {ASTNode} node The AST node being checked. + * @returns {Boolean} True if we are using a ref attribute, false if not. + */ + function isRefAttribute(node) { + return Boolean( + node.type === 'JSXAttribute' && + node.name && + node.name.name === 'ref' + ); + } + + /** + * Checks if a node contains a string value + * @param {ASTNode} node The AST node being checked. + * @returns {Boolean} True if the node contains a string value, false if not. + */ + function containsStringLiteral(node) { + return Boolean( + node.value && + node.value.type === 'Literal' && + typeof node.value.value === 'string' + ); + } + + /** + * Checks if a node contains a string value within a jsx expression + * @param {ASTNode} node The AST node being checked. + * @returns {Boolean} True if the node contains a string value within a jsx expression, false if not. + */ + function containsStringExpressionContainer(node) { + return Boolean( + node.value && + node.value.type === 'JSXExpressionContainer' && + node.value.expression && + node.value.expression.type === 'Literal' && + typeof node.value.expression.value === 'string' + ); + } + + return { + MemberExpression: function(node) { + if (isRefsUsage(node)) { + context.report(node, 'Using this.refs is deprecated.'); + } + }, + JSXAttribute: function(node) { + if ( + isRefAttribute(node) && + (containsStringLiteral(node) || containsStringExpressionContainer(node)) + ) { + context.report(node, 'Using string literals in ref attributes is deprecated.'); + } + } + }; +}); + +module.exports.schema = []; diff --git a/tests/lib/rules/no-string-refs.js b/tests/lib/rules/no-string-refs.js new file mode 100644 index 0000000000..29dd02e855 --- /dev/null +++ b/tests/lib/rules/no-string-refs.js @@ -0,0 +1,114 @@ +/** + * @fileoverview Prevent string definitions for references and prevent referencing this.refs + * @author Tom Hastjarjanto + */ +'use strict'; + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +var rule = require('../../../lib/rules/no-string-refs'); +var RuleTester = require('eslint').RuleTester; + +require('babel-eslint'); + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +var ruleTester = new RuleTester(); +ruleTester.run('no-refs', rule, { + + valid: [{ + code: [ + 'var Hello = React.createClass({', + ' componentDidMount: function() {', + ' var component = this.hello;', + ' },', + ' render: function() {', + ' return
this.hello = c}>Hello {this.props.name}
;', + ' }', + '});' + ].join('\n'), + parser: 'babel-eslint', + ecmaFeatures: { + jsx: true + } + } + ], + + invalid: [{ + code: [ + 'var Hello = React.createClass({', + ' componentDidMount: function() {', + ' var component = this.refs.hello;', + ' },', + ' render: function() {', + ' return
Hello {this.props.name}
;', + ' }', + '});' + ].join('\n'), + parser: 'babel-eslint', + ecmaFeatures: { + classes: true, + jsx: true + }, + errors: [{ + message: 'Using this.refs is deprecated.' + }] + }, { + code: [ + 'var Hello = React.createClass({', + ' render: function() {', + ' return
Hello {this.props.name}
;', + ' }', + '});' + ].join('\n'), + parser: 'babel-eslint', + ecmaFeatures: { + classes: true, + jsx: true + }, + errors: [{ + message: 'Using string literals in ref attributes is deprecated.' + }] + }, { + code: [ + 'var Hello = React.createClass({', + ' render: function() {', + ' return
Hello {this.props.name}
;', + ' }', + '});' + ].join('\n'), + parser: 'babel-eslint', + ecmaFeatures: { + classes: true, + jsx: true + }, + errors: [{ + message: 'Using string literals in ref attributes is deprecated.' + }] + }, { + code: [ + 'var Hello = React.createClass({', + ' componentDidMount: function() {', + ' var component = this.refs.hello;', + ' },', + ' render: function() {', + ' return
Hello {this.props.name}
;', + ' }', + '});' + ].join('\n'), + parser: 'babel-eslint', + ecmaFeatures: { + classes: true, + jsx: true + }, + errors: [{ + message: 'Using this.refs is deprecated.' + }, { + message: 'Using string literals in ref attributes is deprecated.' + }] + } +]});