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