@@ -64,7 +64,7 @@ var RulesOfHooksRule = rule.Rule{
6464 }
6565
6666 segmentID := segment .ID ()
67- if paths , exists := countPathsFromStartCache [segmentID ]; exists {
67+ if paths , exists := countPathsFromStartCache [segmentID ]; exists && paths != nil {
6868 return paths
6969 }
7070
@@ -98,7 +98,9 @@ var RulesOfHooksRule = rule.Rule{
9898 }
9999 }
100100
101- if segment .Reachable () || paths .Cmp (big .NewInt (0 )) > 0 {
101+ if segment .Reachable () && paths .Cmp (big .NewInt (0 )) == 0 {
102+ countPathsFromStartCache [segmentID ] = nil
103+ } else {
102104 countPathsFromStartCache [segmentID ] = paths
103105 }
104106
@@ -318,14 +320,16 @@ var RulesOfHooksRule = rule.Rule{
318320 // Check if we're in a valid context for hooks
319321 if isDirectlyInsideComponentOrHook {
320322 // Check for async function
321- if isAsyncFunction (codePathNode ) {
323+ if isInsideAsyncFunction (codePathNode ) {
322324 ctx .ReportNode (hook , buildAsyncComponentHookMessage (hookText ))
323325 continue
324326 }
325327
328+ pathsCmp := pathsFromStartToEnd .Cmp (allPathsFromStartToEnd )
329+
326330 // Check for conditional calls (except use() and do-while loops)
327331 if ! isCyclic &&
328- pathsFromStartToEnd . Cmp ( allPathsFromStartToEnd ) != 0 &&
332+ pathsCmp != 0 &&
329333 ! isUseHook &&
330334 ! isInsideDoWhileLoop (hook ) {
331335 var message rule.RuleMessage
@@ -341,10 +345,18 @@ var RulesOfHooksRule = rule.Rule{
341345 if isInsideClass (codePathNode ) {
342346 ctx .ReportNode (hook , buildClassHookMessage (hookText ))
343347 } else if codePathFunctionName != "" {
348+ // Custom message if we found an invalid function name.kj
344349 ctx .ReportNode (hook , buildFunctionHookMessage (hookText , codePathFunctionName ))
345350 } else if isTopLevel (codePathNode ) {
351+ // These are dangerous if you have inline requires enabled.
346352 ctx .ReportNode (hook , buildTopLevelHookMessage (hookText ))
347353 } else if isSomewhereInsideComponentOrHook && ! isUseHook {
354+ // Assume in all other cases the user called a hook in some
355+ // random function callback. This should usually be true for
356+ // anonymous function expressions. Hopefully this is clarifying
357+ // enough in the common case that the incorrect message in
358+ // uncommon cases doesn't matter.
359+ // `use(...)` can be called in callbacks.
348360 ctx .ReportNode (hook , buildGenericHookMessage (hookText ))
349361 }
350362 }
@@ -569,16 +581,52 @@ func getFunctionName(node *ast.Node) string {
569581 // Function declaration or function expression names win over any
570582 // assignment statements or other renames.
571583 return node .AsFunctionDeclaration ().Name ().Text ()
584+ case ast .KindFunctionExpression :
585+ name := node .AsFunctionExpression ().Name ()
586+ if name != nil {
587+ return node .AsFunctionExpression ().Name ().Text ()
588+ }
572589 case ast .KindArrowFunction :
573- // const useHook = () => {};
574- return node .AsArrowFunction ().Text ()
590+ if node .Parent != nil {
591+ switch node .Parent .Kind {
592+ case ast .KindVariableDeclaration , // const useHook = () => {};
593+ ast .KindShorthandPropertyAssignment , // ({k = () => { useState(); }} = {});
594+ ast .KindBindingElement , // const {j = () => { useState(); }} = {};
595+ ast .KindPropertyAssignment : // ({f: () => { useState(); }});
596+ if ast .IsInExpressionContext (node ) {
597+ return node .Parent .Name ().Text ()
598+ }
599+ case ast .KindBinaryExpression :
600+ if node .Parent .AsBinaryExpression ().Right == node {
601+ left := node .Parent .AsBinaryExpression ().Left
602+ switch left .Kind {
603+ case ast .KindIdentifier :
604+ // e = () => { useState(); };
605+ return left .AsIdentifier ().Text
606+ case ast .KindPropertyAccessExpression :
607+ // Namespace.useHook = () => { useState(); };
608+ return left .AsPropertyAccessExpression ().Name ().Text ()
609+ }
610+ }
611+ }
612+ }
613+ return ""
575614 case ast .KindMethodDeclaration :
615+ // NOTE: We could also support `ClassProperty` and `MethodDefinition`
616+ // here to be pedantic. However, hooks in a class are an anti-pattern. So
617+ // we don't allow it to error early.
618+ //
619+ // class {useHook = () => {}}
620+ // class {useHook() {}}
621+ if ast .GetContainingClass (node ) != nil {
622+ return ""
623+ }
624+
576625 // {useHook: () => {}}
577626 // {useHook() {}}
578- return node .AsMethodDeclaration ().Text ()
579- default :
580- return ""
627+ return node .AsMethodDeclaration ().Name ().Text ()
581628 }
629+ return ""
582630}
583631
584632// Helper function to check if node is inside a component or hook
@@ -588,10 +636,10 @@ func isInsideComponentOrHook(node *ast.Node) bool {
588636 current := node
589637 for current != nil {
590638 functionName := getFunctionName (current )
591- if isComponentName (functionName ) || isHookName (functionName ) {
639+ if functionName != "" && ( isComponentName (functionName ) || isHookName (functionName ) ) {
592640 return true
593641 }
594- if isForwardRefCallback (node ) || isMemoCallback (node ) {
642+ if isForwardRefCallback (current ) || isMemoCallback (current ) {
595643 return true
596644 }
597645 current = current .Parent
@@ -666,22 +714,10 @@ func isInsideClass(node *ast.Node) bool {
666714
667715// Helper function to check if node is inside an async function
668716func isInsideAsyncFunction (node * ast.Node ) bool {
669- current := node . Parent
717+ current := node
670718 for current != nil {
671- if isFunctionLike (current ) {
672- // TODO: Check if function has async modifier
673- // This requires checking the modifiers array
674- // For now, check specific function types
675- if current .Kind == ast .KindFunctionDeclaration {
676- funcDecl := current .AsFunctionDeclaration ()
677- if funcDecl != nil {
678- // TODO: Check for async modifier in modifiers
679- return false // placeholder
680- }
681- } else if current .Kind == ast .KindArrowFunction {
682- // TODO: Check for async modifier
683- return false // placeholder
684- }
719+ if isAsyncFunction (current ) {
720+ return true
685721 }
686722 current = current .Parent
687723 }
@@ -756,14 +792,15 @@ func isHookCall(node *ast.Node) (bool, string) {
756792
757793// Helper function to check if node is at top level
758794func isTopLevel (node * ast.Node ) bool {
759- current := node .Parent
760- for current != nil {
761- if isFunctionLike (current ) {
762- return false
763- }
764- current = current .Parent
765- }
766- return true
795+ // current := node.Parent
796+ // for current != nil {
797+ // if isFunctionLike(current) {
798+ // return false
799+ // }
800+ // current = current.Parent
801+ // }
802+ // return true
803+ return node .Kind == ast .KindSourceFile
767804}
768805
769806// Helper function to check if a call expression is a React function
@@ -864,8 +901,9 @@ func isInsideDoWhileLoop(node *ast.Node) bool {
864901
865902// Helper function to check if function is async
866903func isAsyncFunction (node * ast.Node ) bool {
867- // This is a simplified implementation
868- // You would check the modifiers for async keyword
904+ if isFunctionLike (node ) {
905+ return ast .HasSyntacticModifier (node , ast .ModifierFlagsAsync )
906+ }
869907 return false
870908}
871909
@@ -919,8 +957,7 @@ func getScope(node *ast.Node) *ast.Node {
919957
920958// Helper function to record all useEffectEvent functions (simplified)
921959func recordAllUseEffectEventFunctions (scope * ast.Node ) {
922- // This is a simplified implementation
923- // In a real implementation, you would traverse the scope and find all useEffectEvent declarations
960+ // !!! useEffectEvent
924961}
925962
926963// Helper function to check if we're inside a component or hook (from scope context)
0 commit comments