1use ctx::BitContext;
2use rustc_hash::FxHashMap;
3use swc_common::SyntaxContext;
4use swc_ecma_ast::*;
5use swc_ecma_utils::{
6 find_pat_ids, ident::IdentLike, ExprCtx, ExprExt, IsEmpty, StmtExt, Type, Value,
7};
8use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
9use swc_timer::timer;
10
11pub use self::ctx::Ctx;
12use self::storage::*;
13use crate::{
14 alias::{collect_infects_from, AliasConfig},
15 marks::Marks,
16 util::{can_end_conditionally, get_object_define_property_name_arg},
17};
18
19mod ctx;
20pub mod storage;
21
22pub fn analyze_with_storage<S, N>(n: &N, marks: Option<Marks>) -> S
27where
28 S: Storage,
29 N: VisitWith<UsageAnalyzer<S>>,
30{
31 analyze_with_custom_storage(Default::default(), n, marks)
32}
33
34pub fn analyze_with_custom_storage<S, N>(data: S, n: &N, marks: Option<Marks>) -> S
35where
36 S: Storage,
37 N: VisitWith<UsageAnalyzer<S>>,
38{
39 let _timer = timer!("analyze");
40
41 let mut v = UsageAnalyzer {
42 data,
43 marks,
44 scope: Default::default(),
45 ctx: Default::default(),
46 expr_ctx: ExprCtx {
47 unresolved_ctxt: SyntaxContext::empty()
48 .apply_mark(marks.map(|m| m.unresolved_mark).unwrap_or_default()),
49 is_unresolved_ref_safe: false,
50 in_strict: false,
51 remaining_depth: 3,
52 },
53 used_recursively: FxHashMap::default(),
54 };
55 n.visit_with(&mut v);
56 let top_scope = v.scope;
57 v.data.top_scope().merge(top_scope.clone(), false);
58
59 v.data.scope(SyntaxContext::empty()).merge(top_scope, false);
60
61 v.data
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum ScopeKind {
66 Fn,
67 Block,
68}
69
70#[derive(Debug, Clone)]
71enum RecursiveUsage {
72 FnOrClass,
73 Var { can_ignore: bool },
74}
75
76#[derive(Debug)]
78pub struct UsageAnalyzer<S>
79where
80 S: Storage,
81{
82 data: S,
83 marks: Option<Marks>,
84 scope: S::ScopeData,
85 ctx: Ctx,
86 expr_ctx: ExprCtx,
87 used_recursively: FxHashMap<Id, RecursiveUsage>,
88}
89
90impl<S> UsageAnalyzer<S>
91where
92 S: Storage,
93{
94 fn with_child<F, Ret>(&mut self, child_ctxt: SyntaxContext, kind: ScopeKind, op: F) -> Ret
95 where
96 F: FnOnce(&mut UsageAnalyzer<S>) -> Ret,
97 {
98 let used_recursively = std::mem::take(&mut self.used_recursively);
99
100 let mut child = UsageAnalyzer {
101 data: S::new(S::need_collect_prop_atom(&self.data)),
102 marks: self.marks,
103 ctx: self.ctx.with(BitContext::IsTopLevel, false),
104 expr_ctx: self.expr_ctx,
105 scope: Default::default(),
106 used_recursively,
107 };
108
109 let ret = op(&mut child);
110 {
111 let child_scope = child.data.scope(child_ctxt);
112
113 child_scope.merge(child.scope.clone(), false);
114 }
115
116 self.scope.merge(child.scope, true);
117 self.data.merge(kind, child.data);
118
119 self.used_recursively = child.used_recursively;
120
121 ret
122 }
123
124 fn visit_pat_id(&mut self, i: &Ident) {
125 let in_left_of_for_loop = self.ctx.bit_ctx.contains(BitContext::InLeftOfForLoop);
126 let in_pat_of_param = self.ctx.bit_ctx.contains(BitContext::InPatOfParam);
127 let in_pat_of_var_decl = self.ctx.bit_ctx.contains(BitContext::InPatOfVarDecl);
128 let in_catch_param = self.ctx.bit_ctx.contains(BitContext::InCatchParam);
129
130 if in_pat_of_var_decl || in_pat_of_param || in_catch_param {
131 let v = self.declare_decl(
132 i,
133 self.ctx.in_pat_of_var_decl_with_init,
134 self.ctx.var_decl_kind_of_pat,
135 false,
136 );
137
138 if in_pat_of_param {
139 v.mark_declared_as_fn_param();
140 }
141
142 if in_pat_of_var_decl && in_left_of_for_loop {
143 v.mark_declared_as_for_init();
144 }
145 } else {
146 self.report_usage(i);
147 }
148 }
149
150 fn report_usage(&mut self, i: &Ident) {
151 if i.sym == "arguments" {
152 self.scope.mark_used_arguments();
153 }
154
155 let i = i.to_id();
156
157 if let Some(recr) = self.used_recursively.get(&i) {
158 if let RecursiveUsage::Var { can_ignore: false } = recr {
159 self.data.report_usage(self.ctx, i.clone());
160 self.data.var_or_default(i.clone()).mark_used_above_decl()
161 }
162 self.data.var_or_default(i.clone()).mark_used_recursively();
163 return;
164 }
165
166 self.data.report_usage(self.ctx, i)
167 }
168
169 fn report_assign_pat(&mut self, p: &Pat, is_read_modify: bool) {
170 for id in find_pat_ids(p) {
171 self.data
173 .report_assign(self.ctx, id, is_read_modify, Value::Unknown)
174 }
175
176 if let Pat::Expr(e) = p {
177 self.mark_mutation_if_member(e.as_member());
178 }
179 }
180
181 fn report_assign_expr_if_ident(&mut self, e: Option<&Ident>, is_op: bool, ty: Value<Type>) {
182 if let Some(i) = e {
183 self.data.report_assign(self.ctx, i.to_id(), is_op, ty)
184 }
185 }
186
187 fn declare_decl(
188 &mut self,
189 i: &Ident,
190 init_type: Option<Value<Type>>,
191 kind: Option<VarDeclKind>,
192 is_fn_decl: bool,
193 ) -> &mut S::VarData {
194 self.scope.add_declared_symbol(i);
195
196 let v = self.data.declare_decl(self.ctx, i, init_type, kind);
197
198 if is_fn_decl {
199 v.mark_declared_as_fn_decl();
200 }
201
202 v
203 }
204
205 fn visit_in_cond<T: VisitWith<Self>>(&mut self, t: &T) {
206 let cnt = self.data.get_initialized_cnt();
207 t.visit_with(self);
208 self.data.truncate_initialized_cnt(cnt)
209 }
210
211 fn visit_children_in_cond<T: VisitWith<Self>>(&mut self, t: &T) {
212 let cnt = self.data.get_initialized_cnt();
213 t.visit_children_with(self);
214 self.data.truncate_initialized_cnt(cnt)
215 }
216
217 fn mark_mutation_if_member(&mut self, e: Option<&MemberExpr>) {
218 if let Some(m) = e {
219 for_each_id_ref_in_expr(&m.obj, &mut |id| {
220 self.data.mark_property_mutation(id.to_id())
221 });
222 }
223 }
224}
225
226impl<S> Visit for UsageAnalyzer<S>
227where
228 S: Storage,
229{
230 noop_visit_type!();
231
232 fn visit_array_lit(&mut self, n: &ArrayLit) {
233 let ctx = self.ctx.with(BitContext::IsIdRef, true);
234 n.visit_children_with(&mut *self.with_ctx(ctx));
235 }
236
237 #[cfg_attr(
238 feature = "tracing-spans",
239 tracing::instrument(level = "debug", skip_all)
240 )]
241 fn visit_arrow_expr(&mut self, n: &ArrowExpr) {
242 self.with_child(n.ctxt, ScopeKind::Fn, |child| {
243 {
244 let ctx = child
245 .ctx
246 .with(BitContext::InPatOfParam, true)
247 .with(BitContext::InlinePrevented, true);
248 n.params.visit_with(&mut *child.with_ctx(ctx));
249 }
250
251 match &*n.body {
252 BlockStmtOrExpr::BlockStmt(body) => {
253 body.visit_with(child);
254 }
255 BlockStmtOrExpr::Expr(body) => {
256 body.visit_with(child);
257 }
258 }
259 })
260 }
261
262 #[cfg_attr(
263 feature = "tracing-spans",
264 tracing::instrument(level = "debug", skip_all)
265 )]
266 fn visit_assign_expr(&mut self, n: &AssignExpr) {
267 let is_op_assign = n.op != op!("=");
268 n.left.visit_with(self);
269
270 let ctx = self.ctx.with(
276 BitContext::IsIdRef,
277 matches!(n.op, op!("=") | op!("||=") | op!("&&=") | op!("??=")),
278 );
279 n.right.visit_with(&mut *self.with_ctx(ctx));
280
281 match &n.left {
282 AssignTarget::Pat(p) => {
283 for id in find_pat_ids(p) {
284 self.data.report_assign(
285 self.ctx,
286 id,
287 is_op_assign,
288 n.right.get_type(self.expr_ctx),
289 )
290 }
291 }
292 AssignTarget::Simple(e) => {
293 self.report_assign_expr_if_ident(
294 e.as_ident().map(Ident::from).as_ref(),
295 is_op_assign,
296 n.right.get_type(self.expr_ctx),
297 );
298 self.mark_mutation_if_member(e.as_member())
299 }
300 };
301
302 if n.op == op!("=") {
303 let left = match &n.left {
304 AssignTarget::Simple(left) => left.leftmost().map(Ident::to_id),
305 AssignTarget::Pat(..) => None,
306 };
307
308 if let Some(left) = left {
309 let mut v = None;
310 for id in collect_infects_from(
311 &n.right,
312 AliasConfig {
313 marks: self.marks,
314 ignore_named_child_scope: true,
315 ..Default::default()
316 },
317 ) {
318 if v.is_none() {
319 v = Some(self.data.var_or_default(left.to_id()));
320 }
321
322 v.as_mut().unwrap().add_infects_to(id.clone());
323 }
324 }
325 }
326 }
327
328 fn visit_assign_pat(&mut self, p: &AssignPat) {
329 p.left.visit_with(self);
330
331 {
332 let ctx = Ctx {
333 bit_ctx: self.ctx.bit_ctx.with(BitContext::InPatOfParam, false),
334 var_decl_kind_of_pat: None,
335 ..self.ctx
336 };
337 p.right.visit_with(&mut *self.with_ctx(ctx))
338 }
339 }
340
341 #[cfg_attr(
342 feature = "tracing-spans",
343 tracing::instrument(level = "debug", skip_all)
344 )]
345 fn visit_await_expr(&mut self, n: &AwaitExpr) {
346 let ctx = self.ctx.with(BitContext::InAwaitArg, true);
347 n.visit_children_with(&mut *self.with_ctx(ctx));
348 }
349
350 fn visit_bin_expr(&mut self, e: &BinExpr) {
351 if e.op.may_short_circuit() {
352 let ctx = self.ctx.with(BitContext::IsIdRef, true);
353 e.left.visit_with(&mut *self.with_ctx(ctx));
354 let ctx = self
355 .ctx
356 .with(BitContext::InCond, true)
357 .with(BitContext::IsIdRef, true);
358 self.with_ctx(ctx).visit_in_cond(&e.right);
359 } else {
360 if e.op == op!("in") {
361 for_each_id_ref_in_expr(&e.right, &mut |obj| {
362 let var = self.data.var_or_default(obj.to_id());
363 var.mark_used_as_ref();
364
365 match &*e.left {
366 Expr::Lit(Lit::Str(prop)) if prop.value.parse::<f64>().is_err() => {
367 var.add_accessed_property(prop.value.clone());
368 }
369
370 Expr::Lit(Lit::Str(_) | Lit::Num(_)) => {}
371 _ => {
372 var.mark_indexed_with_dynamic_key();
373 }
374 }
375 })
376 }
377
378 let ctx = self.ctx.with(BitContext::IsIdRef, false);
379 e.visit_children_with(&mut *self.with_ctx(ctx));
380 }
381 }
382
383 #[cfg_attr(
384 feature = "tracing-spans",
385 tracing::instrument(level = "debug", skip_all)
386 )]
387 fn visit_binding_ident(&mut self, n: &BindingIdent) {
388 self.visit_pat_id(&Ident::from(n));
389 }
390
391 #[cfg_attr(
392 feature = "tracing-spans",
393 tracing::instrument(level = "debug", skip_all)
394 )]
395 fn visit_block_stmt(&mut self, n: &BlockStmt) {
396 self.with_child(n.ctxt, ScopeKind::Block, |child| {
397 n.visit_children_with(child);
398 });
399 }
400
401 #[cfg_attr(
402 feature = "tracing-spans",
403 tracing::instrument(level = "debug", skip_all)
404 )]
405 fn visit_call_expr(&mut self, n: &CallExpr) {
406 if let Some(prop_name) = get_object_define_property_name_arg(n) {
407 self.data.add_property_atom(prop_name.value.clone());
408 }
409
410 let inline_prevented = self.ctx.bit_ctx.contains(BitContext::InlinePrevented)
411 || self
412 .marks
413 .map(|marks| n.ctxt.has_mark(marks.noinline))
414 .unwrap_or_default();
415
416 {
417 let ctx = self.ctx.with(BitContext::InlinePrevented, inline_prevented);
418 n.callee.visit_with(&mut *self.with_ctx(ctx));
419 }
420
421 if let Callee::Expr(callee) = &n.callee {
422 for_each_id_ref_in_expr(callee, &mut |i| {
423 self.data.var_or_default(i.to_id()).mark_used_as_callee();
424 });
425
426 match &**callee {
427 Expr::Fn(callee) => {
428 for (idx, p) in callee.function.params.iter().enumerate() {
429 if let Some(arg) = n.args.get(idx) {
430 if arg.spread.is_some() {
431 break;
432 }
433
434 if is_safe_to_access_prop(&arg.expr) {
435 if let Pat::Ident(id) = &p.pat {
436 self.data
437 .var_or_default(id.to_id())
438 .mark_initialized_with_safe_value();
439 }
440 }
441 }
442 }
443 }
444
445 Expr::Arrow(callee) => {
446 for (idx, p) in callee.params.iter().enumerate() {
447 if let Some(arg) = n.args.get(idx) {
448 if arg.spread.is_some() {
449 break;
450 }
451
452 if is_safe_to_access_prop(&arg.expr) {
453 if let Pat::Ident(id) = &p {
454 self.data
455 .var_or_default(id.to_id())
456 .mark_initialized_with_safe_value();
457 }
458 }
459 }
460 }
461 }
462
463 _ => {}
464 }
465 }
466
467 {
468 let ctx = self
469 .ctx
470 .with(BitContext::InlinePrevented, inline_prevented)
471 .with(BitContext::IsIdRef, true);
472 n.args.visit_with(&mut *self.with_ctx(ctx));
473
474 let call_may_mutate = match &n.callee {
475 Callee::Expr(e) => call_may_mutate(e, self.expr_ctx),
476 _ => true,
477 };
478
479 if call_may_mutate {
480 for a in &n.args {
481 for_each_id_ref_in_expr(&a.expr, &mut |id| {
482 self.data.mark_property_mutation(id.to_id());
483 });
484 }
485 }
486 }
487
488 for arg in &n.args {
489 for_each_id_ref_in_expr(&arg.expr, &mut |arg| {
490 self.data.var_or_default(arg.to_id()).mark_used_as_arg();
491 })
492 }
493
494 if let Callee::Expr(callee) = &n.callee {
495 match &**callee {
496 Expr::Ident(Ident { sym, .. }) if *sym == *"eval" => {
497 self.scope.mark_eval_called();
498 }
499 Expr::Member(m) if !m.obj.is_ident() => {
500 for_each_id_ref_in_expr(&m.obj, &mut |id| {
501 self.data.var_or_default(id.to_id()).mark_used_as_ref()
502 })
503 }
504 _ => {}
505 }
506 }
507 }
508
509 #[cfg_attr(
510 feature = "tracing-spans",
511 tracing::instrument(level = "debug", skip_all)
512 )]
513 fn visit_catch_clause(&mut self, n: &CatchClause) {
514 {
515 let ctx = self
516 .ctx
517 .with(BitContext::InCond, true)
518 .with(BitContext::InCatchParam, true);
519 n.param.visit_with(&mut *self.with_ctx(ctx));
520 }
521
522 {
523 let ctx = self.ctx.with(BitContext::InCond, true);
524 self.with_ctx(ctx).visit_in_cond(&n.body);
525 }
526 }
527
528 #[cfg_attr(
529 feature = "tracing-spans",
530 tracing::instrument(level = "debug", skip_all)
531 )]
532 fn visit_class(&mut self, n: &Class) {
533 n.decorators.visit_with(self);
534
535 {
536 let ctx = self.ctx.with(BitContext::InlinePrevented, true);
537 n.super_class.visit_with(&mut *self.with_ctx(ctx));
538 }
539
540 self.with_child(n.ctxt, ScopeKind::Fn, |child| n.body.visit_with(child))
541 }
542
543 #[cfg_attr(
544 feature = "tracing-spans",
545 tracing::instrument(level = "debug", skip_all)
546 )]
547 fn visit_class_decl(&mut self, n: &ClassDecl) {
548 self.declare_decl(&n.ident, Some(Value::Unknown), None, false);
549
550 n.visit_children_with(self);
551 }
552
553 #[cfg_attr(
554 feature = "tracing-spans",
555 tracing::instrument(level = "debug", skip_all)
556 )]
557 fn visit_class_expr(&mut self, n: &ClassExpr) {
558 n.visit_children_with(self);
559
560 if let Some(id) = &n.ident {
561 self.declare_decl(id, Some(Value::Unknown), None, false);
562 }
563 }
564
565 #[cfg_attr(
566 feature = "tracing-spans",
567 tracing::instrument(level = "debug", skip_all)
568 )]
569 fn visit_class_method(&mut self, n: &ClassMethod) {
570 n.function.decorators.visit_with(self);
571
572 self.with_child(n.function.ctxt, ScopeKind::Fn, |a| {
573 n.key.visit_with(a);
574 {
575 let ctx = a.ctx.with(BitContext::InPatOfParam, true);
576 n.function.params.visit_with(&mut *a.with_ctx(ctx));
577 }
578
579 n.function.visit_with(a);
580 });
581 }
582
583 #[cfg_attr(
584 feature = "tracing-spans",
585 tracing::instrument(level = "debug", skip_all)
586 )]
587 fn visit_class_prop(&mut self, n: &ClassProp) {
588 let ctx = self.ctx.with(BitContext::IsIdRef, true);
589
590 n.visit_children_with(&mut *self.with_ctx(ctx));
591 }
592
593 #[cfg_attr(
594 feature = "tracing-spans",
595 tracing::instrument(level = "debug", skip_all)
596 )]
597 fn visit_computed_prop_name(&mut self, n: &ComputedPropName) {
598 let ctx = self.ctx.with(BitContext::IsIdRef, true);
599
600 n.visit_children_with(&mut *self.with_ctx(ctx));
601 }
602
603 #[cfg_attr(
604 feature = "tracing-spans",
605 tracing::instrument(level = "debug", skip_all)
606 )]
607 fn visit_cond_expr(&mut self, n: &CondExpr) {
608 {
609 let ctx = self.ctx.with(BitContext::IsIdRef, false);
610
611 n.test.visit_with(&mut *self.with_ctx(ctx));
612 }
613
614 {
615 let ctx = self
616 .ctx
617 .with(BitContext::InCond, true)
618 .with(BitContext::IsIdRef, true);
619 self.with_ctx(ctx).visit_in_cond(&n.cons);
620 self.with_ctx(ctx).visit_in_cond(&n.alt);
621 }
622 }
623
624 #[cfg_attr(
625 feature = "tracing-spans",
626 tracing::instrument(level = "debug", skip_all)
627 )]
628 fn visit_constructor(&mut self, n: &Constructor) {
629 self.with_child(n.ctxt, ScopeKind::Fn, |child| {
630 {
631 let ctx = child.ctx.with(BitContext::InPatOfParam, true);
632 n.params.visit_with(&mut *child.with_ctx(ctx));
633 }
634
635 if let Some(body) = &n.body {
637 body.visit_with(child);
638 }
639 })
640 }
641
642 fn visit_default_decl(&mut self, d: &DefaultDecl) {
643 d.visit_children_with(self);
644
645 match d {
646 DefaultDecl::Class(c) => {
647 if let Some(i) = &c.ident {
648 self.data.var_or_default(i.to_id()).prevent_inline();
649 }
650 }
651 DefaultDecl::Fn(f) => {
652 if let Some(i) = &f.ident {
653 self.data.var_or_default(i.to_id()).prevent_inline();
654 }
655 }
656 _ => {}
657 }
658 }
659
660 #[cfg_attr(
661 feature = "tracing-spans",
662 tracing::instrument(level = "debug", skip_all)
663 )]
664 fn visit_do_while_stmt(&mut self, n: &DoWhileStmt) {
665 n.body
666 .visit_with(&mut *self.with_ctx(self.ctx.with(BitContext::ExecutedMultipleTime, true)));
667 n.test
668 .visit_with(&mut *self.with_ctx(self.ctx.with(BitContext::ExecutedMultipleTime, true)));
669 }
670
671 #[cfg_attr(
672 feature = "tracing-spans",
673 tracing::instrument(level = "debug", skip_all)
674 )]
675 fn visit_export_decl(&mut self, n: &ExportDecl) {
676 n.visit_children_with(self);
677
678 match &n.decl {
679 Decl::Class(c) => {
680 self.data.var_or_default(c.ident.to_id()).prevent_inline();
681 }
682 Decl::Fn(f) => {
683 self.data.var_or_default(f.ident.to_id()).prevent_inline();
684 }
685 Decl::Var(v) => {
686 let ids = find_pat_ids(v);
687
688 for id in ids {
689 self.data.var_or_default(id).mark_as_exported();
690 }
691 }
692 _ => {}
693 }
694 }
695
696 #[cfg_attr(
697 feature = "tracing-spans",
698 tracing::instrument(level = "debug", skip_all)
699 )]
700 fn visit_export_default_expr(&mut self, n: &ExportDefaultExpr) {
701 let ctx = self.ctx.with(BitContext::IsIdRef, true);
702
703 n.visit_children_with(&mut *self.with_ctx(ctx));
704 }
705
706 fn visit_export_named_specifier(&mut self, n: &ExportNamedSpecifier) {
707 match &n.orig {
708 ModuleExportName::Ident(orig) => {
709 self.report_usage(orig);
710 let v = self.data.var_or_default(orig.to_id());
711 v.prevent_inline();
712 v.mark_used_as_ref();
713 }
714 ModuleExportName::Str(..) => {}
715 };
716 }
717
718 #[cfg_attr(
719 feature = "tracing-spans",
720 tracing::instrument(level = "debug", skip(self, e))
721 )]
722 fn visit_expr(&mut self, e: &Expr) {
723 let ctx = Ctx {
724 bit_ctx: self
725 .ctx
726 .bit_ctx
727 .with(BitContext::InPatOfVarDecl, false)
728 .with(BitContext::InPatOfParam, false)
729 .with(BitContext::InCatchParam, false),
730 var_decl_kind_of_pat: None,
731 in_pat_of_var_decl_with_init: None,
732 ..self.ctx
733 };
734
735 e.visit_children_with(&mut *self.with_ctx(ctx));
736
737 if let Expr::Ident(i) = e {
738 #[cfg(feature = "tracing-spans")]
739 {
740 }
747
748 self.with_ctx(ctx).report_usage(i);
749 }
750 }
751
752 #[cfg_attr(
753 feature = "tracing-spans",
754 tracing::instrument(level = "debug", skip_all)
755 )]
756 fn visit_expr_or_spread(&mut self, e: &ExprOrSpread) {
757 e.visit_children_with(self);
758
759 if e.spread.is_some() {
760 for_each_id_ref_in_expr(&e.expr, &mut |i| {
761 self.data
762 .var_or_default(i.to_id())
763 .mark_indexed_with_dynamic_key();
764 });
765 }
766 }
767
768 #[cfg_attr(
769 feature = "tracing-spans",
770 tracing::instrument(level = "debug", skip_all)
771 )]
772 fn visit_fn_decl(&mut self, n: &FnDecl) {
773 let ctx = self
774 .ctx
775 .with(BitContext::InDeclWithNoSideEffectForMemberAccess, true);
776 self.with_ctx(ctx)
777 .declare_decl(&n.ident, Some(Value::Known(Type::Obj)), None, true);
778
779 if n.function.body.is_empty() {
780 self.data.var_or_default(n.ident.to_id()).mark_as_pure_fn();
781 }
782
783 let id = n.ident.to_id();
784 self.used_recursively
785 .insert(id.clone(), RecursiveUsage::FnOrClass);
786 n.visit_children_with(self);
787 self.used_recursively.remove(&id);
788
789 {
790 let mut v = None;
791 for id in collect_infects_from(
792 &n.function,
793 AliasConfig {
794 marks: self.marks,
795 ignore_named_child_scope: true,
796 ..Default::default()
797 },
798 ) {
799 if v.is_none() {
800 v = Some(self.data.var_or_default(n.ident.to_id()));
801 }
802
803 v.as_mut().unwrap().add_infects_to(id.clone());
804 }
805 }
806 }
807
808 #[cfg_attr(
809 feature = "tracing-spans",
810 tracing::instrument(level = "debug", skip_all)
811 )]
812 fn visit_fn_expr(&mut self, n: &FnExpr) {
813 if let Some(n_id) = &n.ident {
814 self.data
815 .var_or_default(n_id.to_id())
816 .mark_declared_as_fn_expr();
817
818 self.used_recursively
819 .insert(n_id.to_id(), RecursiveUsage::FnOrClass);
820
821 n.visit_children_with(self);
822
823 {
824 let mut v = None;
825 for id in collect_infects_from(
826 &n.function,
827 AliasConfig {
828 marks: self.marks,
829 ignore_named_child_scope: true,
830 ..Default::default()
831 },
832 ) {
833 if v.is_none() {
834 v = Some(self.data.var_or_default(n_id.to_id()));
835 }
836
837 v.as_mut().unwrap().add_infects_to(id);
838 }
839 }
840 self.used_recursively.remove(&n_id.to_id());
841 } else {
842 n.visit_children_with(self);
843 }
844 }
845
846 #[cfg_attr(
847 feature = "tracing-spans",
848 tracing::instrument(level = "debug", skip_all)
849 )]
850 fn visit_for_in_stmt(&mut self, n: &ForInStmt) {
851 n.right.visit_with(self);
852
853 self.with_child(SyntaxContext::empty(), ScopeKind::Block, |child| {
854 let head_ctx = child
855 .ctx
856 .with(BitContext::InLeftOfForLoop, true)
857 .with(BitContext::IsIdRef, true)
858 .with(BitContext::ExecutedMultipleTime, true)
859 .with(BitContext::InCond, true);
860 n.left.visit_with(&mut *child.with_ctx(head_ctx));
861
862 n.right.visit_with(child);
863
864 if let ForHead::Pat(pat) = &n.left {
865 child.with_ctx(head_ctx).report_assign_pat(pat, true)
866 }
867
868 let ctx = child
869 .ctx
870 .with(BitContext::ExecutedMultipleTime, true)
871 .with(BitContext::InCond, true);
872
873 child.with_ctx(ctx).visit_in_cond(&n.body);
874 });
875 }
876
877 #[cfg_attr(
878 feature = "tracing-spans",
879 tracing::instrument(level = "debug", skip_all)
880 )]
881 fn visit_for_of_stmt(&mut self, n: &ForOfStmt) {
882 n.right.visit_with(self);
883
884 self.with_child(SyntaxContext::empty(), ScopeKind::Block, |child| {
885 let head_ctx = child
886 .ctx
887 .with(BitContext::InLeftOfForLoop, true)
888 .with(BitContext::IsIdRef, true)
889 .with(BitContext::ExecutedMultipleTime, true)
890 .with(BitContext::InCond, true);
891 n.left.visit_with(&mut *child.with_ctx(head_ctx));
892
893 if let ForHead::Pat(pat) = &n.left {
894 child.with_ctx(head_ctx).report_assign_pat(pat, true)
895 }
896
897 let ctx = child
898 .ctx
899 .with(BitContext::ExecutedMultipleTime, true)
900 .with(BitContext::InCond, true);
901 child.with_ctx(ctx).visit_in_cond(&n.body);
902 });
903 }
904
905 #[cfg_attr(
906 feature = "tracing-spans",
907 tracing::instrument(level = "debug", skip_all)
908 )]
909 fn visit_for_stmt(&mut self, n: &ForStmt) {
910 n.init.visit_with(self);
911
912 let ctx = self
913 .ctx
914 .with(BitContext::ExecutedMultipleTime, true)
915 .with(BitContext::InCond, true);
916
917 self.with_ctx(ctx).visit_in_cond(&n.test);
918 self.with_ctx(ctx).visit_in_cond(&n.update);
919 self.with_ctx(ctx).visit_in_cond(&n.body);
920 }
921
922 #[cfg_attr(
923 feature = "tracing-spans",
924 tracing::instrument(level = "debug", skip_all)
925 )]
926 fn visit_function(&mut self, n: &Function) {
927 n.decorators.visit_with(self);
928
929 let ctx = Ctx { ..self.ctx };
930
931 self.with_ctx(ctx)
932 .with_child(n.ctxt, ScopeKind::Fn, |child| {
933 n.params.visit_with(child);
934
935 if let Some(body) = &n.body {
936 body.visit_children_with(child);
939 }
940 })
941 }
942
943 #[cfg_attr(
944 feature = "tracing-spans",
945 tracing::instrument(level = "debug", skip_all)
946 )]
947 fn visit_getter_prop(&mut self, n: &GetterProp) {
948 self.with_child(SyntaxContext::empty(), ScopeKind::Fn, |a| {
949 n.key.visit_with(a);
950
951 n.body.visit_with(a);
952 });
953 }
954
955 #[cfg_attr(
956 feature = "tracing-spans",
957 tracing::instrument(level = "debug", skip_all)
958 )]
959 fn visit_if_stmt(&mut self, n: &IfStmt) {
960 let ctx = self.ctx.with(BitContext::InCond, true);
961 n.test.visit_with(self);
962
963 self.with_ctx(ctx).visit_in_cond(&n.cons);
964 self.with_ctx(ctx).visit_in_cond(&n.alt);
965 }
966
967 fn visit_import_default_specifier(&mut self, n: &ImportDefaultSpecifier) {
968 self.declare_decl(&n.local, Some(Value::Unknown), None, false);
969 }
970
971 fn visit_import_named_specifier(&mut self, n: &ImportNamedSpecifier) {
972 self.declare_decl(&n.local, Some(Value::Unknown), None, false);
973 }
974
975 fn visit_import_star_as_specifier(&mut self, n: &ImportStarAsSpecifier) {
976 self.declare_decl(&n.local, Some(Value::Unknown), None, false);
977 }
978
979 #[cfg_attr(
980 feature = "tracing-spans",
981 tracing::instrument(level = "debug", skip_all)
982 )]
983 fn visit_jsx_element_name(&mut self, n: &JSXElementName) {
984 let ctx = Ctx {
985 bit_ctx: self
986 .ctx
987 .bit_ctx
988 .with(BitContext::InPatOfVarDecl, false)
989 .with(BitContext::InPatOfParam, false)
990 .with(BitContext::InCatchParam, false),
991 var_decl_kind_of_pat: None,
992 in_pat_of_var_decl_with_init: None,
993 ..self.ctx
994 };
995
996 n.visit_children_with(&mut *self.with_ctx(ctx));
997
998 if let JSXElementName::Ident(i) = n {
999 self.with_ctx(ctx).report_usage(i);
1000 self.data
1001 .var_or_default(i.to_id())
1002 .mark_used_as_jsx_callee();
1003 }
1004 }
1005
1006 #[cfg_attr(
1007 feature = "tracing-spans",
1008 tracing::instrument(level = "debug", skip(self, e))
1009 )]
1010 fn visit_member_expr(&mut self, e: &MemberExpr) {
1011 {
1012 let ctx = self.ctx.with(BitContext::IsIdRef, false);
1013 e.obj.visit_with(&mut *self.with_ctx(ctx));
1014 }
1015
1016 if let MemberProp::Computed(c) = &e.prop {
1017 c.visit_with(self);
1018 }
1019
1020 for_each_id_ref_in_expr(&e.obj, &mut |obj| {
1021 let v = self.data.var_or_default(obj.to_id());
1022 v.mark_has_property_access();
1023
1024 if let MemberProp::Computed(prop) = &e.prop {
1025 match &*prop.expr {
1026 Expr::Lit(Lit::Str(s)) if s.value.parse::<f64>().is_err() => {
1027 v.add_accessed_property(s.value.clone());
1028 }
1029
1030 Expr::Lit(Lit::Str(_) | Lit::Num(_)) => {}
1031 _ => {
1032 v.mark_indexed_with_dynamic_key();
1033 }
1034 }
1035 }
1036
1037 if let MemberProp::Ident(prop) = &e.prop {
1038 v.add_accessed_property(prop.sym.clone());
1039 }
1040 });
1041
1042 fn is_root_of_member_expr_declared(member_expr: &MemberExpr, data: &impl Storage) -> bool {
1043 match &*member_expr.obj {
1044 Expr::Member(member_expr) => is_root_of_member_expr_declared(member_expr, data),
1045 Expr::Ident(ident) => data
1046 .get_var_data(ident.to_id())
1047 .map(|var| var.is_declared())
1048 .unwrap_or(false),
1049
1050 _ => false,
1051 }
1052 }
1053
1054 if is_root_of_member_expr_declared(e, &self.data) {
1055 if let MemberProp::Ident(ident) = &e.prop {
1056 self.data.add_property_atom(ident.sym.clone());
1057 }
1058 }
1059 }
1060
1061 #[cfg_attr(
1062 feature = "tracing-spans",
1063 tracing::instrument(level = "debug", skip_all)
1064 )]
1065 fn visit_method_prop(&mut self, n: &MethodProp) {
1066 n.function.decorators.visit_with(self);
1067
1068 self.with_child(n.function.ctxt, ScopeKind::Fn, |a| {
1069 n.key.visit_with(a);
1070 {
1071 let ctx = a.ctx.with(BitContext::InPatOfParam, true);
1072 n.function.params.visit_with(&mut *a.with_ctx(ctx));
1073 }
1074
1075 n.function.visit_with(a);
1076 });
1077 }
1078
1079 fn visit_module(&mut self, n: &Module) {
1080 let ctx = self.ctx.with(BitContext::IsTopLevel, true);
1081 n.visit_children_with(&mut *self.with_ctx(ctx))
1082 }
1083
1084 fn visit_named_export(&mut self, n: &NamedExport) {
1085 if n.src.is_some() {
1086 return;
1087 }
1088 n.visit_children_with(self);
1089 }
1090
1091 #[cfg_attr(
1092 feature = "tracing-spans",
1093 tracing::instrument(level = "debug", skip_all)
1094 )]
1095 fn visit_new_expr(&mut self, n: &NewExpr) {
1096 {
1097 n.callee.visit_with(self);
1098 let ctx = self.ctx.with(BitContext::IsIdRef, true);
1099 n.args.visit_with(&mut *self.with_ctx(ctx));
1100
1101 if call_may_mutate(&n.callee, self.expr_ctx) {
1102 if let Some(args) = &n.args {
1103 for a in args {
1104 for_each_id_ref_in_expr(&a.expr, &mut |id| {
1105 self.data.mark_property_mutation(id.to_id());
1106 });
1107 }
1108 }
1109 }
1110 }
1111 }
1112
1113 #[cfg_attr(
1114 feature = "tracing-spans",
1115 tracing::instrument(level = "debug", skip_all)
1116 )]
1117 fn visit_param(&mut self, n: &Param) {
1118 let ctx = self.ctx.with(BitContext::InPatOfParam, false);
1119 n.decorators.visit_with(&mut *self.with_ctx(ctx));
1120
1121 let ctx = Ctx {
1122 bit_ctx: self
1123 .ctx
1124 .bit_ctx
1125 .with(BitContext::InPatOfParam, true)
1126 .with(BitContext::IsIdRef, true),
1127 var_decl_kind_of_pat: None,
1128 ..self.ctx
1129 };
1130 n.pat.visit_with(&mut *self.with_ctx(ctx));
1131 }
1132
1133 #[cfg_attr(
1134 feature = "tracing-spans",
1135 tracing::instrument(level = "debug", skip_all)
1136 )]
1137 fn visit_pat(&mut self, n: &Pat) {
1138 match n {
1139 Pat::Ident(i) => {
1140 i.visit_with(self);
1141 }
1142 _ => {
1143 let ctx = self
1144 .ctx
1145 .with(BitContext::InDeclWithNoSideEffectForMemberAccess, false);
1146 n.visit_children_with(&mut *self.with_ctx(ctx));
1147 }
1148 }
1149 }
1150
1151 #[cfg_attr(
1152 feature = "tracing-spans",
1153 tracing::instrument(level = "debug", skip_all)
1154 )]
1155 fn visit_private_method(&mut self, n: &PrivateMethod) {
1156 n.function.decorators.visit_with(self);
1157
1158 self.with_child(n.function.ctxt, ScopeKind::Fn, |a| {
1159 n.key.visit_with(a);
1160 {
1161 let ctx = a.ctx.with(BitContext::InPatOfParam, true);
1162 n.function.params.visit_with(&mut *a.with_ctx(ctx));
1163 }
1164
1165 n.function.visit_with(a);
1166 });
1167 }
1168
1169 #[cfg_attr(
1170 feature = "tracing-spans",
1171 tracing::instrument(level = "debug", skip_all)
1172 )]
1173 fn visit_private_prop(&mut self, n: &PrivateProp) {
1174 let ctx = self.ctx.with(BitContext::IsIdRef, true);
1175 n.visit_children_with(&mut *self.with_ctx(ctx));
1176 }
1177
1178 #[cfg_attr(
1179 feature = "tracing-spans",
1180 tracing::instrument(level = "debug", skip_all)
1181 )]
1182 fn visit_prop(&mut self, n: &Prop) {
1183 if let Prop::Shorthand(i) = n {
1184 let ctx = self.ctx.with(BitContext::IsIdRef, true);
1185 self.with_ctx(ctx).report_usage(i);
1186 self.data.add_property_atom(i.sym.clone());
1187 } else {
1188 let ctx = self.ctx.with(BitContext::IsIdRef, true);
1189 n.visit_children_with(&mut *self.with_ctx(ctx));
1190 }
1191 }
1192
1193 fn visit_prop_name(&mut self, node: &PropName) {
1194 node.visit_children_with(self);
1195
1196 match node {
1197 PropName::Ident(ident) => {
1198 self.data.add_property_atom(ident.sym.clone());
1199 }
1200 PropName::Str(s) => {
1201 self.data.add_property_atom(s.value.clone());
1202 }
1203 _ => {}
1204 };
1205 }
1206
1207 fn visit_script(&mut self, n: &Script) {
1208 let ctx = self.ctx.with(BitContext::IsTopLevel, true);
1209 n.visit_children_with(&mut *self.with_ctx(ctx))
1210 }
1211
1212 #[cfg_attr(
1213 feature = "tracing-spans",
1214 tracing::instrument(level = "debug", skip_all)
1215 )]
1216 fn visit_setter_prop(&mut self, n: &SetterProp) {
1217 self.with_child(SyntaxContext::empty(), ScopeKind::Fn, |a| {
1218 n.key.visit_with(a);
1219 {
1220 let ctx = a.ctx.with(BitContext::InPatOfParam, true);
1221 n.param.visit_with(&mut *a.with_ctx(ctx));
1222 }
1223
1224 n.body.visit_with(a);
1225 });
1226 }
1227
1228 #[cfg_attr(
1229 feature = "tracing-spans",
1230 tracing::instrument(level = "debug", skip_all)
1231 )]
1232 fn visit_spread_element(&mut self, e: &SpreadElement) {
1233 e.visit_children_with(self);
1234
1235 for_each_id_ref_in_expr(&e.expr, &mut |i| {
1236 self.data
1237 .var_or_default(i.to_id())
1238 .mark_indexed_with_dynamic_key();
1239 });
1240 }
1241
1242 #[cfg_attr(
1243 feature = "tracing-spans",
1244 tracing::instrument(level = "debug", skip_all)
1245 )]
1246 fn visit_stmt(&mut self, n: &Stmt) {
1247 let ctx = self
1248 .ctx
1249 .with(BitContext::InAwaitArg, false)
1250 .with(BitContext::IsIdRef, true);
1251 n.visit_children_with(&mut *self.with_ctx(ctx));
1252 }
1253
1254 fn visit_stmts(&mut self, stmts: &[Stmt]) {
1255 let mut had_cond = false;
1256
1257 for stmt in stmts {
1258 let ctx = self
1259 .ctx
1260 .with(
1261 BitContext::InCond,
1262 self.ctx.bit_ctx.contains(BitContext::InCond) || had_cond,
1263 )
1264 .with(BitContext::IsIdRef, true);
1265
1266 stmt.visit_with(&mut *self.with_ctx(ctx));
1267
1268 had_cond |= can_end_conditionally(stmt);
1269 }
1270 }
1271
1272 #[cfg_attr(
1273 feature = "tracing-spans",
1274 tracing::instrument(level = "debug", skip(self, e))
1275 )]
1276 fn visit_super_prop_expr(&mut self, e: &SuperPropExpr) {
1277 if let SuperProp::Computed(c) = &e.prop {
1278 let ctx = self.ctx.with(BitContext::IsIdRef, false);
1279 c.visit_with(&mut *self.with_ctx(ctx));
1280 }
1281 }
1282
1283 fn visit_switch_case(&mut self, n: &SwitchCase) {
1284 let ctx = self.ctx.with(BitContext::IsIdRef, false);
1285 n.visit_children_with(&mut *self.with_ctx(ctx))
1286 }
1287
1288 #[cfg_attr(
1289 feature = "tracing-spans",
1290 tracing::instrument(level = "debug", skip_all)
1291 )]
1292 fn visit_switch_stmt(&mut self, n: &SwitchStmt) {
1293 n.discriminant.visit_with(self);
1294
1295 let mut fallthrough = false;
1296
1297 for case in n.cases.iter() {
1298 let ctx = self.ctx.with(BitContext::InCond, true);
1299 if fallthrough {
1300 self.with_ctx(ctx).visit_in_cond(&case.test);
1301 self.with_ctx(ctx).visit_in_cond(&case.cons);
1302 } else {
1303 self.with_ctx(ctx).visit_in_cond(case);
1304 }
1305 fallthrough = !case.cons.iter().rev().any(|s| s.terminates())
1306 }
1307 }
1308
1309 #[cfg_attr(
1310 feature = "tracing-spans",
1311 tracing::instrument(level = "debug", skip_all)
1312 )]
1313 fn visit_tagged_tpl(&mut self, n: &TaggedTpl) {
1314 {
1315 let ctx = self.ctx.with(BitContext::IsIdRef, false);
1316 n.tag.visit_with(&mut *self.with_ctx(ctx));
1317 }
1318
1319 {
1320 let ctx = self.ctx.with(BitContext::IsIdRef, true);
1321 n.tpl.visit_children_with(&mut *self.with_ctx(ctx))
1323 }
1324 }
1325
1326 #[cfg_attr(
1327 feature = "tracing-spans",
1328 tracing::instrument(level = "debug", skip_all)
1329 )]
1330 fn visit_tpl(&mut self, n: &Tpl) {
1331 let ctx = self.ctx.with(BitContext::IsIdRef, false);
1332 n.visit_children_with(&mut *self.with_ctx(ctx))
1333 }
1334
1335 #[cfg_attr(
1336 feature = "tracing-spans",
1337 tracing::instrument(level = "debug", skip_all)
1338 )]
1339 fn visit_try_stmt(&mut self, n: &TryStmt) {
1340 let ctx = self.ctx.with(BitContext::InCond, true);
1341 self.with_ctx(ctx).visit_children_in_cond(n);
1342 }
1343
1344 #[cfg_attr(
1345 feature = "tracing-spans",
1346 tracing::instrument(level = "debug", skip_all)
1347 )]
1348 fn visit_unary_expr(&mut self, n: &UnaryExpr) {
1349 if n.op == op!("delete") {
1350 self.mark_mutation_if_member(n.arg.as_member());
1351 }
1352 n.visit_children_with(self);
1353 }
1354
1355 #[cfg_attr(
1356 feature = "tracing-spans",
1357 tracing::instrument(level = "debug", skip_all)
1358 )]
1359 fn visit_update_expr(&mut self, n: &UpdateExpr) {
1360 n.visit_children_with(self);
1361
1362 self.report_assign_expr_if_ident(n.arg.as_ident(), true, Value::Known(Type::Num));
1363 self.mark_mutation_if_member(n.arg.as_member());
1364 }
1365
1366 #[cfg_attr(
1367 feature = "tracing-spans",
1368 tracing::instrument(level = "debug", skip_all)
1369 )]
1370 fn visit_var_decl(&mut self, n: &VarDecl) {
1371 let ctx = Ctx {
1372 var_decl_kind_of_pat: Some(n.kind),
1373 bit_ctx: self.ctx.bit_ctx.with(BitContext::InAwaitArg, false),
1374 ..self.ctx
1375 };
1376 n.visit_children_with(&mut *self.with_ctx(ctx));
1377
1378 for decl in &n.decls {
1379 if let (Pat::Ident(var), Some(init)) = (&decl.name, decl.init.as_deref()) {
1380 let mut v = None;
1381 for id in collect_infects_from(
1382 init,
1383 AliasConfig {
1384 marks: self.marks,
1385 ignore_named_child_scope: true,
1386 ..Default::default()
1387 },
1388 ) {
1389 if v.is_none() {
1390 v = Some(self.data.var_or_default(var.to_id()));
1391 }
1392
1393 v.as_mut().unwrap().add_infects_to(id.clone());
1394 }
1395 }
1396 }
1397 }
1398
1399 #[cfg_attr(
1400 feature = "tracing-spans",
1401 tracing::instrument(level = "debug", skip(self, e))
1402 )]
1403 fn visit_var_declarator(&mut self, e: &VarDeclarator) {
1404 let prevent_inline = matches!(&e.name, Pat::Ident(BindingIdent {
1405 id: Ident { sym: arguments, .. },
1406 ..
1407 }) if (&**arguments == "arguments"));
1408 {
1409 let ctx = Ctx {
1410 bit_ctx: self
1411 .ctx
1412 .bit_ctx
1413 .with(
1414 BitContext::InlinePrevented,
1415 self.ctx.bit_ctx.contains(BitContext::InlinePrevented) || prevent_inline,
1416 )
1417 .with(BitContext::InPatOfVarDecl, true)
1418 .with(
1419 BitContext::InDeclWithNoSideEffectForMemberAccess,
1420 e.init
1421 .as_deref()
1422 .map(is_safe_to_access_prop)
1423 .unwrap_or(false),
1424 ),
1425 in_pat_of_var_decl_with_init: e
1426 .init
1427 .as_ref()
1428 .map(|init| init.get_type(self.expr_ctx)),
1429 ..self.ctx
1430 };
1431 e.name.visit_with(&mut *self.with_ctx(ctx));
1432 }
1433
1434 {
1435 let ctx = self
1436 .ctx
1437 .with(
1438 BitContext::InlinePrevented,
1439 self.ctx.bit_ctx.contains(BitContext::InlinePrevented) || prevent_inline,
1440 )
1441 .with(BitContext::InPatOfVarDecl, false)
1442 .with(BitContext::IsIdRef, true);
1443 if self.marks.is_some() {
1444 match e {
1445 VarDeclarator {
1446 name: Pat::Ident(id),
1447 init: Some(init),
1448 definite: false,
1449 ..
1450 } => {
1451 let id = id.to_id();
1452 self.used_recursively.insert(
1453 id.clone(),
1454 RecursiveUsage::Var {
1455 can_ignore: !init.may_have_side_effects(self.expr_ctx),
1456 },
1457 );
1458 e.init.visit_with(&mut *self.with_ctx(ctx));
1459 self.used_recursively.remove(&id);
1460 return;
1461 }
1462
1463 VarDeclarator {
1464 name: Pat::Ident(id),
1465 init: None,
1466 ..
1467 } => {
1468 self.data.var_or_default(id.to_id()).mark_as_lazy_init();
1469 return;
1470 }
1471 _ => (),
1472 }
1473 }
1474
1475 e.init.visit_with(&mut *self.with_ctx(ctx));
1476 }
1477 }
1478
1479 #[cfg_attr(
1480 feature = "tracing-spans",
1481 tracing::instrument(level = "debug", skip_all)
1482 )]
1483 fn visit_while_stmt(&mut self, n: &WhileStmt) {
1484 n.test
1485 .visit_with(&mut *self.with_ctx(self.ctx.with(BitContext::ExecutedMultipleTime, true)));
1486 let ctx = self
1487 .ctx
1488 .with(BitContext::ExecutedMultipleTime, true)
1489 .with(BitContext::InCond, true);
1490 self.with_ctx(ctx).visit_in_cond(&n.body);
1491 }
1492
1493 #[cfg_attr(
1494 feature = "tracing-spans",
1495 tracing::instrument(level = "debug", skip_all)
1496 )]
1497 fn visit_with_stmt(&mut self, n: &WithStmt) {
1498 self.scope.mark_with_stmt();
1499 n.visit_children_with(self);
1500 }
1501}
1502
1503fn for_each_id_ref_in_expr(e: &Expr, op: &mut impl FnMut(&Ident)) {
1506 match e {
1507 Expr::Ident(i) => op(i),
1508 Expr::Paren(p) => {
1509 for_each_id_ref_in_expr(&p.expr, op);
1510 }
1511 Expr::Cond(c) => {
1512 for_each_id_ref_in_expr(&c.cons, op);
1513 for_each_id_ref_in_expr(&c.alt, op);
1514 }
1515 Expr::Bin(b @ BinExpr { op: bin_op, .. }) if bin_op.may_short_circuit() => {
1516 for_each_id_ref_in_expr(&b.left, op);
1517 for_each_id_ref_in_expr(&b.right, op);
1518 }
1519
1520 Expr::Class(c) => {
1521 for_each_id_ref_in_class(&c.class, op);
1522 }
1523
1524 Expr::Fn(f) => {
1525 for_each_id_ref_in_fn(&f.function, op);
1526 }
1527
1528 Expr::Seq(s) => {
1529 for_each_id_ref_in_expr(s.exprs.last().unwrap(), op);
1530 }
1531
1532 Expr::Array(arr) => {
1533 arr.elems.iter().flatten().for_each(|e| {
1534 for_each_id_ref_in_expr(&e.expr, op);
1535 });
1536 }
1537
1538 Expr::Object(obj) => {
1539 obj.props.iter().for_each(|p| match p {
1540 PropOrSpread::Spread(p) => {
1541 for_each_id_ref_in_expr(&p.expr, op);
1542 }
1543 PropOrSpread::Prop(p) => match &**p {
1544 Prop::Shorthand(p) => {
1545 op(p);
1546 }
1547 Prop::KeyValue(p) => {
1548 for_each_id_ref_in_prop_name(&p.key, op);
1549 for_each_id_ref_in_expr(&p.value, op);
1550 }
1551 Prop::Assign(p) => {
1552 for_each_id_ref_in_expr(&p.value, op);
1553 }
1554 Prop::Getter(p) => {
1555 for_each_id_ref_in_prop_name(&p.key, op);
1556 }
1557 Prop::Setter(p) => {
1558 for_each_id_ref_in_prop_name(&p.key, op);
1559
1560 for_each_id_ref_in_pat(&p.param, op);
1561 }
1562 Prop::Method(p) => {
1563 for_each_id_ref_in_fn(&p.function, op);
1564 }
1565 },
1566 });
1567 }
1568 _ => {}
1569 }
1570}
1571
1572fn for_each_id_ref_in_class(c: &Class, op: &mut impl FnMut(&Ident)) {
1573 c.body.iter().for_each(|m| match m {
1574 ClassMember::Constructor(m) => {
1575 for_each_id_ref_in_prop_name(&m.key, op);
1576 m.params.iter().for_each(|p| match p {
1577 ParamOrTsParamProp::TsParamProp(..) => {
1578 unreachable!()
1579 }
1580 ParamOrTsParamProp::Param(p) => {
1581 for_each_id_ref_in_pat(&p.pat, op);
1582 }
1583 });
1584 }
1585
1586 ClassMember::Method(m) => {
1587 for_each_id_ref_in_prop_name(&m.key, op);
1588 for_each_id_ref_in_fn(&m.function, op);
1589 }
1590
1591 ClassMember::PrivateMethod(m) => {
1592 for_each_id_ref_in_fn(&m.function, op);
1593 }
1594
1595 ClassMember::ClassProp(m) => {
1596 for_each_id_ref_in_prop_name(&m.key, op);
1597 if let Some(value) = &m.value {
1598 for_each_id_ref_in_expr(value, op);
1599 }
1600 }
1601
1602 ClassMember::PrivateProp(m) => {
1603 if let Some(value) = &m.value {
1604 for_each_id_ref_in_expr(value, op);
1605 }
1606 }
1607
1608 ClassMember::AutoAccessor(m) => {
1609 if let Key::Public(key) = &m.key {
1610 for_each_id_ref_in_prop_name(key, op);
1611 }
1612
1613 if let Some(v) = &m.value {
1614 for_each_id_ref_in_expr(v, op);
1615 }
1616 }
1617
1618 ClassMember::Empty(..)
1619 | ClassMember::StaticBlock(..)
1620 | ClassMember::TsIndexSignature(..) => {}
1621 });
1622}
1623fn for_each_id_ref_in_prop_name(p: &PropName, op: &mut impl FnMut(&Ident)) {
1624 if let PropName::Computed(p) = p {
1625 for_each_id_ref_in_expr(&p.expr, op);
1626 }
1627}
1628
1629fn for_each_id_ref_in_pat(p: &Pat, op: &mut impl FnMut(&Ident)) {
1630 match p {
1631 Pat::Ident(..) => {
1632 }
1634 Pat::Array(p) => {
1635 p.elems.iter().flatten().for_each(|e| {
1636 for_each_id_ref_in_pat(e, op);
1637 });
1638 }
1639 Pat::Rest(p) => {
1640 for_each_id_ref_in_pat(&p.arg, op);
1641 }
1642 Pat::Object(p) => {
1643 p.props.iter().for_each(|p| match p {
1644 ObjectPatProp::KeyValue(p) => {
1645 for_each_id_ref_in_prop_name(&p.key, op);
1646 for_each_id_ref_in_pat(&p.value, op);
1647 }
1648 ObjectPatProp::Assign(p) => {
1649 if let Some(value) = &p.value {
1652 for_each_id_ref_in_expr(value, op);
1653 }
1654 }
1655 ObjectPatProp::Rest(p) => {
1656 for_each_id_ref_in_pat(&p.arg, op);
1657 }
1658 });
1659 }
1660 Pat::Assign(p) => {
1661 for_each_id_ref_in_pat(&p.left, op);
1662 for_each_id_ref_in_expr(&p.right, op);
1663 }
1664 Pat::Invalid(..) => {}
1665 Pat::Expr(p) => {
1666 for_each_id_ref_in_expr(p, op);
1667 }
1668 }
1669}
1670
1671fn for_each_id_ref_in_fn(f: &Function, op: &mut impl FnMut(&Ident)) {
1672 for p in &f.params {
1673 for_each_id_ref_in_pat(&p.pat, op);
1674 }
1675}
1676
1677fn is_safe_to_access_prop(e: &Expr) -> bool {
1679 match e {
1680 Expr::Lit(Lit::Null(..)) => false,
1681 Expr::Lit(..) | Expr::Array(..) | Expr::Fn(..) | Expr::Arrow(..) | Expr::Update(..) => true,
1682 _ => false,
1683 }
1684}
1685
1686fn call_may_mutate(expr: &Expr, expr_ctx: ExprCtx) -> bool {
1687 fn is_global_fn_wont_mutate(s: &Ident, unresolved: SyntaxContext) -> bool {
1688 s.ctxt == unresolved
1689 && matches!(
1690 &*s.sym,
1691 "JSON"
1692 | "String"
1694 | "Number"
1696 | "Date"
1697 | "BigInt"
1698 | "Boolean"
1699 | "Math"
1700 | "Error"
1701 | "console"
1702 | "clearInterval"
1703 | "clearTimeout"
1704 | "setInterval"
1705 | "setTimeout"
1706 | "btoa"
1707 | "decodeURI"
1708 | "decodeURIComponent"
1709 | "encodeURI"
1710 | "encodeURIComponent"
1711 | "escape"
1712 | "eval"
1713 | "EvalError"
1714 | "Function"
1715 | "isFinite"
1716 | "isNaN"
1717 | "parseFloat"
1718 | "parseInt"
1719 | "RegExp"
1720 | "RangeError"
1721 | "ReferenceError"
1722 | "SyntaxError"
1723 | "TypeError"
1724 | "unescape"
1725 | "URIError"
1726 | "atob"
1727 | "globalThis"
1728 | "NaN"
1729 | "Symbol"
1730 | "Promise"
1731 )
1732 }
1733
1734 if expr.is_pure_callee(expr_ctx) {
1735 false
1736 } else {
1737 match expr {
1738 Expr::Ident(i) if is_global_fn_wont_mutate(i, expr_ctx.unresolved_ctxt) => false,
1739 Expr::Member(MemberExpr { obj, .. }) => {
1740 !matches!(&**obj, Expr::Ident(i) if is_global_fn_wont_mutate(i, expr_ctx.unresolved_ctxt))
1741 }
1742 _ => true,
1743 }
1744 }
1745}