Skip to content

Commit 2e02153

Browse files
committed
Merge pull request #362 from Intellicode/no-refs
Add no-string-refs rule (fixes #341)
2 parents f1a1c63 + f1c5560 commit 2e02153

File tree

5 files changed

+249
-2
lines changed

5 files changed

+249
-2
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ Finally, enable all of the rules that you would like to use.
8787
"react/no-direct-mutation-state": 1,
8888
"react/no-multi-comp": 1,
8989
"react/no-set-state": 1,
90+
"react/no-string-refs": 1,
9091
"react/no-unknown-property": 1,
9192
"react/prefer-es6-class": 1,
9293
"react/prop-types": 1,
@@ -128,6 +129,7 @@ Finally, enable all of the rules that you would like to use.
128129
* [no-is-mounted](docs/rules/no-is-mounted.md): Prevent usage of `isMounted`
129130
* [no-multi-comp](docs/rules/no-multi-comp.md): Prevent multiple component definition per file
130131
* [no-set-state](docs/rules/no-set-state.md): Prevent usage of `setState`
132+
* [no-string-refs](docs/rules/no-string-refs.md): Prevent using string references in `ref` attribute.
131133
* [no-unknown-property](docs/rules/no-unknown-property.md): Prevent usage of unknown DOM property
132134
* [prefer-es6-class](docs/rules/prefer-es6-class.md): Enforce ES5 or ES6 class for React Components
133135
* [prop-types](docs/rules/prop-types.md): Prevent missing props validation in a React component definition

docs/rules/no-string-refs.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Prevent using string references (no-string-refs)
2+
3+
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.
4+
5+
## Rule Details
6+
7+
Invalid:
8+
9+
```js
10+
var Hello = React.createClass({
11+
render: function() {
12+
return <div ref="hello">Hello, world.</div>;
13+
}
14+
});
15+
```
16+
17+
```js
18+
var Hello = React.createClass({
19+
componentDidMount: function() {
20+
var component = this.refs.hello;
21+
// ...do something with component
22+
},
23+
render: function() {
24+
return <div ref="hello">Hello, world.</div>;
25+
}
26+
});
27+
```
28+
29+
Valid:
30+
31+
```js
32+
var Hello = React.createClass({
33+
componentDidMount: function() {
34+
var component = this.hello;
35+
// ...do something with component
36+
},
37+
render() {
38+
return <div ref={c => this.hello = c}>Hello, world.</div>;
39+
}
40+
});
41+
```

index.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ module.exports = {
3636
'no-direct-mutation-state': require('./lib/rules/no-direct-mutation-state'),
3737
'forbid-prop-types': require('./lib/rules/forbid-prop-types'),
3838
'prefer-es6-class': require('./lib/rules/prefer-es6-class'),
39-
'jsx-key': require('./lib/rules/jsx-key')
39+
'jsx-key': require('./lib/rules/jsx-key'),
40+
'no-string-refs': require('./lib/rules/no-string-refs')
4041
},
4142
rulesConfig: {
4243
'jsx-uses-react': 0,
@@ -73,6 +74,7 @@ module.exports = {
7374
'no-direct-mutation-state': 0,
7475
'forbid-prop-types': 0,
7576
'prefer-es6-class': 0,
76-
'jsx-key': 0
77+
'jsx-key': 0,
78+
'no-string-refs': 0
7779
}
7880
};

lib/rules/no-string-refs.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/**
2+
* @fileoverview Prevent string definitions for references and prevent referencing this.refs
3+
* @author Tom Hastjarjanto
4+
*/
5+
'use strict';
6+
7+
var Components = require('../util/Components');
8+
9+
// ------------------------------------------------------------------------------
10+
// Rule Definition
11+
// ------------------------------------------------------------------------------
12+
13+
module.exports = Components.detect(function(context, components, utils) {
14+
/**
15+
* Checks if we are using refs
16+
* @param {ASTNode} node The AST node being checked.
17+
* @returns {Boolean} True if we are using refs, false if not.
18+
*/
19+
function isRefsUsage(node) {
20+
return Boolean(
21+
(
22+
utils.getParentES6Component() ||
23+
utils.getParentES5Component()
24+
) &&
25+
node.object.type === 'ThisExpression' &&
26+
node.property.name === 'refs'
27+
);
28+
}
29+
30+
/**
31+
* Checks if we are using a ref attribute
32+
* @param {ASTNode} node The AST node being checked.
33+
* @returns {Boolean} True if we are using a ref attribute, false if not.
34+
*/
35+
function isRefAttribute(node) {
36+
return Boolean(
37+
node.type === 'JSXAttribute' &&
38+
node.name &&
39+
node.name.name === 'ref'
40+
);
41+
}
42+
43+
/**
44+
* Checks if a node contains a string value
45+
* @param {ASTNode} node The AST node being checked.
46+
* @returns {Boolean} True if the node contains a string value, false if not.
47+
*/
48+
function containsStringLiteral(node) {
49+
return Boolean(
50+
node.value &&
51+
node.value.type === 'Literal' &&
52+
typeof node.value.value === 'string'
53+
);
54+
}
55+
56+
/**
57+
* Checks if a node contains a string value within a jsx expression
58+
* @param {ASTNode} node The AST node being checked.
59+
* @returns {Boolean} True if the node contains a string value within a jsx expression, false if not.
60+
*/
61+
function containsStringExpressionContainer(node) {
62+
return Boolean(
63+
node.value &&
64+
node.value.type === 'JSXExpressionContainer' &&
65+
node.value.expression &&
66+
node.value.expression.type === 'Literal' &&
67+
typeof node.value.expression.value === 'string'
68+
);
69+
}
70+
71+
return {
72+
MemberExpression: function(node) {
73+
if (isRefsUsage(node)) {
74+
context.report(node, 'Using this.refs is deprecated.');
75+
}
76+
},
77+
JSXAttribute: function(node) {
78+
if (
79+
isRefAttribute(node) &&
80+
(containsStringLiteral(node) || containsStringExpressionContainer(node))
81+
) {
82+
context.report(node, 'Using string literals in ref attributes is deprecated.');
83+
}
84+
}
85+
};
86+
});
87+
88+
module.exports.schema = [];

tests/lib/rules/no-string-refs.js

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/**
2+
* @fileoverview Prevent string definitions for references and prevent referencing this.refs
3+
* @author Tom Hastjarjanto
4+
*/
5+
'use strict';
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
var rule = require('../../../lib/rules/no-string-refs');
12+
var RuleTester = require('eslint').RuleTester;
13+
14+
require('babel-eslint');
15+
16+
// ------------------------------------------------------------------------------
17+
// Tests
18+
// ------------------------------------------------------------------------------
19+
20+
var ruleTester = new RuleTester();
21+
ruleTester.run('no-refs', rule, {
22+
23+
valid: [{
24+
code: [
25+
'var Hello = React.createClass({',
26+
' componentDidMount: function() {',
27+
' var component = this.hello;',
28+
' },',
29+
' render: function() {',
30+
' return <div ref={c => this.hello = c}>Hello {this.props.name}</div>;',
31+
' }',
32+
'});'
33+
].join('\n'),
34+
parser: 'babel-eslint',
35+
ecmaFeatures: {
36+
jsx: true
37+
}
38+
}
39+
],
40+
41+
invalid: [{
42+
code: [
43+
'var Hello = React.createClass({',
44+
' componentDidMount: function() {',
45+
' var component = this.refs.hello;',
46+
' },',
47+
' render: function() {',
48+
' return <div>Hello {this.props.name}</div>;',
49+
' }',
50+
'});'
51+
].join('\n'),
52+
parser: 'babel-eslint',
53+
ecmaFeatures: {
54+
classes: true,
55+
jsx: true
56+
},
57+
errors: [{
58+
message: 'Using this.refs is deprecated.'
59+
}]
60+
}, {
61+
code: [
62+
'var Hello = React.createClass({',
63+
' render: function() {',
64+
' return <div ref="hello">Hello {this.props.name}</div>;',
65+
' }',
66+
'});'
67+
].join('\n'),
68+
parser: 'babel-eslint',
69+
ecmaFeatures: {
70+
classes: true,
71+
jsx: true
72+
},
73+
errors: [{
74+
message: 'Using string literals in ref attributes is deprecated.'
75+
}]
76+
}, {
77+
code: [
78+
'var Hello = React.createClass({',
79+
' render: function() {',
80+
' return <div ref={\'hello\'}>Hello {this.props.name}</div>;',
81+
' }',
82+
'});'
83+
].join('\n'),
84+
parser: 'babel-eslint',
85+
ecmaFeatures: {
86+
classes: true,
87+
jsx: true
88+
},
89+
errors: [{
90+
message: 'Using string literals in ref attributes is deprecated.'
91+
}]
92+
}, {
93+
code: [
94+
'var Hello = React.createClass({',
95+
' componentDidMount: function() {',
96+
' var component = this.refs.hello;',
97+
' },',
98+
' render: function() {',
99+
' return <div ref="hello">Hello {this.props.name}</div>;',
100+
' }',
101+
'});'
102+
].join('\n'),
103+
parser: 'babel-eslint',
104+
ecmaFeatures: {
105+
classes: true,
106+
jsx: true
107+
},
108+
errors: [{
109+
message: 'Using this.refs is deprecated.'
110+
}, {
111+
message: 'Using string literals in ref attributes is deprecated.'
112+
}]
113+
}
114+
]});

0 commit comments

Comments
 (0)