does_it_throw/
lib.rs

1extern crate swc_common;
2extern crate swc_ecma_ast;
3extern crate swc_ecma_parser;
4extern crate swc_ecma_visit;
5
6use std::collections::HashSet;
7use std::hash::{Hash, Hasher};
8use std::vec;
9
10use swc_ecma_ast::{
11  ArrowExpr, AssignExpr, BlockStmtOrExpr, ClassDecl, ClassMethod, Constructor, Decl, ExportDecl,
12  FnDecl, JSXAttr, JSXAttrOrSpread, JSXAttrValue, JSXExpr, JSXOpeningElement, MemberExpr,
13   ObjectLit, PatOrExpr, Prop, PropName, PropOrSpread, Stmt, VarDeclarator,
14};
15
16use self::swc_common::{sync::Lrc, SourceMap, Span};
17use self::swc_ecma_ast::{
18  CallExpr, EsVersion, Expr, Function, ImportDecl, ImportSpecifier, MemberProp, ModuleExportName,
19  ThrowStmt,
20};
21use self::swc_ecma_parser::{lexer::Lexer, Parser, StringInput, Syntax};
22use self::swc_ecma_visit::Visit;
23
24pub fn analyze_code(content: &str, cm: Lrc<SourceMap>) -> (AnalysisResult, Lrc<SourceMap>) {
25  let fm = cm.new_source_file(swc_common::FileName::Anon, content.into());
26  let lexer = Lexer::new(
27    Syntax::Typescript(swc_ecma_parser::TsConfig {
28      tsx: true,
29      decorators: true,
30      dts: false,
31      no_early_errors: false,
32      disallow_ambiguous_jsx_like: false,
33    }),
34    EsVersion::latest(),
35    StringInput::from(&*fm),
36    None,
37  );
38
39  let mut parser = Parser::new_from(lexer);
40  let module = parser.parse_module().expect("Failed to parse module");
41  let mut collector = ThrowAnalyzer {
42    functions_with_throws: HashSet::new(),
43    json_parse_calls: vec![],
44    fs_access_calls: vec![],
45    import_sources: HashSet::new(),
46    imported_identifiers: Vec::new(),
47    function_name_stack: vec![],
48    current_class_name: None,
49    current_method_name: None,
50    imported_identifier_usages: HashSet::new(),
51  };
52  collector.visit_module(&module);
53  let mut call_collector = CallFinder {
54    functions_with_throws: collector.functions_with_throws.clone(),
55    calls: HashSet::new(),
56    current_class_name: None,
57    instantiations: HashSet::new(),
58    function_name_stack: vec![],
59    object_property_stack: vec![],
60  };
61  call_collector.visit_module(&module);
62  let combined_analyzers = CombinedAnalyzers {
63    throw_analyzer: collector,
64    call_finder: call_collector,
65  };
66
67  (combined_analyzers.into(), cm)
68}
69
70fn prop_name_to_string(prop_name: &PropName) -> String {
71  match prop_name {
72    PropName::Ident(ident) => ident.sym.to_string(),
73    PropName::Str(str_) => str_.value.to_string(),
74    PropName::Num(num) => num.value.to_string(),
75    _ => "anonymous".to_string(), // Fallback for unnamed functions
76  }
77}
78
79struct ThrowFinder {
80  throw_spans: Vec<Span>,
81}
82
83impl Visit for ThrowFinder {
84  fn visit_throw_stmt(&mut self, node: &ThrowStmt) {
85    self.throw_spans.push(node.span)
86  }
87}
88
89#[derive(Clone)]
90pub struct IdentifierUsage {
91  pub usage_span: Span,
92  pub identifier_name: String,
93  pub usage_context: String,
94  pub id: String,
95}
96
97impl IdentifierUsage {
98  pub fn new(usage_span: Span, identifier_name: String, usage_context: String, id: String) -> Self {
99    Self {
100      usage_span,
101      identifier_name,
102      usage_context,
103      id,
104    }
105  }
106}
107
108impl Eq for IdentifierUsage {}
109
110impl PartialEq for IdentifierUsage {
111  fn eq(&self, other: &Self) -> bool {
112    self.id == other.id
113  }
114}
115
116impl Hash for IdentifierUsage {
117  fn hash<H: Hasher>(&self, state: &mut H) {
118    self.id.hash(state);
119    self.usage_span.lo.hash(state);
120    self.usage_span.hi.hash(state);
121  }
122}
123
124#[derive(Default)]
125pub struct AnalysisResult {
126  pub functions_with_throws: HashSet<ThrowMap>,
127  pub calls_to_throws: HashSet<CallToThrowMap>,
128  pub json_parse_calls: Vec<String>,
129  pub fs_access_calls: Vec<String>,
130  pub import_sources: HashSet<String>,
131  pub imported_identifiers: Vec<String>,
132  pub imported_identifier_usages: HashSet<IdentifierUsage>,
133}
134
135struct CombinedAnalyzers {
136  throw_analyzer: ThrowAnalyzer,
137  call_finder: CallFinder,
138}
139
140impl From<CombinedAnalyzers> for AnalysisResult {
141  fn from(analyzers: CombinedAnalyzers) -> Self {
142    Self {
143      functions_with_throws: analyzers.throw_analyzer.functions_with_throws,
144      calls_to_throws: analyzers.call_finder.calls,
145      json_parse_calls: analyzers.throw_analyzer.json_parse_calls,
146      fs_access_calls: analyzers.throw_analyzer.fs_access_calls,
147      import_sources: analyzers.throw_analyzer.import_sources,
148      imported_identifiers: analyzers.throw_analyzer.imported_identifiers,
149      imported_identifier_usages: analyzers.throw_analyzer.imported_identifier_usages,
150    }
151  }
152}
153
154#[derive(Clone)]
155pub struct ThrowMap {
156  pub throw_spans: Vec<Span>,
157  pub throw_statement: Span,
158  pub function_or_method_name: String,
159  pub class_name: Option<String>,
160  pub id: String,
161}
162
163impl PartialEq for ThrowMap {
164  fn eq(&self, other: &Self) -> bool {
165    self.throw_statement == other.throw_statement
166  }
167}
168
169impl Eq for ThrowMap {}
170
171impl Hash for ThrowMap {
172  fn hash<H: Hasher>(&self, state: &mut H) {
173    self.throw_statement.lo.hash(state);
174    self.throw_statement.hi.hash(state);
175    self.throw_statement.ctxt.hash(state);
176  }
177}
178
179struct ThrowAnalyzer {
180  functions_with_throws: HashSet<ThrowMap>,
181  json_parse_calls: Vec<String>,
182  fs_access_calls: Vec<String>,
183  import_sources: HashSet<String>,
184  imported_identifiers: Vec<String>,
185  function_name_stack: Vec<String>,
186  current_class_name: Option<String>,
187  current_method_name: Option<String>,
188  imported_identifier_usages: HashSet<IdentifierUsage>,
189}
190
191impl ThrowAnalyzer {
192  fn check_function_for_throws(&mut self, function: &Function) {
193    let mut throw_finder = ThrowFinder {
194      throw_spans: vec![],
195    };
196    throw_finder.visit_function(function);
197    if !throw_finder.throw_spans.is_empty() {
198      let throw_map = ThrowMap {
199        throw_spans: throw_finder.throw_spans,
200        throw_statement: function.span,
201        function_or_method_name: self
202          .function_name_stack
203          .last()
204          .cloned()
205          .unwrap_or_else(|| "<anonymous>".to_string()),
206        class_name: None,
207        id: format!(
208          "{}-{}",
209          self
210            .current_class_name
211            .clone()
212            .unwrap_or_else(|| "NOT_SET".to_string()),
213          self
214            .function_name_stack
215            .last()
216            .cloned()
217            .unwrap_or_else(|| "<anonymous>".to_string())
218        ),
219      };
220      self.functions_with_throws.insert(throw_map);
221    }
222  }
223
224  fn check_arrow_function_for_throws(&mut self, arrow_function: &swc_ecma_ast::ArrowExpr) {
225    let mut throw_finder = ThrowFinder {
226      throw_spans: vec![],
227    };
228    throw_finder.visit_arrow_expr(arrow_function);
229    if !throw_finder.throw_spans.is_empty() {
230      let throw_map = ThrowMap {
231        throw_spans: throw_finder.throw_spans,
232        throw_statement: arrow_function.span,
233        function_or_method_name: self
234          .function_name_stack
235          .last()
236          .cloned()
237          .unwrap_or_else(|| "<anonymous>".to_string()),
238        class_name: None,
239        id: format!(
240          "{}-{}",
241          self
242            .current_class_name
243            .clone()
244            .unwrap_or_else(|| "NOT_SET".to_string()),
245          self
246            .function_name_stack
247            .last()
248            .cloned()
249            .unwrap_or_else(|| "<anonymous>".to_string())
250        ),
251      };
252      self.functions_with_throws.insert(throw_map);
253    }
254  }
255
256  fn check_constructor_for_throws(&mut self, constructor: &Constructor) {
257    let mut throw_finder = ThrowFinder {
258      throw_spans: vec![],
259    };
260    throw_finder.visit_constructor(constructor);
261    if !throw_finder.throw_spans.is_empty() {
262      let throw_map = ThrowMap {
263        throw_spans: throw_finder.throw_spans,
264        throw_statement: constructor.span,
265        function_or_method_name: self
266          .current_method_name
267          .clone()
268          .unwrap_or_else(|| "<constructor>".to_string()),
269        class_name: self.current_class_name.clone(),
270        id: format!(
271          "{}-{}",
272          self
273            .current_class_name
274            .clone()
275            .unwrap_or_else(|| "NOT_SET".to_string()),
276          self
277            .current_method_name
278            .clone()
279            .unwrap_or_else(|| "<constructor>".to_string())
280        ),
281      };
282      self.functions_with_throws.insert(throw_map);
283    }
284  }
285
286  fn register_import(&mut self, import: &ImportDecl) {
287    self.import_sources.insert(import.src.value.to_string());
288    for specifier in &import.specifiers {
289      match specifier {
290        ImportSpecifier::Default(default_spec) => {
291          self
292            .imported_identifiers
293            .push(default_spec.local.sym.to_string());
294        }
295        ImportSpecifier::Named(named_spec) => {
296          let imported_name = match &named_spec.imported {
297            Some(imported) => match imported {
298              ModuleExportName::Ident(ident) => ident.sym.to_string(),
299              ModuleExportName::Str(str) => str.value.to_string(),
300            },
301            None => named_spec.local.sym.to_string(),
302          };
303          self.imported_identifiers.push(imported_name);
304        }
305        ImportSpecifier::Namespace(namespace_spec) => {
306          self
307            .imported_identifiers
308            .push(namespace_spec.local.sym.to_string());
309        }
310      }
311    }
312  }
313}
314
315// --------- ThrowAnalyzer Visitor implementation ---------
316// `ThrowAnalyzer` uses the Visitor pattern to traverse the AST of JavaScript or TypeScript code.
317// Its primary goal is to identify functions that throw exceptions and record their context.
318// It also records the usage of imported identifiers to help identify the context of function calls.
319
320impl Visit for ThrowAnalyzer {
321  fn visit_call_expr(&mut self, call: &CallExpr) {
322    if let swc_ecma_ast::Callee::Expr(expr) = &call.callee {
323      match &**expr {
324        Expr::Member(member_expr) => {
325          if let Expr::Ident(object_ident) = &*member_expr.obj {
326            self.current_class_name = Some(object_ident.sym.to_string());
327          }
328
329          if let MemberProp::Ident(method_ident) = &member_expr.prop {
330            self.current_method_name = Some(method_ident.sym.to_string());
331          }
332
333          if let (Some(current_class_name), Some(current_method_name)) = (
334            self.current_class_name.clone(),
335            self.current_method_name.clone(),
336          ) {
337            if self.imported_identifiers.contains(&current_class_name) {
338              let usage_context = current_method_name.clone();
339              let id = format!(
340                "{}-{}",
341                self
342                  .current_class_name
343                  .clone()
344                  .unwrap_or_else(|| "NOT_SET".to_string()),
345                usage_context
346              );
347              // Create and store the identifier usage information
348              let usage_map = IdentifierUsage::new(
349                call.span,
350                current_class_name.clone(),
351                usage_context.clone(),
352                id.clone(),
353              );
354              self.imported_identifier_usages.insert(usage_map);
355            }
356          }
357          for arg in &call.args {
358            self.function_name_stack.push(
359              self
360                .current_method_name
361                .clone()
362                .unwrap_or_else(|| "<anonymous>".to_string()),
363            );
364            if let Expr::Arrow(arrow_expr) = &*arg.expr {
365              self.check_arrow_function_for_throws(arrow_expr);
366              self.visit_arrow_expr(&arrow_expr)
367            }
368            if let Expr::Fn(fn_expr) = &*arg.expr {
369              self.check_function_for_throws(&fn_expr.function);
370              self.visit_function(&fn_expr.function)
371            }
372            self.function_name_stack.pop();
373          }
374          self.current_class_name = None;
375          self.current_method_name = None;
376        }
377
378        Expr::Ident(ident) => {
379          let called_function_name = ident.sym.to_string();
380          if self.imported_identifiers.contains(&called_function_name) {
381            let usage_context = self
382              .function_name_stack
383              .last()
384              .cloned()
385              .unwrap_or_else(|| "<anonymous>".to_string());
386            let id = format!(
387              "{}-{}",
388              self
389                .current_class_name
390                .clone()
391                .unwrap_or_else(|| "NOT_SET".to_string()),
392              called_function_name
393            );
394            let usage_map = IdentifierUsage::new(
395              call.span,
396              called_function_name.clone(),
397              usage_context.clone(),
398              id.clone(),
399            );
400            self.imported_identifier_usages.insert(usage_map);
401          }
402          for arg in &call.args {
403            self.function_name_stack.push(called_function_name.clone());
404            if let Expr::Arrow(arrow_expr) = &*arg.expr {
405              self.check_arrow_function_for_throws(arrow_expr);
406              self.visit_arrow_expr(&arrow_expr);
407            }
408            if let Expr::Fn(fn_expr) = &*arg.expr {
409              self.check_function_for_throws(&fn_expr.function);
410              self.visit_function(&fn_expr.function);
411            }
412            self.function_name_stack.pop();
413          }
414        }
415
416        Expr::Arrow(arrow_expr) => {
417          let mut throw_finder = ThrowFinder {
418            throw_spans: vec![],
419          };
420          throw_finder.visit_arrow_expr(arrow_expr);
421          if !throw_finder.throw_spans.is_empty() {
422            let throw_map = ThrowMap {
423              throw_spans: throw_finder.throw_spans,
424              throw_statement: arrow_expr.span,
425              function_or_method_name: self
426                .function_name_stack
427                .last()
428                .cloned()
429                .unwrap_or_else(|| "<anonymous>".to_string()),
430              class_name: None,
431              id: format!(
432                "{}-{}",
433                self
434                  .current_class_name
435                  .clone()
436                  .unwrap_or_else(|| "NOT_SET".to_string()),
437                self
438                  .function_name_stack
439                  .last()
440                  .cloned()
441                  .unwrap_or_else(|| "<anonymous>".to_string())
442              ),
443            };
444            self.functions_with_throws.insert(throw_map);
445          }
446        }
447        _ => {}
448      }
449    }
450  }
451
452  fn visit_fn_decl(&mut self, fn_decl: &FnDecl) {
453    let function_name = fn_decl.ident.sym.to_string();
454    self.function_name_stack.push(function_name);
455
456    swc_ecma_visit::visit_fn_decl(self, fn_decl);
457
458    self.function_name_stack.pop();
459  }
460
461  fn visit_object_lit(&mut self, object_lit: &ObjectLit) {
462    // Iterate over the properties of the object literal
463    for prop in &object_lit.props {
464      match prop {
465        // Check for method properties (e.g., someImportedThrow: () => { ... })
466        PropOrSpread::Prop(prop) => {
467          if let Prop::Method(method_prop) = &**prop {
468            if let Some(method_name) = &method_prop.key.as_ident() {
469              let method_name: String = method_name.sym.to_string();
470
471              self.function_name_stack.push(method_name.clone());
472
473              let mut throw_finder = ThrowFinder {
474                throw_spans: vec![],
475              };
476              throw_finder.visit_function(&method_prop.function);
477
478              if !throw_finder.throw_spans.is_empty() {
479                let throw_map = ThrowMap {
480                  throw_spans: throw_finder.throw_spans,
481                  throw_statement: method_prop.function.span,
482                  function_or_method_name: method_name.clone(),
483                  class_name: self.current_class_name.clone(),
484                  id: format!(
485                    "{}-{}",
486                    self
487                      .current_class_name
488                      .clone()
489                      .unwrap_or_else(|| "NOT_SET".to_string()),
490                    method_name
491                  ),
492                };
493                self.functions_with_throws.insert(throw_map);
494              }
495
496              self.function_name_stack.pop();
497            }
498          }
499          if let Prop::KeyValue(key_value_prop) = &**prop {
500            match &*key_value_prop.value {
501              Expr::Fn(fn_expr) => {
502                let mut throw_finder = ThrowFinder {
503                  throw_spans: vec![],
504                };
505                throw_finder.visit_function(&fn_expr.function);
506                let function_name = prop_name_to_string(&key_value_prop.key);
507
508                if !throw_finder.throw_spans.is_empty() {
509                  let throw_map = ThrowMap {
510                    throw_spans: throw_finder.throw_spans,
511                    throw_statement: fn_expr.function.span,
512                    function_or_method_name: function_name.clone(),
513                    class_name: self.current_class_name.clone(),
514                    id: format!(
515                      "{}-{}",
516                      self
517                        .current_class_name
518                        .clone()
519                        .unwrap_or_else(|| "NOT_SET".to_string()),
520                      function_name
521                    ),
522                  };
523                  self.functions_with_throws.insert(throw_map);
524                }
525              }
526              Expr::Arrow(arrow_expr) => {
527                let mut throw_finder = ThrowFinder {
528                  throw_spans: vec![],
529                };
530                throw_finder.visit_arrow_expr(arrow_expr);
531                let function_name = prop_name_to_string(&key_value_prop.key);
532
533                if !throw_finder.throw_spans.is_empty() {
534                  let throw_map = ThrowMap {
535                    throw_spans: throw_finder.throw_spans,
536                    throw_statement: arrow_expr.span,
537                    function_or_method_name: function_name.clone(),
538                    class_name: self.current_class_name.clone(),
539                    id: format!(
540                      "{}-{}",
541                      self
542                        .current_class_name
543                        .clone()
544                        .unwrap_or_else(|| "NOT_SET".to_string()),
545                      function_name
546                    ),
547                  };
548                  self.functions_with_throws.insert(throw_map);
549                }
550              }
551              _ => {}
552            }
553          }
554        }
555        _ => {}
556      }
557    }
558    swc_ecma_visit::visit_object_lit(self, object_lit);
559  }
560
561  fn visit_var_declarator(&mut self, declarator: &VarDeclarator) {
562    if let Some(ident) = &declarator.name.as_ident() {
563      if let Some(init) = &declarator.init {
564        let function_name = ident.sym.to_string();
565        let mut throw_finder = ThrowFinder {
566          throw_spans: vec![],
567        };
568
569        // Check if the init is a function expression or arrow function
570        if let Expr::Fn(fn_expr) = &**init {
571          self.function_name_stack.push(function_name.clone());
572          throw_finder.visit_function(&fn_expr.function);
573          self.function_name_stack.pop();
574        } else if let Expr::Arrow(arrow_expr) = &**init {
575          self.function_name_stack.push(function_name.clone());
576          throw_finder.visit_arrow_expr(arrow_expr);
577          self.function_name_stack.pop();
578        }
579
580        if !throw_finder.throw_spans.is_empty() {
581          let throw_map = ThrowMap {
582            throw_spans: throw_finder.throw_spans,
583            throw_statement: declarator.span,
584            function_or_method_name: function_name.clone(),
585            class_name: self.current_class_name.clone(),
586            id: format!(
587              "{}-{}",
588              self
589                .current_class_name
590                .clone()
591                .unwrap_or_else(|| "NOT_SET".to_string()),
592              function_name
593            ),
594          };
595          self.functions_with_throws.insert(throw_map);
596        }
597      }
598    }
599    swc_ecma_visit::visit_var_declarator(self, declarator);
600  }
601  fn visit_assign_expr(&mut self, assign_expr: &AssignExpr) {
602    if let PatOrExpr::Expr(expr) = &assign_expr.left {
603      if let Expr::Ident(ident) = &**expr {
604        if matches!(&*assign_expr.right, Expr::Fn(_) | Expr::Arrow(_)) {
605          let function_name = ident.sym.to_string();
606          self.function_name_stack.push(function_name);
607        }
608      }
609    }
610
611    swc_ecma_visit::visit_assign_expr(self, assign_expr);
612
613    if let PatOrExpr::Expr(expr) = &assign_expr.left {
614      if let Expr::Ident(_) = &**expr {
615        if matches!(&*assign_expr.right, Expr::Fn(_) | Expr::Arrow(_)) {
616          self.function_name_stack.pop();
617        }
618      }
619    }
620  }
621
622  fn visit_import_decl(&mut self, import: &ImportDecl) {
623    self.register_import(import);
624    self.import_sources.insert(import.src.value.to_string());
625    swc_ecma_visit::visit_import_decl(self, import);
626  }
627
628  fn visit_function(&mut self, function: &Function) {
629    if let Some(block_stmt) = &function.body {
630      for stmt in &block_stmt.stmts {
631        self.visit_stmt(stmt);
632      }
633    }
634    self.check_function_for_throws(function);
635    swc_ecma_visit::visit_function(self, function);
636  }
637
638  fn visit_arrow_expr(&mut self, arrow_expr: &swc_ecma_ast::ArrowExpr) {
639    match &*arrow_expr.body {
640      BlockStmtOrExpr::BlockStmt(block_stmt) => {
641        for stmt in &block_stmt.stmts {
642          self.visit_stmt(stmt);
643        }
644      }
645      BlockStmtOrExpr::Expr(expr) => {
646        if let Expr::Call(call_expr) = &**expr {
647          self.visit_call_expr(call_expr);
648        } else {
649          // use default implementation for other kinds of expressions (for now)
650          self.visit_expr(expr);
651        }
652      }
653    }
654    swc_ecma_visit::visit_arrow_expr(self, arrow_expr);
655  }
656
657  fn visit_stmt(&mut self, stmt: &Stmt) {
658    match stmt {
659      Stmt::Expr(expr_stmt) => {
660        self.visit_expr(&expr_stmt.expr);
661      }
662      Stmt::Block(block_stmt) => {
663        for stmt in &block_stmt.stmts {
664          self.visit_stmt(stmt);
665        }
666      }
667      Stmt::If(if_stmt) => {
668        self.visit_expr(&if_stmt.test);
669        self.visit_stmt(&*if_stmt.cons);
670        if let Some(alt) = &if_stmt.alt {
671          self.visit_stmt(alt);
672        }
673      }
674      _ => {
675        // For other kinds of statements, we continue with the default implementation (for now)
676        swc_ecma_visit::visit_stmt(self, stmt);
677      }
678    }
679  }
680
681  fn visit_expr(&mut self, expr: &Expr) {
682    if let Expr::Call(call_expr) = &*expr {
683      self.visit_call_expr(call_expr)
684    }
685    swc_ecma_visit::visit_expr(self, expr);
686  }
687
688  fn visit_constructor(&mut self, constructor: &Constructor) {
689    self.current_method_name = Some("<constructor>".to_string());
690    self.check_constructor_for_throws(&constructor);
691    if let Some(constructor) = &constructor.body {
692      for stmt in &constructor.stmts {
693        self.visit_stmt(stmt);
694      }
695    }
696    swc_ecma_visit::visit_constructor(self, constructor);
697    self.current_method_name = None;
698  }
699
700  fn visit_class_method(&mut self, class_method: &ClassMethod) {
701    if let Some(method_name) = &class_method.key.as_ident() {
702      let method_name = method_name.sym.to_string();
703
704      self.function_name_stack.push(method_name.clone());
705
706      let mut throw_finder = ThrowFinder {
707        throw_spans: vec![],
708      };
709      throw_finder.visit_class_method(class_method);
710
711      if !throw_finder.throw_spans.is_empty() {
712        let throw_map = ThrowMap {
713          throw_spans: throw_finder.throw_spans,
714          throw_statement: class_method.span,
715          function_or_method_name: method_name.clone(),
716          class_name: self.current_class_name.clone(),
717          id: format!(
718            "{}-{}",
719            self
720              .current_class_name
721              .clone()
722              .unwrap_or_else(|| "NOT_SET".to_string()),
723            method_name
724          ),
725        };
726        self.functions_with_throws.insert(throw_map);
727      }
728
729      self.function_name_stack.pop();
730    }
731
732    self.function_name_stack.pop();
733
734    swc_ecma_visit::visit_class_method(self, class_method);
735  }
736
737  fn visit_class_decl(&mut self, class_decl: &ClassDecl) {
738    self.current_class_name = Some(class_decl.ident.sym.to_string());
739    self.visit_class(&class_decl.class);
740    self.current_class_name = None;
741  }
742
743  fn visit_export_decl(&mut self, export_decl: &ExportDecl) {
744    if let Decl::Class(class_decl) = &export_decl.decl {
745      self.current_class_name = Some(class_decl.ident.sym.to_string());
746      self.visit_class(&class_decl.class);
747      self.current_class_name = None;
748    } else {
749      swc_ecma_visit::visit_export_decl(self, export_decl);
750    }
751  }
752}
753
754pub struct CallToThrowMap {
755  pub call_span: Span,
756  pub call_function_or_method_name: String,
757  pub call_class_name: Option<String>,
758  pub throw_map: ThrowMap,
759  pub class_name: Option<String>,
760  pub id: String,
761}
762
763impl PartialEq for CallToThrowMap {
764  fn eq(&self, other: &Self) -> bool {
765    self.id == other.id
766  }
767}
768
769impl Eq for CallToThrowMap {}
770
771impl Hash for CallToThrowMap {
772  fn hash<H: Hasher>(&self, state: &mut H) {
773    self.id.hash(state);
774    self.call_span.lo.hash(state);
775    self.call_span.hi.hash(state);
776  }
777}
778
779struct InstantiationsMap {
780  pub class_name: String,
781  pub variable_name: String,
782}
783
784impl PartialEq for InstantiationsMap {
785  fn eq(&self, other: &Self) -> bool {
786    self.variable_name == other.variable_name
787  }
788}
789
790impl Eq for InstantiationsMap {}
791
792impl Hash for InstantiationsMap {
793  fn hash<H: Hasher>(&self, state: &mut H) {
794    self.variable_name.hash(state);
795  }
796}
797
798struct CallFinder {
799  calls: HashSet<CallToThrowMap>,
800  functions_with_throws: HashSet<ThrowMap>,
801  current_class_name: Option<String>,
802  instantiations: HashSet<InstantiationsMap>,
803  function_name_stack: Vec<String>,
804  object_property_stack: Vec<String>,
805}
806
807// ----- CallFinder Visitor implementation -----
808// This module defines structures and implements functionality for identifying and mapping
809// function calls to their respective functions or methods that throw exceptions. It uses
810// SWC's visitor pattern to traverse the AST (Abstract Syntax Tree) of JavaScript or TypeScript code.
811///
812// `CallToThrowMap` records the mapping of a function call to a function that throws.
813// It captures the span of the call, the name of the function/method being called,
814// the class name if the call is a method call, and the `ThrowMap` that provides details
815// about the throw statement in the called function/method.
816///
817// `InstantiationsMap` keeps track of class instantiations by recording the class name
818// and the variable name that holds the instance.
819///
820// `CallFinder` is the core structure that uses the Visitor pattern to traverse the AST nodes.
821// It maintains state as it goes through the code, keeping track of current class names,
822// function name stacks, and object property stacks. As it finds function calls, it tries
823// to match them with known functions or methods that throw exceptions using the data
824// accumulated in `functions_with_throws`. When a match is found, it records the mapping
825// in `calls`. It also tracks instantiations of classes to help resolve method calls.
826
827impl Visit for CallFinder {
828  fn visit_class_decl(&mut self, class_decl: &ClassDecl) {
829    self.current_class_name = Some(class_decl.ident.sym.to_string());
830    self.visit_class(&class_decl.class);
831    self.current_class_name = None;
832  }
833
834  fn visit_fn_decl(&mut self, fn_decl: &FnDecl) {
835    let function_name = fn_decl.ident.sym.to_string();
836    self.function_name_stack.push(function_name);
837
838    swc_ecma_visit::visit_fn_decl(self, fn_decl);
839
840    self.function_name_stack.pop();
841  }
842
843  fn visit_member_expr(&mut self, member_expr: &MemberExpr) {
844    if let MemberProp::Ident(ident) = &member_expr.prop {
845      self.object_property_stack.push(ident.sym.to_string());
846    }
847    swc_ecma_visit::visit_member_expr(self, member_expr);
848    self.object_property_stack.pop();
849  }
850
851  fn visit_class_method(&mut self, method: &ClassMethod) {
852    if let Some(method_ident) = method.key.as_ident() {
853      self
854        .object_property_stack
855        .push(method_ident.sym.to_string());
856    }
857
858    swc_ecma_visit::visit_class_method(self, method);
859
860    self.object_property_stack.pop();
861  }
862
863  fn visit_jsx_opening_element(&mut self, jsx_opening_element: &JSXOpeningElement) {
864    for attr in &jsx_opening_element.attrs {
865      if let JSXAttrOrSpread::JSXAttr(attr) = attr {
866        self.visit_jsx_attr(attr);
867      }
868    }
869  }
870
871  fn visit_jsx_attr(&mut self, jsx_attr: &JSXAttr) {
872    if let Some(JSXAttrValue::JSXExprContainer(expr_container)) = &jsx_attr.value {
873      if let JSXExpr::Expr(expr) = &expr_container.expr {
874        // Check if the expression is a function call
875        if let Expr::Call(call_expr) = &**expr {
876          self.visit_call_expr(call_expr)
877        }
878      }
879    }
880  }
881
882  fn visit_call_expr(&mut self, call: &CallExpr) {
883    if let swc_ecma_ast::Callee::Expr(expr) = &call.callee {
884      match &**expr {
885        Expr::Member(member_expr) => {
886          let mut possible_class_name = None;
887          if let Expr::Ident(object_ident) = &*member_expr.obj {
888            possible_class_name = Some(object_ident.sym.to_string());
889          } else if let Expr::This(_) = &*member_expr.obj {
890            possible_class_name = self.current_class_name.clone();
891          }
892          if let Some(ref obj_name) = possible_class_name {
893            let mut new_class_name = None;
894            for instantiation in self.instantiations.iter() {
895              if &instantiation.variable_name == obj_name {
896                new_class_name = Some(instantiation.class_name.clone());
897              }
898            }
899            if let Some(class_name) = new_class_name {
900              possible_class_name = Some(class_name);
901            }
902          }
903
904          if let MemberProp::Ident(method_ident) = &member_expr.prop {
905            let called_method_name = method_ident.sym.to_string();
906            for throw_map in self.functions_with_throws.iter() {
907              let call_function_or_method_name =
908                if let Some(function_name) = self.function_name_stack.last() {
909                  function_name.clone()
910                } else if let Some(property_name) = self.object_property_stack.last() {
911                  property_name.clone()
912                } else {
913                  "<anonymous>".to_string()
914                };
915              if throw_map.function_or_method_name == called_method_name {
916                let class_name_or_not_set = self
917                  .current_class_name
918                  .clone()
919                  .or(possible_class_name.clone())
920                  .unwrap_or_else(|| "NOT_SET".to_string());
921                let call_to_throw_map = CallToThrowMap {
922                  call_span: call.span,
923                  throw_map: throw_map.clone(),
924                  call_class_name: Some(class_name_or_not_set.clone()),
925                  call_function_or_method_name: call_function_or_method_name.clone(),
926                  class_name: possible_class_name.clone(),
927                  id: format!(
928                    "{}-{}",
929                    class_name_or_not_set,
930                    call_function_or_method_name.clone()
931                  ),
932                };
933                self.calls.insert(call_to_throw_map);
934                break;
935              }
936            }
937            for arg in &call.args {
938              self.function_name_stack.push(method_ident.sym.to_string());
939              self.current_class_name = possible_class_name.clone();
940              if let Expr::Arrow(arrow_expr) = &*arg.expr {
941                self.visit_arrow_expr(arrow_expr);
942              }
943              if let Expr::Fn(fn_expr) = &*arg.expr {
944                self.visit_function(&fn_expr.function);
945              }
946              self.function_name_stack.pop();
947              self.current_class_name = None;
948            }
949          }
950        }
951        Expr::Ident(ident) => {
952          let called_function_name = ident.sym.to_string();
953          for throw_map in self.functions_with_throws.iter() {
954            let potential_throw_id = format!(
955              "{}-{}",
956              self
957                .current_class_name
958                .clone()
959                .unwrap_or_else(|| "NOT_SET".to_string()),
960              called_function_name
961            );
962            if throw_map.id == potential_throw_id {
963              let call_function_or_method_name = self
964                .function_name_stack
965                .last()
966                .cloned()
967                .unwrap_or_else(|| "<anonymous>".to_string());
968              // The function being called is known to throw
969              let call_to_throw_map = CallToThrowMap {
970                call_span: call.span,
971                throw_map: throw_map.clone(),
972                call_class_name: self.current_class_name.clone(),
973                call_function_or_method_name: call_function_or_method_name.clone(),
974                class_name: None,
975                id: format!(
976                  "{}-{}",
977                  self
978                    .current_class_name
979                    .clone()
980                    .unwrap_or_else(|| "NOT_SET".to_string()),
981                  call_function_or_method_name
982                ),
983              };
984              self.calls.insert(call_to_throw_map);
985              break;
986            }
987          }
988          for arg in &call.args {
989            self.function_name_stack.push(called_function_name.clone());
990            if let Expr::Arrow(arrow_expr) = &*arg.expr {
991              self.visit_arrow_expr(arrow_expr);
992            }
993            if let Expr::Fn(fn_expr) = &*arg.expr {
994              self.visit_function(&fn_expr.function);
995            }
996            self.function_name_stack.pop();
997          }
998        }
999        _ => {}
1000      }
1001    }
1002  }
1003
1004  fn visit_var_declarator(&mut self, var_declarator: &VarDeclarator) {
1005    if let Some(init_expr) = &var_declarator.init {
1006      match &**init_expr {
1007        Expr::New(new_expr) => {
1008          if let Expr::Ident(expr) = &*new_expr.callee {
1009            let class_name = expr.sym.to_string();
1010            if let Some(var_ident) = &var_declarator.name.as_ident() {
1011              let var_name = var_ident.sym.to_string();
1012              self.instantiations.insert(InstantiationsMap {
1013                class_name: class_name,
1014                variable_name: var_name,
1015              });
1016            }
1017          }
1018        }
1019        _ => {}
1020      }
1021    }
1022    if let Some(ident) = &var_declarator.name.as_ident() {
1023      if let Some(init) = &var_declarator.init {
1024        if let Expr::Arrow(arrow_expr) = &**init {
1025					self.function_name_stack.push(ident.sym.to_string());
1026					self.visit_arrow_expr(arrow_expr);
1027					self.function_name_stack.pop();
1028				}
1029				if let Expr::Fn(fn_expr) = &**init {
1030					self.function_name_stack.push(ident.sym.to_string());
1031					self.visit_function(&fn_expr.function);
1032					self.function_name_stack.pop();
1033				}
1034      }
1035    }
1036
1037    swc_ecma_visit::visit_var_declarator(self, var_declarator);
1038  }
1039
1040  fn visit_arrow_expr(&mut self, arrow_expr: &ArrowExpr) {
1041    match &*arrow_expr.body {
1042      BlockStmtOrExpr::BlockStmt(block_stmt) => {
1043        for stmt in &block_stmt.stmts {
1044          self.visit_stmt(stmt);
1045        }
1046      }
1047      BlockStmtOrExpr::Expr(expr) => {
1048        if let Expr::Call(call_expr) = &**expr {
1049          self.visit_call_expr(call_expr);
1050        } else {
1051          // use default implementation for other kinds of expressions (for now)
1052          self.visit_expr(expr);
1053        }
1054      }
1055    }
1056  }
1057
1058  fn visit_function(&mut self, function: &Function) {
1059    if let Some(block_stmt) = &function.body {
1060      for stmt in &block_stmt.stmts {
1061        self.visit_stmt(stmt);
1062      }
1063    }
1064  }
1065
1066  fn visit_stmt(&mut self, stmt: &Stmt) {
1067    match stmt {
1068      Stmt::Expr(expr_stmt) => {
1069        self.visit_expr(&expr_stmt.expr);
1070      }
1071      Stmt::Block(block_stmt) => {
1072        for stmt in &block_stmt.stmts {
1073          self.visit_stmt(stmt);
1074        }
1075      }
1076      Stmt::If(if_stmt) => {
1077        self.visit_expr(&if_stmt.test);
1078        self.visit_stmt(&*if_stmt.cons);
1079        if let Some(alt) = &if_stmt.alt {
1080          self.visit_stmt(alt);
1081        }
1082      }
1083      _ => {
1084        // For other kinds of statements, we continue with the default implementation (for now)
1085        swc_ecma_visit::visit_stmt(self, stmt);
1086      }
1087    }
1088  }
1089
1090  fn visit_expr(&mut self, expr: &Expr) {
1091    if let Expr::Call(call_expr) = &*expr {
1092      self.visit_call_expr(call_expr)
1093    }
1094  }
1095}