@@ -12,16 +12,74 @@ const { NODE_TYPES } = require('../constants');
1212 * @returns {string } The function name or 'global' if not in a function
1313 */
1414function findWrappingFunction ( node , ancestors ) {
15+ const REACT_HOOKS = new Set ( [
16+ 'useEffect' ,
17+ 'useLayoutEffect' ,
18+ 'useInsertionEffect' ,
19+ 'useCallback' ,
20+ 'useMemo' ,
21+ 'useReducer' ,
22+ 'useState' ,
23+ 'useImperativeHandle' ,
24+ 'useDeferredValue' ,
25+ 'useTransition'
26+ ] ) ;
27+
28+ let hookName = null ; // e.g. "useEffect" or "useCallback(handleFoo)"
29+ let componentName = null ;
30+ let firstNonHookFunction = null ;
31+
1532 // Traverse ancestors from closest to furthest
1633 for ( let i = ancestors . length - 1 ; i >= 0 ; i -- ) {
1734 const current = ancestors [ i ] ;
18- const functionName = extractFunctionName ( current , node , ancestors [ i - 1 ] ) ;
19-
20- if ( functionName ) {
21- return functionName ;
35+
36+ // Detect React hook call (CallExpression with Identifier callee)
37+ if ( ! hookName && current . type === NODE_TYPES . CALL_EXPRESSION && current . callee && current . callee . type === NODE_TYPES . IDENTIFIER && REACT_HOOKS . has ( current . callee . name ) ) {
38+ hookName = current . callee . name ; // store plain hook name; we'll format later if needed
39+ }
40+
41+ // Existing logic to extract named function contexts
42+ const fnName = extractFunctionName ( current , node , ancestors [ i - 1 ] ) ;
43+ if ( fnName ) {
44+ if ( REACT_HOOKS . has ( stripParens ( fnName . split ( '.' ) [ 0 ] ) ) ) {
45+ // fnName itself is a hook signature like "useCallback(handleFoo)" or "useEffect()"
46+ if ( ! hookName ) hookName = fnName ;
47+ continue ;
48+ }
49+
50+ // First non-hook function up the tree is treated as component/container name
51+ if ( ! componentName ) {
52+ componentName = fnName ;
53+ }
54+
55+ // Early exit when we already have both pieces
56+ if ( hookName && componentName ) {
57+ break ;
58+ }
59+
60+ // Save first non-hook function for fallback when no hook detected
61+ if ( ! firstNonHookFunction ) {
62+ firstNonHookFunction = fnName ;
63+ }
2264 }
2365 }
24-
66+
67+ // If we detected hook + component, compose them
68+ if ( hookName && componentName ) {
69+ const formattedHook = typeof hookName === 'string' && hookName . endsWith ( '()' ) ? hookName . slice ( 0 , - 2 ) : hookName ;
70+ return `${ componentName } .${ formattedHook } ` ;
71+ }
72+
73+ // If only hook signature found (no component) – return the hook signature itself
74+ if ( hookName ) {
75+ return hookName ;
76+ }
77+
78+ // Fallbacks to previous behaviour
79+ if ( firstNonHookFunction ) {
80+ return firstNonHookFunction ;
81+ }
82+
2583 return 'global' ;
2684}
2785
@@ -118,6 +176,13 @@ function isFunctionNode(node) {
118176 ) ;
119177}
120178
179+ /**
180+ * Utility to strip trailing parens from simple hook signatures
181+ */
182+ function stripParens ( name ) {
183+ return name . endsWith ( '()' ) ? name . slice ( 0 , - 2 ) : name ;
184+ }
185+
121186module . exports = {
122187 findWrappingFunction
123188} ;
0 commit comments