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