@@ -23,7 +23,7 @@ function isComponentDefinition(context, node) {
23
23
break ;
24
24
case 'ClassDeclaration' :
25
25
var superClass = node . superClass && context . getSource ( node . superClass ) ;
26
- if ( superClass === 'Component' && superClass === 'React.Component' ) {
26
+ if ( superClass === 'Component' || superClass === 'React.Component' ) {
27
27
return true ;
28
28
}
29
29
break ;
@@ -34,44 +34,51 @@ function isComponentDefinition(context, node) {
34
34
}
35
35
36
36
/**
37
- * Detect if the node is rendering some JSX
37
+ * Check if we are in a stateless function component
38
38
* @param {Object } context The current rule context.
39
39
* @param {ASTNode } node The AST node being checked.
40
- * @returns {Boolean } True the node is rendering some JSX , false if not.
40
+ * @returns {Boolean } True if we are in a stateless function component , false if not.
41
41
*/
42
- function isRenderingJSX ( context , node ) {
43
- var tokens = context . getTokens ( node ) ;
44
- for ( var i = 0 , j = tokens . length ; i < j ; i ++ ) {
45
- var hasJSX = / ^ J S X / . test ( tokens [ i ] . type ) ;
46
- var hasReact =
47
- tokens [ i ] . type === 'Identifier' && tokens [ i ] . value === 'React' &&
48
- tokens [ i + 2 ] && tokens [ i + 2 ] . type === 'Identifier' && tokens [ i + 2 ] . value === 'createElement' ;
49
- if ( ! hasJSX && ! hasReact ) {
50
- continue ;
42
+ function isStatelessFunctionComponent ( context , node ) {
43
+ if ( node . type !== 'ReturnStatement' ) {
44
+ return false ;
45
+ }
46
+
47
+ var scope = context . getScope ( ) ;
48
+ while ( scope ) {
49
+ if ( scope . type === 'class' ) {
50
+ return false ;
51
51
}
52
- return true ;
52
+ scope = scope . upper ;
53
53
}
54
- return false ;
54
+
55
+ var returnsJSX =
56
+ node . argument &&
57
+ node . argument . type === 'JSXElement'
58
+ ;
59
+ var returnsReactCreateElement =
60
+ node . argument &&
61
+ node . argument . callee &&
62
+ node . argument . callee . property &&
63
+ node . argument . callee . property . name === 'createElement'
64
+ ;
65
+
66
+ return Boolean ( returnsJSX || returnsReactCreateElement ) ;
55
67
}
56
68
57
69
/**
58
- * Check if a class has a valid render method
59
- * @param {Object } context The current rule context.
60
- * @param {ASTNode } node The AST node being checked.
61
- * @returns {Boolean } True the class has a valid render method, false if not.
70
+ * Get the identifiers of a React component ASTNode
71
+ * @param {ASTNode } node The React component ASTNode being checked.
72
+ * @returns {Object } The component identifiers.
62
73
*/
63
- function isClassWithRender ( context , node ) {
64
- if ( node . type !== 'ClassDeclaration' ) {
65
- return false ;
66
- }
67
- for ( var i = 0 , j = node . body . body . length ; i < j ; i ++ ) {
68
- var declaration = node . body . body [ i ] ;
69
- if ( declaration . type !== 'MethodDefinition' || declaration . key . name !== 'render' ) {
70
- continue ;
71
- }
72
- return isRenderingJSX ( context , declaration ) ;
73
- }
74
- return false ;
74
+ function getIdentifiers ( node ) {
75
+ var name = node . id && node . id . name || DEFAULT_COMPONENT_NAME ;
76
+ var id = name + ':' + node . loc . start . line + ':' + node . loc . start . column ;
77
+
78
+ return {
79
+ id : id ,
80
+ name : name
81
+ } ;
75
82
}
76
83
77
84
/**
@@ -80,40 +87,31 @@ function isClassWithRender(context, node) {
80
87
* @param {ASTNode } node The AST node being checked.
81
88
* @returns {ASTNode } The ASTNode of the React component.
82
89
*/
83
- function getNode ( context , node ) {
84
- var componentNode = null ;
90
+ function getNode ( context , node , list ) {
85
91
var ancestors = context . getAncestors ( ) . reverse ( ) ;
86
92
87
93
ancestors . unshift ( node ) ;
88
94
89
95
for ( var i = 0 , j = ancestors . length ; i < j ; i ++ ) {
90
96
if ( isComponentDefinition ( context , ancestors [ i ] ) ) {
91
- componentNode = ancestors [ i ] ;
92
- break ;
97
+ return ancestors [ i ] ;
93
98
}
94
- if ( isClassWithRender ( context , ancestors [ i ] ) ) {
95
- componentNode = ancestors [ i ] ;
96
- break ;
99
+ // Node is already in the component list
100
+ var identifiers = getIdentifiers ( ancestors [ i ] ) ;
101
+ if ( list && list [ identifiers . id ] ) {
102
+ return ancestors [ i ] ;
97
103
}
98
-
99
104
}
100
105
101
- return componentNode ;
102
- }
103
-
104
- /**
105
- * Get the identifiers of a React component ASTNode
106
- * @param {ASTNode } node The React component ASTNode being checked.
107
- * @returns {Object } The component identifiers.
108
- */
109
- function getIdentifiers ( node ) {
110
- var name = node . id && node . id . name || DEFAULT_COMPONENT_NAME ;
111
- var id = name + ':' + node . loc . start . line + ':' + node . loc . start . column ;
106
+ if ( isStatelessFunctionComponent ( context , node ) ) {
107
+ var scope = context . getScope ( ) ;
108
+ while ( scope . upper && scope . type !== 'function' ) {
109
+ scope = scope . upper ;
110
+ }
111
+ return scope . block ;
112
+ }
112
113
113
- return {
114
- id : id ,
115
- name : name
116
- } ;
114
+ return null ;
117
115
}
118
116
119
117
/**
@@ -171,10 +169,11 @@ List.prototype.getList = function() {
171
169
* @returns {Object } The added component.
172
170
*/
173
171
List . prototype . set = function ( context , node , customProperties ) {
174
- var componentNode = getNode ( context , node ) ;
172
+ var componentNode = getNode ( context , node , this . _list ) ;
175
173
if ( ! componentNode ) {
176
174
return null ;
177
175
}
176
+
178
177
var identifiers = getIdentifiers ( componentNode ) ;
179
178
180
179
var component = util . _extend ( {
0 commit comments