@@ -581,45 +581,62 @@ function componentRule(rule, context) {
581581 * @returns {ASTNode } component node, null if we are not in a component
582582 */
583583 getParentStatelessComponent ( ) {
584- let scope = context . getScope ( ) ;
585- while ( scope ) {
586- const node = scope . block ;
587- const isFunction = / F u n c t i o n / . test ( node . type ) ; // Functions
588- const isArrowFunction = astUtil . isArrowFunction ( node ) ;
589- const enclosingScope = isArrowFunction ? utils . getArrowFunctionScope ( scope ) : scope ;
590- const enclosingScopeParent = enclosingScope && enclosingScope . block . parent ;
591- const isClass = enclosingScope && astUtil . isClass ( enclosingScope . block ) ;
592- const isMethod = enclosingScopeParent && enclosingScopeParent . type === 'MethodDefinition' ; // Classes methods
593- const isArgument = node . parent && node . parent . type === 'CallExpression' ; // Arguments (callback, etc.)
594- // Attribute Expressions inside JSX Elements (<button onClick={() => props.handleClick()}></button>)
595- const isJSXExpressionContainer = node . parent && node . parent . type === 'JSXExpressionContainer' ;
596- const pragmaComponentWrapper = this . getPragmaComponentWrapper ( node ) ;
597- if ( isFunction && pragmaComponentWrapper ) {
598- return pragmaComponentWrapper ;
584+ /**
585+ * @param {ASTNode } node
586+ * @returns {boolean }
587+ */
588+ function isInAllowedPositiionForComponent ( node ) {
589+ switch ( node . parent . type ) {
590+ case 'VariableDeclarator' :
591+ case 'AssignmentExpression' :
592+ case 'Property' :
593+ case 'ReturnStatement' :
594+ case 'ExportDefaultDeclaration' : {
595+ return true ;
596+ }
597+ case 'SequenceExpression' : {
598+ return isInAllowedPositiionForComponent ( node . parent ) &&
599+ node === node . parent . expressions [ node . parent . expressions . length - 1 ] ;
600+ }
601+ default :
602+ return false ;
599603 }
600- // Stop moving up if we reach a class or an argument (like a callback)
601- if ( isClass || isArgument ) {
602- return null ;
604+ }
605+
606+ /**
607+ * Get node if node is a stateless component, or node.parent in cases like
608+ * `React.memo` or `React.forwardRef`. Otherwise returns `undefined`.
609+ * @param {ASTNode } node
610+ * @returns {ASTNode | undefined }
611+ */
612+ function getStatelessComponent ( node ) {
613+ if ( node . type === 'FunctionDeclaration' ) {
614+ if ( utils . isReturningJSXOrNull ( node ) ) {
615+ return node ;
616+ }
603617 }
604- // Return the node if it is a function that is not a class method and is not inside a JSX Element
605- if ( isFunction && ! isMethod && ! isJSXExpressionContainer && utils . isReturningJSXOrNull ( node ) ) {
606- return node ;
618+
619+ if ( node . type === 'FunctionExpression' || node . type === 'ArrowFunctionExpression' ) {
620+ if ( isInAllowedPositiionForComponent ( node ) && utils . isReturningJSXOrNull ( node ) ) {
621+ return node ;
622+ }
623+
624+ // Case like `React.memo(() => <></>)` or `React.forwardRef(...)`
625+ const pragmaComponentWrapper = utils . getPragmaComponentWrapper ( node ) ;
626+ if ( pragmaComponentWrapper ) {
627+ return pragmaComponentWrapper ;
628+ }
607629 }
608- scope = scope . upper ;
630+
631+ return undefined ;
609632 }
610- return null ;
611- } ,
612633
613- /**
614- * Get an enclosing scope used to find `this` value by an arrow function
615- * @param {Scope } scope Current scope
616- * @returns {Scope } An enclosing scope used by an arrow function
617- */
618- getArrowFunctionScope ( scope ) {
619- scope = scope . upper ;
634+ let scope = context . getScope ( ) ;
620635 while ( scope ) {
621- if ( astUtil . isFunction ( scope . block ) || astUtil . isClass ( scope . block ) ) {
622- return scope ;
636+ const node = scope . block ;
637+ const statelessComponent = getStatelessComponent ( node ) ;
638+ if ( statelessComponent ) {
639+ return statelessComponent ;
623640 }
624641 scope = scope . upper ;
625642 }
0 commit comments