@@ -64,7 +64,7 @@ var RulesOfHooksRule = rule.Rule{
64
64
}
65
65
66
66
segmentID := segment .ID ()
67
- if paths , exists := countPathsFromStartCache [segmentID ]; exists {
67
+ if paths , exists := countPathsFromStartCache [segmentID ]; exists && paths != nil {
68
68
return paths
69
69
}
70
70
@@ -98,7 +98,9 @@ var RulesOfHooksRule = rule.Rule{
98
98
}
99
99
}
100
100
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 {
102
104
countPathsFromStartCache [segmentID ] = paths
103
105
}
104
106
@@ -318,14 +320,16 @@ var RulesOfHooksRule = rule.Rule{
318
320
// Check if we're in a valid context for hooks
319
321
if isDirectlyInsideComponentOrHook {
320
322
// Check for async function
321
- if isAsyncFunction (codePathNode ) {
323
+ if isInsideAsyncFunction (codePathNode ) {
322
324
ctx .ReportNode (hook , buildAsyncComponentHookMessage (hookText ))
323
325
continue
324
326
}
325
327
328
+ pathsCmp := pathsFromStartToEnd .Cmp (allPathsFromStartToEnd )
329
+
326
330
// Check for conditional calls (except use() and do-while loops)
327
331
if ! isCyclic &&
328
- pathsFromStartToEnd . Cmp ( allPathsFromStartToEnd ) != 0 &&
332
+ pathsCmp != 0 &&
329
333
! isUseHook &&
330
334
! isInsideDoWhileLoop (hook ) {
331
335
var message rule.RuleMessage
@@ -341,10 +345,18 @@ var RulesOfHooksRule = rule.Rule{
341
345
if isInsideClass (codePathNode ) {
342
346
ctx .ReportNode (hook , buildClassHookMessage (hookText ))
343
347
} else if codePathFunctionName != "" {
348
+ // Custom message if we found an invalid function name.kj
344
349
ctx .ReportNode (hook , buildFunctionHookMessage (hookText , codePathFunctionName ))
345
350
} else if isTopLevel (codePathNode ) {
351
+ // These are dangerous if you have inline requires enabled.
346
352
ctx .ReportNode (hook , buildTopLevelHookMessage (hookText ))
347
353
} 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.
348
360
ctx .ReportNode (hook , buildGenericHookMessage (hookText ))
349
361
}
350
362
}
@@ -569,16 +581,52 @@ func getFunctionName(node *ast.Node) string {
569
581
// Function declaration or function expression names win over any
570
582
// assignment statements or other renames.
571
583
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
+ }
572
589
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 ""
575
614
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
+
576
625
// {useHook: () => {}}
577
626
// {useHook() {}}
578
- return node .AsMethodDeclaration ().Text ()
579
- default :
580
- return ""
627
+ return node .AsMethodDeclaration ().Name ().Text ()
581
628
}
629
+ return ""
582
630
}
583
631
584
632
// Helper function to check if node is inside a component or hook
@@ -588,10 +636,10 @@ func isInsideComponentOrHook(node *ast.Node) bool {
588
636
current := node
589
637
for current != nil {
590
638
functionName := getFunctionName (current )
591
- if isComponentName (functionName ) || isHookName (functionName ) {
639
+ if functionName != "" && ( isComponentName (functionName ) || isHookName (functionName ) ) {
592
640
return true
593
641
}
594
- if isForwardRefCallback (node ) || isMemoCallback (node ) {
642
+ if isForwardRefCallback (current ) || isMemoCallback (current ) {
595
643
return true
596
644
}
597
645
current = current .Parent
@@ -666,22 +714,10 @@ func isInsideClass(node *ast.Node) bool {
666
714
667
715
// Helper function to check if node is inside an async function
668
716
func isInsideAsyncFunction (node * ast.Node ) bool {
669
- current := node . Parent
717
+ current := node
670
718
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
685
721
}
686
722
current = current .Parent
687
723
}
@@ -756,14 +792,15 @@ func isHookCall(node *ast.Node) (bool, string) {
756
792
757
793
// Helper function to check if node is at top level
758
794
func 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
767
804
}
768
805
769
806
// Helper function to check if a call expression is a React function
@@ -864,8 +901,9 @@ func isInsideDoWhileLoop(node *ast.Node) bool {
864
901
865
902
// Helper function to check if function is async
866
903
func 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
+ }
869
907
return false
870
908
}
871
909
@@ -919,8 +957,7 @@ func getScope(node *ast.Node) *ast.Node {
919
957
920
958
// Helper function to record all useEffectEvent functions (simplified)
921
959
func 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
924
961
}
925
962
926
963
// Helper function to check if we're inside a component or hook (from scope context)
0 commit comments