11use std:: borrow:: Cow ;
22
3+ use rustc_hash:: FxHashSet ;
4+
35use oxc_ast:: {
46 AstKind ,
57 ast:: { BindingIdentifier , * } ,
@@ -514,6 +516,14 @@ pub fn get_preceding_indent_str(source_text: &str, span: Span) -> Option<&str> {
514516}
515517
516518pub fn could_be_error ( ctx : & LintContext , expr : & Expression ) -> bool {
519+ could_be_error_impl ( ctx, expr, & mut FxHashSet :: default ( ) )
520+ }
521+
522+ fn could_be_error_impl (
523+ ctx : & LintContext ,
524+ expr : & Expression ,
525+ visited : & mut FxHashSet < SymbolId > ,
526+ ) -> bool {
517527 match expr. get_inner_expression ( ) {
518528 Expression :: NewExpression ( _)
519529 | Expression :: AwaitExpression ( _)
@@ -527,42 +537,54 @@ pub fn could_be_error(ctx: &LintContext, expr: &Expression) -> bool {
527537 Expression :: AssignmentExpression ( expr) => {
528538 if matches ! ( expr. operator, AssignmentOperator :: Assign | AssignmentOperator :: LogicalAnd )
529539 {
530- return could_be_error ( ctx, & expr. right ) ;
540+ return could_be_error_impl ( ctx, & expr. right , visited ) ;
531541 }
532542
533543 if matches ! (
534544 expr. operator,
535545 AssignmentOperator :: LogicalOr | AssignmentOperator :: LogicalNullish
536546 ) {
537- return expr. left . get_expression ( ) . is_none_or ( |expr| could_be_error ( ctx, expr) )
538- || could_be_error ( ctx, & expr. right ) ;
547+ return expr
548+ . left
549+ . get_expression ( )
550+ . is_none_or ( |expr| could_be_error_impl ( ctx, expr, visited) )
551+ || could_be_error_impl ( ctx, & expr. right , visited) ;
539552 }
540553
541554 false
542555 }
543556 Expression :: SequenceExpression ( expr) => {
544- expr. expressions . last ( ) . is_some_and ( |expr| could_be_error ( ctx, expr) )
557+ expr. expressions . last ( ) . is_some_and ( |expr| could_be_error_impl ( ctx, expr, visited ) )
545558 }
546559 Expression :: LogicalExpression ( expr) => {
547560 if matches ! ( expr. operator, LogicalOperator :: And ) {
548- return could_be_error ( ctx, & expr. right ) ;
561+ return could_be_error_impl ( ctx, & expr. right , visited ) ;
549562 }
550563
551- could_be_error ( ctx, & expr. left ) || could_be_error ( ctx, & expr. right )
564+ could_be_error_impl ( ctx, & expr. left , visited)
565+ || could_be_error_impl ( ctx, & expr. right , visited)
552566 }
553567 Expression :: ConditionalExpression ( expr) => {
554- could_be_error ( ctx, & expr. consequent ) || could_be_error ( ctx, & expr. alternate )
568+ could_be_error_impl ( ctx, & expr. consequent , visited)
569+ || could_be_error_impl ( ctx, & expr. alternate , visited)
555570 }
556571 Expression :: Identifier ( ident) => {
557572 let reference = ctx. scoping ( ) . get_reference ( ident. reference_id ( ) ) ;
558573 let Some ( symbol_id) = reference. symbol_id ( ) else {
559574 return true ;
560575 } ;
576+
577+ // Check if we've already visited this symbol to prevent infinite recursion
578+ // Return true (could be error) when we encounter a circular reference since we can't determine the type
579+ if !visited. insert ( symbol_id) {
580+ return true ;
581+ }
582+
561583 let decl = ctx. nodes ( ) . get_node ( ctx. scoping ( ) . symbol_declaration ( symbol_id) ) ;
562584 match decl. kind ( ) {
563585 AstKind :: VariableDeclarator ( decl) => {
564586 if let Some ( init) = & decl. init {
565- could_be_error ( ctx, init)
587+ could_be_error_impl ( ctx, init, visited )
566588 } else {
567589 // TODO: warn about throwing undefined
568590 false
0 commit comments