swc_ecma_usage_analyzer/analyzer/
mod.rs

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
22/// TODO: Track assignments to variables via `arguments`.
23/// TODO: Scope-local. (Including block)
24///
25/// If `marks` is [None], markers are ignored.
26pub 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/// This assumes there are no two variable with same name and same span hygiene.
77#[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            // It's hard to determined the type of pat assignment
172            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        // We mark bar in
271        //
272        // foo[i] = bar
273        //
274        // as `used_as_ref`.
275        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            // Bypass visit_block_stmt
636            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                // debug!(
741                //     "Usage: `{}``; update = {:?}, assign_lhs = {:?} ",
742                //     i,
743                //     self.ctx.in_update_arg,
744                //     self.ctx.in_assign_lhs
745                // );
746            }
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                    // We use visit_children_with instead of visit_with to bypass block scope
937                    // handler.
938                    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            // Bypass visit_tpl
1322            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
1503/// - `a` => `a`
1504/// - `a ? b : c` => `b`, `c`
1505fn 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            // IdentifierBinding is not IdentifierReference
1633        }
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                    // We skip key because it's IdentifierBinding
1650
1651                    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
1677// Support for pure_getters
1678fn 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                // | "Array"
1693                | "String"
1694                // | "Object"
1695                | "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}