@@ -23,7 +23,7 @@ function isComponentDefinition(context, node) {
2323 break ;
2424 case 'ClassDeclaration' :
2525 var superClass = node . superClass && context . getSource ( node . superClass ) ;
26- if ( superClass === 'Component' && superClass === 'React.Component' ) {
26+ if ( superClass === 'Component' || superClass === 'React.Component' ) {
2727 return true ;
2828 }
2929 break ;
@@ -34,44 +34,51 @@ function isComponentDefinition(context, node) {
3434}
3535
3636/**
37- * Detect if the node is rendering some JSX
37+ * Check if we are in a stateless function component
3838 * @param {Object } context The current rule context.
3939 * @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.
4141 */
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 ;
5151 }
52- return true ;
52+ scope = scope . upper ;
5353 }
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 ) ;
5567}
5668
5769/**
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.
6273 */
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+ } ;
7582}
7683
7784/**
@@ -80,40 +87,31 @@ function isClassWithRender(context, node) {
8087 * @param {ASTNode } node The AST node being checked.
8188 * @returns {ASTNode } The ASTNode of the React component.
8289 */
83- function getNode ( context , node ) {
84- var componentNode = null ;
90+ function getNode ( context , node , list ) {
8591 var ancestors = context . getAncestors ( ) . reverse ( ) ;
8692
8793 ancestors . unshift ( node ) ;
8894
8995 for ( var i = 0 , j = ancestors . length ; i < j ; i ++ ) {
9096 if ( isComponentDefinition ( context , ancestors [ i ] ) ) {
91- componentNode = ancestors [ i ] ;
92- break ;
97+ return ancestors [ i ] ;
9398 }
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 ] ;
97103 }
98-
99104 }
100105
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+ }
112113
113- return {
114- id : id ,
115- name : name
116- } ;
114+ return null ;
117115}
118116
119117/**
@@ -171,10 +169,11 @@ List.prototype.getList = function() {
171169 * @returns {Object } The added component.
172170 */
173171List . prototype . set = function ( context , node , customProperties ) {
174- var componentNode = getNode ( context , node ) ;
172+ var componentNode = getNode ( context , node , this . _list ) ;
175173 if ( ! componentNode ) {
176174 return null ;
177175 }
176+
178177 var identifiers = getIdentifiers ( componentNode ) ;
179178
180179 var component = util . _extend ( {
0 commit comments