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(), }
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
315impl 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(¤t_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 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 for prop in &object_lit.props {
464 match prop {
465 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 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 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 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
807impl 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 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 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 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 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}