Skip to content

Commit

Permalink
Basic implementation to prevent refs usage
Browse files Browse the repository at this point in the history
Add check for string usage in ref attributes

Add unit tests

Add docs

Improve docs

Improve tests

Improve docs

Add rule to index.js

Fix linting errors

Rename from no-refs to no-string-refs

Update ESLint project readme

Add titles
  • Loading branch information
Intellicode committed Dec 22, 2015
1 parent 712244f commit f1c5560
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 2 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
41 changes: 41 additions & 0 deletions docs/rules/no-string-refs.md
Original file line number Diff line number Diff line change
@@ -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 <div ref="hello">Hello, world.</div>;
}
});
```

```js
var Hello = React.createClass({
componentDidMount: function() {
var component = this.refs.hello;
// ...do something with component
},
render: function() {
return <div ref="hello">Hello, world.</div>;
}
});
```

Valid:

```js
var Hello = React.createClass({
componentDidMount: function() {
var component = this.hello;
// ...do something with component
},
render() {
return <div ref={c => this.hello = c}>Hello, world.</div>;
}
});
```
6 changes: 4 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
}
};
88 changes: 88 additions & 0 deletions lib/rules/no-string-refs.js
Original file line number Diff line number Diff line change
@@ -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 = [];
114 changes: 114 additions & 0 deletions tests/lib/rules/no-string-refs.js
Original file line number Diff line number Diff line change
@@ -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 <div ref={c => this.hello = c}>Hello {this.props.name}</div>;',
' }',
'});'
].join('\n'),
parser: 'babel-eslint',
ecmaFeatures: {
jsx: true
}
}
],

invalid: [{
code: [
'var Hello = React.createClass({',
' componentDidMount: function() {',
' var component = this.refs.hello;',
' },',
' render: function() {',
' return <div>Hello {this.props.name}</div>;',
' }',
'});'
].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 <div ref="hello">Hello {this.props.name}</div>;',
' }',
'});'
].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 <div ref={\'hello\'}>Hello {this.props.name}</div>;',
' }',
'});'
].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 <div ref="hello">Hello {this.props.name}</div>;',
' }',
'});'
].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.'
}]
}
]});

0 comments on commit f1c5560

Please sign in to comment.