Skip to main content

turbopack_ecmascript/analyzer/
graph.rs

1use std::{
2    iter,
3    mem::{replace, take},
4    sync::Arc,
5};
6
7use anyhow::{Ok, Result};
8use rustc_hash::{FxHashMap, FxHashSet};
9use smallvec::SmallVec;
10use swc_core::{
11    atoms::Atom,
12    base::try_with_handler,
13    common::{
14        GLOBALS, Mark, SourceMap, Span, Spanned, SyntaxContext, comments::Comments,
15        pass::AstNodePath, sync::Lrc,
16    },
17    ecma::{
18        ast::*,
19        atoms::atom,
20        utils::contains_ident_ref,
21        visit::{fields::*, *},
22    },
23};
24use turbo_rcstr::{RcStr, rcstr};
25use turbo_tasks::ResolvedVc;
26use turbopack_core::{resolve::ExportUsage, source::Source};
27
28use super::{
29    ConstantValue, ImportMap, JsValue, ObjectPart, WellKnownFunctionKind, is_unresolved_id,
30};
31use crate::{
32    AnalyzeMode, SpecifiedModuleType,
33    analyzer::{WellKnownObjectKind, is_unresolved},
34    references::{constant_value::parse_single_expr_lit, for_each_ident_in_pat},
35    utils::{AstPathRange, unparen},
36};
37
38#[derive(Debug)]
39pub struct EffectsBlock {
40    pub effects: Vec<Effect>,
41    pub range: AstPathRange,
42}
43
44impl EffectsBlock {
45    pub fn is_empty(&self) -> bool {
46        self.effects.is_empty()
47    }
48}
49
50#[derive(Debug)]
51pub enum ConditionalKind {
52    /// The blocks of an `if` statement without an `else` block.
53    If { then: Box<EffectsBlock> },
54    /// The blocks of an `if ... else` or `if { ... return ... } ...` statement.
55    IfElse {
56        then: Box<EffectsBlock>,
57        r#else: Box<EffectsBlock>,
58    },
59    /// The blocks of an `if ... else` statement.
60    Else { r#else: Box<EffectsBlock> },
61    /// The blocks of an `if { ... return ... } else { ... } ...` or `if { ... }
62    /// else { ... return ... } ...` statement.
63    IfElseMultiple {
64        then: Vec<Box<EffectsBlock>>,
65        r#else: Vec<Box<EffectsBlock>>,
66    },
67    /// The expressions on the right side of the `?:` operator.
68    Ternary {
69        then: Box<EffectsBlock>,
70        r#else: Box<EffectsBlock>,
71    },
72    /// The expression on the right side of the `&&` operator.
73    And { expr: Box<EffectsBlock> },
74    /// The expression on the right side of the `||` operator.
75    Or { expr: Box<EffectsBlock> },
76    /// The expression on the right side of the `??` operator.
77    NullishCoalescing { expr: Box<EffectsBlock> },
78    /// The expression on the right side of a labeled statement.
79    Labeled { body: Box<EffectsBlock> },
80}
81
82impl ConditionalKind {
83    /// Normalizes all contained values.
84    pub fn normalize(&mut self) {
85        match self {
86            ConditionalKind::If { then: block }
87            | ConditionalKind::Else { r#else: block }
88            | ConditionalKind::And { expr: block, .. }
89            | ConditionalKind::Or { expr: block, .. }
90            | ConditionalKind::NullishCoalescing { expr: block, .. } => {
91                for effect in &mut block.effects {
92                    effect.normalize();
93                }
94            }
95            ConditionalKind::IfElse { then, r#else, .. }
96            | ConditionalKind::Ternary { then, r#else, .. } => {
97                for effect in &mut then.effects {
98                    effect.normalize();
99                }
100                for effect in &mut r#else.effects {
101                    effect.normalize();
102                }
103            }
104            ConditionalKind::IfElseMultiple { then, r#else, .. } => {
105                for block in then.iter_mut().chain(r#else.iter_mut()) {
106                    for effect in &mut block.effects {
107                        effect.normalize();
108                    }
109                }
110            }
111            ConditionalKind::Labeled { body } => {
112                for effect in &mut body.effects {
113                    effect.normalize();
114                }
115            }
116        }
117    }
118}
119
120#[derive(Debug)]
121pub enum EffectArg {
122    Value(JsValue),
123    Closure(JsValue, Box<EffectsBlock>),
124    Spread,
125}
126
127impl EffectArg {
128    /// Normalizes all contained values.
129    pub fn normalize(&mut self) {
130        match self {
131            EffectArg::Value(value) => value.normalize(),
132            EffectArg::Closure(value, effects) => {
133                value.normalize();
134                for effect in &mut effects.effects {
135                    effect.normalize();
136                }
137            }
138            EffectArg::Spread => {}
139        }
140    }
141}
142
143#[derive(Debug)]
144pub enum Effect {
145    /// Some condition which affects which effects might be executed. If the
146    /// condition evaluates to some compile-time constant, we can use that
147    /// to determine which effects are executed and remove the others.
148    Conditional {
149        condition: Box<JsValue>,
150        kind: Box<ConditionalKind>,
151        /// The ast path to the condition.
152        ast_path: Vec<AstParentKind>,
153        span: Span,
154    },
155    /// A function call or a new call of a function.
156    Call {
157        func: Box<JsValue>,
158        args: Vec<EffectArg>,
159        ast_path: Vec<AstParentKind>,
160        span: Span,
161        in_try: bool,
162        new: bool,
163    },
164    /// A function call or a new call of a property of an object.
165    MemberCall {
166        obj: Box<JsValue>,
167        prop: Box<JsValue>,
168        args: Vec<EffectArg>,
169        ast_path: Vec<AstParentKind>,
170        span: Span,
171        in_try: bool,
172        new: bool,
173    },
174    /// A property access.
175    Member {
176        obj: Box<JsValue>,
177        prop: Box<JsValue>,
178        ast_path: Vec<AstParentKind>,
179        span: Span,
180    },
181    /// A reference to an imported binding.
182    ImportedBinding {
183        esm_reference_index: usize,
184        export: Option<RcStr>,
185        ast_path: Vec<AstParentKind>,
186        span: Span,
187    },
188    /// A reference to a free var access.
189    FreeVar {
190        var: Atom,
191        ast_path: Vec<AstParentKind>,
192        span: Span,
193    },
194    /// A typeof expression
195    TypeOf {
196        arg: Box<JsValue>,
197        ast_path: Vec<AstParentKind>,
198        span: Span,
199    },
200    // TODO ImportMeta should be replaced with Member
201    /// A reference to `import.meta`.
202    ImportMeta {
203        ast_path: Vec<AstParentKind>,
204        span: Span,
205    },
206    /// A dynamic import() call, potentially with export usage extracted from
207    /// usage patterns. Export usage is detected from these patterns:
208    ///
209    /// - `const { a, b } = await import('./lib')` (destructured await)
210    /// - `(await import('./lib')).a` (member access on await)
211    /// - `import('./lib').then(({ a, b }) => {})` (arrow .then() callback)
212    /// - `import('./lib').then(function({ a, b }) {})` (function .then() callback)
213    /// - `import(/* webpackExports: ["a"] */ './lib')` (magic comment)
214    /// - `import(/* turbopackExports: ["a"] */ './lib')` (magic comment)
215    DynamicImport {
216        args: Vec<EffectArg>,
217        ast_path: Vec<AstParentKind>,
218        span: Span,
219        in_try: bool,
220        /// The export usage extracted from the usage pattern.
221        export_usage: ExportUsage,
222    },
223    /// Unreachable code, e.g. after a `return` statement.
224    Unreachable { start_ast_path: Vec<AstParentKind> },
225}
226
227impl Effect {
228    /// Normalizes all contained values.
229    pub fn normalize(&mut self) {
230        match self {
231            Effect::Conditional {
232                condition, kind, ..
233            } => {
234                condition.normalize();
235                kind.normalize();
236            }
237            Effect::Call { func, args, .. } => {
238                func.normalize();
239                for arg in args.iter_mut() {
240                    arg.normalize();
241                }
242            }
243            Effect::MemberCall {
244                obj, prop, args, ..
245            } => {
246                obj.normalize();
247                prop.normalize();
248                for arg in args.iter_mut() {
249                    arg.normalize();
250                }
251            }
252            Effect::Member { obj, prop, .. } => {
253                obj.normalize();
254                prop.normalize();
255            }
256            Effect::DynamicImport { args, .. } => {
257                for arg in args.iter_mut() {
258                    arg.normalize();
259                }
260            }
261            Effect::ImportedBinding { .. } => {}
262            Effect::TypeOf { arg, .. } => {
263                arg.normalize();
264            }
265            Effect::FreeVar { .. } => {}
266            Effect::ImportMeta { .. } => {}
267            Effect::Unreachable { .. } => {}
268        }
269    }
270}
271
272pub enum AssignmentScope {
273    ModuleEval,
274    Function,
275}
276
277#[derive(Debug, Copy, Clone, PartialEq, Eq)]
278pub enum AssignmentScopes {
279    AllInModuleEvalScope,
280    AllInFunctionScopes,
281    Mixed,
282}
283impl AssignmentScopes {
284    fn new(initial: AssignmentScope) -> Self {
285        match initial {
286            AssignmentScope::ModuleEval => AssignmentScopes::AllInModuleEvalScope,
287            AssignmentScope::Function => AssignmentScopes::AllInFunctionScopes,
288        }
289    }
290
291    fn merge(self, other: AssignmentScope) -> Self {
292        // If the other assignment kind is the same as the current one, return the current one.
293        if self == Self::new(other) {
294            self
295        } else {
296            AssignmentScopes::Mixed
297        }
298    }
299}
300
301#[derive(Debug, Clone)]
302pub struct VarMeta {
303    pub value: JsValue,
304    /// Tracks the locations where this was assigned to:
305    /// - [`AssignmentScopes::AllInModuleEvalScope`] if it was assigned only in the root scope
306    /// - [`AssignmentScopes::AllInFunctionScopes`] if it was assigned in any set of function
307    ///   scopes
308    /// - [`AssignmentScopes::Mixed`] if it was assigned in both
309    ///
310    /// This is used to track the _liveness_ of exports.
311    pub assignment_scopes: AssignmentScopes,
312}
313
314impl VarMeta {
315    pub fn new(value: JsValue, kind: AssignmentScope) -> Self {
316        Self {
317            value,
318            assignment_scopes: AssignmentScopes::new(kind),
319        }
320    }
321
322    pub fn normalize(&mut self) {
323        self.value.normalize();
324    }
325
326    fn add_alt(&mut self, value: JsValue, kind: AssignmentScope) {
327        self.value.add_alt(value);
328        self.assignment_scopes = self.assignment_scopes.merge(kind);
329    }
330}
331
332#[derive(Clone, Debug)]
333pub enum DeclUsage {
334    SideEffects,
335    Bindings(FxHashSet<Id>),
336}
337impl Default for DeclUsage {
338    fn default() -> Self {
339        DeclUsage::Bindings(Default::default())
340    }
341}
342impl DeclUsage {
343    fn add_usage(&mut self, user: &Id) {
344        match self {
345            Self::Bindings(set) => {
346                set.insert(user.clone());
347            }
348            Self::SideEffects => {}
349        }
350    }
351    fn make_side_effects(&mut self) {
352        *self = Self::SideEffects;
353    }
354}
355
356#[derive(Debug)]
357pub struct VarGraph {
358    pub values: FxHashMap<Id, VarMeta>,
359
360    /// Map [`JsValue::FreeVar`] names to their [`Id`] to facilitate lookups into [`Self::values`].
361    ///
362    /// Doesn't necessarily contain every [`FreeVar`][JsValue::FreeVar], just those who have
363    /// non-trivial values.
364    pub free_var_ids: FxHashMap<Atom, Id>,
365
366    pub effects: Vec<Effect>,
367
368    // ident -> immediate usage (top level decl)
369    pub decl_usages: FxHashMap<Id, DeclUsage>,
370    // import -> immediate usage (top level decl)
371    pub import_usages: FxHashMap<usize, DeclUsage>,
372    // export name -> top level decl
373    pub exports: FxHashMap<Atom, Id>,
374}
375
376impl VarGraph {
377    pub fn normalize(&mut self) {
378        for value in self.values.values_mut() {
379            value.normalize();
380        }
381        for effect in self.effects.iter_mut() {
382            effect.normalize();
383        }
384    }
385}
386
387pub fn create_graph(
388    m: &Program,
389    eval_context: &EvalContext,
390    analyze_mode: AnalyzeMode,
391) -> VarGraph {
392    let mut graph = VarGraph {
393        values: Default::default(),
394        free_var_ids: Default::default(),
395        effects: Default::default(),
396        decl_usages: Default::default(),
397        import_usages: Default::default(),
398        exports: Default::default(),
399    };
400
401    m.visit_with_ast_path(
402        &mut Analyzer {
403            analyze_mode,
404            data: &mut graph,
405            eval_context,
406            state: Default::default(),
407            effects: Default::default(),
408            hoisted_effects: Default::default(),
409        },
410        &mut Default::default(),
411    );
412
413    graph.normalize();
414
415    graph
416}
417
418/// A context used for assembling the evaluation graph.
419#[derive(Debug)]
420pub struct EvalContext {
421    /// Should be the same [`Mark`] used by [`swc_core::ecma::transforms::base::resolver`].
422    pub(crate) unresolved_mark: Mark,
423    /// Should be the same [`Mark`] used by [`swc_core::ecma::transforms::base::resolver`].
424    pub(crate) top_level_mark: Mark,
425    pub(crate) imports: ImportMap,
426    pub(crate) force_free_values: Arc<FxHashSet<Id>>,
427}
428
429impl EvalContext {
430    /// Produce a new [`EvalContext`] from a [`Program`].
431    ///
432    /// If you wish to support `webpackIgnore` or `turbopackIgnore` comments, you must pass those
433    /// in, since the AST does not include comments by default.
434    ///
435    /// You should use the same `unresolved_mark` and `top_level_mark` [Mark] values for this
436    /// context that you passed to [`swc_core::ecma::transforms::base::resolver`].
437    pub fn new(
438        module: Option<&Program>,
439        unresolved_mark: Mark,
440        top_level_mark: Mark,
441        force_free_values: Arc<FxHashSet<Id>>,
442        comments: Option<&dyn Comments>,
443        source: Option<ResolvedVc<Box<dyn Source>>>,
444    ) -> Self {
445        Self {
446            unresolved_mark,
447            top_level_mark,
448            imports: module.map_or(ImportMap::default(), |m| {
449                ImportMap::analyze(m, source, comments)
450            }),
451            force_free_values,
452        }
453    }
454
455    pub fn is_esm(&self, specified_type: SpecifiedModuleType) -> bool {
456        self.imports.is_esm(specified_type)
457    }
458
459    fn eval_prop_name(&self, prop: &PropName) -> JsValue {
460        match prop {
461            PropName::Ident(ident) => ident.sym.clone().into(),
462            PropName::Str(str) => str.value.clone().to_atom_lossy().into_owned().into(),
463            PropName::Num(num) => num.value.into(),
464            PropName::Computed(ComputedPropName { expr, .. }) => self.eval(expr),
465            PropName::BigInt(bigint) => (*bigint.value.clone()).into(),
466        }
467    }
468
469    fn eval_member_prop(&self, prop: &MemberProp) -> Option<JsValue> {
470        match prop {
471            MemberProp::Ident(ident) => Some(ident.sym.clone().into()),
472            MemberProp::Computed(ComputedPropName { expr, .. }) => Some(self.eval(expr)),
473            MemberProp::PrivateName(_) => None,
474        }
475    }
476
477    fn eval_tpl(&self, e: &Tpl, raw: bool) -> JsValue {
478        debug_assert!(e.quasis.len() == e.exprs.len() + 1);
479
480        let mut values = vec![];
481
482        for idx in 0..(e.quasis.len() + e.exprs.len()) {
483            if idx.is_multiple_of(2) {
484                let idx = idx / 2;
485                let e = &e.quasis[idx];
486                if raw {
487                    // Ignore empty strings quasis, happens frequently with e.g. after the
488                    // placeholder in `something${v}`.
489                    if !e.raw.is_empty() {
490                        values.push(JsValue::from(e.raw.clone()));
491                    }
492                } else {
493                    match &e.cooked {
494                        Some(v) => {
495                            if !v.is_empty() {
496                                values.push(JsValue::from(v.clone().to_atom_lossy().into_owned()));
497                            }
498                        }
499                        // This is actually unreachable
500                        None => return JsValue::unknown_empty(true, ""),
501                    }
502                }
503            } else {
504                let idx = idx / 2;
505                let e = &e.exprs[idx];
506
507                values.push(self.eval(e));
508            }
509        }
510
511        match values.len() {
512            0 => JsValue::Constant(ConstantValue::Str(rcstr!("").into())),
513            1 => values.into_iter().next().unwrap(),
514            _ => JsValue::concat(values),
515        }
516    }
517
518    fn eval_ident(&self, i: &Ident) -> JsValue {
519        let id = i.to_id();
520        if let Some(imported) = self.imports.get_import(&id) {
521            return imported;
522        }
523        if is_unresolved(i, self.unresolved_mark) || self.force_free_values.contains(&id) {
524            // These are special globals that we shouldn't consider to be free variables and we can
525            // model their values mostly useful for truthy/falsy checks.
526            match i.sym.as_str() {
527                "undefined" => JsValue::Constant(ConstantValue::Undefined),
528                "NaN" => JsValue::Constant(ConstantValue::Num(f64::NAN.into())),
529                "Infinity" => JsValue::Constant(ConstantValue::Num(f64::INFINITY.into())),
530                _ => JsValue::FreeVar(i.sym.clone()),
531            }
532        } else {
533            JsValue::Variable(id)
534        }
535    }
536
537    pub fn eval(&self, e: &Expr) -> JsValue {
538        debug_assert!(
539            GLOBALS.is_set(),
540            "Eval requires globals from its parsed result"
541        );
542        match e {
543            Expr::Paren(e) => self.eval(&e.expr),
544            Expr::Lit(e) => JsValue::Constant(e.clone().into()),
545            Expr::Ident(i) => self.eval_ident(i),
546
547            Expr::Unary(UnaryExpr {
548                op: op!("void"),
549                // Only treat literals as constant undefined, allowing arbitrary values inside here
550                // would mean that they can have sideeffects, and `JsValue::Constant` can't model
551                // that.
552                arg: box Expr::Lit(_),
553                ..
554            }) => JsValue::Constant(ConstantValue::Undefined),
555
556            Expr::Unary(UnaryExpr {
557                op: op!("!"), arg, ..
558            }) => {
559                let arg = self.eval(arg);
560
561                JsValue::logical_not(Box::new(arg))
562            }
563
564            Expr::Unary(UnaryExpr {
565                op: op!("typeof"),
566                arg,
567                ..
568            }) => {
569                let arg = self.eval(arg);
570
571                JsValue::type_of(Box::new(arg))
572            }
573
574            Expr::Bin(BinExpr {
575                op: op!(bin, "+"),
576                left,
577                right,
578                ..
579            }) => {
580                let l = self.eval(left);
581                let r = self.eval(right);
582
583                match (l, r) {
584                    (JsValue::Add(c, l), r) => JsValue::Add(
585                        c + r.total_nodes(),
586                        l.into_iter().chain(iter::once(r)).collect(),
587                    ),
588                    (l, r) => JsValue::add(vec![l, r]),
589                }
590            }
591
592            Expr::Bin(BinExpr {
593                op: op!("&&"),
594                left,
595                right,
596                ..
597            }) => JsValue::logical_and(vec![self.eval(left), self.eval(right)]),
598
599            Expr::Bin(BinExpr {
600                op: op!("||"),
601                left,
602                right,
603                ..
604            }) => JsValue::logical_or(vec![self.eval(left), self.eval(right)]),
605
606            Expr::Bin(BinExpr {
607                op: op!("??"),
608                left,
609                right,
610                ..
611            }) => JsValue::nullish_coalescing(vec![self.eval(left), self.eval(right)]),
612
613            Expr::Bin(BinExpr {
614                op: op!("=="),
615                left,
616                right,
617                ..
618            }) => JsValue::equal(Box::new(self.eval(left)), Box::new(self.eval(right))),
619
620            Expr::Bin(BinExpr {
621                op: op!("!="),
622                left,
623                right,
624                ..
625            }) => JsValue::not_equal(Box::new(self.eval(left)), Box::new(self.eval(right))),
626
627            Expr::Bin(BinExpr {
628                op: op!("==="),
629                left,
630                right,
631                ..
632            }) => JsValue::strict_equal(Box::new(self.eval(left)), Box::new(self.eval(right))),
633
634            Expr::Bin(BinExpr {
635                op: op!("!=="),
636                left,
637                right,
638                ..
639            }) => JsValue::strict_not_equal(Box::new(self.eval(left)), Box::new(self.eval(right))),
640
641            &Expr::Cond(CondExpr {
642                box ref cons,
643                box ref alt,
644                box ref test,
645                ..
646            }) => {
647                let test = self.eval(test);
648                if let Some(truthy) = test.is_truthy() {
649                    if truthy {
650                        self.eval(cons)
651                    } else {
652                        self.eval(alt)
653                    }
654                } else {
655                    JsValue::tenary(
656                        Box::new(test),
657                        Box::new(self.eval(cons)),
658                        Box::new(self.eval(alt)),
659                    )
660                }
661            }
662
663            Expr::Tpl(e) => self.eval_tpl(e, false),
664
665            Expr::TaggedTpl(TaggedTpl {
666                tag:
667                    box Expr::Member(MemberExpr {
668                        obj: box Expr::Ident(tag_obj),
669                        prop: MemberProp::Ident(tag_prop),
670                        ..
671                    }),
672                tpl,
673                ..
674            }) => {
675                if &*tag_obj.sym == "String"
676                    && &*tag_prop.sym == "raw"
677                    && is_unresolved(tag_obj, self.unresolved_mark)
678                {
679                    self.eval_tpl(tpl, true)
680                } else {
681                    JsValue::unknown_empty(true, "tagged template literal is not supported yet")
682                }
683            }
684
685            Expr::Fn(expr) => {
686                if let Some(ident) = &expr.ident {
687                    JsValue::Variable(ident.to_id())
688                } else {
689                    JsValue::Variable((
690                        format!("*anonymous function {}*", expr.function.span.lo.0).into(),
691                        SyntaxContext::empty(),
692                    ))
693                }
694            }
695            Expr::Arrow(expr) => JsValue::Variable((
696                format!("*arrow function {}*", expr.span.lo.0).into(),
697                SyntaxContext::empty(),
698            )),
699
700            Expr::Await(AwaitExpr { arg, .. }) => JsValue::awaited(Box::new(self.eval(arg))),
701
702            Expr::Seq(e) => {
703                let mut seq = e.exprs.iter().map(|e| self.eval(e)).peekable();
704                let mut side_effects = false;
705                let mut last = seq.next().unwrap();
706                for e in seq {
707                    side_effects |= last.has_side_effects();
708                    last = e;
709                }
710                if side_effects {
711                    last.make_unknown(true, "sequence with side effects");
712                }
713                last
714            }
715
716            Expr::Member(MemberExpr {
717                obj,
718                prop: MemberProp::Ident(prop),
719                ..
720            }) => {
721                let obj = self.eval(obj);
722                JsValue::member(Box::new(obj), Box::new(prop.sym.clone().into()))
723            }
724
725            Expr::Member(MemberExpr {
726                obj,
727                prop: MemberProp::Computed(computed),
728                ..
729            }) => {
730                let obj = self.eval(obj);
731                let prop = self.eval(&computed.expr);
732                JsValue::member(Box::new(obj), Box::new(prop))
733            }
734
735            Expr::New(NewExpr {
736                callee: box callee,
737                args,
738                ..
739            }) => {
740                // We currently do not handle spreads.
741                if args.iter().flatten().any(|arg| arg.spread.is_some()) {
742                    return JsValue::unknown_empty(true, "spread in new calls is not supported");
743                }
744
745                let args: Vec<_> = args
746                    .iter()
747                    .flatten()
748                    .map(|arg| self.eval(&arg.expr))
749                    .collect();
750                let callee = Box::new(self.eval(callee));
751
752                JsValue::new(callee, args)
753            }
754
755            Expr::Call(CallExpr {
756                callee: Callee::Expr(box callee),
757                args,
758                ..
759            }) => {
760                // We currently do not handle spreads.
761                if args.iter().any(|arg| arg.spread.is_some()) {
762                    return JsValue::unknown_empty(
763                        true,
764                        "spread in function calls is not supported",
765                    );
766                }
767
768                let args = args.iter().map(|arg| self.eval(&arg.expr)).collect();
769                if let Expr::Member(MemberExpr { obj, prop, .. }) = unparen(callee) {
770                    let obj = Box::new(self.eval(obj));
771                    let prop = Box::new(match prop {
772                        MemberProp::Ident(i) => i.sym.clone().into(),
773                        MemberProp::PrivateName(_) => {
774                            return JsValue::unknown_empty(
775                                false,
776                                "private names in function calls is not supported",
777                            );
778                        }
779                        MemberProp::Computed(ComputedPropName { expr, .. }) => self.eval(expr),
780                    });
781                    JsValue::member_call(obj, prop, args)
782                } else {
783                    let callee = Box::new(self.eval(callee));
784
785                    JsValue::call(callee, args)
786                }
787            }
788
789            Expr::Call(CallExpr {
790                callee: Callee::Super(_),
791                args,
792                ..
793            }) => {
794                // We currently do not handle spreads.
795                if args.iter().any(|arg| arg.spread.is_some()) {
796                    return JsValue::unknown_empty(
797                        true,
798                        "spread in function calls is not supported",
799                    );
800                }
801
802                let args = args.iter().map(|arg| self.eval(&arg.expr)).collect();
803
804                JsValue::super_call(args)
805            }
806
807            Expr::Call(CallExpr {
808                callee: Callee::Import(_),
809                args,
810                ..
811            }) => {
812                // We currently do not handle spreads.
813                if args.iter().any(|arg| arg.spread.is_some()) {
814                    return JsValue::unknown_empty(true, "spread in import() is not supported");
815                }
816                let args = args.iter().map(|arg| self.eval(&arg.expr)).collect();
817
818                let callee = Box::new(JsValue::FreeVar(atom!("import")));
819
820                JsValue::call(callee, args)
821            }
822
823            Expr::Array(arr) => {
824                if arr.elems.iter().flatten().any(|v| v.spread.is_some()) {
825                    return JsValue::unknown_empty(true, "spread is not supported");
826                }
827
828                let arr = arr
829                    .elems
830                    .iter()
831                    .map(|e| match e {
832                        Some(e) => self.eval(&e.expr),
833                        _ => JsValue::Constant(ConstantValue::Undefined),
834                    })
835                    .collect();
836                JsValue::array(arr)
837            }
838
839            Expr::Object(obj) => JsValue::object(
840                obj.props
841                    .iter()
842                    .map(|prop| match prop {
843                        PropOrSpread::Spread(SpreadElement { expr, .. }) => {
844                            ObjectPart::Spread(self.eval(expr))
845                        }
846                        PropOrSpread::Prop(box Prop::KeyValue(KeyValueProp { key, box value })) => {
847                            ObjectPart::KeyValue(self.eval_prop_name(key), self.eval(value))
848                        }
849                        PropOrSpread::Prop(box Prop::Shorthand(ident)) => ObjectPart::KeyValue(
850                            ident.sym.clone().into(),
851                            self.eval(&Expr::Ident(ident.clone())),
852                        ),
853                        _ => ObjectPart::Spread(JsValue::unknown_empty(
854                            true,
855                            "unsupported object part",
856                        )),
857                    })
858                    .collect(),
859            ),
860
861            Expr::MetaProp(MetaPropExpr {
862                kind: MetaPropKind::ImportMeta,
863                ..
864            }) => JsValue::WellKnownObject(WellKnownObjectKind::ImportMeta),
865
866            Expr::Assign(AssignExpr { op, .. }) => match op {
867                // TODO: `self.eval(right)` would be the value, but we need to handle the side
868                // effect of that expression
869                AssignOp::Assign => JsValue::unknown_empty(true, "assignment expression"),
870                _ => JsValue::unknown_empty(true, "compound assignment expression"),
871            },
872
873            _ => JsValue::unknown_empty(true, "unsupported expression"),
874        }
875    }
876
877    pub fn eval_single_expr_lit(expr_lit: &RcStr) -> Result<JsValue> {
878        let cm = Lrc::new(SourceMap::default());
879
880        let js_value = try_with_handler(cm, Default::default(), |_| {
881            GLOBALS.set(&Default::default(), || {
882                let expr = parse_single_expr_lit(expr_lit);
883                let eval_context = EvalContext::new(
884                    None,
885                    Mark::new(),
886                    Mark::new(),
887                    Default::default(),
888                    None,
889                    None,
890                );
891
892                Ok(eval_context.eval(&expr))
893            })
894        })
895        .map_err(|e| e.to_pretty_error())?;
896
897        Ok(js_value)
898    }
899}
900
901enum EarlyReturn {
902    Always {
903        prev_effects: Vec<Effect>,
904        start_ast_path: Vec<AstParentKind>,
905    },
906    Conditional {
907        prev_effects: Vec<Effect>,
908        start_ast_path: Vec<AstParentKind>,
909
910        condition: Box<JsValue>,
911        then: Option<Box<EffectsBlock>>,
912        r#else: Option<Box<EffectsBlock>>,
913        /// The ast path to the condition.
914        condition_ast_path: Vec<AstParentKind>,
915        span: Span,
916
917        early_return_condition_value: bool,
918    },
919}
920
921pub fn as_parent_path_skip(
922    ast_path: &AstNodePath<AstParentNodeRef<'_>>,
923    skip: usize,
924) -> Vec<AstParentKind> {
925    ast_path
926        .iter()
927        .take(ast_path.len() - skip)
928        .map(|n| n.kind())
929        .collect()
930}
931
932struct Analyzer<'a> {
933    analyze_mode: AnalyzeMode,
934
935    data: &'a mut VarGraph,
936    state: analyzer_state::AnalyzerState,
937
938    effects: Vec<Effect>,
939    /// Effects collected from hoisted declarations. See https://developer.mozilla.org/en-US/docs/Glossary/Hoisting
940    /// Tracked separately so we can preserve effects from hoisted declarations even when we don't
941    /// collect effects from the declaring context.
942    hoisted_effects: Vec<Effect>,
943
944    eval_context: &'a EvalContext,
945}
946
947trait FunctionLike {
948    fn is_async(&self) -> bool {
949        false
950    }
951    fn is_generator(&self) -> bool {
952        false
953    }
954    fn span(&self) -> Span;
955    fn binds_this(&self) -> bool {
956        true
957    }
958}
959
960impl FunctionLike for Function {
961    fn is_async(&self) -> bool {
962        self.is_async
963    }
964    fn is_generator(&self) -> bool {
965        self.is_generator
966    }
967    fn span(&self) -> Span {
968        self.span
969    }
970}
971impl FunctionLike for ArrowExpr {
972    fn is_async(&self) -> bool {
973        self.is_async
974    }
975    fn is_generator(&self) -> bool {
976        self.is_generator
977    }
978    fn span(&self) -> Span {
979        self.span
980    }
981    fn binds_this(&self) -> bool {
982        false
983    }
984}
985
986impl FunctionLike for Constructor {
987    fn span(&self) -> Span {
988        self.span
989    }
990}
991impl FunctionLike for GetterProp {
992    fn span(&self) -> Span {
993        self.span
994    }
995}
996impl FunctionLike for SetterProp {
997    fn span(&self) -> Span {
998        self.span
999    }
1000}
1001
1002#[derive(PartialEq, Eq, Debug, Copy, Clone)]
1003enum LexicalContext {
1004    // In the root of a function scope
1005    Function { id: u32, binds_this: bool },
1006    // A placeholder for identify anonymous blocks
1007    // If we have Block->Block then we are in an anonymous block
1008    // If we have Function->Block or ControlFlow->Block then we are just in a function root
1009    Block,
1010    // In some kind of control flow
1011    ControlFlow { is_try: bool },
1012
1013    // Class bodies do rebind `this` and are in many ways like a function
1014    ClassBody,
1015}
1016
1017mod analyzer_state {
1018    use super::*;
1019
1020    /// Contains fields of `Analyzer` that should only be modified using helper methods. These are
1021    /// intentionally private to the rest of the `Analyzer` implementation.
1022    #[derive(Default)]
1023    pub struct AnalyzerState {
1024        pat_value: Option<JsValue>,
1025        /// Return values of the current function.
1026        ///
1027        /// This is configured to [Some] by function handlers and filled by the
1028        /// return statement handler.
1029        cur_fn_return_values: Option<Vec<JsValue>>,
1030        /// Stack of early returns for control flow analysis.
1031        early_return_stack: Vec<EarlyReturn>,
1032        lexical_stack: Vec<LexicalContext>,
1033        var_decl_kind: Option<VarDeclKind>,
1034
1035        cur_top_level_decl_name: Option<Id>,
1036    }
1037
1038    impl AnalyzerState {
1039        /// Returns the identifier of the current top level declaration.
1040        pub(super) fn cur_top_level_decl_name(&self) -> &Option<Id> {
1041            &self.cur_top_level_decl_name
1042        }
1043    }
1044
1045    impl Analyzer<'_> {
1046        /// Returns true if we are in a function. False if we are in the root scope.
1047        pub(super) fn is_in_fn(&self) -> bool {
1048            self.state
1049                .lexical_stack
1050                .iter()
1051                .any(|b| matches!(b, LexicalContext::Function { .. }))
1052        }
1053
1054        pub(super) fn is_in_try(&self) -> bool {
1055            self.state
1056                .lexical_stack
1057                .iter()
1058                .rev()
1059                .take_while(|b| !matches!(b, LexicalContext::Function { .. }))
1060                .any(|b| *b == LexicalContext::ControlFlow { is_try: true })
1061        }
1062
1063        /// Returns true if we are currently in a block scope that isn't at the root of a function
1064        /// or a module.
1065        pub(super) fn is_in_nested_block_scope(&self) -> bool {
1066            match &self.state.lexical_stack[self.state.lexical_stack.len().saturating_sub(2)..] {
1067                [LexicalContext::Block]
1068                | [LexicalContext::Function { .. }, LexicalContext::Block] => false,
1069                [] => {
1070                    unreachable!()
1071                }
1072
1073                _ => true,
1074            }
1075        }
1076
1077        pub(super) fn cur_lexical_context(&self) -> LexicalContext {
1078            *self.state.lexical_stack.last().unwrap()
1079        }
1080
1081        /// Returns the identifier of the current function.
1082        /// must be called only if `is_in_fn` is true
1083        pub(super) fn cur_fn_ident(&self) -> u32 {
1084            *self
1085                .state
1086                .lexical_stack
1087                .iter()
1088                .rev()
1089                .filter_map(|b| {
1090                    if let LexicalContext::Function { id, .. } = b {
1091                        Some(id)
1092                    } else {
1093                        None
1094                    }
1095                })
1096                .next()
1097                .expect("not in a function")
1098        }
1099
1100        /// Returns true if `this` is bound in any active scope
1101        pub(super) fn is_this_bound(&self) -> bool {
1102            self.state.lexical_stack.iter().rev().any(|b| {
1103                matches!(
1104                    b,
1105                    LexicalContext::Function {
1106                        id: _,
1107                        binds_this: true
1108                    } | LexicalContext::ClassBody
1109                )
1110            })
1111        }
1112
1113        /// Adds a return value to the current function.
1114        /// Panics if we are not in a function scope
1115        pub(super) fn add_return_value(&mut self, value: JsValue) {
1116            self.state
1117                .cur_fn_return_values
1118                .as_mut()
1119                .expect("not in a function")
1120                .push(value);
1121        }
1122
1123        /// The RHS (or some part of it) of an pattern or assignment (e.g. `PatAssignTarget`,
1124        /// `SimpleAssignTarget`, function arguments, etc.), read by the individual parts of LHS
1125        /// (target).
1126        ///
1127        /// Consumes the value, setting it to `None`, and returning the previous value. This avoids
1128        /// extra clones.
1129        pub(super) fn take_pat_value(&mut self) -> Option<JsValue> {
1130            self.state.pat_value.take()
1131        }
1132
1133        // Runs `func` (usually something that visits children) with the given
1134        // [`Analyzer::take_pat_value`], restoring the value back to the previous value (usually
1135        // `None`) afterwards.
1136        pub(super) fn with_pat_value<T>(
1137            &mut self,
1138            value: Option<JsValue>,
1139            func: impl FnOnce(&mut Self) -> T,
1140        ) -> T {
1141            let prev_value = replace(&mut self.state.pat_value, value);
1142            let out = func(self);
1143            self.state.pat_value = prev_value;
1144            out
1145        }
1146
1147        /// Runs `func` with the given variable declaration kind, restoring the previous kind
1148        /// afterwards.
1149        pub(super) fn with_decl_kind<T>(
1150            &mut self,
1151            kind: Option<VarDeclKind>,
1152            func: impl FnOnce(&mut Self) -> T,
1153        ) -> T {
1154            let prev_kind = replace(&mut self.state.var_decl_kind, kind);
1155            let out = func(self);
1156            self.state.var_decl_kind = prev_kind;
1157            out
1158        }
1159
1160        /// Returns the current variable declaration kind.
1161        pub(super) fn var_decl_kind(&self) -> Option<VarDeclKind> {
1162            self.state.var_decl_kind
1163        }
1164
1165        /// Runs `func` with the current function identifier and return values initialized for the
1166        /// block.
1167        pub(super) fn enter_fn(
1168            &mut self,
1169            function: &impl FunctionLike,
1170            visitor: impl FnOnce(&mut Self),
1171        ) -> JsValue {
1172            let fn_id = function.span().lo.0;
1173            let prev_return_values = self.state.cur_fn_return_values.replace(vec![]);
1174
1175            self.with_block(
1176                LexicalContext::Function {
1177                    id: fn_id,
1178                    binds_this: function.binds_this(),
1179                },
1180                |this| visitor(this),
1181            );
1182            let return_values = self.state.cur_fn_return_values.take().unwrap();
1183            self.state.cur_fn_return_values = prev_return_values;
1184
1185            JsValue::function(
1186                fn_id,
1187                function.is_async(),
1188                function.is_generator(),
1189                match return_values.len() {
1190                    0 => JsValue::Constant(ConstantValue::Undefined),
1191                    1 => return_values.into_iter().next().unwrap(),
1192                    _ => JsValue::alternatives(return_values),
1193                },
1194            )
1195        }
1196
1197        /// Helper to access the early_return_stack mutably (for push operations)
1198        pub(super) fn early_return_stack_mut(&mut self) -> &mut Vec<EarlyReturn> {
1199            &mut self.state.early_return_stack
1200        }
1201
1202        /// Records an unconditional early return (return, throw, or finally block that always
1203        /// returns). Takes ownership of current effects and pushes them onto the early return
1204        /// stack.
1205        pub(super) fn add_early_return_always(
1206            &mut self,
1207            ast_path: &AstNodePath<AstParentNodeRef<'_>>,
1208        ) {
1209            let early_return = EarlyReturn::Always {
1210                prev_effects: take(&mut self.effects),
1211                start_ast_path: as_parent_path(ast_path),
1212            };
1213            self.early_return_stack_mut().push(early_return);
1214        }
1215
1216        /// Runs `func` with a fresh early return stack, restoring the previous stack afterwards.
1217        /// Returns the result of `func` and whether the block always returns (from
1218        /// `end_early_return_block`).
1219        pub(super) fn enter_control_flow<T>(
1220            &mut self,
1221            func: impl FnOnce(&mut Self) -> T,
1222        ) -> (T, bool) {
1223            self.enter_block(LexicalContext::ControlFlow { is_try: false }, |this| {
1224                func(this)
1225            })
1226        }
1227        /// Runs `func` with a fresh early return stack, restoring the previous stack afterwards.
1228        /// Returns the result of `func` and whether the block always returns (from
1229        /// `end_early_return_block`).
1230        pub(super) fn enter_try<T>(&mut self, func: impl FnOnce(&mut Self) -> T) -> (T, bool) {
1231            self.enter_block(LexicalContext::ControlFlow { is_try: true }, |this| {
1232                func(this)
1233            })
1234        }
1235
1236        /// Runs `func` with a fresh early return stack, restoring the previous stack afterwards.
1237        /// Returns the result of `func` and whether the block always returns (from
1238        /// `end_early_return_block`).
1239        pub(super) fn enter_block<T>(
1240            &mut self,
1241            block_kind: LexicalContext,
1242            func: impl FnOnce(&mut Self) -> T,
1243        ) -> (T, bool) {
1244            let prev_early_return_stack = take(&mut self.state.early_return_stack);
1245            let result = self.with_block(block_kind, func);
1246            let always_returns = self.end_early_return_block();
1247            self.state.early_return_stack = prev_early_return_stack;
1248            (result, always_returns)
1249        }
1250
1251        /// Pushes a block onto the stack without performing early return logic.
1252        pub(super) fn with_block<T>(
1253            &mut self,
1254            block_kind: LexicalContext,
1255            func: impl FnOnce(&mut Self) -> T,
1256        ) -> T {
1257            self.state.lexical_stack.push(block_kind);
1258            let result = func(self);
1259            let old = self.state.lexical_stack.pop();
1260            debug_assert_eq!(old, Some(block_kind));
1261            result
1262        }
1263
1264        /// Ends a conditional block. All early returns are integrated into the
1265        /// effects. Returns true if the whole block always early returns.
1266        fn end_early_return_block(&mut self) -> bool {
1267            let mut always_returns = false;
1268            while let Some(early_return) = self.state.early_return_stack.pop() {
1269                match early_return {
1270                    EarlyReturn::Always {
1271                        prev_effects,
1272                        start_ast_path,
1273                    } => {
1274                        self.effects = prev_effects;
1275                        if self.analyze_mode.is_code_gen() {
1276                            self.effects.push(Effect::Unreachable { start_ast_path });
1277                        }
1278                        always_returns = true;
1279                    }
1280                    EarlyReturn::Conditional {
1281                        prev_effects,
1282                        start_ast_path,
1283                        condition,
1284                        then,
1285                        r#else,
1286                        condition_ast_path,
1287                        span,
1288                        early_return_condition_value,
1289                    } => {
1290                        let block = Box::new(EffectsBlock {
1291                            effects: take(&mut self.effects),
1292                            range: AstPathRange::StartAfter(start_ast_path),
1293                        });
1294                        self.effects = prev_effects;
1295                        let kind = match (then, r#else, early_return_condition_value) {
1296                            (None, None, false) => ConditionalKind::If { then: block },
1297                            (None, None, true) => ConditionalKind::IfElseMultiple {
1298                                then: vec![block],
1299                                r#else: vec![],
1300                            },
1301                            (Some(then), None, false) => ConditionalKind::IfElseMultiple {
1302                                then: vec![then, block],
1303                                r#else: vec![],
1304                            },
1305                            (Some(then), None, true) => ConditionalKind::IfElse {
1306                                then,
1307                                r#else: block,
1308                            },
1309                            (Some(then), Some(r#else), false) => ConditionalKind::IfElseMultiple {
1310                                then: vec![then, block],
1311                                r#else: vec![r#else],
1312                            },
1313                            (Some(then), Some(r#else), true) => ConditionalKind::IfElseMultiple {
1314                                then: vec![then],
1315                                r#else: vec![r#else, block],
1316                            },
1317                            (None, Some(r#else), false) => ConditionalKind::IfElse {
1318                                then: block,
1319                                r#else,
1320                            },
1321                            (None, Some(r#else), true) => ConditionalKind::IfElseMultiple {
1322                                then: vec![],
1323                                r#else: vec![r#else, block],
1324                            },
1325                        };
1326                        self.effects.push(Effect::Conditional {
1327                            condition,
1328                            kind: Box::new(kind),
1329                            ast_path: condition_ast_path,
1330                            span,
1331                        })
1332                    }
1333                }
1334            }
1335            always_returns
1336        }
1337
1338        /// Runs `visitor` with the current top level declaration identifier
1339        pub(super) fn enter_top_level_decl<T>(
1340            &mut self,
1341            name: &Ident,
1342            visitor: impl FnOnce(&mut Self) -> T,
1343        ) -> T {
1344            let is_top_level_fn = self.state.cur_top_level_decl_name.is_none();
1345            if is_top_level_fn {
1346                self.state.cur_top_level_decl_name = Some(name.to_id());
1347            }
1348            let result = visitor(self);
1349            if is_top_level_fn {
1350                self.state.cur_top_level_decl_name = None;
1351            }
1352            result
1353        }
1354    }
1355}
1356
1357pub fn as_parent_path(ast_path: &AstNodePath<AstParentNodeRef<'_>>) -> Vec<AstParentKind> {
1358    ast_path.iter().map(|n| n.kind()).collect()
1359}
1360
1361/// Extracts export names from usage patterns on a dynamic import.
1362///
1363/// Supports two patterns:
1364/// 1. Destructuring: `const { cat, dog } = await import('./lib')` → `PartialNamespaceObject(["cat",
1365///    "dog"])`
1366/// 2. Member access: `(await import('./lib')).cat` → `PartialNamespaceObject(["cat"])`
1367///
1368/// For `const {} = await import('./lib')`, returns `Evaluation`.
1369/// For `const mod = await import('./lib')` or non-recognized patterns, returns `All`.
1370/// For patterns with rest elements or computed keys, returns `All` (conservative).
1371fn extract_dynamic_import_export_usage(
1372    ast_path: &AstNodePath<AstParentNodeRef<'_>>,
1373) -> ExportUsage {
1374    // Walk up the AST path from the import() call to find usage patterns that
1375    // reveal which exports are needed. Supported patterns:
1376    //
1377    // 1. Destructured await:     const { a, b } = await import('./lib')
1378    // 2. Member access on await: (await import('./lib')).a
1379    // 3. Arrow .then() callback: import('./lib').then(({ a, b }) => {})
1380    // 4. Function .then() callback: import('./lib').then(function({ a, b }) {})
1381    //
1382    // Only allow Expr wrappers, AwaitExpr, ParenExpr, and Callee as intermediate
1383    // nodes to ensure the import result flows directly into the usage site.
1384    let mut seen_await = false;
1385    let mut seen_then = false;
1386    let names = 'outer: {
1387        for node_ref in ast_path.iter().rev() {
1388            match node_ref {
1389                // Only extract names when `await` is present — without await, the
1390                // destructuring targets the Promise, not the module namespace.
1391                AstParentNodeRef::VarDeclarator(decl, VarDeclaratorField::Init) if seen_await => {
1392                    break 'outer extract_names_from_object_pat(&decl.name);
1393                }
1394                // Member access: (await import('./lib')).someExport
1395                // Only valid after AwaitExpr — without await, it's a Promise method
1396                AstParentNodeRef::MemberExpr(member, MemberExprField::Obj) if seen_await => {
1397                    break 'outer extract_name_from_member_prop(&member.prop);
1398                }
1399                // Promise .then() pattern: import('./lib').then(({ name }) => {})
1400                // Without await, check if this is a .then() call and extract from callback
1401                AstParentNodeRef::MemberExpr(member, MemberExprField::Obj) => {
1402                    if matches!(&member.prop, MemberProp::Ident(ident) if &*ident.sym == "then") {
1403                        seen_then = true;
1404                        continue;
1405                    }
1406                    break 'outer None;
1407                }
1408                // After seeing .then MemberExpr, the next CallExpr is the .then() call
1409                // — extract destructured parameter names from the first callback argument
1410                AstParentNodeRef::CallExpr(call, CallExprField::Callee) if seen_then => {
1411                    break 'outer extract_names_from_then_callback(call);
1412                }
1413                AstParentNodeRef::AwaitExpr(_, AwaitExprField::Arg) => {
1414                    seen_await = true;
1415                    continue;
1416                }
1417                // Allowed intermediate nodes
1418                AstParentNodeRef::Expr(..)
1419                | AstParentNodeRef::ParenExpr(_, ParenExprField::Expr)
1420                | AstParentNodeRef::Callee(_, CalleeField::Expr) => continue,
1421                // Any other node means the import is nested in something else
1422                _ => break 'outer None,
1423            }
1424        }
1425        None
1426    };
1427    match names {
1428        Some(names) if names.is_empty() => ExportUsage::Evaluation,
1429        Some(names) => ExportUsage::PartialNamespaceObject(names),
1430        None => ExportUsage::All,
1431    }
1432}
1433
1434/// Extract export names from the first argument of a `.then()` callback.
1435/// Supports both arrow functions and function expressions with destructured
1436/// first parameters.
1437fn extract_names_from_then_callback(call: &CallExpr) -> Option<SmallVec<[RcStr; 1]>> {
1438    let first_arg = call.args.first()?;
1439    if first_arg.spread.is_some() {
1440        return None;
1441    }
1442    match &*first_arg.expr {
1443        // Arrow function: import('./lib').then(({ name }) => {})
1444        Expr::Arrow(arrow) => {
1445            let first_param = arrow.params.first()?;
1446            extract_names_from_object_pat(first_param)
1447        }
1448        // Function expression: import('./lib').then(function({ name }) {})
1449        Expr::Fn(fn_expr) => {
1450            let first_param = fn_expr.function.params.first()?;
1451            extract_names_from_object_pat(&first_param.pat)
1452        }
1453        _ => None,
1454    }
1455}
1456
1457fn extract_name_from_member_prop(prop: &MemberProp) -> Option<SmallVec<[RcStr; 1]>> {
1458    match prop {
1459        MemberProp::Ident(ident) => Some(SmallVec::from_buf([ident.sym.as_str().into()])),
1460        MemberProp::Computed(ComputedPropName {
1461            expr: box Expr::Lit(Lit::Str(s)),
1462            ..
1463        }) => s.value.as_str().map(|v| SmallVec::from_buf([v.into()])),
1464        _ => None,
1465    }
1466}
1467
1468fn extract_names_from_object_pat(pat: &Pat) -> Option<SmallVec<[RcStr; 1]>> {
1469    let Pat::Object(obj_pat) = pat else {
1470        return None;
1471    };
1472    let mut names = SmallVec::new();
1473    for prop in &obj_pat.props {
1474        match prop {
1475            ObjectPatProp::KeyValue(kv) => match &kv.key {
1476                PropName::Ident(ident) => names.push(ident.sym.as_str().into()),
1477                PropName::Str(s) => match s.value.as_str() {
1478                    Some(str_val) => names.push(str_val.into()),
1479                    None => return None, // non-UTF-8 string key
1480                },
1481                _ => return None, // computed key, can't determine statically
1482            },
1483            ObjectPatProp::Assign(assign) => {
1484                names.push(assign.key.sym.as_str().into());
1485            }
1486            ObjectPatProp::Rest(_) => return None, // rest pattern means all exports needed
1487        }
1488    }
1489    Some(names)
1490}
1491
1492pub fn as_parent_path_with(
1493    ast_path: &AstNodePath<AstParentNodeRef<'_>>,
1494    additional: AstParentKind,
1495) -> Vec<AstParentKind> {
1496    ast_path
1497        .iter()
1498        .map(|n| n.kind())
1499        .chain([additional])
1500        .collect()
1501}
1502
1503enum CallOrNewExpr<'ast> {
1504    Call(&'ast CallExpr),
1505    New(&'ast NewExpr),
1506}
1507impl CallOrNewExpr<'_> {
1508    fn as_call(&self) -> Option<&CallExpr> {
1509        match *self {
1510            CallOrNewExpr::Call(n) => Some(n),
1511            CallOrNewExpr::New(_) => None,
1512        }
1513    }
1514    fn as_new(&self) -> Option<&NewExpr> {
1515        match *self {
1516            CallOrNewExpr::Call(_) => None,
1517            CallOrNewExpr::New(n) => Some(n),
1518        }
1519    }
1520}
1521
1522impl Analyzer<'_> {
1523    fn add_value(&mut self, id: Id, value: JsValue) {
1524        if is_unresolved_id(&id, self.eval_context.unresolved_mark) {
1525            self.data.free_var_ids.insert(id.0.clone(), id.clone());
1526        }
1527
1528        let kind = if self.is_in_fn() {
1529            AssignmentScope::Function
1530        } else {
1531            AssignmentScope::ModuleEval
1532        };
1533        if let Some(prev) = self.data.values.get_mut(&id) {
1534            prev.add_alt(value, kind);
1535        } else {
1536            self.data.values.insert(id, VarMeta::new(value, kind));
1537        }
1538        // TODO(kdy1): We may need to report an error for this.
1539        // Variables declared with `var` are hoisted, but using undefined as its
1540        // value does not seem like a good idea.
1541    }
1542
1543    fn add_value_from_expr(&mut self, id: Id, value: &Expr) {
1544        let value = self.eval_context.eval(value);
1545
1546        self.add_value(id, value);
1547    }
1548
1549    fn add_effect(&mut self, effect: Effect) {
1550        self.effects.push(effect);
1551    }
1552
1553    fn check_iife<'ast: 'r, 'r>(
1554        &mut self,
1555        n: &'ast CallExpr,
1556        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
1557    ) -> bool {
1558        fn unparen<'ast: 'r, 'r, T>(
1559            expr: &'ast Expr,
1560            ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
1561            f: impl FnOnce(&'ast Expr, &mut AstNodePath<AstParentNodeRef<'r>>) -> T,
1562        ) -> T {
1563            if let Some(inner_expr) = expr.as_paren() {
1564                let mut ast_path =
1565                    ast_path.with_guard(AstParentNodeRef::Expr(expr, ExprField::Paren));
1566                let mut ast_path = ast_path.with_guard(AstParentNodeRef::ParenExpr(
1567                    inner_expr,
1568                    ParenExprField::Expr,
1569                ));
1570                unparen(&inner_expr.expr, &mut ast_path, f)
1571            } else {
1572                f(expr, ast_path)
1573            }
1574        }
1575
1576        if n.args.iter().any(|arg| arg.spread.is_some()) {
1577            return false;
1578        }
1579
1580        let Some(expr) = n.callee.as_expr() else {
1581            return false;
1582        };
1583
1584        let fn_expr = {
1585            let mut ast_path =
1586                ast_path.with_guard(AstParentNodeRef::CallExpr(n, CallExprField::Callee));
1587            let mut ast_path =
1588                ast_path.with_guard(AstParentNodeRef::Callee(&n.callee, CalleeField::Expr));
1589            unparen(expr, &mut ast_path, |expr, ast_path| match expr {
1590                Expr::Fn(fn_expr @ FnExpr { function, ident }) => {
1591                    let mut ast_path =
1592                        ast_path.with_guard(AstParentNodeRef::Expr(expr, ExprField::Fn));
1593                    {
1594                        let mut ast_path = ast_path
1595                            .with_guard(AstParentNodeRef::FnExpr(fn_expr, FnExprField::Ident));
1596                        self.visit_opt_ident(ident, &mut ast_path);
1597
1598                        // We cannot analyze recursive IIFE
1599                        if let Some(ident) = ident
1600                            && contains_ident_ref(&function.body, ident)
1601                        {
1602                            return false;
1603                        }
1604                    }
1605
1606                    {
1607                        let mut ast_path = ast_path
1608                            .with_guard(AstParentNodeRef::FnExpr(fn_expr, FnExprField::Function));
1609                        // We don't handle the value of the function here, though we could to better
1610                        // model the value of this 'call'
1611                        self.enter_fn(&**function, |this| {
1612                            this.handle_iife_function(function, &mut ast_path, &n.args);
1613                        });
1614                    }
1615
1616                    true
1617                }
1618
1619                Expr::Arrow(arrow_expr) => {
1620                    let mut ast_path =
1621                        ast_path.with_guard(AstParentNodeRef::Expr(expr, ExprField::Arrow));
1622                    let args = &n.args;
1623                    // We don't handle the value of the function here, though we could to better
1624                    // model the value of this 'call'
1625                    self.enter_fn(arrow_expr, |this| {
1626                        this.handle_iife_arrow(arrow_expr, args, &mut ast_path);
1627                    });
1628                    true
1629                }
1630                _ => false,
1631            })
1632        };
1633
1634        if !fn_expr {
1635            return false;
1636        }
1637
1638        let mut ast_path = ast_path.with_guard(AstParentNodeRef::CallExpr(
1639            n,
1640            CallExprField::Args(usize::MAX),
1641        ));
1642
1643        self.visit_expr_or_spreads(&n.args, &mut ast_path);
1644
1645        true
1646    }
1647
1648    fn handle_iife_arrow<'ast: 'r, 'r>(
1649        &mut self,
1650        arrow_expr: &'ast ArrowExpr,
1651        args: &[ExprOrSpread],
1652        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
1653    ) {
1654        let ArrowExpr {
1655            params,
1656            body,
1657            is_async: _,
1658            is_generator: _,
1659            return_type,
1660            span: _,
1661            type_params,
1662            ctxt: _,
1663        } = arrow_expr;
1664        let mut iter = args.iter();
1665        for (i, param) in params.iter().enumerate() {
1666            let mut ast_path = ast_path.with_guard(AstParentNodeRef::ArrowExpr(
1667                arrow_expr,
1668                ArrowExprField::Params(i),
1669            ));
1670            let pat_value = iter.next().map(|arg| self.eval_context.eval(&arg.expr));
1671            self.with_pat_value(pat_value, |this| this.visit_pat(param, &mut ast_path));
1672        }
1673        {
1674            let mut ast_path = ast_path.with_guard(AstParentNodeRef::ArrowExpr(
1675                arrow_expr,
1676                ArrowExprField::Body,
1677            ));
1678            self.visit_block_stmt_or_expr(body, &mut ast_path);
1679        }
1680
1681        {
1682            let mut ast_path = ast_path.with_guard(AstParentNodeRef::ArrowExpr(
1683                arrow_expr,
1684                ArrowExprField::ReturnType,
1685            ));
1686            self.visit_opt_ts_type_ann(return_type, &mut ast_path);
1687        }
1688
1689        {
1690            let mut ast_path = ast_path.with_guard(AstParentNodeRef::ArrowExpr(
1691                arrow_expr,
1692                ArrowExprField::TypeParams,
1693            ));
1694            self.visit_opt_ts_type_param_decl(type_params, &mut ast_path);
1695        }
1696    }
1697
1698    fn handle_iife_function<'ast: 'r, 'r>(
1699        &mut self,
1700        function: &'ast Function,
1701        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
1702        args: &[ExprOrSpread],
1703    ) {
1704        let mut iter = args.iter();
1705        let Function {
1706            body,
1707            decorators,
1708            is_async: _,
1709            is_generator: _,
1710            params,
1711            return_type,
1712            span: _,
1713            type_params,
1714            ctxt: _,
1715        } = function;
1716        for (i, param) in params.iter().enumerate() {
1717            let mut ast_path = ast_path.with_guard(AstParentNodeRef::Function(
1718                function,
1719                FunctionField::Params(i),
1720            ));
1721            if let Some(arg) = iter.next() {
1722                self.with_pat_value(Some(self.eval_context.eval(&arg.expr)), |this| {
1723                    this.visit_param(param, &mut ast_path)
1724                });
1725            } else {
1726                self.visit_param(param, &mut ast_path);
1727            }
1728        }
1729
1730        {
1731            let mut ast_path =
1732                ast_path.with_guard(AstParentNodeRef::Function(function, FunctionField::Body));
1733
1734            self.visit_opt_block_stmt(body, &mut ast_path);
1735        }
1736
1737        {
1738            let mut ast_path = ast_path.with_guard(AstParentNodeRef::Function(
1739                function,
1740                FunctionField::Decorators(usize::MAX),
1741            ));
1742
1743            self.visit_decorators(decorators, &mut ast_path);
1744        }
1745
1746        {
1747            let mut ast_path = ast_path.with_guard(AstParentNodeRef::Function(
1748                function,
1749                FunctionField::ReturnType,
1750            ));
1751
1752            self.visit_opt_ts_type_ann(return_type, &mut ast_path);
1753        }
1754
1755        {
1756            let mut ast_path = ast_path.with_guard(AstParentNodeRef::Function(
1757                function,
1758                FunctionField::TypeParams,
1759            ));
1760
1761            self.visit_opt_ts_type_param_decl(type_params, &mut ast_path);
1762        }
1763    }
1764
1765    fn check_call_expr_for_effects<'ast: 'r, 'n, 'r>(
1766        &mut self,
1767        callee: &'n Callee,
1768        args: impl Iterator<Item = &'ast ExprOrSpread>,
1769        span: Span,
1770        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
1771        n: CallOrNewExpr<'ast>,
1772    ) {
1773        let new = n.as_new().is_some();
1774        let args = args
1775            .enumerate()
1776            .map(|(i, arg)| {
1777                let mut ast_path = ast_path.with_guard(match n {
1778                    CallOrNewExpr::Call(n) => AstParentNodeRef::CallExpr(n, CallExprField::Args(i)),
1779                    CallOrNewExpr::New(n) => AstParentNodeRef::NewExpr(n, NewExprField::Args(i)),
1780                });
1781                if arg.spread.is_none() {
1782                    let value = self.eval_context.eval(&arg.expr);
1783
1784                    let block_path = match &*arg.expr {
1785                        Expr::Fn(FnExpr { .. }) => {
1786                            let mut path = as_parent_path(&ast_path);
1787                            path.push(AstParentKind::ExprOrSpread(ExprOrSpreadField::Expr));
1788                            path.push(AstParentKind::Expr(ExprField::Fn));
1789                            path.push(AstParentKind::FnExpr(FnExprField::Function));
1790                            path.push(AstParentKind::Function(FunctionField::Body));
1791                            Some(path)
1792                        }
1793                        Expr::Arrow(ArrowExpr {
1794                            body: box BlockStmtOrExpr::BlockStmt(_),
1795                            ..
1796                        }) => {
1797                            let mut path = as_parent_path(&ast_path);
1798                            path.push(AstParentKind::ExprOrSpread(ExprOrSpreadField::Expr));
1799                            path.push(AstParentKind::Expr(ExprField::Arrow));
1800                            path.push(AstParentKind::ArrowExpr(ArrowExprField::Body));
1801                            path.push(AstParentKind::BlockStmtOrExpr(
1802                                BlockStmtOrExprField::BlockStmt,
1803                            ));
1804                            Some(path)
1805                        }
1806                        Expr::Arrow(ArrowExpr {
1807                            body: box BlockStmtOrExpr::Expr(_),
1808                            ..
1809                        }) => {
1810                            let mut path = as_parent_path(&ast_path);
1811                            path.push(AstParentKind::ExprOrSpread(ExprOrSpreadField::Expr));
1812                            path.push(AstParentKind::Expr(ExprField::Arrow));
1813                            path.push(AstParentKind::ArrowExpr(ArrowExprField::Body));
1814                            path.push(AstParentKind::BlockStmtOrExpr(BlockStmtOrExprField::Expr));
1815                            Some(path)
1816                        }
1817                        _ => None,
1818                    };
1819                    if let Some(path) = block_path {
1820                        let old_effects = take(&mut self.effects);
1821                        arg.visit_with_ast_path(self, &mut ast_path);
1822                        let effects = replace(&mut self.effects, old_effects);
1823                        EffectArg::Closure(
1824                            value,
1825                            Box::new(EffectsBlock {
1826                                effects,
1827                                range: AstPathRange::Exact(path),
1828                            }),
1829                        )
1830                    } else {
1831                        arg.visit_with_ast_path(self, &mut ast_path);
1832                        EffectArg::Value(value)
1833                    }
1834                } else {
1835                    arg.visit_with_ast_path(self, &mut ast_path);
1836                    EffectArg::Spread
1837                }
1838            })
1839            .collect();
1840
1841        match callee {
1842            Callee::Import(_) => {
1843                // Prefer webpackExports/turbopackExports comment (authoritative when present)
1844                let attrs = self.eval_context.imports.get_attributes(span);
1845                let export_usage = if let Some(names) = &attrs.export_names {
1846                    if names.is_empty() {
1847                        ExportUsage::Evaluation
1848                    } else {
1849                        ExportUsage::PartialNamespaceObject(names.clone())
1850                    }
1851                } else {
1852                    // Fall back to AST path walking (works when import is not wrapped)
1853                    extract_dynamic_import_export_usage(ast_path)
1854                };
1855                self.add_effect(Effect::DynamicImport {
1856                    args,
1857                    ast_path: as_parent_path(ast_path),
1858                    span,
1859                    in_try: self.is_in_try(),
1860                    export_usage,
1861                });
1862            }
1863            Callee::Expr(box expr) => {
1864                if let Expr::Member(MemberExpr { obj, prop, .. }) = unparen(expr) {
1865                    let obj_value = Box::new(self.eval_context.eval(obj));
1866                    let prop_value = match prop {
1867                        // TODO avoid clone
1868                        MemberProp::Ident(i) => Box::new(i.sym.clone().into()),
1869                        MemberProp::PrivateName(_) => Box::new(JsValue::unknown_empty(
1870                            false,
1871                            "private names in member expressions are not supported",
1872                        )),
1873                        MemberProp::Computed(ComputedPropName { expr, .. }) => {
1874                            Box::new(self.eval_context.eval(expr))
1875                        }
1876                    };
1877                    self.add_effect(Effect::MemberCall {
1878                        obj: obj_value,
1879                        prop: prop_value,
1880                        args,
1881                        ast_path: as_parent_path(ast_path),
1882                        span,
1883                        in_try: self.is_in_try(),
1884                        new,
1885                    });
1886                } else {
1887                    let fn_value = Box::new(self.eval_context.eval(expr));
1888                    self.add_effect(Effect::Call {
1889                        func: fn_value,
1890                        args,
1891                        ast_path: as_parent_path(ast_path),
1892                        span,
1893                        in_try: self.is_in_try(),
1894                        new,
1895                    });
1896                }
1897            }
1898            Callee::Super(_) => self.add_effect(Effect::Call {
1899                func: Box::new(
1900                    self.eval_context
1901                        // Unwrap because `new super(..)` isn't valid anyway
1902                        .eval(&Expr::Call(n.as_call().unwrap().clone())),
1903                ),
1904                args,
1905                ast_path: as_parent_path(ast_path),
1906                span,
1907                in_try: self.is_in_try(),
1908                new,
1909            }),
1910        }
1911    }
1912
1913    fn check_member_expr_for_effects<'ast: 'r, 'r>(
1914        &mut self,
1915        member_expr: &'ast MemberExpr,
1916        ast_path: &AstNodePath<AstParentNodeRef<'r>>,
1917    ) {
1918        if !self.analyze_mode.is_code_gen() {
1919            return;
1920        }
1921
1922        let obj_value = Box::new(self.eval_context.eval(&member_expr.obj));
1923        let prop_value = match &member_expr.prop {
1924            // TODO avoid clone
1925            MemberProp::Ident(i) => Box::new(i.sym.clone().into()),
1926            MemberProp::PrivateName(_) => {
1927                return;
1928            }
1929            MemberProp::Computed(ComputedPropName { expr, .. }) => {
1930                Box::new(self.eval_context.eval(expr))
1931            }
1932        };
1933        self.add_effect(Effect::Member {
1934            obj: obj_value,
1935            prop: prop_value,
1936            ast_path: as_parent_path(ast_path),
1937            span: member_expr.span(),
1938        });
1939    }
1940}
1941
1942impl VisitAstPath for Analyzer<'_> {
1943    fn visit_import_specifier<'ast: 'r, 'r>(
1944        &mut self,
1945        _import_specifier: &'ast ImportSpecifier,
1946        _ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
1947    ) {
1948        // Skip these nodes entirely: We gather imports in a separate pass
1949    }
1950
1951    fn visit_assign_expr<'ast: 'r, 'r>(
1952        &mut self,
1953        n: &'ast AssignExpr,
1954        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
1955    ) {
1956        // LHS
1957        {
1958            let mut ast_path =
1959                ast_path.with_guard(AstParentNodeRef::AssignExpr(n, AssignExprField::Left));
1960
1961            let pat_value = match (n.op, n.left.as_ident()) {
1962                (AssignOp::Assign, _) => self.eval_context.eval(&n.right),
1963                (AssignOp::AndAssign | AssignOp::OrAssign | AssignOp::NullishAssign, Some(_)) => {
1964                    // We can handle the right value as alternative to the existing value
1965                    self.eval_context.eval(&n.right)
1966                }
1967                (AssignOp::AddAssign, Some(key)) => {
1968                    let left = self.eval_context.eval(&Expr::Ident(key.clone().into()));
1969                    let right = self.eval_context.eval(&n.right);
1970                    JsValue::add(vec![left, right])
1971                }
1972                _ => JsValue::unknown_empty(true, "unsupported assign operation"),
1973            };
1974            self.with_pat_value(Some(pat_value), |this| {
1975                n.left.visit_children_with_ast_path(this, &mut ast_path)
1976            });
1977        }
1978
1979        // RHS
1980        {
1981            let mut ast_path =
1982                ast_path.with_guard(AstParentNodeRef::AssignExpr(n, AssignExprField::Right));
1983            self.visit_expr(&n.right, &mut ast_path);
1984        }
1985    }
1986
1987    fn visit_update_expr<'ast: 'r, 'r>(
1988        &mut self,
1989        n: &'ast UpdateExpr,
1990        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
1991    ) {
1992        if let Some(key) = n.arg.as_ident() {
1993            self.add_value(
1994                key.to_id(),
1995                JsValue::unknown_empty(true, "updated with update expression"),
1996            );
1997        }
1998
1999        let mut ast_path =
2000            ast_path.with_guard(AstParentNodeRef::UpdateExpr(n, UpdateExprField::Arg));
2001        self.visit_expr(&n.arg, &mut ast_path);
2002    }
2003
2004    fn visit_call_expr<'ast: 'r, 'r>(
2005        &mut self,
2006        n: &'ast CallExpr,
2007        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
2008    ) {
2009        // We handle `define(function (require) {})` here.
2010        if let Callee::Expr(callee) = &n.callee
2011            && n.args.len() == 1
2012            && let Some(require_var_id) = extract_var_from_umd_factory(callee, &n.args)
2013        {
2014            self.add_value(
2015                require_var_id,
2016                JsValue::unknown_if(
2017                    self.eval_context
2018                        .imports
2019                        .get_attributes(n.callee.span())
2020                        .ignore,
2021                    JsValue::WellKnownFunction(WellKnownFunctionKind::Require),
2022                    true,
2023                    "ignored require",
2024                ),
2025            );
2026        }
2027
2028        if self.check_iife(n, ast_path) {
2029            return;
2030        }
2031
2032        // special behavior of IIFEs
2033        {
2034            let mut ast_path =
2035                ast_path.with_guard(AstParentNodeRef::CallExpr(n, CallExprField::Callee));
2036            n.callee.visit_with_ast_path(self, &mut ast_path);
2037        }
2038
2039        self.check_call_expr_for_effects(
2040            &n.callee,
2041            n.args.iter(),
2042            n.span(),
2043            ast_path,
2044            CallOrNewExpr::Call(n),
2045        );
2046    }
2047
2048    fn visit_new_expr<'ast: 'r, 'r>(
2049        &mut self,
2050        n: &'ast NewExpr,
2051        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
2052    ) {
2053        {
2054            let mut ast_path =
2055                ast_path.with_guard(AstParentNodeRef::NewExpr(n, NewExprField::Callee));
2056            n.callee.visit_with_ast_path(self, &mut ast_path);
2057        }
2058
2059        self.check_call_expr_for_effects(
2060            &Callee::Expr(n.callee.clone()),
2061            n.args.iter().flatten(),
2062            n.span(),
2063            ast_path,
2064            CallOrNewExpr::New(n),
2065        );
2066    }
2067
2068    fn visit_member_expr<'ast: 'r, 'r>(
2069        &mut self,
2070        member_expr: &'ast MemberExpr,
2071        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
2072    ) {
2073        self.check_member_expr_for_effects(member_expr, ast_path);
2074        member_expr.visit_children_with_ast_path(self, ast_path);
2075    }
2076
2077    fn visit_expr<'ast: 'r, 'r>(
2078        &mut self,
2079        n: &'ast Expr,
2080        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
2081    ) {
2082        self.with_decl_kind(None, |this| {
2083            n.visit_children_with_ast_path(this, ast_path);
2084        });
2085    }
2086
2087    fn visit_params<'ast: 'r, 'r>(
2088        &mut self,
2089        n: &'ast [Param],
2090        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
2091    ) {
2092        let cur_fn_ident = self.cur_fn_ident();
2093        for (index, p) in n.iter().enumerate() {
2094            self.with_pat_value(Some(JsValue::Argument(cur_fn_ident, index)), |this| {
2095                let mut ast_path = ast_path.with_index_guard(index);
2096                p.visit_with_ast_path(this, &mut ast_path);
2097            });
2098        }
2099    }
2100
2101    fn visit_param<'ast: 'r, 'r>(
2102        &mut self,
2103        n: &'ast Param,
2104        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
2105    ) {
2106        let Param {
2107            decorators,
2108            pat,
2109            span: _,
2110        } = n;
2111        self.with_decl_kind(None, |this| {
2112            // Decorators don't have access to the parameter values, so omit them
2113            this.with_pat_value(None, |this| {
2114                let mut ast_path = ast_path.with_guard(AstParentNodeRef::Param(
2115                    n,
2116                    ParamField::Decorators(usize::MAX),
2117                ));
2118                this.visit_decorators(decorators, &mut ast_path);
2119            });
2120            {
2121                let mut ast_path = ast_path.with_guard(AstParentNodeRef::Param(n, ParamField::Pat));
2122                this.visit_pat(pat, &mut ast_path);
2123            }
2124        });
2125    }
2126
2127    fn visit_fn_decl<'ast: 'r, 'r>(
2128        &mut self,
2129        decl: &'ast FnDecl,
2130        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
2131    ) {
2132        let fn_value = self.enter_top_level_decl(&decl.ident, |this| {
2133            this.enter_fn(&*decl.function, |this| {
2134                decl.visit_children_with_ast_path(this, ast_path);
2135            })
2136        });
2137
2138        // Take all effects produced by the function and move them to hoisted effects since
2139        // function declarations are hoisted.
2140        // This accounts for the fact that even with `if (true) { return f} function f() {} ` `f` is
2141        // hoisted earlier of the condition. so we still need to process effects for it.
2142        // TODO(lukesandberg): shouldn't this just be the effects associated with the function.
2143        self.hoisted_effects.append(&mut self.effects);
2144
2145        self.add_value(decl.ident.to_id(), fn_value);
2146    }
2147
2148    fn visit_fn_expr<'ast: 'r, 'r>(
2149        &mut self,
2150        expr: &'ast FnExpr,
2151        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
2152    ) {
2153        let fn_value = self.enter_fn(&*expr.function, |this| {
2154            expr.visit_children_with_ast_path(this, ast_path);
2155        });
2156        if let Some(ident) = &expr.ident {
2157            self.add_value(ident.to_id(), fn_value);
2158        } else {
2159            self.add_value(
2160                (
2161                    format!("*anonymous function {}*", expr.function.span.lo.0).into(),
2162                    SyntaxContext::empty(),
2163                ),
2164                fn_value,
2165            );
2166        }
2167    }
2168
2169    fn visit_arrow_expr<'ast: 'r, 'r>(
2170        &mut self,
2171        expr: &'ast ArrowExpr,
2172        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
2173    ) {
2174        let fn_value = self.enter_fn(expr, |this| {
2175            let fn_id = this.cur_fn_ident();
2176            for (index, p) in expr.params.iter().enumerate() {
2177                this.with_pat_value(Some(JsValue::Argument(fn_id, index)), |this| {
2178                    let mut ast_path = ast_path.with_guard(AstParentNodeRef::ArrowExpr(
2179                        expr,
2180                        ArrowExprField::Params(index),
2181                    ));
2182                    p.visit_with_ast_path(this, &mut ast_path);
2183                });
2184            }
2185
2186            {
2187                let mut ast_path =
2188                    ast_path.with_guard(AstParentNodeRef::ArrowExpr(expr, ArrowExprField::Body));
2189                expr.body.visit_with_ast_path(this, &mut ast_path);
2190                // If body is a single expression treat it as a Block with an return statement
2191                if let BlockStmtOrExpr::Expr(inner_expr) = &*expr.body {
2192                    let implicit_return_value = this.eval_context.eval(inner_expr);
2193                    this.add_return_value(implicit_return_value);
2194                }
2195            }
2196        });
2197        self.add_value(
2198            (
2199                format!("*arrow function {}*", expr.span.lo.0).into(),
2200                SyntaxContext::empty(),
2201            ),
2202            fn_value,
2203        );
2204    }
2205
2206    fn visit_class_decl<'ast: 'r, 'r>(
2207        &mut self,
2208        decl: &'ast ClassDecl,
2209        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
2210    ) {
2211        self.add_value_from_expr(
2212            decl.ident.to_id(),
2213            &Expr::Class(ClassExpr {
2214                ident: Some(decl.ident.clone()),
2215                class: decl.class.clone(),
2216            }),
2217        );
2218        decl.visit_children_with_ast_path(self, ast_path);
2219    }
2220
2221    fn visit_class<'ast: 'r, 'r>(
2222        &mut self,
2223        node: &'ast Class,
2224        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
2225    ) {
2226        self.enter_block(LexicalContext::ClassBody, |this| {
2227            node.visit_children_with_ast_path(this, ast_path);
2228        });
2229    }
2230
2231    fn visit_getter_prop<'ast: 'r, 'r>(
2232        &mut self,
2233        node: &'ast GetterProp,
2234        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
2235    ) {
2236        self.enter_fn(node, |this| {
2237            node.visit_children_with_ast_path(this, ast_path);
2238        });
2239    }
2240
2241    fn visit_setter_prop<'ast: 'r, 'r>(
2242        &mut self,
2243        node: &'ast SetterProp,
2244        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
2245    ) {
2246        self.enter_fn(node, |this| {
2247            node.visit_children_with_ast_path(this, ast_path);
2248        });
2249    }
2250
2251    fn visit_constructor<'ast: 'r, 'r>(
2252        &mut self,
2253        node: &'ast Constructor,
2254        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
2255    ) {
2256        self.enter_fn(node, |this| {
2257            node.visit_children_with_ast_path(this, ast_path);
2258        });
2259    }
2260
2261    fn visit_class_method<'ast: 'r, 'r>(
2262        &mut self,
2263        node: &'ast ClassMethod,
2264        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
2265    ) {
2266        self.enter_fn(&*node.function, |this| {
2267            node.visit_children_with_ast_path(this, ast_path);
2268        });
2269    }
2270
2271    fn visit_private_method<'ast: 'r, 'r>(
2272        &mut self,
2273        node: &'ast PrivateMethod,
2274        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
2275    ) {
2276        self.enter_fn(&*node.function, |this| {
2277            node.visit_children_with_ast_path(this, ast_path);
2278        });
2279    }
2280
2281    fn visit_method_prop<'ast: 'r, 'r>(
2282        &mut self,
2283        node: &'ast MethodProp,
2284        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
2285    ) {
2286        self.enter_fn(&*node.function, |this| {
2287            node.visit_children_with_ast_path(this, ast_path);
2288        });
2289    }
2290
2291    fn visit_var_decl<'ast: 'r, 'r>(
2292        &mut self,
2293        n: &'ast VarDecl,
2294        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
2295    ) {
2296        self.with_decl_kind(Some(n.kind), |this| {
2297            n.visit_children_with_ast_path(this, ast_path);
2298        });
2299    }
2300
2301    fn visit_var_declarator<'ast: 'r, 'r>(
2302        &mut self,
2303        n: &'ast VarDeclarator,
2304        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
2305    ) {
2306        // LHS
2307        {
2308            let mut ast_path =
2309                ast_path.with_guard(AstParentNodeRef::VarDeclarator(n, VarDeclaratorField::Name));
2310
2311            if let Some(var_decl_kind) = self.var_decl_kind()
2312                && let Some(init) = &n.init
2313            {
2314                // For case like
2315                //
2316                // if (shouldRun()) {
2317                //   var x = true;
2318                // }
2319                // if (x) {
2320                // }
2321                //
2322                // The variable `x` is undefined
2323
2324                let should_include_undefined =
2325                    var_decl_kind == VarDeclKind::Var && self.is_in_nested_block_scope();
2326                let init_value = self.eval_context.eval(init);
2327                let pat_value = Some(if should_include_undefined {
2328                    JsValue::alternatives(vec![
2329                        init_value,
2330                        JsValue::Constant(ConstantValue::Undefined),
2331                    ])
2332                } else {
2333                    init_value
2334                });
2335                self.with_pat_value(pat_value, |this| {
2336                    this.visit_pat(&n.name, &mut ast_path);
2337                });
2338            } else {
2339                // Don't use `with_pat_value(None, ...)` here. A `VarDecl` can occur inside of a
2340                // `ForOfStmt` with no `init` field, but still have a `pat_value` set that we want
2341                // to inherit.
2342                self.visit_pat(&n.name, &mut ast_path);
2343            }
2344        }
2345
2346        // RHS
2347        {
2348            let mut ast_path =
2349                ast_path.with_guard(AstParentNodeRef::VarDeclarator(n, VarDeclaratorField::Init));
2350
2351            self.visit_opt_expr(&n.init, &mut ast_path);
2352        }
2353    }
2354
2355    fn visit_for_in_stmt<'ast: 'r, 'r>(
2356        &mut self,
2357        n: &'ast ForInStmt,
2358        ast_path: &mut swc_core::ecma::visit::AstNodePath<'r>,
2359    ) {
2360        {
2361            let mut ast_path =
2362                ast_path.with_guard(AstParentNodeRef::ForInStmt(n, ForInStmtField::Right));
2363            n.right.visit_with_ast_path(self, &mut ast_path);
2364        }
2365
2366        {
2367            let mut ast_path =
2368                ast_path.with_guard(AstParentNodeRef::ForInStmt(n, ForInStmtField::Left));
2369            self.with_pat_value(
2370                // TODO this should really be
2371                // `Some(JsValue::iteratedKeys(Box::new(self.eval_context.eval(&n.right))))`
2372                Some(JsValue::unknown_empty(
2373                    false,
2374                    "for-in variable currently not analyzed",
2375                )),
2376                |this| {
2377                    n.left.visit_with_ast_path(this, &mut ast_path);
2378                },
2379            )
2380        }
2381
2382        let mut ast_path =
2383            ast_path.with_guard(AstParentNodeRef::ForInStmt(n, ForInStmtField::Body));
2384
2385        self.enter_control_flow(|this| {
2386            n.body.visit_with_ast_path(this, &mut ast_path);
2387        });
2388    }
2389
2390    fn visit_for_of_stmt<'ast: 'r, 'r>(
2391        &mut self,
2392        n: &'ast ForOfStmt,
2393        ast_path: &mut swc_core::ecma::visit::AstNodePath<'r>,
2394    ) {
2395        {
2396            let mut ast_path =
2397                ast_path.with_guard(AstParentNodeRef::ForOfStmt(n, ForOfStmtField::Right));
2398            n.right.visit_with_ast_path(self, &mut ast_path);
2399        }
2400
2401        let iterable = self.eval_context.eval(&n.right);
2402
2403        // TODO n.await is ignored (async interables)
2404        self.with_pat_value(Some(JsValue::iterated(Box::new(iterable))), |this| {
2405            let mut ast_path =
2406                ast_path.with_guard(AstParentNodeRef::ForOfStmt(n, ForOfStmtField::Left));
2407            n.left.visit_with_ast_path(this, &mut ast_path);
2408        });
2409
2410        let mut ast_path =
2411            ast_path.with_guard(AstParentNodeRef::ForOfStmt(n, ForOfStmtField::Body));
2412
2413        self.enter_control_flow(|this| {
2414            n.body.visit_with_ast_path(this, &mut ast_path);
2415        });
2416    }
2417
2418    fn visit_for_stmt<'ast: 'r, 'r>(
2419        &mut self,
2420        n: &'ast ForStmt,
2421        ast_path: &mut swc_core::ecma::visit::AstNodePath<'r>,
2422    ) {
2423        {
2424            let mut ast_path =
2425                ast_path.with_guard(AstParentNodeRef::ForStmt(n, ForStmtField::Init));
2426            n.init.visit_with_ast_path(self, &mut ast_path);
2427        }
2428        self.enter_control_flow(|this| {
2429            {
2430                let mut ast_path =
2431                    ast_path.with_guard(AstParentNodeRef::ForStmt(n, ForStmtField::Test));
2432                n.test.visit_with_ast_path(this, &mut ast_path);
2433            }
2434            {
2435                let mut ast_path =
2436                    ast_path.with_guard(AstParentNodeRef::ForStmt(n, ForStmtField::Body));
2437                n.body.visit_with_ast_path(this, &mut ast_path);
2438            }
2439            {
2440                let mut ast_path =
2441                    ast_path.with_guard(AstParentNodeRef::ForStmt(n, ForStmtField::Update));
2442                n.update.visit_with_ast_path(this, &mut ast_path);
2443            }
2444        });
2445    }
2446
2447    fn visit_while_stmt<'ast: 'r, 'r>(
2448        &mut self,
2449        n: &'ast WhileStmt,
2450        ast_path: &mut swc_core::ecma::visit::AstNodePath<'r>,
2451    ) {
2452        // Enter control flow for everything (test and body both repeat in loop iterations)
2453        self.enter_control_flow(|this| {
2454            {
2455                let mut ast_path =
2456                    ast_path.with_guard(AstParentNodeRef::WhileStmt(n, WhileStmtField::Test));
2457                n.test.visit_with_ast_path(this, &mut ast_path);
2458            }
2459            {
2460                let mut ast_path =
2461                    ast_path.with_guard(AstParentNodeRef::WhileStmt(n, WhileStmtField::Body));
2462                n.body.visit_with_ast_path(this, &mut ast_path);
2463            }
2464        });
2465    }
2466
2467    fn visit_do_while_stmt<'ast: 'r, 'r>(
2468        &mut self,
2469        n: &'ast DoWhileStmt,
2470        ast_path: &mut swc_core::ecma::visit::AstNodePath<'r>,
2471    ) {
2472        // Enter control flow for everything (body and test both are part of loop iterations)
2473        self.enter_control_flow(|this| {
2474            {
2475                let mut ast_path =
2476                    ast_path.with_guard(AstParentNodeRef::DoWhileStmt(n, DoWhileStmtField::Body));
2477                n.body.visit_with_ast_path(this, &mut ast_path);
2478            }
2479            {
2480                let mut ast_path =
2481                    ast_path.with_guard(AstParentNodeRef::DoWhileStmt(n, DoWhileStmtField::Test));
2482                n.test.visit_with_ast_path(this, &mut ast_path);
2483            }
2484        });
2485    }
2486
2487    fn visit_simple_assign_target<'ast: 'r, 'r>(
2488        &mut self,
2489        n: &'ast SimpleAssignTarget,
2490        ast_path: &mut swc_core::ecma::visit::AstNodePath<'r>,
2491    ) {
2492        let value = self.take_pat_value();
2493        if let SimpleAssignTarget::Ident(i) = n {
2494            n.visit_children_with_ast_path(self, ast_path);
2495
2496            self.add_value(
2497                i.to_id(),
2498                value.unwrap_or_else(|| {
2499                    JsValue::unknown(JsValue::Variable(i.to_id()), false, "pattern without value")
2500                }),
2501            );
2502            return;
2503        }
2504
2505        n.visit_children_with_ast_path(self, ast_path);
2506    }
2507
2508    fn visit_assign_target_pat<'ast: 'r, 'r>(
2509        &mut self,
2510        pat: &'ast AssignTargetPat,
2511        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
2512    ) {
2513        let value = self
2514            .take_pat_value()
2515            .unwrap_or_else(|| JsValue::unknown_empty(false, "pattern without value"));
2516        match pat {
2517            AssignTargetPat::Array(arr) => {
2518                let mut ast_path = ast_path.with_guard(AstParentNodeRef::AssignTargetPat(
2519                    pat,
2520                    AssignTargetPatField::Array,
2521                ));
2522                self.handle_array_pat_with_value(arr, value, &mut ast_path);
2523            }
2524            AssignTargetPat::Object(obj) => {
2525                let mut ast_path = ast_path.with_guard(AstParentNodeRef::AssignTargetPat(
2526                    pat,
2527                    AssignTargetPatField::Object,
2528                ));
2529                self.handle_object_pat_with_value(obj, value, &mut ast_path);
2530            }
2531            AssignTargetPat::Invalid(_) => {}
2532        }
2533    }
2534
2535    fn visit_pat<'ast: 'r, 'r>(
2536        &mut self,
2537        pat: &'ast Pat,
2538        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
2539    ) {
2540        let value = self.take_pat_value();
2541        match pat {
2542            Pat::Ident(i) => {
2543                self.add_value(
2544                    i.to_id(),
2545                    value.unwrap_or_else(|| {
2546                        JsValue::unknown(
2547                            JsValue::Variable(i.to_id()),
2548                            false,
2549                            "pattern without value",
2550                        )
2551                    }),
2552                );
2553            }
2554
2555            Pat::Array(arr) => {
2556                let mut ast_path = ast_path.with_guard(AstParentNodeRef::Pat(pat, PatField::Array));
2557                let value =
2558                    value.unwrap_or_else(|| JsValue::unknown_empty(false, "pattern without value"));
2559                self.handle_array_pat_with_value(arr, value, &mut ast_path);
2560            }
2561
2562            Pat::Object(obj) => {
2563                let mut ast_path =
2564                    ast_path.with_guard(AstParentNodeRef::Pat(pat, PatField::Object));
2565                let value =
2566                    value.unwrap_or_else(|| JsValue::unknown_empty(false, "pattern without value"));
2567                self.handle_object_pat_with_value(obj, value, &mut ast_path);
2568            }
2569
2570            _ => pat.visit_children_with_ast_path(self, ast_path),
2571        }
2572    }
2573
2574    fn visit_return_stmt<'ast: 'r, 'r>(
2575        &mut self,
2576        stmt: &'ast ReturnStmt,
2577        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
2578    ) {
2579        stmt.visit_children_with_ast_path(self, ast_path);
2580
2581        // Technically a top level return is illegal, but node supports it due to how module
2582        // wrapping works.
2583        if self.is_in_fn() {
2584            let return_value = stmt
2585                .arg
2586                .as_deref()
2587                .map(|e| self.eval_context.eval(e))
2588                .unwrap_or(JsValue::Constant(ConstantValue::Undefined));
2589
2590            self.add_return_value(return_value);
2591        }
2592
2593        self.add_early_return_always(ast_path);
2594    }
2595
2596    fn visit_ident<'ast: 'r, 'r>(
2597        &mut self,
2598        ident: &'ast Ident,
2599        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
2600    ) {
2601        // Note: `Ident` is (generally) only used for nodes referencing a variable, as it has scope
2602        // information. In other cases (e.g. object literals, properties of member expressions),
2603        // `IdentName` is used instead.
2604
2605        // Note: The `Ident` children of `ImportSpecifier` are not visited because
2606        // `visit_import_specifier` bails out.
2607
2608        // Attempt to add import effects.
2609        if let Some((esm_reference_index, export)) =
2610            self.eval_context.imports.get_binding(&ident.to_id())
2611        {
2612            let usage = self
2613                .data
2614                .import_usages
2615                .entry(esm_reference_index)
2616                .or_default();
2617            if let Some(top_level) = self.state.cur_top_level_decl_name() {
2618                usage.add_usage(top_level);
2619            } else {
2620                usage.make_side_effects();
2621            }
2622
2623            // Optimization: Look for a MemberExpr to see if we only access a few members from the
2624            // module, add those specific effects instead of depending on the entire module.
2625            //
2626            // export.is_none() checks for a namespace import (*).
2627            if export.is_none()
2628                && !self
2629                    .eval_context
2630                    .imports
2631                    .should_import_all(esm_reference_index)
2632                && let Some(AstParentNodeRef::MemberExpr(member, MemberExprField::Obj)) =
2633                    ast_path.get(ast_path.len() - 2)
2634                && let Some(prop) = self.eval_context.eval_member_prop(&member.prop)
2635                && let Some(prop_str) = prop.as_str()
2636            {
2637                // a namespace member access like
2638                // `import * as ns from "..."; ns.exportName`
2639                self.add_effect(Effect::ImportedBinding {
2640                    esm_reference_index,
2641                    export: Some(prop_str.into()),
2642                    // point to the MemberExpression instead
2643                    ast_path: as_parent_path_skip(ast_path, 1),
2644                    span: member.span(),
2645                });
2646            } else {
2647                self.add_effect(Effect::ImportedBinding {
2648                    esm_reference_index,
2649                    export,
2650                    ast_path: as_parent_path(ast_path),
2651                    span: ident.span(),
2652                })
2653            }
2654            return;
2655        }
2656
2657        // If this identifier is free, produce an effect so we can potentially replace it later.
2658        if self.analyze_mode.is_code_gen()
2659            && let JsValue::FreeVar(var) = self.eval_context.eval_ident(ident)
2660        {
2661            // TODO(lukesandberg): we should consider filtering effects here, e.g. there is no
2662            // benefit in an Effect for `window` or `Math`
2663            self.add_effect(Effect::FreeVar {
2664                var,
2665                ast_path: as_parent_path(ast_path),
2666                span: ident.span(),
2667            })
2668        }
2669
2670        if !is_unresolved(ident, self.eval_context.unresolved_mark) {
2671            if let Some(top_level) = self.state.cur_top_level_decl_name() {
2672                if !(ident.sym == top_level.0 && ident.ctxt == top_level.1) {
2673                    self.data
2674                        .decl_usages
2675                        .entry(ident.to_id())
2676                        .or_default()
2677                        .add_usage(top_level);
2678                }
2679            } else {
2680                self.data
2681                    .decl_usages
2682                    .entry(ident.to_id())
2683                    .or_default()
2684                    .make_side_effects();
2685            }
2686        }
2687    }
2688
2689    fn visit_this_expr<'ast: 'r, 'r>(
2690        &mut self,
2691        node: &'ast ThisExpr,
2692        ast_path: &mut swc_core::ecma::visit::AstNodePath<'r>,
2693    ) {
2694        if self.analyze_mode.is_code_gen() && !self.is_this_bound() {
2695            // Otherwise 'this' is free
2696            self.add_effect(Effect::FreeVar {
2697                var: atom!("this"),
2698                ast_path: as_parent_path(ast_path),
2699                span: node.span(),
2700            })
2701        }
2702    }
2703
2704    fn visit_meta_prop_expr<'ast: 'r, 'r>(
2705        &mut self,
2706        expr: &'ast MetaPropExpr,
2707        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
2708    ) {
2709        if self.analyze_mode.is_code_gen() && expr.kind == MetaPropKind::ImportMeta {
2710            // MetaPropExpr also covers `new.target`. Only consider `import.meta`
2711            // an effect.
2712            self.add_effect(Effect::ImportMeta {
2713                span: expr.span,
2714                ast_path: as_parent_path(ast_path),
2715            })
2716        }
2717    }
2718
2719    fn visit_program<'ast: 'r, 'r>(
2720        &mut self,
2721        program: &'ast Program,
2722        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
2723    ) {
2724        self.effects = take(&mut self.data.effects);
2725        self.enter_block(LexicalContext::Block, |this| {
2726            program.visit_children_with_ast_path(this, ast_path);
2727        });
2728        self.effects.append(&mut self.hoisted_effects);
2729        self.data.effects = take(&mut self.effects);
2730    }
2731
2732    fn visit_cond_expr<'ast: 'r, 'r>(
2733        &mut self,
2734        expr: &'ast CondExpr,
2735        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
2736    ) {
2737        {
2738            let mut ast_path =
2739                ast_path.with_guard(AstParentNodeRef::CondExpr(expr, CondExprField::Test));
2740            expr.test.visit_with_ast_path(self, &mut ast_path);
2741        }
2742
2743        let prev_effects = take(&mut self.effects);
2744        let then = {
2745            let mut ast_path =
2746                ast_path.with_guard(AstParentNodeRef::CondExpr(expr, CondExprField::Cons));
2747            expr.cons.visit_with_ast_path(self, &mut ast_path);
2748            Box::new(EffectsBlock {
2749                effects: take(&mut self.effects),
2750                range: AstPathRange::Exact(as_parent_path(&ast_path)),
2751            })
2752        };
2753        let r#else = {
2754            let mut ast_path =
2755                ast_path.with_guard(AstParentNodeRef::CondExpr(expr, CondExprField::Alt));
2756            expr.alt.visit_with_ast_path(self, &mut ast_path);
2757            Box::new(EffectsBlock {
2758                effects: take(&mut self.effects),
2759                range: AstPathRange::Exact(as_parent_path(&ast_path)),
2760            })
2761        };
2762        self.effects = prev_effects;
2763
2764        self.add_conditional_effect(
2765            &expr.test,
2766            ast_path,
2767            AstParentKind::CondExpr(CondExprField::Test),
2768            expr.span(),
2769            ConditionalKind::Ternary { then, r#else },
2770        );
2771    }
2772
2773    fn visit_if_stmt<'ast: 'r, 'r>(
2774        &mut self,
2775        stmt: &'ast IfStmt,
2776        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
2777    ) {
2778        {
2779            let mut ast_path =
2780                ast_path.with_guard(AstParentNodeRef::IfStmt(stmt, IfStmtField::Test));
2781            stmt.test.visit_with_ast_path(self, &mut ast_path);
2782        }
2783        let prev_effects = take(&mut self.effects);
2784        let then_returning;
2785        let then = {
2786            let mut ast_path =
2787                ast_path.with_guard(AstParentNodeRef::IfStmt(stmt, IfStmtField::Cons));
2788            then_returning = self
2789                .enter_control_flow(|this| {
2790                    stmt.cons.visit_with_ast_path(this, &mut ast_path);
2791                })
2792                .1;
2793
2794            Box::new(EffectsBlock {
2795                effects: take(&mut self.effects),
2796                range: AstPathRange::Exact(as_parent_path(&ast_path)),
2797            })
2798        };
2799        let mut else_returning = false;
2800        let r#else = stmt.alt.as_ref().map(|alt| {
2801            let mut ast_path =
2802                ast_path.with_guard(AstParentNodeRef::IfStmt(stmt, IfStmtField::Alt));
2803            else_returning = self
2804                .enter_control_flow(|this| {
2805                    alt.visit_with_ast_path(this, &mut ast_path);
2806                })
2807                .1;
2808
2809            Box::new(EffectsBlock {
2810                effects: take(&mut self.effects),
2811                range: AstPathRange::Exact(as_parent_path(&ast_path)),
2812            })
2813        });
2814        self.effects = prev_effects;
2815        self.add_conditional_if_effect_with_early_return(
2816            &stmt.test,
2817            ast_path,
2818            AstParentKind::IfStmt(IfStmtField::Test),
2819            stmt.span(),
2820            (!then.is_empty()).then_some(then),
2821            r#else.and_then(|block| (!block.is_empty()).then_some(block)),
2822            then_returning,
2823            else_returning,
2824        );
2825    }
2826
2827    fn visit_try_stmt<'ast: 'r, 'r>(
2828        &mut self,
2829        stmt: &'ast TryStmt,
2830        ast_path: &mut swc_core::ecma::visit::AstNodePath<'r>,
2831    ) {
2832        // TODO: if both try and catch return unconditionally, then so does the whole try statement
2833        let prev_effects = take(&mut self.effects);
2834
2835        let mut block = {
2836            let mut ast_path =
2837                ast_path.with_guard(AstParentNodeRef::TryStmt(stmt, TryStmtField::Block));
2838            self.enter_try(|this| {
2839                stmt.block.visit_with_ast_path(this, &mut ast_path);
2840            });
2841
2842            take(&mut self.effects)
2843        };
2844        let mut handler = if let Some(handler) = stmt.handler.as_ref() {
2845            let mut ast_path =
2846                ast_path.with_guard(AstParentNodeRef::TryStmt(stmt, TryStmtField::Handler));
2847            self.enter_control_flow(|this| {
2848                handler.visit_with_ast_path(this, &mut ast_path);
2849            });
2850            take(&mut self.effects)
2851        } else {
2852            vec![]
2853        };
2854        self.effects = prev_effects;
2855        self.effects.append(&mut block);
2856        self.effects.append(&mut handler);
2857        if let Some(finalizer) = stmt.finalizer.as_ref() {
2858            let finally_returns_unconditionally = {
2859                let mut ast_path =
2860                    ast_path.with_guard(AstParentNodeRef::TryStmt(stmt, TryStmtField::Finalizer));
2861                self.enter_control_flow(|this| {
2862                    finalizer.visit_with_ast_path(this, &mut ast_path);
2863                })
2864                .1
2865            };
2866            // If a finally block early returns the parent block does too.
2867            if finally_returns_unconditionally {
2868                self.add_early_return_always(ast_path);
2869            }
2870        };
2871    }
2872
2873    fn visit_switch_case<'ast: 'r, 'r>(
2874        &mut self,
2875        case: &'ast SwitchCase,
2876        ast_path: &mut swc_core::ecma::visit::AstNodePath<'r>,
2877    ) {
2878        let prev_effects = take(&mut self.effects);
2879        self.enter_control_flow(|this| {
2880            case.visit_children_with_ast_path(this, ast_path);
2881        });
2882        let mut effects = take(&mut self.effects);
2883        self.effects = prev_effects;
2884        self.effects.append(&mut effects);
2885    }
2886
2887    fn visit_block_stmt<'ast: 'r, 'r>(
2888        &mut self,
2889        n: &'ast BlockStmt,
2890        ast_path: &mut swc_core::ecma::visit::AstNodePath<'r>,
2891    ) {
2892        match self.cur_lexical_context() {
2893            LexicalContext::Function { .. } => {
2894                let mut effects = take(&mut self.effects);
2895                let hoisted_effects = take(&mut self.hoisted_effects);
2896
2897                let (_, returns_unconditionally) =
2898                    self.enter_block(LexicalContext::Block, |this| {
2899                        n.visit_children_with_ast_path(this, ast_path);
2900                    });
2901                // By handling this logic here instead of in enter_fn, we naturally skip it
2902                // for arrow functions with single expression bodies, since they just don't hit this
2903                // path.
2904                if !returns_unconditionally {
2905                    self.add_return_value(JsValue::Constant(ConstantValue::Undefined));
2906                }
2907                self.effects.append(&mut self.hoisted_effects);
2908                effects.append(&mut self.effects);
2909                self.hoisted_effects = hoisted_effects;
2910                self.effects = effects;
2911            }
2912            LexicalContext::ControlFlow { .. } => {
2913                self.with_block(LexicalContext::Block, |this| {
2914                    n.visit_children_with_ast_path(this, ast_path)
2915                });
2916            }
2917            LexicalContext::Block => {
2918                // Handle anonymous block statement
2919                // e.g., enter a new control flow context and because it is 'unconditiona' we
2920                // need to propagate early returns
2921                let (_, returns_early) = self.enter_control_flow(|this| {
2922                    n.visit_children_with_ast_path(this, ast_path);
2923                });
2924                if returns_early {
2925                    self.add_early_return_always(ast_path);
2926                }
2927            }
2928            LexicalContext::ClassBody => {
2929                // this would be something like a `static` initialization block
2930                // there is no early return logic required here so just visit children
2931                n.visit_children_with_ast_path(self, ast_path);
2932            }
2933        }
2934    }
2935
2936    fn visit_unary_expr<'ast: 'r, 'r>(
2937        &mut self,
2938        n: &'ast UnaryExpr,
2939        ast_path: &mut swc_core::ecma::visit::AstNodePath<'r>,
2940    ) {
2941        if n.op == UnaryOp::TypeOf && self.analyze_mode.is_code_gen() {
2942            let arg_value = Box::new(self.eval_context.eval(&n.arg));
2943
2944            self.add_effect(Effect::TypeOf {
2945                arg: arg_value,
2946                ast_path: as_parent_path(ast_path),
2947                span: n.span(),
2948            });
2949        }
2950
2951        n.visit_children_with_ast_path(self, ast_path);
2952    }
2953
2954    fn visit_labeled_stmt<'ast: 'r, 'r>(
2955        &mut self,
2956        stmt: &'ast LabeledStmt,
2957        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
2958    ) {
2959        let mut prev_effects = take(&mut self.effects);
2960        self.enter_control_flow(|this| {
2961            stmt.visit_children_with_ast_path(this, ast_path);
2962        });
2963
2964        let effects = take(&mut self.effects);
2965
2966        prev_effects.push(Effect::Conditional {
2967            condition: Box::new(JsValue::unknown_empty(true, "labeled statement")),
2968            kind: Box::new(ConditionalKind::Labeled {
2969                body: Box::new(EffectsBlock {
2970                    effects,
2971                    range: AstPathRange::Exact(as_parent_path_with(
2972                        ast_path,
2973                        AstParentKind::LabeledStmt(LabeledStmtField::Body),
2974                    )),
2975                }),
2976            }),
2977            ast_path: as_parent_path(ast_path),
2978            span: stmt.span,
2979        });
2980
2981        self.effects = prev_effects;
2982    }
2983
2984    fn visit_export_decl<'ast: 'r, 'r>(
2985        &mut self,
2986        node: &'ast ExportDecl,
2987        ast_path: &mut swc_core::ecma::visit::AstNodePath<'r>,
2988    ) {
2989        match &node.decl {
2990            Decl::Class(node) => {
2991                self.data
2992                    .exports
2993                    .insert(node.ident.sym.clone(), node.ident.to_id());
2994            }
2995            Decl::Fn(node) => {
2996                self.data
2997                    .exports
2998                    .insert(node.ident.sym.clone(), node.ident.to_id());
2999            }
3000            Decl::Var(node) => {
3001                for VarDeclarator { name, .. } in &node.decls {
3002                    for_each_ident_in_pat(name, &mut |name, ctxt| {
3003                        self.data.exports.insert(name.clone(), (name.clone(), ctxt));
3004                    });
3005                }
3006            }
3007            _ => {}
3008        };
3009        node.visit_children_with_ast_path(self, ast_path);
3010    }
3011
3012    fn visit_export_named_specifier<'ast: 'r, 'r>(
3013        &mut self,
3014        node: &'ast ExportNamedSpecifier,
3015        ast_path: &mut swc_core::ecma::visit::AstNodePath<'r>,
3016    ) {
3017        let export_name = node
3018            .exported
3019            .as_ref()
3020            .unwrap_or(&node.orig)
3021            .atom()
3022            .into_owned();
3023        self.data.exports.insert(
3024            export_name,
3025            match &node.orig {
3026                ModuleExportName::Ident(ident) => ident.to_id(),
3027                ModuleExportName::Str(_) => unreachable!("exporting a string should be impossible"),
3028            },
3029        );
3030        node.visit_children_with_ast_path(self, ast_path);
3031    }
3032}
3033
3034impl Analyzer<'_> {
3035    fn add_conditional_if_effect_with_early_return(
3036        &mut self,
3037        test: &Expr,
3038        ast_path: &AstNodePath<AstParentNodeRef<'_>>,
3039        condition_ast_kind: AstParentKind,
3040        span: Span,
3041        then: Option<Box<EffectsBlock>>,
3042        r#else: Option<Box<EffectsBlock>>,
3043        early_return_when_true: bool,
3044        early_return_when_false: bool,
3045    ) {
3046        if then.is_none() && r#else.is_none() && !early_return_when_false && !early_return_when_true
3047        {
3048            return;
3049        }
3050        let condition = Box::new(self.eval_context.eval(test));
3051        if condition.is_unknown() {
3052            if let Some(mut then) = then {
3053                self.effects.append(&mut then.effects);
3054            }
3055            if let Some(mut r#else) = r#else {
3056                self.effects.append(&mut r#else.effects);
3057            }
3058            return;
3059        }
3060        match (early_return_when_true, early_return_when_false) {
3061            (true, false) => {
3062                let early_return = EarlyReturn::Conditional {
3063                    prev_effects: take(&mut self.effects),
3064                    start_ast_path: as_parent_path(ast_path),
3065                    condition,
3066                    then,
3067                    r#else,
3068                    condition_ast_path: as_parent_path_with(ast_path, condition_ast_kind),
3069                    span,
3070                    early_return_condition_value: true,
3071                };
3072                self.early_return_stack_mut().push(early_return);
3073            }
3074            (false, true) => {
3075                let early_return = EarlyReturn::Conditional {
3076                    prev_effects: take(&mut self.effects),
3077                    start_ast_path: as_parent_path(ast_path),
3078                    condition,
3079                    then,
3080                    r#else,
3081                    condition_ast_path: as_parent_path_with(ast_path, condition_ast_kind),
3082                    span,
3083                    early_return_condition_value: false,
3084                };
3085                self.early_return_stack_mut().push(early_return);
3086            }
3087            (false, false) | (true, true) => {
3088                let kind = match (then, r#else) {
3089                    (Some(then), Some(r#else)) => ConditionalKind::IfElse { then, r#else },
3090                    (Some(then), None) => ConditionalKind::If { then },
3091                    (None, Some(r#else)) => ConditionalKind::Else { r#else },
3092                    (None, None) => {
3093                        // No effects, ignore
3094                        return;
3095                    }
3096                };
3097                self.add_effect(Effect::Conditional {
3098                    condition,
3099                    kind: Box::new(kind),
3100                    ast_path: as_parent_path_with(ast_path, condition_ast_kind),
3101                    span,
3102                });
3103                if early_return_when_false && early_return_when_true {
3104                    let early_return = EarlyReturn::Always {
3105                        prev_effects: take(&mut self.effects),
3106                        start_ast_path: as_parent_path(ast_path),
3107                    };
3108                    self.early_return_stack_mut().push(early_return);
3109                }
3110            }
3111        }
3112    }
3113
3114    fn add_conditional_effect(
3115        &mut self,
3116        test: &Expr,
3117        ast_path: &AstNodePath<AstParentNodeRef<'_>>,
3118        ast_kind: AstParentKind,
3119        span: Span,
3120        mut cond_kind: ConditionalKind,
3121    ) {
3122        let condition = Box::new(self.eval_context.eval(test));
3123        if condition.is_unknown() {
3124            match &mut cond_kind {
3125                ConditionalKind::If { then } => {
3126                    self.effects.append(&mut then.effects);
3127                }
3128                ConditionalKind::Else { r#else } => {
3129                    self.effects.append(&mut r#else.effects);
3130                }
3131                ConditionalKind::IfElse { then, r#else }
3132                | ConditionalKind::Ternary { then, r#else } => {
3133                    self.effects.append(&mut then.effects);
3134                    self.effects.append(&mut r#else.effects);
3135                }
3136                ConditionalKind::IfElseMultiple { then, r#else } => {
3137                    for block in then {
3138                        self.effects.append(&mut block.effects);
3139                    }
3140                    for block in r#else {
3141                        self.effects.append(&mut block.effects);
3142                    }
3143                }
3144                ConditionalKind::And { expr }
3145                | ConditionalKind::Or { expr }
3146                | ConditionalKind::NullishCoalescing { expr } => {
3147                    self.effects.append(&mut expr.effects);
3148                }
3149                ConditionalKind::Labeled { body } => {
3150                    self.effects.append(&mut body.effects);
3151                }
3152            }
3153        } else {
3154            self.add_effect(Effect::Conditional {
3155                condition,
3156                kind: Box::new(cond_kind),
3157                ast_path: as_parent_path_with(ast_path, ast_kind),
3158                span,
3159            });
3160        }
3161    }
3162
3163    fn handle_array_pat_with_value<'ast: 'r, 'r>(
3164        &mut self,
3165        arr: &'ast ArrayPat,
3166        pat_value: JsValue,
3167        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
3168    ) {
3169        match pat_value {
3170            JsValue::Array { items, .. } => {
3171                for (idx, (elem_pat, value_item)) in arr
3172                    .elems
3173                    .iter()
3174                    // TODO: This does not handle inline spreads correctly
3175                    // e.g. `let [a,..b,c] = [1,2,3]`
3176                    .zip(items.into_iter().map(Some).chain(iter::repeat(None)))
3177                    .enumerate()
3178                {
3179                    self.with_pat_value(value_item, |this| {
3180                        let mut ast_path = ast_path
3181                            .with_guard(AstParentNodeRef::ArrayPat(arr, ArrayPatField::Elems(idx)));
3182                        elem_pat.visit_with_ast_path(this, &mut ast_path);
3183                    });
3184                }
3185            }
3186            value => {
3187                for (idx, elem) in arr.elems.iter().enumerate() {
3188                    let pat_value = Some(JsValue::member(
3189                        Box::new(value.clone()),
3190                        Box::new(JsValue::Constant(ConstantValue::Num((idx as f64).into()))),
3191                    ));
3192                    self.with_pat_value(pat_value, |this| {
3193                        let mut ast_path = ast_path
3194                            .with_guard(AstParentNodeRef::ArrayPat(arr, ArrayPatField::Elems(idx)));
3195                        elem.visit_with_ast_path(this, &mut ast_path);
3196                    });
3197                }
3198            }
3199        }
3200    }
3201
3202    fn handle_object_pat_with_value<'ast: 'r, 'r>(
3203        &mut self,
3204        obj: &'ast ObjectPat,
3205        pat_value: JsValue,
3206        ast_path: &mut AstNodePath<AstParentNodeRef<'r>>,
3207    ) {
3208        for (i, prop) in obj.props.iter().enumerate() {
3209            let mut ast_path =
3210                ast_path.with_guard(AstParentNodeRef::ObjectPat(obj, ObjectPatField::Props(i)));
3211            match prop {
3212                ObjectPatProp::KeyValue(kv) => {
3213                    let mut ast_path = ast_path.with_guard(AstParentNodeRef::ObjectPatProp(
3214                        prop,
3215                        ObjectPatPropField::KeyValue,
3216                    ));
3217                    let KeyValuePatProp { key, value } = kv;
3218                    let key_value = self.eval_context.eval_prop_name(key);
3219                    {
3220                        let mut ast_path = ast_path.with_guard(AstParentNodeRef::KeyValuePatProp(
3221                            kv,
3222                            KeyValuePatPropField::Key,
3223                        ));
3224                        key.visit_with_ast_path(self, &mut ast_path);
3225                    }
3226                    let pat_value = Some(JsValue::member(
3227                        Box::new(pat_value.clone()),
3228                        Box::new(key_value),
3229                    ));
3230                    self.with_pat_value(pat_value, |this| {
3231                        let mut ast_path = ast_path.with_guard(AstParentNodeRef::KeyValuePatProp(
3232                            kv,
3233                            KeyValuePatPropField::Value,
3234                        ));
3235                        value.visit_with_ast_path(this, &mut ast_path);
3236                    });
3237                }
3238                ObjectPatProp::Assign(assign) => {
3239                    let mut ast_path = ast_path.with_guard(AstParentNodeRef::ObjectPatProp(
3240                        prop,
3241                        ObjectPatPropField::Assign,
3242                    ));
3243                    let AssignPatProp { key, value, .. } = assign;
3244                    let key_value = key.sym.clone().into();
3245                    {
3246                        let mut ast_path = ast_path.with_guard(AstParentNodeRef::AssignPatProp(
3247                            assign,
3248                            AssignPatPropField::Key,
3249                        ));
3250                        key.visit_with_ast_path(self, &mut ast_path);
3251                    }
3252                    self.add_value(
3253                        key.to_id(),
3254                        if let Some(box value) = value {
3255                            let value = self.eval_context.eval(value);
3256                            JsValue::alternatives(vec![
3257                                JsValue::member(Box::new(pat_value.clone()), Box::new(key_value)),
3258                                value,
3259                            ])
3260                        } else {
3261                            JsValue::member(Box::new(pat_value.clone()), Box::new(key_value))
3262                        },
3263                    );
3264                    {
3265                        let mut ast_path = ast_path.with_guard(AstParentNodeRef::AssignPatProp(
3266                            assign,
3267                            AssignPatPropField::Value,
3268                        ));
3269                        value.visit_with_ast_path(self, &mut ast_path);
3270                    }
3271                }
3272
3273                _ => prop.visit_with_ast_path(self, &mut ast_path),
3274            }
3275        }
3276    }
3277}
3278
3279fn extract_var_from_umd_factory(callee: &Expr, args: &[ExprOrSpread]) -> Option<Id> {
3280    match unparen(callee) {
3281        Expr::Ident(Ident { sym, .. }) => {
3282            if &**sym == "define"
3283                && let Expr::Fn(FnExpr { function, .. }) = &*args[0].expr
3284            {
3285                let params = &*function.params;
3286                if params.len() == 1
3287                    && let Pat::Ident(param) = &params[0].pat
3288                    && &*param.id.sym == "require"
3289                {
3290                    return Some(param.to_id());
3291                }
3292            }
3293        }
3294
3295        // umd may use (function (factory){
3296        //   // Somewhere, define(['require', 'exports'], factory)
3297        // }(function (require, exports){}))
3298        //
3299        // In all module system which has `require`, `require` in the factory function can be
3300        // treated as a well-known require.
3301        Expr::Fn(FnExpr { function, .. }) => {
3302            let params = &*function.params;
3303            if params.len() == 1
3304                && let Some(FnExpr { function, .. }) =
3305                    args.first().and_then(|arg| arg.expr.as_fn_expr())
3306            {
3307                let params = &*function.params;
3308                if !params.is_empty()
3309                    && let Pat::Ident(param) = &params[0].pat
3310                    && &*param.id.sym == "require"
3311                {
3312                    return Some(param.to_id());
3313                }
3314            }
3315        }
3316
3317        _ => {}
3318    }
3319
3320    None
3321}