Skip to content

Commit 68ed8af

Browse files
committed
Rewrite require-render-return rule (fixes #542, fixes #543)
1 parent 42cfd68 commit 68ed8af

File tree

2 files changed

+87
-27
lines changed

2 files changed

+87
-27
lines changed

lib/rules/require-render-return.js

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,41 +10,45 @@ var Components = require('../util/Components');
1010
// Rule Definition
1111
// ------------------------------------------------------------------------------
1212

13-
module.exports = Components.detect(function(context) {
14-
/**
15-
* Helper for checking if parent node is render method.
16-
* @param {ASTNode} node to check
17-
* @returns {boolean} If previous node is method and name is render returns true, otherwise false;
18-
*/
19-
function checkParent(node) {
20-
return (node.parent.type === 'Property' || node.parent.type === 'MethodDefinition')
21-
&& node.parent.key
22-
&& node.parent.key.name === 'render';
23-
}
13+
module.exports = Components.detect(function(context, components) {
2414

2515
/**
26-
* Helper for checking if child node exists and if it's ReturnStatement
27-
* @param {ASTNode} node to check
28-
* @returns {boolean} True if ReturnStatement exists, otherwise false
16+
* Mark a return statement as present
17+
* @param {ASTNode} node The AST node being checked.
2918
*/
30-
function checkReturnStatementExistence(node) {
31-
if (!node.body && !node.body.body && !node.body.body.length) {
32-
return false;
33-
}
34-
35-
var hasReturnStatement = node.body.body.some(function(elem) {
36-
return elem.type === 'ReturnStatement';
19+
function markReturnStatementPresent(node) {
20+
components.set(node, {
21+
hasReturnStatement: true
3722
});
38-
39-
return hasReturnStatement;
4023
}
4124

42-
4325
return {
44-
FunctionExpression: function(node) {
45-
if (checkParent(node) && !checkReturnStatementExistence(node)) {
26+
ReturnStatement: function(node) {
27+
var ancestors = context.getAncestors(node).reverse();
28+
var depth = 0;
29+
for (var i = 0, j = ancestors.length; i < j; i++) {
30+
if (/Function(Expression|Declaration)$/.test(ancestors[i].type)) {
31+
depth++;
32+
}
33+
if (
34+
(ancestors[i].type !== 'Property' && ancestors[i].type !== 'MethodDefinition') ||
35+
ancestors[i].key.name !== 'render' ||
36+
depth > 1
37+
) {
38+
continue;
39+
}
40+
markReturnStatementPresent(node);
41+
}
42+
},
43+
44+
'Program:exit': function() {
45+
var list = components.list();
46+
for (var component in list) {
47+
if (!list.hasOwnProperty(component) || list[component].hasReturnStatement) {
48+
continue;
49+
}
4650
context.report({
47-
node: node,
51+
node: list[component].node,
4852
message: 'Your render method should have return statement'
4953
});
5054
}

tests/lib/rules/require-render-return.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ var ruleTester = new RuleTester();
2828
ruleTester.run('require-render-return', rule, {
2929

3030
valid: [{
31+
// ES6 class
3132
code: [
3233
'class Hello extends React.Component {',
3334
' render() {',
@@ -37,6 +38,7 @@ ruleTester.run('require-render-return', rule, {
3738
].join('\n'),
3839
parserOptions: parserOptions
3940
}, {
41+
// ES5 class
4042
code: [
4143
'var Hello = React.createClass({',
4244
' displayName: \'Hello\',',
@@ -46,9 +48,47 @@ ruleTester.run('require-render-return', rule, {
4648
'});'
4749
].join('\n'),
4850
parserOptions: parserOptions
51+
}, {
52+
// Return in a switch...case
53+
code: [
54+
'var Hello = React.createClass({',
55+
' render: function() {',
56+
' switch (this.props.name) {',
57+
' case \'Foo\':',
58+
' return <div>Hello Foo</div>;',
59+
' default:',
60+
' return <div>Hello {this.props.name}</div>;',
61+
' }',
62+
' }',
63+
'});'
64+
].join('\n'),
65+
parserOptions: parserOptions
66+
}, {
67+
// Return in a if...else
68+
code: [
69+
'var Hello = React.createClass({',
70+
' render: function() {',
71+
' if (this.props.name === \'Foo\') {',
72+
' return <div>Hello Foo</div>;',
73+
' } else {',
74+
' return <div>Hello {this.props.name}</div>;',
75+
' }',
76+
' }',
77+
'});'
78+
].join('\n'),
79+
parserOptions: parserOptions
80+
}, {
81+
// Not a React component
82+
code: [
83+
'class Hello {',
84+
' render() {}',
85+
'}'
86+
].join('\n'),
87+
parserOptions: parserOptions
4988
}],
5089

5190
invalid: [{
91+
// Missing return in ES5 class
5292
code: [
5393
'var Hello = React.createClass({',
5494
' displayName: \'Hello\',',
@@ -60,6 +100,7 @@ ruleTester.run('require-render-return', rule, {
60100
message: 'Your render method should have return statement'
61101
}]
62102
}, {
103+
// Missing return in ES6 class
63104
code: [
64105
'class Hello extends React.Component {',
65106
' render() {} ',
@@ -69,5 +110,20 @@ ruleTester.run('require-render-return', rule, {
69110
errors: [{
70111
message: 'Your render method should have return statement'
71112
}]
113+
}, {
114+
// Missing return (but one is present in a sub-function)
115+
code: [
116+
'class Hello extends React.Component {',
117+
' render() {',
118+
' const names = this.props.names.map(function(name) {',
119+
' return <div>{name}</div>',
120+
' });',
121+
' } ',
122+
'}'
123+
].join('\n'),
124+
parserOptions: parserOptions,
125+
errors: [{
126+
message: 'Your render method should have return statement'
127+
}]
72128
}
73129
]});

0 commit comments

Comments
 (0)