Skip to main content

turbopack_ecmascript/analyzer/
mod.rs

1#![allow(clippy::redundant_closure_call)]
2
3use std::{
4    borrow::Cow,
5    fmt::{self, Display, Formatter, Write},
6    hash::{BuildHasherDefault, Hash, Hasher},
7    mem::take,
8    sync::{Arc, LazyLock},
9};
10
11use anyhow::{Result, bail};
12use either::Either;
13use num_bigint::BigInt;
14use num_traits::identities::Zero;
15use rustc_hash::FxHasher;
16use smallvec::SmallVec;
17use swc_core::{
18    atoms::Wtf8Atom,
19    common::Mark,
20    ecma::{
21        ast::{Id, Ident, Lit},
22        atoms::Atom,
23    },
24};
25use turbo_esregex::EsRegex;
26use turbo_rcstr::{RcStr, rcstr};
27use turbo_tasks::{FxIndexMap, FxIndexSet, Vc};
28use turbopack_core::compile_time_info::{
29    CompileTimeDefineValue, DefinableNameSegmentRef, DefinableNameSegmentRefs, FreeVarReference,
30    TotalOrderF64,
31};
32
33use self::imports::ImportAnnotations;
34pub(crate) use self::imports::ImportMap;
35use crate::{
36    analyzer::graph::{EvalContext, VarGraph},
37    references::require_context::RequireContextMap,
38    utils::StringifyJs,
39};
40
41pub mod builtin;
42pub mod graph;
43pub mod imports;
44pub mod linker;
45pub mod side_effects;
46pub mod top_level_await;
47pub mod well_known;
48
49#[derive(Debug, Clone, Hash, PartialEq, Eq)]
50pub enum ObjectPart {
51    KeyValue(JsValue, JsValue),
52    Spread(JsValue),
53}
54
55impl Default for ObjectPart {
56    fn default() -> Self {
57        ObjectPart::Spread(Default::default())
58    }
59}
60
61#[derive(Debug, Clone, Hash, PartialEq, Eq)]
62pub struct ConstantNumber(pub TotalOrderF64);
63
64impl ConstantNumber {
65    pub fn as_u32_index(&self) -> Option<usize> {
66        let index: u32 = *self.0 as u32;
67        (index as f64 == *self.0).then_some(index as usize)
68    }
69}
70impl From<f64> for ConstantNumber {
71    fn from(value: f64) -> Self {
72        ConstantNumber(value.into())
73    }
74}
75
76#[derive(Debug, Clone)]
77pub enum ConstantString {
78    Atom(Atom),
79    RcStr(RcStr),
80}
81
82impl ConstantString {
83    pub fn as_str(&self) -> &str {
84        match self {
85            Self::Atom(s) => s,
86            Self::RcStr(s) => s,
87        }
88    }
89
90    pub fn as_rcstr(&self) -> RcStr {
91        match self {
92            Self::Atom(s) => RcStr::from(s.as_str()),
93            Self::RcStr(s) => s.clone(),
94        }
95    }
96
97    pub fn as_atom(&self) -> Cow<'_, Atom> {
98        match self {
99            Self::Atom(s) => Cow::Borrowed(s),
100            Self::RcStr(s) => Cow::Owned(s.as_str().into()),
101        }
102    }
103
104    pub fn is_empty(&self) -> bool {
105        self.as_str().is_empty()
106    }
107}
108
109impl PartialEq for ConstantString {
110    fn eq(&self, other: &Self) -> bool {
111        self.as_str() == other.as_str()
112    }
113}
114
115impl Eq for ConstantString {}
116
117impl Hash for ConstantString {
118    fn hash<H: Hasher>(&self, state: &mut H) {
119        self.as_str().hash(state);
120    }
121}
122
123impl Display for ConstantString {
124    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125        self.as_str().fmt(f)
126    }
127}
128
129impl From<Atom> for ConstantString {
130    fn from(v: Atom) -> Self {
131        ConstantString::Atom(v)
132    }
133}
134
135impl From<&'static str> for ConstantString {
136    fn from(v: &'static str) -> Self {
137        ConstantString::Atom(v.into())
138    }
139}
140
141impl From<String> for ConstantString {
142    fn from(v: String) -> Self {
143        ConstantString::Atom(v.into())
144    }
145}
146
147impl From<RcStr> for ConstantString {
148    fn from(v: RcStr) -> Self {
149        ConstantString::RcStr(v)
150    }
151}
152
153#[derive(Debug, Clone, Hash, PartialEq, Eq, Default)]
154pub enum ConstantValue {
155    #[default]
156    Undefined,
157    Str(ConstantString),
158    Num(ConstantNumber),
159    True,
160    False,
161    Null,
162    BigInt(Box<BigInt>),
163    Regex(Box<(Atom, Atom)>),
164}
165
166impl ConstantValue {
167    pub fn as_str(&self) -> Option<&str> {
168        match self {
169            Self::Str(s) => Some(s.as_str()),
170            _ => None,
171        }
172    }
173
174    pub fn as_bool(&self) -> Option<bool> {
175        match self {
176            Self::True => Some(true),
177            Self::False => Some(false),
178            _ => None,
179        }
180    }
181
182    pub fn is_truthy(&self) -> bool {
183        match self {
184            Self::Undefined | Self::False | Self::Null => false,
185            Self::True | Self::Regex(..) => true,
186            Self::Str(s) => !s.is_empty(),
187            Self::Num(ConstantNumber(n)) => **n != 0.0,
188            Self::BigInt(n) => !n.is_zero(),
189        }
190    }
191
192    pub fn is_nullish(&self) -> bool {
193        match self {
194            Self::Undefined | Self::Null => true,
195            Self::Str(..)
196            | Self::Num(..)
197            | Self::True
198            | Self::False
199            | Self::BigInt(..)
200            | Self::Regex(..) => false,
201        }
202    }
203
204    pub fn is_empty_string(&self) -> bool {
205        match self {
206            Self::Str(s) => s.is_empty(),
207            _ => false,
208        }
209    }
210
211    pub fn is_value_type(&self) -> bool {
212        !matches!(self, Self::Regex(..))
213    }
214}
215
216impl From<bool> for ConstantValue {
217    fn from(v: bool) -> Self {
218        match v {
219            true => ConstantValue::True,
220            false => ConstantValue::False,
221        }
222    }
223}
224
225impl From<&'_ str> for ConstantValue {
226    fn from(v: &str) -> Self {
227        ConstantValue::Str(ConstantString::Atom(v.into()))
228    }
229}
230
231impl From<Lit> for ConstantValue {
232    fn from(v: Lit) -> Self {
233        match v {
234            Lit::Str(v) => {
235                ConstantValue::Str(ConstantString::Atom(v.value.to_atom_lossy().into_owned()))
236            }
237            Lit::Bool(v) => {
238                if v.value {
239                    ConstantValue::True
240                } else {
241                    ConstantValue::False
242                }
243            }
244            Lit::Null(_) => ConstantValue::Null,
245            Lit::Num(v) => ConstantValue::Num(ConstantNumber(v.value.into())),
246            Lit::BigInt(v) => ConstantValue::BigInt(v.value),
247            Lit::Regex(v) => ConstantValue::Regex(Box::new((v.exp, v.flags))),
248            Lit::JSXText(v) => ConstantValue::Str(ConstantString::Atom(v.value)),
249        }
250    }
251}
252
253impl Display for ConstantValue {
254    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
255        match self {
256            ConstantValue::Undefined => write!(f, "undefined"),
257            ConstantValue::Str(str) => write!(f, "{}", StringifyJs(str.as_str())),
258            ConstantValue::True => write!(f, "true"),
259            ConstantValue::False => write!(f, "false"),
260            ConstantValue::Null => write!(f, "null"),
261            ConstantValue::Num(ConstantNumber(n)) => write!(f, "{n}"),
262            ConstantValue::BigInt(n) => write!(f, "{n}"),
263            ConstantValue::Regex(regex) => write!(f, "/{}/{}", regex.0, regex.1),
264        }
265    }
266}
267
268#[derive(Debug, Clone, Hash, PartialEq, Eq)]
269pub struct ModuleValue {
270    pub module: Wtf8Atom,
271    pub annotations: Option<Arc<ImportAnnotations>>,
272}
273
274#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
275pub enum LogicalOperator {
276    And,
277    Or,
278    NullishCoalescing,
279}
280
281impl LogicalOperator {
282    fn joiner(&self) -> &'static str {
283        match self {
284            LogicalOperator::And => " && ",
285            LogicalOperator::Or => " || ",
286            LogicalOperator::NullishCoalescing => " ?? ",
287        }
288    }
289    fn multi_line_joiner(&self) -> &'static str {
290        match self {
291            LogicalOperator::And => "&& ",
292            LogicalOperator::Or => "|| ",
293            LogicalOperator::NullishCoalescing => "?? ",
294        }
295    }
296}
297
298#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
299pub enum BinaryOperator {
300    Equal,
301    NotEqual,
302    StrictEqual,
303    StrictNotEqual,
304}
305
306impl BinaryOperator {
307    fn joiner(&self) -> &'static str {
308        match self {
309            BinaryOperator::Equal => " == ",
310            BinaryOperator::NotEqual => " != ",
311            BinaryOperator::StrictEqual => " === ",
312            BinaryOperator::StrictNotEqual => " !== ",
313        }
314    }
315
316    fn positive_op(&self) -> (PositiveBinaryOperator, bool) {
317        match self {
318            BinaryOperator::Equal => (PositiveBinaryOperator::Equal, false),
319            BinaryOperator::NotEqual => (PositiveBinaryOperator::Equal, true),
320            BinaryOperator::StrictEqual => (PositiveBinaryOperator::StrictEqual, false),
321            BinaryOperator::StrictNotEqual => (PositiveBinaryOperator::StrictEqual, true),
322        }
323    }
324}
325
326#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
327pub enum PositiveBinaryOperator {
328    Equal,
329    StrictEqual,
330}
331
332#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
333pub enum JsValueUrlKind {
334    Absolute,
335    Relative,
336}
337
338impl Display for JsValueUrlKind {
339    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
340        f.write_str(match self {
341            JsValueUrlKind::Absolute => "absolute",
342            JsValueUrlKind::Relative => "relative",
343        })
344    }
345}
346
347/// The four categories of [JsValue]s.
348enum JsValueMetaKind {
349    /// Doesn't contain nested values.
350    Leaf,
351    /// Contains nested values. Nested values represent some structure and can't
352    /// be replaced during linking. They might contain placeholders.
353    Nested,
354    /// Contains nested values. Operations are replaced during linking. They
355    /// might contain placeholders.
356    Operation,
357    /// These values are replaced during linking.
358    Placeholder,
359}
360
361#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
362pub enum LogicalProperty {
363    Truthy,
364    Falsy,
365    Nullish,
366    NonNullish,
367}
368
369impl Display for LogicalProperty {
370    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
371        match self {
372            LogicalProperty::Truthy => write!(f, "truthy"),
373            LogicalProperty::Falsy => write!(f, "falsy"),
374            LogicalProperty::Nullish => write!(f, "nullish"),
375            LogicalProperty::NonNullish => write!(f, "non-nullish"),
376        }
377    }
378}
379
380/// TODO: Use `Arc`
381///
382/// There are 4 kinds of values: Leaves, Nested, Operations, and Placeholders
383/// (see `JsValueMetaKind` for details). Values are processed in two phases:
384/// - Analyze phase: We convert AST into `JsValue`s. We don't have contextual information so we need
385///   to insert placeholders to represent that.
386/// - Link phase: We try to reduce a value to a constant value. The link phase has 5 substeps that
387///   are executed on each node in the graph depth-first. When a value is modified, we need to visit
388///   the new children again.
389/// - Replace variables with their values. This replaces [JsValue::Variable]. No variable should be
390///   remaining after that.
391/// - Replace placeholders with contextual information. This usually replaces [JsValue::FreeVar] and
392///   [JsValue::Module]. Some [JsValue::Call] on well- known functions might also be replaced. No
393///   free vars or modules should be remaining after that.
394/// - Replace operations on well-known objects and functions. This handles [JsValue::Call] and
395///   [JsValue::Member] on well-known objects and functions.
396/// - Replace all built-in functions with their values when they are compile-time constant.
397/// - For optimization, any nested operations are replaced with [JsValue::Unknown]. So only one
398///   layer of operation remains. Any remaining operation or placeholder can be treated as unknown.
399#[derive(Debug, Clone, Hash, PartialEq, Eq)]
400pub enum JsValue {
401    // LEAF VALUES
402    // ----------------------------
403    /// A constant primitive value.
404    Constant(ConstantValue),
405    /// A constant URL object.
406    Url(ConstantString, JsValueUrlKind),
407    /// Some kind of well-known object
408    /// (must not be an array, otherwise Array.concat needs to be changed)
409    WellKnownObject(WellKnownObjectKind),
410    /// Some kind of well-known function
411    WellKnownFunction(WellKnownFunctionKind),
412    /// Not-analyzable value. Might contain the original value for additional
413    /// info. Has a reason string for explanation.
414    Unknown {
415        original_value: Option<Arc<JsValue>>,
416        reason: RcStr,
417        has_side_effects: bool,
418    },
419
420    // NESTED VALUES
421    // ----------------------------
422    /// An array of nested values
423    Array {
424        total_nodes: u32,
425        items: Vec<JsValue>,
426        mutable: bool,
427    },
428    /// An object of nested values
429    Object {
430        total_nodes: u32,
431        parts: Vec<ObjectPart>,
432        mutable: bool,
433    },
434    /// A list of alternative values
435    Alternatives {
436        total_nodes: u32,
437        values: Vec<JsValue>,
438        logical_property: Option<LogicalProperty>,
439    },
440    /// A function reference. The return value might contain [JsValue::Argument]
441    /// placeholders that need to be replaced when calling this function.
442    /// `(total_node_count, func_ident, return_value)`
443    Function(u32, u32, Box<JsValue>),
444
445    // OPERATIONS
446    // ----------------------------
447    /// A string concatenation of values.
448    /// `foo.${unknownVar}.js` => 'foo' + Unknown + '.js'
449    Concat(u32, Vec<JsValue>),
450    /// An addition of values.
451    /// This can be converted to [JsValue::Concat] if the type of the variable
452    /// is string.
453    Add(u32, Vec<JsValue>),
454    /// Logical negation `!expr`
455    Not(u32, Box<JsValue>),
456    /// Logical operator chain e. g. `expr && expr`
457    Logical(u32, LogicalOperator, Vec<JsValue>),
458    /// Binary expression e. g. `expr == expr`
459    Binary(u32, Box<JsValue>, BinaryOperator, Box<JsValue>),
460    /// A constructor call. `(total_node_count, list)` — see [`CallList`].
461    New(u32, CallList),
462    /// A function call without a `this` context. `(total_node_count, list)` — see [`CallList`].
463    Call(u32, CallList),
464    /// A super call to the parent constructor.
465    /// `(total_node_count, args)`
466    SuperCall(u32, Vec<JsValue>),
467    /// A function call with a `this` context. `(total_node_count, list)` — see [`MemberCallList`].
468    MemberCall(u32, MemberCallList),
469    /// A member access `obj[prop]`
470    /// `(total_node_count, obj, prop)`
471    Member(u32, Box<JsValue>, Box<JsValue>),
472    /// A tenary operator `test ? cons : alt`
473    /// `(total_node_count, test, cons, alt)`
474    Tenary(u32, Box<JsValue>, Box<JsValue>, Box<JsValue>),
475    /// A promise resolving to some value
476    /// `(total_node_count, value)`
477    Promise(u32, Box<JsValue>),
478    /// An await call (potentially) unwrapping a promise.
479    /// `(total_node_count, value)`
480    Awaited(u32, Box<JsValue>),
481
482    /// A for-of loop
483    ///
484    /// `(total_node_count, iterable)`
485    Iterated(u32, Box<JsValue>),
486
487    /// A `typeof` expression.
488    ///
489    /// `(total_node_count, operand)`
490    TypeOf(u32, Box<JsValue>),
491
492    // PLACEHOLDERS
493    // ----------------------------
494    /// A reference to a variable.
495    Variable(Id),
496    /// A reference to an function argument.
497    /// (func_ident, arg_index)
498    Argument(u32, usize),
499    // TODO no predefined kinds, only Atom
500    /// A reference to a free variable.
501    FreeVar(Atom),
502    /// This is a reference to a imported module.
503    Module(ModuleValue),
504}
505
506/// Storage for [`JsValue::MemberCall`]: `[args..., prop, obj]`.
507///
508/// The reversed layout (obj/prop at the tail) is what makes the `replace_builtin`
509/// fallthrough path cheap: `pop` obj, `pop` prop, and the remaining `Vec` **is** the args
510/// `Vec` with no reallocation.
511///
512/// The custom `Debug` impl re-emits the pre-refactor derived shape
513/// (`MemberCall(total, obj, prop, [args])`) by writing obj/prop/args as siblings inside the
514/// parent's `debug_tuple`. This keeps fixture snapshots identical to the 4-tuple-payload
515/// version without forcing a hand-written `Debug` on every `JsValue` arm.
516#[derive(Default, Clone, Hash, PartialEq, Eq)]
517pub struct MemberCallList(Vec<JsValue>);
518
519impl fmt::Debug for MemberCallList {
520    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
521        // Layout: [args..., prop, obj]
522        let n = self.0.len();
523        let obj = &self.0[n - 1];
524        let prop = &self.0[n - 2];
525        let args = &self.0[..n - 2];
526        if f.alternate() {
527            // The parent `debug_tuple` writes the field's leading indent for us (via
528            // PadAdapter) and appends `,\n` after we return. Emitting
529            // `<obj>,\n<prop>,\n<args>` with no trailing comma makes us appear as three
530            // sibling fields in the parent's pretty-print output.
531            writeln!(f, "{obj:#?},")?;
532            writeln!(f, "{prop:#?},")?;
533            write!(f, "{args:#?}")
534        } else {
535            write!(f, "{obj:?}, {prop:?}, {args:?}")
536        }
537    }
538}
539
540impl MemberCallList {
541    fn from_parts(obj: JsValue, prop: JsValue, args: Vec<JsValue>) -> Self {
542        let mut list = args;
543        list.reserve_exact(2);
544        list.push(prop);
545        list.push(obj);
546        Self(list)
547    }
548
549    fn from_iter<I>(obj: JsValue, prop: JsValue, args: I) -> Self
550    where
551        I: IntoIterator<Item = JsValue>,
552        I::IntoIter: ExactSizeIterator,
553    {
554        let args = args.into_iter();
555        let mut list = Vec::with_capacity(args.len() + 2);
556        list.extend(args);
557        list.push(prop);
558        list.push(obj);
559        Self(list)
560    }
561
562    /// The receiver object. Lives at the tail of the underlying `Vec`.
563    pub fn obj(&self) -> &JsValue {
564        &self.0[self.0.len() - 1]
565    }
566
567    pub fn obj_mut(&mut self) -> &mut JsValue {
568        let n = self.0.len();
569        &mut self.0[n - 1]
570    }
571
572    /// The accessed property. Lives one slot before `obj`.
573    pub fn prop(&self) -> &JsValue {
574        &self.0[self.0.len() - 2]
575    }
576
577    pub fn prop_mut(&mut self) -> &mut JsValue {
578        let n = self.0.len();
579        &mut self.0[n - 2]
580    }
581
582    /// The call arguments — everything before `prop` and `obj`.
583    pub fn args(&self) -> &[JsValue] {
584        let n = self.0.len();
585        &self.0[..n - 2]
586    }
587
588    pub fn args_mut(&mut self) -> &mut [JsValue] {
589        let n = self.0.len();
590        &mut self.0[..n - 2]
591    }
592
593    /// Borrow `args`, `prop`, and `obj` simultaneously as mutable references. The single
594    /// `Vec` storage means callers can't get these via separate accessor calls.
595    pub fn as_parts_mut(&mut self) -> (&mut [JsValue], &mut JsValue, &mut JsValue) {
596        let n = self.0.len();
597        let (args, tail) = self.0.split_at_mut(n - 2);
598        let (prop_slot, obj_slot) = tail.split_at_mut(1);
599        (args, &mut prop_slot[0], &mut obj_slot[0])
600    }
601
602    /// Take everything out. The returned `args` `Vec` reuses the original allocation — no
603    /// copy. That's the point of storing obj/prop at the tail.
604    pub fn into_parts(mut self) -> (JsValue, JsValue, Vec<JsValue>) {
605        let obj = self.0.pop().unwrap();
606        let prop = self.0.pop().unwrap();
607        (obj, prop, self.0)
608    }
609
610    fn total_nodes(&self) -> u32 {
611        total_nodes(&self.0)
612    }
613
614    fn for_each_children(&self, visitor: &mut impl FnMut(&JsValue)) {
615        self.0.iter().for_each(visitor)
616    }
617    fn for_each_children_mut(&mut self, visitor: &mut impl FnMut(&mut JsValue) -> bool) -> bool {
618        let mut modified = false;
619        for child in self.0.iter_mut() {
620            if visitor(child) {
621                modified = true;
622            }
623        }
624
625        modified
626    }
627
628    fn all_similar(l: &Self, r: &Self, depth: usize) -> bool {
629        JsValue::all_similar(&l.0, &r.0, depth)
630    }
631}
632
633/// Storage for [`JsValue::Call`] and [`JsValue::New`]: `[args..., callee]`.
634///
635/// Same trick as [`MemberCallList`]: keeping the callee at the tail lets
636/// `replace_builtin`-style fallthrough paths `pop` it off cheaply and reuse the remaining
637/// `Vec` as the owned args with no reallocation.
638///
639/// The custom `Debug` impl re-emits the pre-refactor `(callee, [args])` shape so fixture
640/// snapshots remain identical to the 3-tuple-payload version.
641#[derive(Default, Clone, Hash, PartialEq, Eq)]
642pub struct CallList(Vec<JsValue>);
643
644impl fmt::Debug for CallList {
645    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
646        // Layout: [args..., callee]
647        let n = self.0.len();
648        let callee = &self.0[n - 1];
649        let args = &self.0[..n - 1];
650        if f.alternate() {
651            // Same trick as MemberCallList: emit two sibling fields inside the parent
652            // `debug_tuple`'s pretty-print output.
653            writeln!(f, "{callee:#?},")?;
654            write!(f, "{args:#?}")
655        } else {
656            write!(f, "{callee:?}, {args:?}")
657        }
658    }
659}
660
661impl CallList {
662    fn from_parts(callee: JsValue, args: Vec<JsValue>) -> Self {
663        let mut list = args;
664        list.reserve_exact(1);
665        list.push(callee);
666        Self(list)
667    }
668
669    fn from_iter<I>(callee: JsValue, args: I) -> Self
670    where
671        I: IntoIterator<Item = JsValue>,
672        I::IntoIter: ExactSizeIterator,
673    {
674        let args = args.into_iter();
675        let mut list = Vec::with_capacity(args.len() + 1);
676        list.extend(args);
677        list.push(callee);
678        Self(list)
679    }
680
681    /// The callee. Lives at the tail of the underlying `Vec`.
682    pub fn callee(&self) -> &JsValue {
683        self.0.last().expect("CallList must always have a callee")
684    }
685
686    pub fn callee_mut(&mut self) -> &mut JsValue {
687        self.0
688            .last_mut()
689            .expect("CallList must always have a callee")
690    }
691
692    /// The call arguments — everything before the callee.
693    pub fn args(&self) -> &[JsValue] {
694        let n = self.0.len();
695        &self.0[..n - 1]
696    }
697
698    pub fn args_mut(&mut self) -> &mut [JsValue] {
699        let n = self.0.len();
700        &mut self.0[..n - 1]
701    }
702
703    /// Borrow `args` and `callee` simultaneously as mutable references. The single `Vec`
704    /// storage means callers can't get these via separate accessor calls.
705    pub fn as_parts_mut(&mut self) -> (&mut [JsValue], &mut JsValue) {
706        let n = self.0.len();
707        let (args, callee_slot) = self.0.split_at_mut(n - 1);
708        (args, &mut callee_slot[0])
709    }
710
711    /// Take everything out. The returned `args` `Vec` reuses the original allocation — no
712    /// copy. That's the point of storing the callee at the tail.
713    pub fn into_parts(mut self) -> (JsValue, Vec<JsValue>) {
714        let callee = self.0.pop().unwrap();
715        (callee, self.0)
716    }
717
718    fn total_nodes(&self) -> u32 {
719        total_nodes(&self.0)
720    }
721
722    fn for_each_children(&self, visitor: &mut impl FnMut(&JsValue)) {
723        self.0.iter().for_each(visitor)
724    }
725    fn for_each_children_mut(&mut self, visitor: &mut impl FnMut(&mut JsValue) -> bool) -> bool {
726        let mut modified = false;
727        for child in self.0.iter_mut() {
728            if visitor(child) {
729                modified = true;
730            }
731        }
732
733        modified
734    }
735
736    fn all_similar(l: &Self, r: &Self, depth: usize) -> bool {
737        JsValue::all_similar(&l.0, &r.0, depth)
738    }
739}
740
741impl From<&'_ str> for JsValue {
742    fn from(v: &str) -> Self {
743        ConstantValue::Str(ConstantString::Atom(v.into())).into()
744    }
745}
746
747impl From<Atom> for JsValue {
748    fn from(v: Atom) -> Self {
749        ConstantValue::Str(ConstantString::Atom(v)).into()
750    }
751}
752
753impl From<BigInt> for JsValue {
754    fn from(v: BigInt) -> Self {
755        Self::from(Box::new(v))
756    }
757}
758
759impl From<Box<BigInt>> for JsValue {
760    fn from(v: Box<BigInt>) -> Self {
761        ConstantValue::BigInt(v).into()
762    }
763}
764
765impl From<f64> for JsValue {
766    fn from(v: f64) -> Self {
767        ConstantValue::Num(ConstantNumber(v.into())).into()
768    }
769}
770
771impl From<RcStr> for JsValue {
772    fn from(v: RcStr) -> Self {
773        ConstantValue::Str(v.into()).into()
774    }
775}
776
777impl From<String> for JsValue {
778    fn from(v: String) -> Self {
779        RcStr::from(v).into()
780    }
781}
782
783impl From<swc_core::ecma::ast::Str> for JsValue {
784    fn from(v: swc_core::ecma::ast::Str) -> Self {
785        ConstantValue::Str(ConstantString::Atom(v.value.to_atom_lossy().into_owned())).into()
786    }
787}
788
789impl From<ConstantValue> for JsValue {
790    fn from(v: ConstantValue) -> Self {
791        JsValue::Constant(v)
792    }
793}
794
795impl TryFrom<&CompileTimeDefineValue> for JsValue {
796    type Error = anyhow::Error;
797
798    fn try_from(value: &CompileTimeDefineValue) -> Result<Self> {
799        Ok(JsValue::Constant(match value {
800            CompileTimeDefineValue::Undefined => ConstantValue::Undefined,
801            CompileTimeDefineValue::Null => ConstantValue::Null,
802            CompileTimeDefineValue::Bool(b) => (*b).into(),
803            CompileTimeDefineValue::Number(n) => ConstantValue::Num(ConstantNumber(*n)),
804            CompileTimeDefineValue::BigInt(n) => ConstantValue::BigInt(n.clone()),
805            CompileTimeDefineValue::String(s) => s.as_str().into(),
806            CompileTimeDefineValue::Regex(pattern, flags) => {
807                ConstantValue::Regex(Box::new((pattern.as_str().into(), flags.as_str().into())))
808            }
809            CompileTimeDefineValue::Array(a) => {
810                let mut js_value = JsValue::Array {
811                    total_nodes: a.len() as u32,
812                    items: a.iter().map(|i| i.try_into()).collect::<Result<Vec<_>>>()?,
813                    mutable: false,
814                };
815                js_value.update_total_nodes();
816                return Ok(js_value);
817            }
818            CompileTimeDefineValue::Object(m) => {
819                let mut js_value = JsValue::Object {
820                    total_nodes: m.len() as u32,
821                    parts: m
822                        .iter()
823                        .map(|(k, v)| {
824                            Ok::<ObjectPart, anyhow::Error>(ObjectPart::KeyValue(
825                                k.clone().into(),
826                                v.try_into()?,
827                            ))
828                        })
829                        .collect::<Result<Vec<_>>>()?,
830                    mutable: false,
831                };
832                js_value.update_total_nodes();
833                return Ok(js_value);
834            }
835            CompileTimeDefineValue::Evaluate(s) => {
836                return EvalContext::eval_single_expr_lit(s);
837            }
838        }))
839    }
840}
841
842impl TryFrom<&ConstantValue> for CompileTimeDefineValue {
843    type Error = anyhow::Error;
844
845    fn try_from(value: &ConstantValue) -> Result<Self> {
846        Ok(match value {
847            ConstantValue::Undefined => CompileTimeDefineValue::Undefined,
848            ConstantValue::Null => CompileTimeDefineValue::Null,
849            ConstantValue::True => CompileTimeDefineValue::Bool(true),
850            ConstantValue::False => CompileTimeDefineValue::Bool(false),
851            ConstantValue::Num(n) => CompileTimeDefineValue::Number(n.0),
852            ConstantValue::Str(s) => CompileTimeDefineValue::String(s.as_rcstr()),
853            ConstantValue::BigInt(n) => CompileTimeDefineValue::BigInt(n.clone()),
854            ConstantValue::Regex(regex) => CompileTimeDefineValue::Regex(
855                RcStr::from(regex.0.as_str()),
856                RcStr::from(regex.1.as_str()),
857            ),
858        })
859    }
860}
861
862impl TryFrom<&FreeVarReference> for JsValue {
863    type Error = anyhow::Error;
864
865    fn try_from(value: &FreeVarReference) -> Result<Self> {
866        match value {
867            FreeVarReference::Value(v) => v.try_into(),
868            FreeVarReference::Ident(_) => Ok(JsValue::unknown_empty(
869                false,
870                rcstr!("compile time injected ident"),
871            )),
872            FreeVarReference::Member(_, _) => Ok(JsValue::unknown_empty(
873                false,
874                rcstr!("compile time injected member"),
875            )),
876            FreeVarReference::EcmaScriptModule { .. } => Ok(JsValue::unknown_empty(
877                false,
878                rcstr!("compile time injected free var module"),
879            )),
880            FreeVarReference::ReportUsage { inner, .. } => {
881                if let Some(inner) = &inner {
882                    inner.as_ref().try_into()
883                } else {
884                    Ok(JsValue::unknown_empty(
885                        false,
886                        rcstr!("compile time injected free var error"),
887                    ))
888                }
889            }
890            FreeVarReference::InputRelative(kind) => {
891                use turbopack_core::compile_time_info::InputRelativeConstant;
892                Ok(JsValue::unknown_empty(
893                    false,
894                    match kind {
895                        InputRelativeConstant::DirName => {
896                            rcstr!("compile time injected free var referencing the directory name")
897                        }
898                        InputRelativeConstant::FileName => {
899                            rcstr!("compile time injected free var referencing the file name")
900                        }
901                    },
902                ))
903            }
904        }
905    }
906}
907
908impl Default for JsValue {
909    fn default() -> Self {
910        JsValue::unknown_empty(false, rcstr!(""))
911    }
912}
913
914impl Display for ObjectPart {
915    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
916        match self {
917            ObjectPart::KeyValue(key, value) => write!(f, "{key}: {value}"),
918            ObjectPart::Spread(value) => write!(f, "...{value}"),
919        }
920    }
921}
922
923impl Display for JsValue {
924    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
925        match self {
926            JsValue::Constant(v) => write!(f, "{v}"),
927            JsValue::Url(url, kind) => write!(f, "{url} {kind}"),
928            JsValue::Array { items, mutable, .. } => write!(
929                f,
930                "{}[{}]",
931                if *mutable { "" } else { "frozen " },
932                items
933                    .iter()
934                    .map(|v| v.to_string())
935                    .collect::<Vec<_>>()
936                    .join(", ")
937            ),
938            JsValue::Object { parts, mutable, .. } => write!(
939                f,
940                "{}{{{}}}",
941                if *mutable { "" } else { "frozen " },
942                parts
943                    .iter()
944                    .map(|v| v.to_string())
945                    .collect::<Vec<_>>()
946                    .join(", ")
947            ),
948            JsValue::Alternatives {
949                total_nodes: _,
950                values: list,
951                logical_property,
952            } => {
953                let list = list
954                    .iter()
955                    .map(|v| v.to_string())
956                    .collect::<Vec<_>>()
957                    .join(" | ");
958                if let Some(logical_property) = logical_property {
959                    write!(f, "({list}){{{logical_property}}}")
960                } else {
961                    write!(f, "({list})")
962                }
963            }
964            JsValue::FreeVar(name) => write!(f, "FreeVar({name:?})"),
965            JsValue::Variable(name) => write!(f, "Variable({}#{:?})", name.0, name.1),
966            JsValue::Concat(_, list) => write!(
967                f,
968                "`{}`",
969                list.iter()
970                    .map(|v| v
971                        .as_str()
972                        .map_or_else(|| format!("${{{v}}}"), |str| str.to_string()))
973                    .collect::<Vec<_>>()
974                    .join("")
975            ),
976            JsValue::Add(_, list) => write!(
977                f,
978                "({})",
979                list.iter()
980                    .map(|v| v.to_string())
981                    .collect::<Vec<_>>()
982                    .join(" + ")
983            ),
984            JsValue::Not(_, value) => write!(f, "!({value})"),
985            JsValue::Logical(_, op, list) => write!(
986                f,
987                "({})",
988                list.iter()
989                    .map(|v| v.to_string())
990                    .collect::<Vec<_>>()
991                    .join(op.joiner())
992            ),
993            JsValue::Binary(_, a, op, b) => write!(f, "({}{}{})", a, op.joiner(), b),
994            JsValue::Tenary(_, test, cons, alt) => write!(f, "({test} ? {cons} : {alt})"),
995            JsValue::New(_, call) => write!(
996                f,
997                "new {}({})",
998                call.callee(),
999                call.args()
1000                    .iter()
1001                    .map(|v| v.to_string())
1002                    .collect::<Vec<_>>()
1003                    .join(", ")
1004            ),
1005            JsValue::Call(_, call) => write!(
1006                f,
1007                "{}({})",
1008                call.callee(),
1009                call.args()
1010                    .iter()
1011                    .map(|v| v.to_string())
1012                    .collect::<Vec<_>>()
1013                    .join(", ")
1014            ),
1015            JsValue::SuperCall(_, args) => write!(
1016                f,
1017                "super({})",
1018                args.iter()
1019                    .map(|v| v.to_string())
1020                    .collect::<Vec<_>>()
1021                    .join(", ")
1022            ),
1023            JsValue::MemberCall(_, call) => write!(
1024                f,
1025                "{}[{}]({})",
1026                call.obj(),
1027                call.prop(),
1028                call.args()
1029                    .iter()
1030                    .map(|v| v.to_string())
1031                    .collect::<Vec<_>>()
1032                    .join(", ")
1033            ),
1034            JsValue::Member(_, obj, prop) => write!(f, "{obj}[{prop}]"),
1035            JsValue::Module(ModuleValue {
1036                module: name,
1037                annotations,
1038            }) => {
1039                write!(
1040                    f,
1041                    "Module({}, {})",
1042                    name.to_string_lossy(),
1043                    if let Some(annotations) = annotations {
1044                        Either::Left(annotations)
1045                    } else {
1046                        Either::Right("{}")
1047                    }
1048                )
1049            }
1050            JsValue::Unknown { .. } => write!(f, "???"),
1051            JsValue::WellKnownObject(obj) => write!(f, "WellKnownObject({obj:?})"),
1052            JsValue::WellKnownFunction(func) => write!(f, "WellKnownFunction({func:?})"),
1053            JsValue::Function(_, func_ident, return_value) => {
1054                write!(f, "Function#{func_ident}(return = {return_value:?})")
1055            }
1056            JsValue::Argument(func_ident, index) => {
1057                write!(f, "arguments[{index}#{func_ident}]")
1058            }
1059            JsValue::Iterated(_, iterable) => write!(f, "Iterated({iterable})"),
1060            JsValue::TypeOf(_, operand) => write!(f, "typeof({operand})"),
1061            JsValue::Promise(_, operand) => write!(f, "Promise<{operand}>"),
1062            JsValue::Awaited(_, operand) => write!(f, "await({operand})"),
1063        }
1064    }
1065}
1066
1067fn pretty_join(
1068    items: &[String],
1069    indent_depth: usize,
1070    single_line_separator: &str,
1071    multi_line_separator_end: &str,
1072    multi_line_separator_start: &str,
1073) -> String {
1074    let multi_line = items
1075        .iter()
1076        .any(|item| item.len() > 50 || item.contains('\n'))
1077        || items
1078            .iter()
1079            .map(|item| item.len() + single_line_separator.len())
1080            .sum::<usize>()
1081            > 100;
1082    if !multi_line {
1083        items.join(single_line_separator)
1084    } else if multi_line_separator_start.is_empty() {
1085        format!(
1086            "\n{}{}\n{}",
1087            "    ".repeat(indent_depth + 1),
1088            items.join(&format!(
1089                "{multi_line_separator_end}\n{}",
1090                "    ".repeat(indent_depth + 1)
1091            )),
1092            "    ".repeat(indent_depth)
1093        )
1094    } else {
1095        format!(
1096            "\n{}{multi_line_separator_start}{}\n{}",
1097            " ".repeat(indent_depth * 4 + 4 - multi_line_separator_start.len()),
1098            items.join(&format!(
1099                "{multi_line_separator_end}\n{}{multi_line_separator_start}",
1100                " ".repeat(indent_depth * 4 + 4 - multi_line_separator_start.len())
1101            )),
1102            "    ".repeat(indent_depth)
1103        )
1104    }
1105}
1106
1107fn total_nodes(vec: &[JsValue]) -> u32 {
1108    vec.iter().map(|v| v.total_nodes()).sum::<u32>()
1109}
1110
1111// Private meta methods
1112impl JsValue {
1113    fn meta_type(&self) -> JsValueMetaKind {
1114        match self {
1115            JsValue::Constant(..)
1116            | JsValue::Url(..)
1117            | JsValue::WellKnownObject(..)
1118            | JsValue::WellKnownFunction(..)
1119            | JsValue::Unknown { .. } => JsValueMetaKind::Leaf,
1120            JsValue::Array { .. }
1121            | JsValue::Object { .. }
1122            | JsValue::Alternatives { .. }
1123            | JsValue::Function(..)
1124            | JsValue::Promise(..)
1125            | JsValue::Member(..) => JsValueMetaKind::Nested,
1126            JsValue::Concat(..)
1127            | JsValue::Add(..)
1128            | JsValue::Not(..)
1129            | JsValue::Logical(..)
1130            | JsValue::Binary(..)
1131            | JsValue::New(..)
1132            | JsValue::Call(..)
1133            | JsValue::SuperCall(..)
1134            | JsValue::Tenary(..)
1135            | JsValue::MemberCall(..)
1136            | JsValue::Iterated(..)
1137            | JsValue::Awaited(..)
1138            | JsValue::TypeOf(..) => JsValueMetaKind::Operation,
1139            JsValue::Variable(..)
1140            | JsValue::Argument(..)
1141            | JsValue::FreeVar(..)
1142            | JsValue::Module(..) => JsValueMetaKind::Placeholder,
1143        }
1144    }
1145}
1146
1147// Constructors
1148impl JsValue {
1149    pub fn alternatives(list: Vec<JsValue>) -> Self {
1150        Self::Alternatives {
1151            total_nodes: 1 + total_nodes(&list),
1152            values: list,
1153            logical_property: None,
1154        }
1155    }
1156
1157    pub fn alternatives_with_additional_property(
1158        list: Vec<JsValue>,
1159        logical_property: LogicalProperty,
1160    ) -> Self {
1161        Self::Alternatives {
1162            total_nodes: 1 + total_nodes(&list),
1163            values: list,
1164            logical_property: Some(logical_property),
1165        }
1166    }
1167
1168    pub fn concat(list: Vec<JsValue>) -> Self {
1169        Self::Concat(1 + total_nodes(&list), list)
1170    }
1171
1172    pub fn add(list: Vec<JsValue>) -> Self {
1173        Self::Add(1 + total_nodes(&list), list)
1174    }
1175
1176    pub fn logical_and(list: Vec<JsValue>) -> Self {
1177        Self::Logical(1 + total_nodes(&list), LogicalOperator::And, list)
1178    }
1179
1180    pub fn logical_or(list: Vec<JsValue>) -> Self {
1181        Self::Logical(1 + total_nodes(&list), LogicalOperator::Or, list)
1182    }
1183
1184    pub fn nullish_coalescing(list: Vec<JsValue>) -> Self {
1185        Self::Logical(
1186            1 + total_nodes(&list),
1187            LogicalOperator::NullishCoalescing,
1188            list,
1189        )
1190    }
1191
1192    pub fn tenary(test: Box<JsValue>, cons: Box<JsValue>, alt: Box<JsValue>) -> Self {
1193        Self::Tenary(
1194            1 + test.total_nodes() + cons.total_nodes() + alt.total_nodes(),
1195            test,
1196            cons,
1197            alt,
1198        )
1199    }
1200
1201    pub fn iterated(iterable: Box<JsValue>) -> Self {
1202        Self::Iterated(1 + iterable.total_nodes(), iterable)
1203    }
1204
1205    pub fn equal(a: Box<JsValue>, b: Box<JsValue>) -> Self {
1206        Self::Binary(
1207            1 + a.total_nodes() + b.total_nodes(),
1208            a,
1209            BinaryOperator::Equal,
1210            b,
1211        )
1212    }
1213
1214    pub fn not_equal(a: Box<JsValue>, b: Box<JsValue>) -> Self {
1215        Self::Binary(
1216            1 + a.total_nodes() + b.total_nodes(),
1217            a,
1218            BinaryOperator::NotEqual,
1219            b,
1220        )
1221    }
1222
1223    pub fn strict_equal(a: Box<JsValue>, b: Box<JsValue>) -> Self {
1224        Self::Binary(
1225            1 + a.total_nodes() + b.total_nodes(),
1226            a,
1227            BinaryOperator::StrictEqual,
1228            b,
1229        )
1230    }
1231
1232    pub fn strict_not_equal(a: Box<JsValue>, b: Box<JsValue>) -> Self {
1233        Self::Binary(
1234            1 + a.total_nodes() + b.total_nodes(),
1235            a,
1236            BinaryOperator::StrictNotEqual,
1237            b,
1238        )
1239    }
1240
1241    pub fn logical_not(inner: Box<JsValue>) -> Self {
1242        Self::Not(1 + inner.total_nodes(), inner)
1243    }
1244
1245    pub fn type_of(operand: Box<JsValue>) -> Self {
1246        Self::TypeOf(1 + operand.total_nodes(), operand)
1247    }
1248
1249    pub fn array(items: Vec<JsValue>) -> Self {
1250        Self::Array {
1251            total_nodes: 1 + total_nodes(&items),
1252            items,
1253            mutable: true,
1254        }
1255    }
1256
1257    pub fn frozen_array(items: Vec<JsValue>) -> Self {
1258        Self::Array {
1259            total_nodes: 1 + total_nodes(&items),
1260            items,
1261            mutable: false,
1262        }
1263    }
1264
1265    pub fn function(
1266        func_ident: u32,
1267        is_async: bool,
1268        is_generator: bool,
1269        return_value: JsValue,
1270    ) -> Self {
1271        // Check generator first to handle async generators
1272        let return_value = if is_generator {
1273            JsValue::WellKnownObject(WellKnownObjectKind::Generator)
1274        } else if is_async {
1275            JsValue::promise(return_value)
1276        } else {
1277            return_value
1278        };
1279        Self::Function(
1280            1 + return_value.total_nodes(),
1281            func_ident,
1282            Box::new(return_value),
1283        )
1284    }
1285
1286    pub fn object(list: Vec<ObjectPart>) -> Self {
1287        Self::Object {
1288            total_nodes: 1 + list
1289                .iter()
1290                .map(|v| match v {
1291                    ObjectPart::KeyValue(k, v) => k.total_nodes() + v.total_nodes(),
1292                    ObjectPart::Spread(s) => s.total_nodes(),
1293                })
1294                .sum::<u32>(),
1295            parts: list,
1296            mutable: true,
1297        }
1298    }
1299
1300    pub fn frozen_object(list: Vec<ObjectPart>) -> Self {
1301        Self::Object {
1302            total_nodes: 1 + list
1303                .iter()
1304                .map(|v| match v {
1305                    ObjectPart::KeyValue(k, v) => k.total_nodes() + v.total_nodes(),
1306                    ObjectPart::Spread(s) => s.total_nodes(),
1307                })
1308                .sum::<u32>(),
1309            parts: list,
1310            mutable: false,
1311        }
1312    }
1313
1314    /// Build a `JsValue::New` from a callee and an owned args `Vec`.
1315    ///
1316    /// Pushes `f` onto `args` to form the `[args..., callee]` layout. If `args.capacity()`
1317    /// equals `args.len()`, this triggers a Vec realloc — only use this overload when the
1318    /// caller already has a `Vec` that is likely to have spare capacity for the trailing
1319    /// slot (e.g. an `args` Vec returned from [`CallList::into_parts`] or
1320    /// [`MemberCallList::into_parts`]). For from-scratch construction use
1321    /// [`JsValue::new_from_iter`], which pre-sizes the underlying allocation exactly.
1322    pub fn new_from_parts(f: JsValue, args: Vec<JsValue>) -> Self {
1323        let total = 1 + f.total_nodes() + total_nodes(&args);
1324        Self::New(total, CallList::from_parts(f, args))
1325    }
1326
1327    /// Build a `JsValue::New` from a callee and an args iterator with a known length.
1328    ///
1329    /// Allocates the underlying `Vec` with exact capacity (`args.len() + 1`), so no realloc
1330    /// occurs.
1331    pub fn new_from_iter<I>(f: JsValue, args: I) -> Self
1332    where
1333        I: IntoIterator<Item = JsValue>,
1334        I::IntoIter: ExactSizeIterator,
1335    {
1336        let list = CallList::from_iter(f, args);
1337        let total = 1 + total_nodes(&list.0);
1338        Self::New(total, list)
1339    }
1340
1341    /// Build a `JsValue::Call` from a callee and an owned args `Vec`.
1342    ///
1343    /// See [`JsValue::new_from_parts`] for the realloc caveat — only use this when the
1344    /// caller already has a `Vec` that is likely to be correctly sized (typically one
1345    /// obtained from [`CallList::into_parts`] / [`MemberCallList::into_parts`]). For
1346    /// from-scratch construction use [`JsValue::call_from_iter`].
1347    pub fn call_from_parts(f: JsValue, args: Vec<JsValue>) -> Self {
1348        let total = 1 + f.total_nodes() + total_nodes(&args);
1349        Self::Call(total, CallList::from_parts(f, args))
1350    }
1351
1352    /// Build a `JsValue::Call` from a callee and an args iterator with a known length.
1353    ///
1354    /// Allocates the underlying `Vec` with exact capacity (`args.len() + 1`), so no realloc
1355    /// occurs.
1356    pub fn call_from_iter<I>(f: JsValue, args: I) -> Self
1357    where
1358        I: IntoIterator<Item = JsValue>,
1359        I::IntoIter: ExactSizeIterator,
1360    {
1361        let list = CallList::from_iter(f, args);
1362        let total = 1 + total_nodes(&list.0);
1363        Self::Call(total, list)
1364    }
1365
1366    pub fn super_call(args: Vec<JsValue>) -> Self {
1367        Self::SuperCall(1 + total_nodes(&args), args)
1368    }
1369
1370    /// Build a `JsValue::MemberCall` from `obj`, `prop`, and an owned args `Vec`.
1371    ///
1372    /// See [`JsValue::new_from_parts`] for the realloc caveat — only use this when the
1373    /// caller already has a `Vec` that is likely to be correctly sized (typically one
1374    /// obtained from [`MemberCallList::into_parts`]). For from-scratch construction use
1375    /// [`JsValue::member_call_from_iter`].
1376    pub fn member_call_from_parts(o: JsValue, p: JsValue, args: Vec<JsValue>) -> Self {
1377        let total = 1 + o.total_nodes() + p.total_nodes() + total_nodes(&args);
1378        Self::MemberCall(total, MemberCallList::from_parts(o, p, args))
1379    }
1380
1381    /// Build a `JsValue::MemberCall` from `obj`, `prop`, and an args iterator with a known
1382    /// length.
1383    ///
1384    /// Allocates the underlying `Vec` with exact capacity (`args.len() + 2`), so no realloc
1385    /// occurs.
1386    pub fn member_call_from_iter<I>(o: JsValue, p: JsValue, args: I) -> Self
1387    where
1388        I: IntoIterator<Item = JsValue>,
1389        I::IntoIter: ExactSizeIterator,
1390    {
1391        let list = MemberCallList::from_iter(o, p, args);
1392        let total = 1 + total_nodes(&list.0);
1393        Self::MemberCall(total, list)
1394    }
1395
1396    pub fn member(o: Box<JsValue>, p: Box<JsValue>) -> Self {
1397        Self::Member(1 + o.total_nodes() + p.total_nodes(), o, p)
1398    }
1399
1400    pub fn promise(operand: JsValue) -> Self {
1401        // In ecmascript Promise<Promise<T>> is equivalent to Promise<T>
1402        if let JsValue::Promise(_, _) = operand {
1403            return operand;
1404        }
1405        Self::Promise(1 + operand.total_nodes(), Box::new(operand))
1406    }
1407
1408    pub fn awaited(operand: Box<JsValue>) -> Self {
1409        Self::Awaited(1 + operand.total_nodes(), operand)
1410    }
1411
1412    pub fn unknown(value: impl Into<Arc<JsValue>>, side_effects: bool, reason: RcStr) -> Self {
1413        Self::Unknown {
1414            original_value: Some(value.into()),
1415            reason,
1416            has_side_effects: side_effects,
1417        }
1418    }
1419
1420    pub fn unknown_empty(side_effects: bool, reason: RcStr) -> Self {
1421        Self::Unknown {
1422            original_value: None,
1423            reason,
1424            has_side_effects: side_effects,
1425        }
1426    }
1427
1428    pub fn unknown_if(is_unknown: bool, value: JsValue, side_effects: bool, reason: RcStr) -> Self {
1429        if is_unknown {
1430            Self::Unknown {
1431                original_value: Some(value.into()),
1432                reason,
1433                has_side_effects: side_effects,
1434            }
1435        } else {
1436            value
1437        }
1438    }
1439}
1440
1441// Methods regarding node count
1442impl JsValue {
1443    pub fn has_children(&self) -> bool {
1444        self.total_nodes() > 1
1445    }
1446
1447    pub fn total_nodes(&self) -> u32 {
1448        match self {
1449            JsValue::Constant(_)
1450            | JsValue::Url(_, _)
1451            | JsValue::FreeVar(_)
1452            | JsValue::Variable(_)
1453            | JsValue::Module(..)
1454            | JsValue::WellKnownObject(_)
1455            | JsValue::WellKnownFunction(_)
1456            | JsValue::Unknown { .. }
1457            | JsValue::Argument(..) => 1,
1458
1459            JsValue::Array { total_nodes: c, .. }
1460            | JsValue::Object { total_nodes: c, .. }
1461            | JsValue::Alternatives { total_nodes: c, .. }
1462            | JsValue::Concat(c, _)
1463            | JsValue::Add(c, _)
1464            | JsValue::Not(c, _)
1465            | JsValue::Logical(c, _, _)
1466            | JsValue::Binary(c, _, _, _)
1467            | JsValue::Tenary(c, _, _, _)
1468            | JsValue::New(c, _)
1469            | JsValue::Call(c, _)
1470            | JsValue::SuperCall(c, _)
1471            | JsValue::MemberCall(c, _)
1472            | JsValue::Member(c, _, _)
1473            | JsValue::Function(c, _, _)
1474            | JsValue::Iterated(c, ..)
1475            | JsValue::Promise(c, ..)
1476            | JsValue::Awaited(c, ..)
1477            | JsValue::TypeOf(c, ..) => *c,
1478        }
1479    }
1480
1481    fn update_total_nodes(&mut self) {
1482        match self {
1483            JsValue::Constant(_)
1484            | JsValue::Url(_, _)
1485            | JsValue::FreeVar(_)
1486            | JsValue::Variable(_)
1487            | JsValue::Module(..)
1488            | JsValue::WellKnownObject(_)
1489            | JsValue::WellKnownFunction(_)
1490            | JsValue::Unknown { .. }
1491            | JsValue::Argument(..) => {}
1492
1493            JsValue::Array {
1494                total_nodes: c,
1495                items: list,
1496                ..
1497            }
1498            | JsValue::Alternatives {
1499                total_nodes: c,
1500                values: list,
1501                ..
1502            }
1503            | JsValue::Concat(c, list)
1504            | JsValue::Add(c, list)
1505            | JsValue::Logical(c, _, list) => {
1506                *c = 1 + total_nodes(list);
1507            }
1508
1509            JsValue::Binary(c, a, _, b) => {
1510                *c = 1 + a.total_nodes() + b.total_nodes();
1511            }
1512            JsValue::Tenary(c, test, cons, alt) => {
1513                *c = 1 + test.total_nodes() + cons.total_nodes() + alt.total_nodes();
1514            }
1515            JsValue::Not(c, r) => {
1516                *c = 1 + r.total_nodes();
1517            }
1518            JsValue::Promise(c, r) => {
1519                *c = 1 + r.total_nodes();
1520            }
1521            JsValue::Awaited(c, r) => {
1522                *c = 1 + r.total_nodes();
1523            }
1524
1525            JsValue::Object {
1526                total_nodes: c,
1527                parts,
1528                mutable: _,
1529            } => {
1530                *c = 1 + parts
1531                    .iter()
1532                    .map(|v| match v {
1533                        ObjectPart::KeyValue(k, v) => k.total_nodes() + v.total_nodes(),
1534                        ObjectPart::Spread(s) => s.total_nodes(),
1535                    })
1536                    .sum::<u32>();
1537            }
1538            JsValue::New(c, call) => {
1539                *c = 1 + call.total_nodes();
1540            }
1541            JsValue::Call(c, call) => {
1542                *c = 1 + call.total_nodes();
1543            }
1544            JsValue::SuperCall(c, args) => {
1545                *c = 1 + total_nodes(args);
1546            }
1547            JsValue::MemberCall(c, call) => {
1548                *c = 1 + call.total_nodes();
1549            }
1550            JsValue::Member(c, o, p) => {
1551                *c = 1 + o.total_nodes() + p.total_nodes();
1552            }
1553            JsValue::Function(c, _, r) => {
1554                *c = 1 + r.total_nodes();
1555            }
1556
1557            JsValue::Iterated(c, iterable) => {
1558                *c = 1 + iterable.total_nodes();
1559            }
1560
1561            JsValue::TypeOf(c, operand) => {
1562                *c = 1 + operand.total_nodes();
1563            }
1564        }
1565    }
1566
1567    #[cfg(debug_assertions)]
1568    pub fn debug_assert_total_nodes_up_to_date(&mut self) {
1569        let old = self.total_nodes();
1570        self.update_total_nodes();
1571        assert_eq!(
1572            old,
1573            self.total_nodes(),
1574            "total nodes not up to date {self:?}"
1575        );
1576    }
1577
1578    #[cfg(not(debug_assertions))]
1579    pub fn debug_assert_total_nodes_up_to_date(&mut self) {}
1580}
1581
1582// Methods for explaining a value
1583impl JsValue {
1584    pub fn explain_args(args: &[JsValue], depth: usize, unknown_depth: usize) -> (String, String) {
1585        let mut hints = Vec::new();
1586        let args = args
1587            .iter()
1588            .map(|arg| arg.explain_internal(&mut hints, 1, depth, unknown_depth))
1589            .collect::<Vec<_>>();
1590        let explainer = pretty_join(&args, 0, ", ", ",", "");
1591        (
1592            explainer,
1593            hints.into_iter().fold(String::new(), |mut out, h| {
1594                let _ = write!(out, "\n{h}");
1595                out
1596            }),
1597        )
1598    }
1599
1600    pub fn explain(&self, depth: usize, unknown_depth: usize) -> (String, String) {
1601        let mut hints = Vec::new();
1602        let explainer = self.explain_internal(&mut hints, 0, depth, unknown_depth);
1603        (
1604            explainer,
1605            hints.into_iter().fold(String::new(), |mut out, h| {
1606                let _ = write!(out, "\n{h}");
1607                out
1608            }),
1609        )
1610    }
1611
1612    fn explain_internal_inner(
1613        &self,
1614        hints: &mut Vec<String>,
1615        indent_depth: usize,
1616        depth: usize,
1617        unknown_depth: usize,
1618    ) -> String {
1619        if depth == 0 {
1620            return "...".to_string();
1621        }
1622        // let i = hints.len();
1623
1624        // if explainer.len() < 100 {
1625        self.explain_internal(hints, indent_depth, depth - 1, unknown_depth)
1626        // }
1627        // hints.truncate(i);
1628        // hints.push(String::new());
1629        // hints[i] = format!(
1630        //     "- *{}* {}",
1631        //     i,
1632        //     self.explain_internal(hints, 1, depth - 1, unknown_depth)
1633        // );
1634        // format!("*{}*", i)
1635    }
1636
1637    fn explain_internal(
1638        &self,
1639        hints: &mut Vec<String>,
1640        indent_depth: usize,
1641        depth: usize,
1642        unknown_depth: usize,
1643    ) -> String {
1644        match self {
1645            JsValue::Constant(v) => format!("{v}"),
1646            JsValue::Array { items, mutable, .. } => format!(
1647                "{}[{}]",
1648                if *mutable { "" } else { "frozen " },
1649                pretty_join(
1650                    &items
1651                        .iter()
1652                        .map(|v| v.explain_internal_inner(
1653                            hints,
1654                            indent_depth + 1,
1655                            depth,
1656                            unknown_depth
1657                        ))
1658                        .collect::<Vec<_>>(),
1659                    indent_depth,
1660                    ", ",
1661                    ",",
1662                    ""
1663                )
1664            ),
1665            JsValue::Object { parts, mutable, .. } => format!(
1666                "{}{{{}}}",
1667                if *mutable { "" } else { "frozen " },
1668                pretty_join(
1669                    &parts
1670                        .iter()
1671                        .map(|v| match v {
1672                            ObjectPart::KeyValue(key, value) => format!(
1673                                "{}: {}",
1674                                key.explain_internal_inner(
1675                                    hints,
1676                                    indent_depth + 1,
1677                                    depth,
1678                                    unknown_depth
1679                                ),
1680                                value.explain_internal_inner(
1681                                    hints,
1682                                    indent_depth + 1,
1683                                    depth,
1684                                    unknown_depth
1685                                )
1686                            ),
1687                            ObjectPart::Spread(value) => format!(
1688                                "...{}",
1689                                value.explain_internal_inner(
1690                                    hints,
1691                                    indent_depth + 1,
1692                                    depth,
1693                                    unknown_depth
1694                                )
1695                            ),
1696                        })
1697                        .collect::<Vec<_>>(),
1698                    indent_depth,
1699                    ", ",
1700                    ",",
1701                    ""
1702                )
1703            ),
1704            JsValue::Url(url, kind) => format!("{url} {kind}"),
1705            JsValue::Alternatives {
1706                total_nodes: _,
1707                values,
1708                logical_property,
1709            } => {
1710                let list = pretty_join(
1711                    &values
1712                        .iter()
1713                        .map(|v| {
1714                            v.explain_internal_inner(hints, indent_depth + 1, depth, unknown_depth)
1715                        })
1716                        .collect::<Vec<_>>(),
1717                    indent_depth,
1718                    " | ",
1719                    "",
1720                    "| ",
1721                );
1722                if let Some(logical_property) = logical_property {
1723                    format!("({list}){{{logical_property}}}")
1724                } else {
1725                    format!("({list})")
1726                }
1727            }
1728            JsValue::FreeVar(name) => format!("FreeVar({name})"),
1729            JsValue::Variable(name) => {
1730                format!("{}", name.0)
1731            }
1732            JsValue::Argument(_, index) => {
1733                format!("arguments[{index}]")
1734            }
1735            JsValue::Concat(_, list) => format!(
1736                "`{}`",
1737                list.iter()
1738                    .map(|v| v.as_str().map_or_else(
1739                        || format!(
1740                            "${{{}}}",
1741                            v.explain_internal_inner(hints, indent_depth + 1, depth, unknown_depth)
1742                        ),
1743                        |str| str.to_string()
1744                    ))
1745                    .collect::<Vec<_>>()
1746                    .join("")
1747            ),
1748            JsValue::Add(_, list) => format!(
1749                "({})",
1750                pretty_join(
1751                    &list
1752                        .iter()
1753                        .map(|v| v.explain_internal_inner(
1754                            hints,
1755                            indent_depth + 1,
1756                            depth,
1757                            unknown_depth
1758                        ))
1759                        .collect::<Vec<_>>(),
1760                    indent_depth,
1761                    " + ",
1762                    "",
1763                    "+ "
1764                )
1765            ),
1766            JsValue::Logical(_, op, list) => format!(
1767                "({})",
1768                pretty_join(
1769                    &list
1770                        .iter()
1771                        .map(|v| v.explain_internal_inner(
1772                            hints,
1773                            indent_depth + 1,
1774                            depth,
1775                            unknown_depth
1776                        ))
1777                        .collect::<Vec<_>>(),
1778                    indent_depth,
1779                    op.joiner(),
1780                    "",
1781                    op.multi_line_joiner()
1782                )
1783            ),
1784            JsValue::Binary(_, a, op, b) => format!(
1785                "({}{}{})",
1786                a.explain_internal_inner(hints, indent_depth, depth, unknown_depth),
1787                op.joiner(),
1788                b.explain_internal_inner(hints, indent_depth, depth, unknown_depth),
1789            ),
1790            JsValue::Tenary(_, test, cons, alt) => format!(
1791                "({} ? {} : {})",
1792                test.explain_internal_inner(hints, indent_depth, depth, unknown_depth),
1793                cons.explain_internal_inner(hints, indent_depth, depth, unknown_depth),
1794                alt.explain_internal_inner(hints, indent_depth, depth, unknown_depth),
1795            ),
1796            JsValue::Not(_, value) => format!(
1797                "!({})",
1798                value.explain_internal_inner(hints, indent_depth, depth, unknown_depth)
1799            ),
1800            JsValue::Iterated(_, iterable) => {
1801                format!(
1802                    "Iterated({})",
1803                    iterable.explain_internal_inner(hints, indent_depth, depth, unknown_depth)
1804                )
1805            }
1806            JsValue::TypeOf(_, operand) => {
1807                format!(
1808                    "typeof({})",
1809                    operand.explain_internal_inner(hints, indent_depth, depth, unknown_depth)
1810                )
1811            }
1812            JsValue::Promise(_, operand) => {
1813                format!(
1814                    "Promise<{}>",
1815                    operand.explain_internal_inner(hints, indent_depth, depth, unknown_depth)
1816                )
1817            }
1818            JsValue::Awaited(_, operand) => {
1819                format!(
1820                    "await({})",
1821                    operand.explain_internal_inner(hints, indent_depth, depth, unknown_depth)
1822                )
1823            }
1824            JsValue::New(_, call) => format!(
1825                "new {}({})",
1826                call.callee()
1827                    .explain_internal_inner(hints, indent_depth, depth, unknown_depth),
1828                pretty_join(
1829                    &call
1830                        .args()
1831                        .iter()
1832                        .map(|v| v.explain_internal_inner(
1833                            hints,
1834                            indent_depth + 1,
1835                            depth,
1836                            unknown_depth
1837                        ))
1838                        .collect::<Vec<_>>(),
1839                    indent_depth,
1840                    ", ",
1841                    ",",
1842                    ""
1843                )
1844            ),
1845            JsValue::Call(_, call) => format!(
1846                "{}({})",
1847                call.callee()
1848                    .explain_internal_inner(hints, indent_depth, depth, unknown_depth),
1849                pretty_join(
1850                    &call
1851                        .args()
1852                        .iter()
1853                        .map(|v| v.explain_internal_inner(
1854                            hints,
1855                            indent_depth + 1,
1856                            depth,
1857                            unknown_depth
1858                        ))
1859                        .collect::<Vec<_>>(),
1860                    indent_depth,
1861                    ", ",
1862                    ",",
1863                    ""
1864                )
1865            ),
1866            JsValue::SuperCall(_, args) => {
1867                format!(
1868                    "super({})",
1869                    pretty_join(
1870                        &args
1871                            .iter()
1872                            .map(|v| v.explain_internal_inner(
1873                                hints,
1874                                indent_depth + 1,
1875                                depth,
1876                                unknown_depth
1877                            ))
1878                            .collect::<Vec<_>>(),
1879                        indent_depth,
1880                        ", ",
1881                        ",",
1882                        ""
1883                    )
1884                )
1885            }
1886            JsValue::MemberCall(_, call) => format!(
1887                "{}[{}]({})",
1888                call.obj()
1889                    .explain_internal_inner(hints, indent_depth, depth, unknown_depth),
1890                call.prop()
1891                    .explain_internal_inner(hints, indent_depth, depth, unknown_depth),
1892                pretty_join(
1893                    &call
1894                        .args()
1895                        .iter()
1896                        .map(|v| v.explain_internal_inner(
1897                            hints,
1898                            indent_depth + 1,
1899                            depth,
1900                            unknown_depth
1901                        ))
1902                        .collect::<Vec<_>>(),
1903                    indent_depth,
1904                    ", ",
1905                    ",",
1906                    ""
1907                )
1908            ),
1909            JsValue::Member(_, obj, prop) => {
1910                format!(
1911                    "{}[{}]",
1912                    obj.explain_internal_inner(hints, indent_depth, depth, unknown_depth),
1913                    prop.explain_internal_inner(hints, indent_depth, depth, unknown_depth)
1914                )
1915            }
1916            JsValue::Module(ModuleValue {
1917                module: name,
1918                annotations,
1919            }) => {
1920                format!(
1921                    "module<{}, {}>",
1922                    name.to_string_lossy(),
1923                    if let Some(annotations) = annotations {
1924                        Either::Left(annotations)
1925                    } else {
1926                        Either::Right("{}")
1927                    }
1928                )
1929            }
1930            JsValue::Unknown {
1931                original_value: inner,
1932                reason: explainer,
1933                has_side_effects,
1934            } => {
1935                let has_side_effects = *has_side_effects;
1936                if unknown_depth == 0 || explainer.is_empty() {
1937                    "???".to_string()
1938                } else if let Some(inner) = inner {
1939                    let i = hints.len();
1940                    hints.push(String::new());
1941                    hints[i] = format!(
1942                        "- *{}* {}\n  ⚠️  {}{}",
1943                        i,
1944                        inner.explain_internal(hints, 1, depth, unknown_depth - 1),
1945                        explainer,
1946                        if has_side_effects {
1947                            "\n  ⚠️  This value might have side effects"
1948                        } else {
1949                            ""
1950                        }
1951                    );
1952                    format!("???*{i}*")
1953                } else {
1954                    let i = hints.len();
1955                    hints.push(String::new());
1956                    hints[i] = format!(
1957                        "- *{}* {}{}",
1958                        i,
1959                        explainer,
1960                        if has_side_effects {
1961                            "\n  ⚠️  This value might have side effects"
1962                        } else {
1963                            ""
1964                        }
1965                    );
1966                    format!("???*{i}*")
1967                }
1968            }
1969            JsValue::WellKnownObject(obj) => {
1970                let (name, explainer) = match obj {
1971                    WellKnownObjectKind::Generator => (
1972                        "Generator",
1973                        "A Generator or AsyncGenerator object: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator",
1974                    ),
1975                    WellKnownObjectKind::GlobalObject => (
1976                        "Object",
1977                        "The global Object variable",
1978                    ),
1979                    WellKnownObjectKind::PathModule | WellKnownObjectKind::PathModuleDefault => (
1980                        "path",
1981                        "The Node.js path module: https://nodejs.org/api/path.html",
1982                    ),
1983                    WellKnownObjectKind::FsModule | WellKnownObjectKind::FsModuleDefault => (
1984                        "fs",
1985                        "The Node.js fs module: https://nodejs.org/api/fs.html",
1986                    ),
1987                    WellKnownObjectKind::FsExtraModule | WellKnownObjectKind::FsExtraModuleDefault => (
1988                        "fs-extra",
1989                        "The Node.js fs-extra module: https://github.com/jprichardson/node-fs-extra",
1990                    ),
1991                    WellKnownObjectKind::FsModulePromises => (
1992                        "fs/promises",
1993                        "The Node.js fs module: https://nodejs.org/api/fs.html#promises-api",
1994                    ),
1995                    WellKnownObjectKind::UrlModule | WellKnownObjectKind::UrlModuleDefault => (
1996                        "url",
1997                        "The Node.js url module: https://nodejs.org/api/url.html",
1998                    ),
1999                    WellKnownObjectKind::ModuleModule | WellKnownObjectKind::ModuleModuleDefault => (
2000                        "module",
2001                        "The Node.js `module` module: https://nodejs.org/api/module.html",
2002                    ),
2003                    WellKnownObjectKind::WorkerThreadsModule | WellKnownObjectKind::WorkerThreadsModuleDefault => (
2004                        "worker_threads",
2005                        "The Node.js `worker_threads` module: https://nodejs.org/api/worker_threads.html",
2006                    ),
2007                    WellKnownObjectKind::ChildProcessModule | WellKnownObjectKind::ChildProcessModuleDefault => (
2008                        "child_process",
2009                        "The Node.js child_process module: https://nodejs.org/api/child_process.html",
2010                    ),
2011                    WellKnownObjectKind::OsModule | WellKnownObjectKind::OsModuleDefault => (
2012                        "os",
2013                        "The Node.js os module: https://nodejs.org/api/os.html",
2014                    ),
2015                    WellKnownObjectKind::NodeProcessModule => (
2016                        "process",
2017                        "The Node.js process module: https://nodejs.org/api/process.html",
2018                    ),
2019                    WellKnownObjectKind::NodeProcessArgv => (
2020                        "process.argv",
2021                        "The Node.js process.argv property: https://nodejs.org/api/process.html#processargv",
2022                    ),
2023                    WellKnownObjectKind::NodeProcessEnv => (
2024                        "process.env",
2025                        "The Node.js process.env property: https://nodejs.org/api/process.html#processenv",
2026                    ),
2027                    WellKnownObjectKind::NodePreGyp => (
2028                        "@mapbox/node-pre-gyp",
2029                        "The Node.js @mapbox/node-pre-gyp module: https://github.com/mapbox/node-pre-gyp",
2030                    ),
2031                    WellKnownObjectKind::NodeExpressApp => (
2032                        "express",
2033                        "The Node.js express package: https://github.com/expressjs/express"
2034                    ),
2035                    WellKnownObjectKind::NodeProtobufLoader => (
2036                        "@grpc/proto-loader",
2037                        "The Node.js @grpc/proto-loader package: https://github.com/grpc/grpc-node"
2038                    ),
2039                    WellKnownObjectKind::NodeBuffer => (
2040                        "Buffer",
2041                        "The Node.js Buffer object: https://nodejs.org/api/buffer.html#class-buffer"
2042                    ),
2043                    WellKnownObjectKind::RequireCache => (
2044                        "require.cache",
2045                        "The CommonJS require.cache object: https://nodejs.org/api/modules.html#requirecache"
2046                    ),
2047                    WellKnownObjectKind::ImportMeta => (
2048                        "import.meta",
2049                        "The import.meta object"
2050                    ),
2051                    WellKnownObjectKind::ModuleHot => (
2052                        "module.hot",
2053                        "The module.hot HMR API"
2054                    ),
2055                };
2056                if depth > 0 {
2057                    let i = hints.len();
2058                    hints.push(format!("- *{i}* {name}: {explainer}"));
2059                    format!("{name}*{i}*")
2060                } else {
2061                    name.to_string()
2062                }
2063            }
2064            JsValue::WellKnownFunction(func) => {
2065                let (name, explainer) = match func {
2066                    WellKnownFunctionKind::ArrayFilter => (
2067                      "Array.prototype.filter".to_string(),
2068                      "The standard Array.prototype.filter method: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter"
2069                    ),
2070                    WellKnownFunctionKind::ArrayForEach => (
2071                      "Array.prototype.forEach".to_string(),
2072                      "The standard Array.prototype.forEach method: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach"
2073                    ),
2074                    WellKnownFunctionKind::ArrayMap => (
2075                      "Array.prototype.map".to_string(),
2076                      "The standard Array.prototype.map method: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map"
2077                    ),
2078                    WellKnownFunctionKind::ObjectAssign => (
2079                        "Object.assign".to_string(),
2080                        "Object.assign method: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/assign",
2081                    ),
2082                    WellKnownFunctionKind::PathJoin => (
2083                        "path.join".to_string(),
2084                        "The Node.js path.join method: https://nodejs.org/api/path.html#pathjoinpaths",
2085                    ),
2086                    WellKnownFunctionKind::PathDirname => (
2087                        "path.dirname".to_string(),
2088                        "The Node.js path.dirname method: https://nodejs.org/api/path.html#pathdirnamepath",
2089                    ),
2090                    WellKnownFunctionKind::PathResolve(cwd) => (
2091                        format!("path.resolve({cwd})"),
2092                        "The Node.js path.resolve method: https://nodejs.org/api/path.html#pathresolvepaths",
2093                    ),
2094                    WellKnownFunctionKind::Import => (
2095                        "import".to_string(),
2096                        "The dynamic import() method from the ESM specification: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#dynamic_imports"
2097                    ),
2098                    WellKnownFunctionKind::Require => ("require".to_string(), "The require method from CommonJS"),
2099                    WellKnownFunctionKind::RequireFrom(rel) => (
2100                        format!("createRequire('{rel}')"),
2101                        "The return value of Node.js module.createRequire: https://nodejs.org/api/module.html#modulecreaterequirefilename"
2102                    ),
2103                    WellKnownFunctionKind::RequireResolve => ("require.resolve".to_string(), "The require.resolve method from CommonJS"),
2104                    WellKnownFunctionKind::RequireContext => ("require.context".to_string(), "The require.context method from webpack"),
2105                    WellKnownFunctionKind::RequireContextRequire(..) => ("require.context(...)".to_string(), "The require.context(...) method from webpack: https://webpack.js.org/api/module-methods/#requirecontext"),
2106                    WellKnownFunctionKind::RequireContextRequireKeys(..) => ("require.context(...).keys".to_string(), "The require.context(...).keys method from webpack: https://webpack.js.org/guides/dependency-management/#requirecontext"),
2107                    WellKnownFunctionKind::RequireContextRequireResolve(..) => ("require.context(...).resolve".to_string(), "The require.context(...).resolve method from webpack: https://webpack.js.org/guides/dependency-management/#requirecontext"),
2108                    WellKnownFunctionKind::Define => ("define".to_string(), "The define method from AMD"),
2109                    WellKnownFunctionKind::FsReadMethod(name) => (
2110                        format!("fs.{name}"),
2111                        "A file reading method from the Node.js fs module: https://nodejs.org/api/fs.html",
2112                    ),
2113                    WellKnownFunctionKind::FsReadDir => (
2114                        "fs.readdir".to_string(),
2115                        "The Node.js fs.readdir method: https://nodejs.org/api/fs.html",
2116                    ),
2117                    WellKnownFunctionKind::PathToFileUrl => (
2118                        "url.pathToFileURL".to_string(),
2119                        "The Node.js url.pathToFileURL method: https://nodejs.org/api/url.html#urlpathtofileurlpath",
2120                    ),
2121                    WellKnownFunctionKind::CreateRequire => (
2122                        "module.createRequire".to_string(),
2123                        "The Node.js module.createRequire method: https://nodejs.org/api/module.html#modulecreaterequirefilename",
2124                    ),
2125                    WellKnownFunctionKind::ChildProcessSpawnMethod(name) => (
2126                        format!("child_process.{name}"),
2127                        "A process spawning method from the Node.js child_process module: https://nodejs.org/api/child_process.html",
2128                    ),
2129                    WellKnownFunctionKind::ChildProcessFork => (
2130                        "child_process.fork".to_string(),
2131                        "The Node.js child_process.fork method: https://nodejs.org/api/child_process.html#child_processforkmodulepath-args-options",
2132                    ),
2133                    WellKnownFunctionKind::OsArch => (
2134                        "os.arch".to_string(),
2135                        "The Node.js os.arch method: https://nodejs.org/api/os.html#os_os_arch",
2136                    ),
2137                    WellKnownFunctionKind::OsPlatform => (
2138                        "os.process".to_string(),
2139                        "The Node.js os.process method: https://nodejs.org/api/os.html#os_os_process",
2140                    ),
2141                    WellKnownFunctionKind::OsEndianness => (
2142                        "os.endianness".to_string(),
2143                        "The Node.js os.endianness method: https://nodejs.org/api/os.html#os_os_endianness",
2144                    ),
2145                    WellKnownFunctionKind::ProcessCwd => (
2146                        "process.cwd".to_string(),
2147                        "The Node.js process.cwd method: https://nodejs.org/api/process.html#processcwd",
2148                    ),
2149                    WellKnownFunctionKind::NodePreGypFind => (
2150                        "binary.find".to_string(),
2151                        "The Node.js @mapbox/node-pre-gyp module: https://github.com/mapbox/node-pre-gyp",
2152                    ),
2153                    WellKnownFunctionKind::NodeGypBuild => (
2154                        "node-gyp-build".to_string(),
2155                        "The Node.js node-gyp-build module: https://github.com/prebuild/node-gyp-build"
2156                    ),
2157                    WellKnownFunctionKind::NodeBindings => (
2158                        "bindings".to_string(),
2159                        "The Node.js bindings module: https://github.com/TooTallNate/node-bindings"
2160                    ),
2161                    WellKnownFunctionKind::NodeExpress => (
2162                        "express".to_string(),
2163                        "require('express')() : https://github.com/expressjs/express"
2164                    ),
2165                    WellKnownFunctionKind::NodeExpressSet => (
2166                        "set".to_string(),
2167                        "require('express')().set('view engine', 'jade')  https://github.com/expressjs/express"
2168                    ),
2169                    WellKnownFunctionKind::NodeStrongGlobalize => (
2170                      "SetRootDir".to_string(),
2171                      "require('strong-globalize')()  https://github.com/strongloop/strong-globalize"
2172                    ),
2173                    WellKnownFunctionKind::NodeStrongGlobalizeSetRootDir => (
2174                      "SetRootDir".to_string(),
2175                      "require('strong-globalize').SetRootDir(__dirname)  https://github.com/strongloop/strong-globalize"
2176                    ),
2177                    WellKnownFunctionKind::NodeResolveFrom => (
2178                      "resolveFrom".to_string(),
2179                      "require('resolve-from')(__dirname, 'node-gyp/bin/node-gyp')  https://github.com/sindresorhus/resolve-from"
2180                    ),
2181                    WellKnownFunctionKind::NodeProtobufLoad => (
2182                      "load/loadSync".to_string(),
2183                      "require('@grpc/proto-loader').load(filepath, { includeDirs: [root] }) https://github.com/grpc/grpc-node"
2184                    ),
2185                    WellKnownFunctionKind::NodeWorkerConstructor => (
2186                      "Worker".to_string(),
2187                      "The Node.js worker_threads Worker constructor: https://nodejs.org/api/worker_threads.html#worker_threads_class_worker"
2188                    ),
2189                    WellKnownFunctionKind::WorkerConstructor => (
2190                      "Worker".to_string(),
2191                      "The standard Worker constructor: https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker"
2192                    ),
2193                    WellKnownFunctionKind::SharedWorkerConstructor => (
2194                      "SharedWorker".to_string(),
2195                      "The standard SharedWorker constructor: https://developer.mozilla.org/en-US/docs/Web/API/SharedWorker/SharedWorker"
2196                    ),
2197                    WellKnownFunctionKind::URLConstructor => (
2198                      "URL".to_string(),
2199                      "The standard URL constructor: https://developer.mozilla.org/en-US/docs/Web/API/URL/URL"
2200                    ),
2201                    WellKnownFunctionKind::ModuleHotAccept => (
2202                      "module.hot.accept".to_string(),
2203                      "The module.hot.accept HMR API: https://webpack.js.org/api/hot-module-replacement/#accept"
2204                    ),
2205                    WellKnownFunctionKind::ModuleHotDecline => (
2206                      "module.hot.decline".to_string(),
2207                      "The module.hot.decline HMR API: https://webpack.js.org/api/hot-module-replacement/#decline"
2208                    ),
2209                    WellKnownFunctionKind::ImportMetaGlob => (
2210                      "import.meta.glob".to_string(),
2211                      "The import.meta.glob() function from Vite: https://vite.dev/guide/features.html#glob-import"
2212                    ),
2213                };
2214                if depth > 0 {
2215                    let i = hints.len();
2216                    hints.push(format!("- *{i}* {name}: {explainer}"));
2217                    format!("{name}*{i}*")
2218                } else {
2219                    name
2220                }
2221            }
2222            JsValue::Function(_, _, return_value) => {
2223                if depth > 0 {
2224                    format!(
2225                        "(...) => {}",
2226                        return_value.explain_internal(
2227                            hints,
2228                            indent_depth,
2229                            depth - 1,
2230                            unknown_depth
2231                        )
2232                    )
2233                } else {
2234                    "(...) => ...".to_string()
2235                }
2236            }
2237        }
2238    }
2239}
2240
2241// Unknown management
2242impl JsValue {
2243    /// Convert the value into unknown with a specific reason.
2244    pub fn make_unknown(&mut self, side_effects: bool, reason: RcStr) {
2245        *self = JsValue::unknown(take(self), side_effects || self.has_side_effects(), reason);
2246    }
2247
2248    /// Convert the owned value into unknown with a specific reason.
2249    pub fn into_unknown(mut self, side_effects: bool, reason: RcStr) -> Self {
2250        self.make_unknown(side_effects, reason);
2251        self
2252    }
2253
2254    /// Convert the value into unknown with a specific reason, but don't retain
2255    /// the original value.
2256    pub fn make_unknown_without_content(&mut self, side_effects: bool, reason: RcStr) {
2257        *self = JsValue::unknown_empty(side_effects || self.has_side_effects(), reason);
2258    }
2259
2260    /// Make all nested operations unknown when the value is an operation.
2261    pub fn make_nested_operations_unknown(&mut self) -> bool {
2262        fn inner(this: &mut JsValue) -> bool {
2263            if matches!(this.meta_type(), JsValueMetaKind::Operation) {
2264                this.make_unknown(false, rcstr!("nested operation"));
2265                true
2266            } else {
2267                this.for_each_children_mut(&mut inner)
2268            }
2269        }
2270        if matches!(self.meta_type(), JsValueMetaKind::Operation) {
2271            self.for_each_children_mut(&mut inner)
2272        } else {
2273            false
2274        }
2275    }
2276
2277    pub fn add_unknown_mutations(&mut self, side_effects: bool) {
2278        self.add_alt(JsValue::unknown_empty(
2279            side_effects,
2280            rcstr!("unknown mutation"),
2281        ));
2282    }
2283}
2284
2285// Definable name management
2286impl JsValue {
2287    /// When the value has a user-definable name, return it in segments. Otherwise
2288    /// returns None.
2289    /// It also returns a boolean whether the variable was potentially reassigned.
2290    /// - any free var has itself as user-definable name: ["foo"]
2291    /// - any member access adds the identifier as segment after the object: ["foo", "prop"]
2292    /// - some well-known objects/functions have a user-definable names: ["import"]
2293    /// - member calls without arguments also have a user-definable name: ["foo", Call("func")]
2294    /// - typeof expressions add `typeof` after the argument's segments: ["foo", "typeof"]
2295    pub fn get_definable_name(
2296        &self,
2297        var_graph: Option<&VarGraph>,
2298    ) -> Option<(DefinableNameSegmentRefs<'_>, bool)> {
2299        let mut current = self;
2300        let mut segments = SmallVec::new();
2301        let mut potentially_reassigned = false;
2302        loop {
2303            match current {
2304                JsValue::FreeVar(name) => {
2305                    if var_graph.is_some_and(|var_graph| {
2306                        var_graph
2307                            .free_var_ids
2308                            .get(name)
2309                            .is_some_and(|id| var_graph.values.contains_key(id))
2310                    }) {
2311                        // `foo` was potentially reassigned
2312                        potentially_reassigned = true;
2313                    }
2314                    segments.push(DefinableNameSegmentRef::Name(name));
2315                    break;
2316                }
2317                JsValue::Member(_, obj, prop) => {
2318                    if let Some(prop) = prop.as_str() {
2319                        segments.push(DefinableNameSegmentRef::Name(prop));
2320                    } else {
2321                        return None;
2322                    }
2323                    current = obj;
2324                }
2325                JsValue::WellKnownObject(obj) => {
2326                    if let Some(name) = obj.as_define_name() {
2327                        segments.extend(
2328                            name.iter()
2329                                .rev()
2330                                .copied()
2331                                .map(DefinableNameSegmentRef::Name),
2332                        );
2333                        break;
2334                    } else {
2335                        return None;
2336                    }
2337                }
2338                JsValue::WellKnownFunction(func) => {
2339                    if let Some(name) = func.as_define_name() {
2340                        segments.extend(
2341                            name.iter()
2342                                .rev()
2343                                .copied()
2344                                .map(DefinableNameSegmentRef::Name),
2345                        );
2346                        break;
2347                    } else {
2348                        return None;
2349                    }
2350                }
2351                JsValue::MemberCall(_, call) if call.args().is_empty() => {
2352                    if let Some(prop) = call.prop().as_str() {
2353                        segments.push(DefinableNameSegmentRef::Call(prop));
2354                    } else {
2355                        return None;
2356                    }
2357                    current = call.obj();
2358                }
2359                JsValue::TypeOf(_, arg) => {
2360                    segments.push(DefinableNameSegmentRef::TypeOf);
2361                    current = arg;
2362                }
2363                _ => return None,
2364            }
2365        }
2366        segments.reverse();
2367        Some((DefinableNameSegmentRefs(segments), potentially_reassigned))
2368    }
2369}
2370
2371// Compile-time information gathering
2372impl JsValue {
2373    /// Returns the constant string if the value represents a constant string.
2374    pub fn as_str(&self) -> Option<&str> {
2375        match self {
2376            JsValue::Constant(c) => c.as_str(),
2377            _ => None,
2378        }
2379    }
2380
2381    /// Returns the constant bool if the value represents a constant boolean.
2382    pub fn as_bool(&self) -> Option<bool> {
2383        match self {
2384            JsValue::Constant(c) => c.as_bool(),
2385            _ => None,
2386        }
2387    }
2388
2389    pub fn has_side_effects(&self) -> bool {
2390        match self {
2391            JsValue::Constant(_) => false,
2392            JsValue::Concat(_, values)
2393            | JsValue::Add(_, values)
2394            | JsValue::Logical(_, _, values)
2395            | JsValue::Alternatives {
2396                total_nodes: _,
2397                values,
2398                logical_property: _,
2399            } => values.iter().any(JsValue::has_side_effects),
2400            JsValue::Binary(_, a, _, b) => a.has_side_effects() || b.has_side_effects(),
2401            JsValue::Tenary(_, test, cons, alt) => {
2402                test.has_side_effects() || cons.has_side_effects() || alt.has_side_effects()
2403            }
2404            JsValue::Not(_, value) => value.has_side_effects(),
2405            JsValue::Array { items, .. } => items.iter().any(JsValue::has_side_effects),
2406            JsValue::Object { parts, .. } => parts.iter().any(|v| match v {
2407                ObjectPart::KeyValue(k, v) => k.has_side_effects() || v.has_side_effects(),
2408                ObjectPart::Spread(v) => v.has_side_effects(),
2409            }),
2410            // As function bodies aren't analyzed for side-effects, we have to assume every call can
2411            // have sideeffects as well.
2412            // Otherwise it would be
2413            // `func_body(callee).has_side_effects() ||
2414            //      callee.has_side_effects() || args.iter().any(JsValue::has_side_effects`
2415            JsValue::New(_, _call) => true,
2416            JsValue::Call(_, _call) => true,
2417            JsValue::SuperCall(_, _args) => true,
2418            JsValue::MemberCall(_, _call) => true,
2419            JsValue::Member(_, obj, prop) => obj.has_side_effects() || prop.has_side_effects(),
2420            JsValue::Function(_, _, _) => false,
2421            JsValue::Url(_, _) => false,
2422            JsValue::Variable(_) => false,
2423            JsValue::Module(_) => false,
2424            JsValue::WellKnownObject(_) => false,
2425            JsValue::WellKnownFunction(_) => false,
2426            JsValue::FreeVar(_) => false,
2427            JsValue::Unknown {
2428                has_side_effects, ..
2429            } => *has_side_effects,
2430            JsValue::Argument(_, _) => false,
2431            JsValue::Iterated(_, iterable) => iterable.has_side_effects(),
2432            JsValue::TypeOf(_, operand) => operand.has_side_effects(),
2433            JsValue::Promise(_, operand) => operand.has_side_effects(),
2434            JsValue::Awaited(_, operand) => operand.has_side_effects(),
2435        }
2436    }
2437
2438    /// Checks if the value is truthy. Returns None if we don't know. Returns
2439    /// Some if we know if or if not the value is truthy.
2440    pub fn is_truthy(&self) -> Option<bool> {
2441        match self {
2442            JsValue::Constant(c) => Some(c.is_truthy()),
2443            JsValue::Concat(..) => self.is_empty_string().map(|x| !x),
2444            JsValue::Url(..)
2445            | JsValue::Array { .. }
2446            | JsValue::Object { .. }
2447            | JsValue::WellKnownObject(..)
2448            | JsValue::WellKnownFunction(..)
2449            | JsValue::Function(..) => Some(true),
2450            JsValue::Alternatives {
2451                total_nodes: _,
2452                values,
2453                logical_property,
2454            } => match logical_property {
2455                Some(LogicalProperty::Truthy) => Some(true),
2456                Some(LogicalProperty::Falsy) => Some(false),
2457                Some(LogicalProperty::Nullish) => Some(false),
2458                _ => merge_if_known(values, JsValue::is_truthy),
2459            },
2460            JsValue::Not(_, value) => value.is_truthy().map(|x| !x),
2461            JsValue::Logical(_, op, list) => match op {
2462                LogicalOperator::And => all_if_known(list, JsValue::is_truthy),
2463                LogicalOperator::Or => any_if_known(list, JsValue::is_truthy),
2464                LogicalOperator::NullishCoalescing => {
2465                    shortcircuit_if_known(list, JsValue::is_not_nullish, JsValue::is_truthy)
2466                }
2467            },
2468            JsValue::Binary(_, box a, op, box b) => {
2469                let (positive_op, negate) = op.positive_op();
2470                match (positive_op, a, b) {
2471                    (
2472                        PositiveBinaryOperator::StrictEqual,
2473                        JsValue::Constant(a),
2474                        JsValue::Constant(b),
2475                    ) if a.is_value_type() => Some(a == b),
2476                    (
2477                        PositiveBinaryOperator::StrictEqual,
2478                        JsValue::Constant(a),
2479                        JsValue::Constant(b),
2480                    ) if a.is_value_type() => {
2481                        let same_type = {
2482                            use ConstantValue::*;
2483                            matches!(
2484                                (a, b),
2485                                (Num(_), Num(_))
2486                                    | (Str(_), Str(_))
2487                                    | (BigInt(_), BigInt(_))
2488                                    | (True | False, True | False)
2489                                    | (Undefined, Undefined)
2490                                    | (Null, Null)
2491                            )
2492                        };
2493                        if same_type { Some(a == b) } else { None }
2494                    }
2495                    (
2496                        PositiveBinaryOperator::Equal,
2497                        JsValue::Constant(ConstantValue::Str(a)),
2498                        JsValue::Constant(ConstantValue::Str(b)),
2499                    ) => Some(a == b),
2500                    (
2501                        PositiveBinaryOperator::Equal,
2502                        JsValue::Constant(ConstantValue::Num(a)),
2503                        JsValue::Constant(ConstantValue::Num(b)),
2504                    ) => Some(a == b),
2505                    _ => None,
2506                }
2507                .map(|x| x ^ negate)
2508            }
2509            _ => None,
2510        }
2511    }
2512
2513    /// Checks if the value is falsy. Returns None if we don't know. Returns
2514    /// Some if we know if or if not the value is falsy.
2515    pub fn is_falsy(&self) -> Option<bool> {
2516        self.is_truthy().map(|x| !x)
2517    }
2518
2519    /// Checks if the value is nullish (null or undefined). Returns None if we
2520    /// don't know. Returns Some if we know if or if not the value is nullish.
2521    pub fn is_nullish(&self) -> Option<bool> {
2522        match self {
2523            JsValue::Constant(c) => Some(c.is_nullish()),
2524            JsValue::Concat(..)
2525            | JsValue::Url(..)
2526            | JsValue::Array { .. }
2527            | JsValue::Object { .. }
2528            | JsValue::WellKnownObject(..)
2529            | JsValue::WellKnownFunction(..)
2530            | JsValue::Not(..)
2531            | JsValue::Binary(..)
2532            | JsValue::Function(..) => Some(false),
2533            JsValue::Alternatives {
2534                total_nodes: _,
2535                values,
2536                logical_property,
2537            } => match logical_property {
2538                Some(LogicalProperty::Nullish) => Some(true),
2539                _ => merge_if_known(values, JsValue::is_nullish),
2540            },
2541            JsValue::Logical(_, op, list) => match op {
2542                LogicalOperator::And => {
2543                    shortcircuit_if_known(list, JsValue::is_truthy, JsValue::is_nullish)
2544                }
2545                LogicalOperator::Or => {
2546                    shortcircuit_if_known(list, JsValue::is_falsy, JsValue::is_nullish)
2547                }
2548                LogicalOperator::NullishCoalescing => all_if_known(list, JsValue::is_nullish),
2549            },
2550            _ => None,
2551        }
2552    }
2553
2554    /// Checks if we know that the value is not nullish. Returns None if we
2555    /// don't know. Returns Some if we know if or if not the value is not
2556    /// nullish.
2557    pub fn is_not_nullish(&self) -> Option<bool> {
2558        self.is_nullish().map(|x| !x)
2559    }
2560
2561    /// Checks if we know that the value is an empty string. Returns None if we
2562    /// don't know. Returns Some if we know if or if not the value is an empty
2563    /// string.
2564    pub fn is_empty_string(&self) -> Option<bool> {
2565        match self {
2566            JsValue::Constant(c) => Some(c.is_empty_string()),
2567            JsValue::Concat(_, list) => all_if_known(list, JsValue::is_empty_string),
2568            JsValue::Alternatives {
2569                total_nodes: _,
2570                values,
2571                logical_property: _,
2572            } => merge_if_known(values, JsValue::is_empty_string),
2573            JsValue::Logical(_, op, list) => match op {
2574                LogicalOperator::And => {
2575                    shortcircuit_if_known(list, JsValue::is_truthy, JsValue::is_empty_string)
2576                }
2577                LogicalOperator::Or => {
2578                    shortcircuit_if_known(list, JsValue::is_falsy, JsValue::is_empty_string)
2579                }
2580                LogicalOperator::NullishCoalescing => {
2581                    shortcircuit_if_known(list, JsValue::is_not_nullish, JsValue::is_empty_string)
2582                }
2583            },
2584            // Booleans are not empty strings
2585            JsValue::Not(..) | JsValue::Binary(..) => Some(false),
2586            // Objects are not empty strings
2587            JsValue::Url(..)
2588            | JsValue::Array { .. }
2589            | JsValue::Object { .. }
2590            | JsValue::WellKnownObject(..)
2591            | JsValue::WellKnownFunction(..)
2592            | JsValue::Function(..) => Some(false),
2593            _ => None,
2594        }
2595    }
2596
2597    /// Returns true, if the value is unknown and storing it as condition
2598    /// doesn't make sense. This is for optimization purposes.
2599    pub fn is_unknown(&self) -> bool {
2600        match self {
2601            JsValue::Unknown { .. } => true,
2602            JsValue::Alternatives {
2603                total_nodes: _,
2604                values,
2605                logical_property: _,
2606            } => values.iter().any(|x| x.is_unknown()),
2607            _ => false,
2608        }
2609    }
2610
2611    /// Checks if we know that the value is a string. Returns None if we
2612    /// don't know. Returns Some if we know if or if not the value is a string.
2613    pub fn is_string(&self) -> Option<bool> {
2614        match self {
2615            JsValue::Constant(ConstantValue::Str(..))
2616            | JsValue::Concat(..)
2617            | JsValue::TypeOf(..) => Some(true),
2618
2619            // Objects are not strings
2620            JsValue::Constant(..)
2621            | JsValue::Array { .. }
2622            | JsValue::Object { .. }
2623            | JsValue::Url(..)
2624            | JsValue::Module(..)
2625            | JsValue::Function(..)
2626            | JsValue::WellKnownObject(_)
2627            | JsValue::WellKnownFunction(_)
2628            | JsValue::Promise(_, _) => Some(false),
2629
2630            // Booleans are not strings
2631            JsValue::Not(..) | JsValue::Binary(..) => Some(false),
2632
2633            JsValue::Add(_, list) => any_if_known(list, JsValue::is_string),
2634            JsValue::Logical(_, op, list) => match op {
2635                LogicalOperator::And => {
2636                    shortcircuit_if_known(list, JsValue::is_truthy, JsValue::is_string)
2637                }
2638                LogicalOperator::Or => {
2639                    shortcircuit_if_known(list, JsValue::is_falsy, JsValue::is_string)
2640                }
2641                LogicalOperator::NullishCoalescing => {
2642                    shortcircuit_if_known(list, JsValue::is_not_nullish, JsValue::is_string)
2643                }
2644            },
2645
2646            JsValue::Alternatives {
2647                total_nodes: _,
2648                values,
2649                logical_property: _,
2650            } => merge_if_known(values, JsValue::is_string),
2651
2652            JsValue::Call(_, call)
2653                if matches!(
2654                    call.callee(),
2655                    JsValue::WellKnownFunction(
2656                        WellKnownFunctionKind::RequireResolve
2657                            | WellKnownFunctionKind::PathJoin
2658                            | WellKnownFunctionKind::PathResolve(..)
2659                            | WellKnownFunctionKind::OsArch
2660                            | WellKnownFunctionKind::OsPlatform
2661                            | WellKnownFunctionKind::PathDirname
2662                            | WellKnownFunctionKind::PathToFileUrl
2663                            | WellKnownFunctionKind::ProcessCwd,
2664                    )
2665                ) =>
2666            {
2667                Some(true)
2668            }
2669
2670            JsValue::Awaited(_, operand) => match &**operand {
2671                JsValue::Promise(_, v) => v.is_string(),
2672                v => v.is_string(),
2673            },
2674
2675            JsValue::FreeVar(..)
2676            | JsValue::Variable(_)
2677            | JsValue::Unknown { .. }
2678            | JsValue::Argument(..)
2679            | JsValue::New(..)
2680            | JsValue::Call(..)
2681            | JsValue::MemberCall(..)
2682            | JsValue::Member(..)
2683            | JsValue::Tenary(..)
2684            | JsValue::SuperCall(..)
2685            | JsValue::Iterated(..) => None,
2686        }
2687    }
2688
2689    /// Checks if we know that the value starts with a given string. Returns
2690    /// None if we don't know. Returns Some if we know if or if not the
2691    /// value starts with the given string.
2692    pub fn starts_with(&self, str: &str) -> Option<bool> {
2693        if let Some(s) = self.as_str() {
2694            return Some(s.starts_with(str));
2695        }
2696        match self {
2697            JsValue::Alternatives {
2698                total_nodes: _,
2699                values,
2700                logical_property: _,
2701            } => merge_if_known(values, |a| a.starts_with(str)),
2702            JsValue::Concat(_, list) => {
2703                if let Some(item) = list.iter().next() {
2704                    if item.starts_with(str) == Some(true) {
2705                        Some(true)
2706                    } else if let Some(s) = item.as_str() {
2707                        if str.starts_with(s) {
2708                            None
2709                        } else {
2710                            Some(false)
2711                        }
2712                    } else {
2713                        None
2714                    }
2715                } else {
2716                    Some(false)
2717                }
2718            }
2719
2720            _ => None,
2721        }
2722    }
2723
2724    /// Checks if we know that the value ends with a given string. Returns
2725    /// None if we don't know. Returns Some if we know if or if not the
2726    /// value ends with the given string.
2727    pub fn ends_with(&self, str: &str) -> Option<bool> {
2728        if let Some(s) = self.as_str() {
2729            return Some(s.ends_with(str));
2730        }
2731        match self {
2732            JsValue::Alternatives {
2733                total_nodes: _,
2734                values,
2735                logical_property: _,
2736            } => merge_if_known(values, |alt| alt.ends_with(str)),
2737            JsValue::Concat(_, list) => {
2738                if let Some(item) = list.last() {
2739                    if item.ends_with(str) == Some(true) {
2740                        Some(true)
2741                    } else if let Some(s) = item.as_str() {
2742                        if str.ends_with(s) { None } else { Some(false) }
2743                    } else {
2744                        None
2745                    }
2746                } else {
2747                    Some(false)
2748                }
2749            }
2750
2751            _ => None,
2752        }
2753    }
2754}
2755
2756/// Compute the compile-time value of all elements of the list. If all evaluate
2757/// to the same value return that. Otherwise return None.
2758fn merge_if_known<T: Copy>(
2759    list: impl IntoIterator<Item = T>,
2760    func: impl Fn(T) -> Option<bool>,
2761) -> Option<bool> {
2762    let mut current = None;
2763    for item in list.into_iter().map(func) {
2764        if item.is_some() {
2765            if current.is_none() {
2766                current = item;
2767            } else if current != item {
2768                return None;
2769            }
2770        } else {
2771            return None;
2772        }
2773    }
2774    current
2775}
2776
2777/// Evaluates all elements of the list and returns Some(true) if all elements
2778/// are compile-time true. If any element is compile-time false, return
2779/// Some(false). Otherwise return None.
2780fn all_if_known<T: Copy>(
2781    list: impl IntoIterator<Item = T>,
2782    func: impl Fn(T) -> Option<bool>,
2783) -> Option<bool> {
2784    let mut unknown = false;
2785    for item in list.into_iter().map(func) {
2786        match item {
2787            Some(false) => return Some(false),
2788            None => unknown = true,
2789            _ => {}
2790        }
2791    }
2792    if unknown { None } else { Some(true) }
2793}
2794
2795/// Evaluates all elements of the list and returns Some(true) if any element is
2796/// compile-time true. If all elements are compile-time false, return
2797/// Some(false). Otherwise return None.
2798fn any_if_known<T: Copy>(
2799    list: impl IntoIterator<Item = T>,
2800    func: impl Fn(T) -> Option<bool>,
2801) -> Option<bool> {
2802    all_if_known(list, |x| func(x).map(|x| !x)).map(|x| !x)
2803}
2804
2805/// Selects the first element of the list where `use_item` is compile-time true.
2806/// For this element returns the result of `item_value`. Otherwise returns None.
2807fn shortcircuit_if_known<T: Copy>(
2808    list: impl IntoIterator<Item = T>,
2809    use_item: impl Fn(T) -> Option<bool>,
2810    item_value: impl FnOnce(T) -> Option<bool>,
2811) -> Option<bool> {
2812    let mut it = list.into_iter().peekable();
2813    while let Some(item) = it.next() {
2814        if it.peek().is_none() {
2815            return item_value(item);
2816        } else {
2817            match use_item(item) {
2818                Some(true) => return item_value(item),
2819                None => return None,
2820                _ => {}
2821            }
2822        }
2823    }
2824    None
2825}
2826
2827// Visiting
2828impl JsValue {
2829    /// Calls a function for each child of the node. Allows mutating the node.
2830    /// Updates the total nodes count after mutation.
2831    pub fn for_each_children_mut(
2832        &mut self,
2833        visitor: &mut impl FnMut(&mut JsValue) -> bool,
2834    ) -> bool {
2835        match self {
2836            JsValue::Alternatives {
2837                total_nodes: _,
2838                values: list,
2839                logical_property: _,
2840            }
2841            | JsValue::Concat(_, list)
2842            | JsValue::Add(_, list)
2843            | JsValue::Logical(_, _, list)
2844            | JsValue::Array { items: list, .. } => {
2845                let mut modified = false;
2846                for item in list.iter_mut() {
2847                    if visitor(item) {
2848                        modified = true
2849                    }
2850                }
2851                if modified {
2852                    self.update_total_nodes();
2853                }
2854                modified
2855            }
2856            JsValue::Not(_, value) => {
2857                let modified = visitor(value);
2858                if modified {
2859                    self.update_total_nodes();
2860                }
2861                modified
2862            }
2863            JsValue::Object { parts, .. } => {
2864                let mut modified = false;
2865                for item in parts.iter_mut() {
2866                    match item {
2867                        ObjectPart::KeyValue(key, value) => {
2868                            if visitor(key) {
2869                                modified = true
2870                            }
2871                            if visitor(value) {
2872                                modified = true
2873                            }
2874                        }
2875                        ObjectPart::Spread(value) => {
2876                            if visitor(value) {
2877                                modified = true
2878                            }
2879                        }
2880                    }
2881                }
2882                if modified {
2883                    self.update_total_nodes();
2884                }
2885                modified
2886            }
2887            JsValue::New(_, call) => {
2888                let modified = call.for_each_children_mut(visitor);
2889                if modified {
2890                    self.update_total_nodes();
2891                }
2892                modified
2893            }
2894            JsValue::Call(_, call) => {
2895                let modified = call.for_each_children_mut(visitor);
2896                if modified {
2897                    self.update_total_nodes();
2898                }
2899                modified
2900            }
2901            JsValue::SuperCall(_, args) => {
2902                let mut modified = false;
2903                for item in args.iter_mut() {
2904                    if visitor(item) {
2905                        modified = true
2906                    }
2907                }
2908                if modified {
2909                    self.update_total_nodes();
2910                }
2911                modified
2912            }
2913            JsValue::MemberCall(_, call) => {
2914                let modified = call.for_each_children_mut(visitor);
2915
2916                if modified {
2917                    self.update_total_nodes();
2918                }
2919                modified
2920            }
2921            JsValue::Function(_, _, return_value) => {
2922                let modified = visitor(return_value);
2923
2924                if modified {
2925                    self.update_total_nodes();
2926                }
2927                modified
2928            }
2929            JsValue::Binary(_, a, _, b) => {
2930                let m1 = visitor(a);
2931                let m2 = visitor(b);
2932                let modified = m1 || m2;
2933                if modified {
2934                    self.update_total_nodes();
2935                }
2936                modified
2937            }
2938            JsValue::Tenary(_, test, cons, alt) => {
2939                let m1 = visitor(test);
2940                let m2 = visitor(cons);
2941                let m3 = visitor(alt);
2942                let modified = m1 || m2 || m3;
2943                if modified {
2944                    self.update_total_nodes();
2945                }
2946                modified
2947            }
2948            JsValue::Member(_, obj, prop) => {
2949                let m1 = visitor(obj);
2950                let m2 = visitor(prop);
2951                let modified = m1 || m2;
2952                if modified {
2953                    self.update_total_nodes();
2954                }
2955                modified
2956            }
2957
2958            JsValue::Iterated(_, operand)
2959            | JsValue::TypeOf(_, operand)
2960            | JsValue::Promise(_, operand)
2961            | JsValue::Awaited(_, operand) => {
2962                let modified = visitor(operand);
2963                if modified {
2964                    self.update_total_nodes();
2965                }
2966                modified
2967            }
2968
2969            JsValue::Constant(_)
2970            | JsValue::FreeVar(_)
2971            | JsValue::Variable(_)
2972            | JsValue::Module(..)
2973            | JsValue::Url(_, _)
2974            | JsValue::WellKnownObject(_)
2975            | JsValue::WellKnownFunction(_)
2976            | JsValue::Unknown { .. }
2977            | JsValue::Argument(..) => false,
2978        }
2979    }
2980
2981    /// Calls a function for only early children. Allows mutating the
2982    /// node. Updates the total nodes count after mutation.
2983    pub fn for_each_early_children_mut(
2984        &mut self,
2985        visitor: &mut impl FnMut(&mut JsValue) -> bool,
2986    ) -> bool {
2987        match self {
2988            JsValue::New(_, call) if !call.args().is_empty() => {
2989                let m = visitor(call.callee_mut());
2990                if m {
2991                    self.update_total_nodes();
2992                }
2993                m
2994            }
2995            JsValue::Call(_, call) if !call.args().is_empty() => {
2996                let m = visitor(call.callee_mut());
2997                if m {
2998                    self.update_total_nodes();
2999                }
3000                m
3001            }
3002            JsValue::MemberCall(_, call) if !call.args().is_empty() => {
3003                let m1 = visitor(call.prop_mut());
3004                let m2 = visitor(call.obj_mut());
3005                let modified = m1 || m2;
3006                if modified {
3007                    self.update_total_nodes();
3008                }
3009                modified
3010            }
3011            JsValue::Member(_, obj, _) => {
3012                let m = visitor(obj);
3013                if m {
3014                    self.update_total_nodes();
3015                }
3016                m
3017            }
3018            _ => false,
3019        }
3020    }
3021
3022    /// Calls a function for only late children. Allows mutating the
3023    /// node. Updates the total nodes count after mutation.
3024    pub fn for_each_late_children_mut(
3025        &mut self,
3026        visitor: &mut impl FnMut(&mut JsValue) -> bool,
3027    ) -> bool {
3028        match self {
3029            JsValue::New(_, call) if !call.args().is_empty() => {
3030                let mut modified = false;
3031                for item in call.args_mut().iter_mut() {
3032                    if visitor(item) {
3033                        modified = true
3034                    }
3035                }
3036                if modified {
3037                    self.update_total_nodes();
3038                }
3039                modified
3040            }
3041            JsValue::Call(_, call) if !call.args().is_empty() => {
3042                let mut modified = false;
3043                for item in call.args_mut().iter_mut() {
3044                    if visitor(item) {
3045                        modified = true
3046                    }
3047                }
3048                if modified {
3049                    self.update_total_nodes();
3050                }
3051                modified
3052            }
3053            JsValue::MemberCall(_, call) if !call.args().is_empty() => {
3054                let mut modified = false;
3055                for item in call.args_mut().iter_mut() {
3056                    if visitor(item) {
3057                        modified = true
3058                    }
3059                }
3060                if modified {
3061                    self.update_total_nodes();
3062                }
3063                modified
3064            }
3065            JsValue::Member(_, _, prop) => {
3066                let m = visitor(prop);
3067                if m {
3068                    self.update_total_nodes();
3069                }
3070                m
3071            }
3072            _ => self.for_each_children_mut(visitor),
3073        }
3074    }
3075
3076    /// Visit the node and all its children with a function.
3077    pub fn visit(&self, visitor: &mut impl FnMut(&JsValue)) {
3078        self.for_each_children(&mut |value| value.visit(visitor));
3079        visitor(self);
3080    }
3081
3082    /// Calls a function for all children of the node.
3083    pub fn for_each_children(&self, visitor: &mut impl FnMut(&JsValue)) {
3084        match self {
3085            JsValue::Alternatives {
3086                total_nodes: _,
3087                values: list,
3088                logical_property: _,
3089            }
3090            | JsValue::Concat(_, list)
3091            | JsValue::Add(_, list)
3092            | JsValue::Logical(_, _, list)
3093            | JsValue::Array { items: list, .. } => {
3094                for item in list.iter() {
3095                    visitor(item);
3096                }
3097            }
3098            JsValue::Not(_, value) => {
3099                visitor(value);
3100            }
3101            JsValue::Object { parts, .. } => {
3102                for item in parts.iter() {
3103                    match item {
3104                        ObjectPart::KeyValue(key, value) => {
3105                            visitor(key);
3106                            visitor(value);
3107                        }
3108                        ObjectPart::Spread(value) => {
3109                            visitor(value);
3110                        }
3111                    }
3112                }
3113            }
3114            JsValue::New(_, call) => {
3115                call.for_each_children(visitor);
3116            }
3117            JsValue::Call(_, call) => {
3118                call.for_each_children(visitor);
3119            }
3120            JsValue::SuperCall(_, args) => {
3121                for item in args.iter() {
3122                    visitor(item);
3123                }
3124            }
3125            JsValue::MemberCall(_, call) => {
3126                call.for_each_children(visitor);
3127            }
3128            JsValue::Function(_, _, return_value) => {
3129                visitor(return_value);
3130            }
3131            JsValue::Member(_, obj, prop) => {
3132                visitor(obj);
3133                visitor(prop);
3134            }
3135            JsValue::Binary(_, a, _, b) => {
3136                visitor(a);
3137                visitor(b);
3138            }
3139            JsValue::Tenary(_, test, cons, alt) => {
3140                visitor(test);
3141                visitor(cons);
3142                visitor(alt);
3143            }
3144
3145            JsValue::Iterated(_, operand)
3146            | JsValue::TypeOf(_, operand)
3147            | JsValue::Promise(_, operand)
3148            | JsValue::Awaited(_, operand) => {
3149                visitor(operand);
3150            }
3151
3152            JsValue::Constant(_)
3153            | JsValue::FreeVar(_)
3154            | JsValue::Variable(_)
3155            | JsValue::Module(..)
3156            | JsValue::Url(_, _)
3157            | JsValue::WellKnownObject(_)
3158            | JsValue::WellKnownFunction(_)
3159            | JsValue::Unknown { .. }
3160            | JsValue::Argument(..) => {}
3161        }
3162    }
3163}
3164
3165// Alternatives management
3166impl JsValue {
3167    /// Add an alternative to the current value. Might be a no-op if the value
3168    /// already contains this alternative. Potentially expensive operation
3169    /// as it has to compare the value with all existing alternatives.
3170    fn add_alt(&mut self, v: Self) {
3171        if self == &v {
3172            return;
3173        }
3174
3175        if let JsValue::Alternatives {
3176            total_nodes: c,
3177            values,
3178            logical_property: _,
3179        } = self
3180        {
3181            if !values.contains(&v) {
3182                *c += v.total_nodes();
3183                values.push(v);
3184            }
3185        } else {
3186            let l = take(self);
3187            *self = JsValue::Alternatives {
3188                total_nodes: 1 + l.total_nodes() + v.total_nodes(),
3189                values: vec![l, v],
3190                logical_property: None,
3191            };
3192        }
3193    }
3194}
3195
3196// Normalization
3197impl JsValue {
3198    /// Normalizes only the current node. Nested alternatives, concatenations,
3199    /// or operations are collapsed.
3200    pub fn normalize_shallow(&mut self) {
3201        match self {
3202            JsValue::Alternatives {
3203                total_nodes: _,
3204                values,
3205                logical_property: _,
3206            } => {
3207                if values.len() == 1 {
3208                    *self = take(&mut values[0]);
3209                } else {
3210                    let mut set = FxIndexSet::with_capacity_and_hasher(
3211                        values.len(),
3212                        BuildHasherDefault::<FxHasher>::default(),
3213                    );
3214                    for v in take(values) {
3215                        match v {
3216                            JsValue::Alternatives {
3217                                total_nodes: _,
3218                                values,
3219                                logical_property: _,
3220                            } => {
3221                                for v in values {
3222                                    set.insert(SimilarJsValue(v));
3223                                }
3224                            }
3225                            v => {
3226                                set.insert(SimilarJsValue(v));
3227                            }
3228                        }
3229                    }
3230                    if set.len() == 1 {
3231                        *self = set.into_iter().next().unwrap().0;
3232                    } else {
3233                        *values = set.into_iter().map(|v| v.0).collect();
3234                        self.update_total_nodes();
3235                    }
3236                }
3237            }
3238            JsValue::Concat(_, v) => {
3239                // Remove empty strings
3240                v.retain(|v| v.as_str() != Some(""));
3241
3242                // TODO(kdy1): Remove duplicate
3243                let mut new: Vec<JsValue> = vec![];
3244                for v in take(v) {
3245                    if let Some(str) = v.as_str() {
3246                        if let Some(last) = new.last_mut() {
3247                            if let Some(last_str) = last.as_str() {
3248                                *last = [last_str, str].concat().into();
3249                            } else {
3250                                new.push(v);
3251                            }
3252                        } else {
3253                            new.push(v);
3254                        }
3255                    } else if let JsValue::Concat(_, v) = v {
3256                        new.extend(v);
3257                    } else {
3258                        new.push(v);
3259                    }
3260                }
3261                if new.len() == 1 {
3262                    *self = new.into_iter().next().unwrap();
3263                } else {
3264                    *v = new;
3265                    self.update_total_nodes();
3266                }
3267            }
3268            JsValue::Add(_, v) => {
3269                let mut added: Vec<JsValue> = Vec::new();
3270                let mut iter = take(v).into_iter();
3271                while let Some(item) = iter.next() {
3272                    if item.is_string() == Some(true) {
3273                        let mut concat = match added.len() {
3274                            0 => Vec::new(),
3275                            1 => vec![added.into_iter().next().unwrap()],
3276                            _ => vec![JsValue::Add(
3277                                1 + added.iter().map(|v| v.total_nodes()).sum::<u32>(),
3278                                added,
3279                            )],
3280                        };
3281                        concat.push(item);
3282                        for item in iter.by_ref() {
3283                            concat.push(item);
3284                        }
3285                        *self = JsValue::Concat(
3286                            1 + concat.iter().map(|v| v.total_nodes()).sum::<u32>(),
3287                            concat,
3288                        );
3289                        return;
3290                    } else {
3291                        added.push(item);
3292                    }
3293                }
3294                if added.len() == 1 {
3295                    *self = added.into_iter().next().unwrap();
3296                } else {
3297                    *v = added;
3298                    self.update_total_nodes();
3299                }
3300            }
3301            JsValue::Logical(_, op, list)
3302                // Nested logical expressions can be normalized: e. g. `a && (b && c)` => `a &&
3303                // b && c`
3304                if list.iter().any(|v| {
3305                    if let JsValue::Logical(_, inner_op, _) = v {
3306                        inner_op == op
3307                    } else {
3308                        false
3309                    }
3310                }) => {
3311                    // Taking the old list and constructing a new merged list
3312                    for mut v in take(list).into_iter() {
3313                        if let JsValue::Logical(_, inner_op, inner_list) = &mut v {
3314                            if inner_op == op {
3315                                list.append(inner_list);
3316                            } else {
3317                                list.push(v);
3318                            }
3319                        } else {
3320                            list.push(v);
3321                        }
3322                    }
3323                    self.update_total_nodes();
3324                }
3325            _ => {}
3326        }
3327    }
3328
3329    /// Normalizes the current node and all nested nodes.
3330    pub fn normalize(&mut self) {
3331        self.for_each_children_mut(&mut |child| {
3332            child.normalize();
3333            true
3334        });
3335        self.normalize_shallow();
3336    }
3337}
3338
3339// Similarity
3340// Like equality, but with depth limit
3341impl JsValue {
3342    fn all_similar(a: &[JsValue], b: &[JsValue], depth: usize) -> bool {
3343        if a.len() != b.len() {
3344            return false;
3345        }
3346        a.iter().zip(b.iter()).all(|(a, b)| a.similar(b, depth))
3347    }
3348    /// Check if the values are equal up to the given depth. Might return false
3349    /// even if the values are equal when hitting the depth limit.
3350    fn similar(&self, other: &JsValue, depth: usize) -> bool {
3351        if depth == 0 {
3352            return false;
3353        }
3354
3355        fn all_parts_similar(a: &[ObjectPart], b: &[ObjectPart], depth: usize) -> bool {
3356            if a.len() != b.len() {
3357                return false;
3358            }
3359            a.iter().zip(b.iter()).all(|(a, b)| match (a, b) {
3360                (ObjectPart::KeyValue(lk, lv), ObjectPart::KeyValue(rk, rv)) => {
3361                    lk.similar(rk, depth) && lv.similar(rv, depth)
3362                }
3363                (ObjectPart::Spread(l), ObjectPart::Spread(r)) => l.similar(r, depth),
3364                _ => false,
3365            })
3366        }
3367        match (self, other) {
3368            (JsValue::Constant(l), JsValue::Constant(r)) => l == r,
3369            (
3370                JsValue::Array {
3371                    total_nodes: lc,
3372                    items: li,
3373                    mutable: lm,
3374                },
3375                JsValue::Array {
3376                    total_nodes: rc,
3377                    items: ri,
3378                    mutable: rm,
3379                },
3380            ) => lc == rc && lm == rm && Self::all_similar(li, ri, depth - 1),
3381            (
3382                JsValue::Object {
3383                    total_nodes: lc,
3384                    parts: lp,
3385                    mutable: lm,
3386                },
3387                JsValue::Object {
3388                    total_nodes: rc,
3389                    parts: rp,
3390                    mutable: rm,
3391                },
3392            ) => lc == rc && lm == rm && all_parts_similar(lp, rp, depth - 1),
3393            (JsValue::Url(l, kl), JsValue::Url(r, kr)) => l == r && kl == kr,
3394            (
3395                JsValue::Alternatives {
3396                    total_nodes: lc,
3397                    values: l,
3398                    logical_property: lp,
3399                },
3400                JsValue::Alternatives {
3401                    total_nodes: rc,
3402                    values: r,
3403                    logical_property: rp,
3404                },
3405            ) => lc == rc && Self::all_similar(l, r, depth - 1) && lp == rp,
3406            (JsValue::FreeVar(l), JsValue::FreeVar(r)) => l == r,
3407            (JsValue::Variable(l), JsValue::Variable(r)) => l == r,
3408            (JsValue::Concat(lc, l), JsValue::Concat(rc, r)) => {
3409                lc == rc && Self::all_similar(l, r, depth - 1)
3410            }
3411            (JsValue::Add(lc, l), JsValue::Add(rc, r)) => {
3412                lc == rc && Self::all_similar(l, r, depth - 1)
3413            }
3414            (JsValue::Logical(lc, lo, l), JsValue::Logical(rc, ro, r)) => {
3415                lc == rc && lo == ro && Self::all_similar(l, r, depth - 1)
3416            }
3417            (JsValue::Not(lc, l), JsValue::Not(rc, r)) => lc == rc && l.similar(r, depth - 1),
3418            (JsValue::New(lc, ll), JsValue::New(rc, rl)) => {
3419                lc == rc && CallList::all_similar(ll, rl, depth - 1)
3420            }
3421            (JsValue::Call(lc, ll), JsValue::Call(rc, rl)) => {
3422                lc == rc && CallList::all_similar(ll, rl, depth - 1)
3423            }
3424            (JsValue::MemberCall(lc, ll), JsValue::MemberCall(rc, rl)) => {
3425                lc == rc && MemberCallList::all_similar(ll, rl, depth - 1)
3426            }
3427            (JsValue::Member(lc, lo, lp), JsValue::Member(rc, ro, rp)) => {
3428                lc == rc && lo.similar(ro, depth - 1) && lp.similar(rp, depth - 1)
3429            }
3430            (JsValue::Binary(lc, la, lo, lb), JsValue::Binary(rc, ra, ro, rb)) => {
3431                lc == rc && lo == ro && la.similar(ra, depth - 1) && lb.similar(rb, depth - 1)
3432            }
3433            (
3434                JsValue::Module(ModuleValue {
3435                    module: l,
3436                    annotations: la,
3437                }),
3438                JsValue::Module(ModuleValue {
3439                    module: r,
3440                    annotations: ra,
3441                }),
3442            ) => l == r && la == ra,
3443            (JsValue::WellKnownObject(l), JsValue::WellKnownObject(r)) => l == r,
3444            (JsValue::WellKnownFunction(l), JsValue::WellKnownFunction(r)) => l == r,
3445            (
3446                JsValue::Unknown {
3447                    original_value: _,
3448                    reason: l,
3449                    has_side_effects: ls,
3450                },
3451                JsValue::Unknown {
3452                    original_value: _,
3453                    reason: r,
3454                    has_side_effects: rs,
3455                },
3456            ) => l == r && ls == rs,
3457            (JsValue::Function(lc, _, l), JsValue::Function(rc, _, r)) => {
3458                lc == rc && l.similar(r, depth - 1)
3459            }
3460            (JsValue::Argument(li, l), JsValue::Argument(ri, r)) => li == ri && l == r,
3461            _ => false,
3462        }
3463    }
3464
3465    /// Hashes the value up to the given depth.
3466    fn similar_hash<H: std::hash::Hasher>(&self, state: &mut H, depth: usize) {
3467        if depth == 0 {
3468            self.total_nodes().hash(state);
3469            return;
3470        }
3471
3472        fn all_similar_hash<H: std::hash::Hasher>(slice: &[JsValue], state: &mut H, depth: usize) {
3473            for item in slice {
3474                item.similar_hash(state, depth);
3475            }
3476        }
3477
3478        fn all_parts_similar_hash<H: std::hash::Hasher>(
3479            slice: &[ObjectPart],
3480            state: &mut H,
3481            depth: usize,
3482        ) {
3483            for item in slice {
3484                match item {
3485                    ObjectPart::KeyValue(key, value) => {
3486                        key.similar_hash(state, depth);
3487                        value.similar_hash(state, depth);
3488                    }
3489                    ObjectPart::Spread(value) => {
3490                        value.similar_hash(state, depth);
3491                    }
3492                }
3493            }
3494        }
3495
3496        match self {
3497            JsValue::Constant(v) => Hash::hash(v, state),
3498            JsValue::Object { parts, .. } => all_parts_similar_hash(parts, state, depth - 1),
3499            JsValue::Url(v, kind) => {
3500                Hash::hash(v, state);
3501                Hash::hash(kind, state);
3502            }
3503            JsValue::FreeVar(v) => Hash::hash(v, state),
3504            JsValue::Variable(v) => Hash::hash(v, state),
3505            JsValue::Array { items: v, .. }
3506            | JsValue::Alternatives {
3507                total_nodes: _,
3508                values: v,
3509                logical_property: _,
3510            }
3511            | JsValue::Concat(_, v)
3512            | JsValue::Add(_, v)
3513            | JsValue::Logical(_, _, v) => all_similar_hash(v, state, depth - 1),
3514            JsValue::Not(_, v) => v.similar_hash(state, depth - 1),
3515            JsValue::New(_, call) => {
3516                call.for_each_children(&mut |child: &JsValue| {
3517                    child.similar_hash(state, depth - 1);
3518                });
3519            }
3520            JsValue::Call(_, call) => {
3521                call.for_each_children(&mut |child: &JsValue| {
3522                    child.similar_hash(state, depth - 1);
3523                });
3524            }
3525            JsValue::SuperCall(_, args) => {
3526                all_similar_hash(args, state, depth - 1);
3527            }
3528            JsValue::MemberCall(_, call) => {
3529                call.for_each_children(&mut |child: &JsValue| {
3530                    child.similar_hash(state, depth - 1);
3531                });
3532            }
3533            JsValue::Member(_, o, p) => {
3534                o.similar_hash(state, depth - 1);
3535                p.similar_hash(state, depth - 1);
3536            }
3537            JsValue::Binary(_, a, o, b) => {
3538                a.similar_hash(state, depth - 1);
3539                o.hash(state);
3540                b.similar_hash(state, depth - 1);
3541            }
3542            JsValue::Tenary(_, test, cons, alt) => {
3543                test.similar_hash(state, depth - 1);
3544                cons.similar_hash(state, depth - 1);
3545                alt.similar_hash(state, depth - 1);
3546            }
3547            JsValue::Iterated(_, operand)
3548            | JsValue::TypeOf(_, operand)
3549            | JsValue::Promise(_, operand)
3550            | JsValue::Awaited(_, operand) => {
3551                operand.similar_hash(state, depth - 1);
3552            }
3553            JsValue::Module(ModuleValue {
3554                module: v,
3555                annotations: a,
3556            }) => {
3557                Hash::hash(v, state);
3558                Hash::hash(a, state);
3559            }
3560            JsValue::WellKnownObject(v) => Hash::hash(v, state),
3561            JsValue::WellKnownFunction(v) => Hash::hash(v, state),
3562            JsValue::Unknown {
3563                original_value: _,
3564                reason: v,
3565                has_side_effects,
3566            } => {
3567                Hash::hash(v, state);
3568                Hash::hash(has_side_effects, state);
3569            }
3570            JsValue::Function(_, _, v) => v.similar_hash(state, depth - 1),
3571            JsValue::Argument(i, v) => {
3572                Hash::hash(i, state);
3573                Hash::hash(v, state);
3574            }
3575        }
3576    }
3577}
3578
3579/// The depth to use when comparing values for similarity.
3580const SIMILAR_EQ_DEPTH: usize = 3;
3581/// The depth to use when hashing values for similarity.
3582const SIMILAR_HASH_DEPTH: usize = 2;
3583
3584/// A wrapper around `JsValue` that implements `PartialEq` and `Hash` by
3585/// comparing the values with a depth of [SIMILAR_EQ_DEPTH] and hashing values
3586/// with a depth of [SIMILAR_HASH_DEPTH].
3587struct SimilarJsValue(JsValue);
3588
3589impl PartialEq for SimilarJsValue {
3590    fn eq(&self, other: &Self) -> bool {
3591        self.0.similar(&other.0, SIMILAR_EQ_DEPTH)
3592    }
3593}
3594
3595impl Eq for SimilarJsValue {}
3596
3597impl Hash for SimilarJsValue {
3598    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
3599        self.0.similar_hash(state, SIMILAR_HASH_DEPTH)
3600    }
3601}
3602
3603/// A list of well-known objects that have special meaning in the analysis.
3604#[derive(Debug, Clone, Hash, PartialEq, Eq)]
3605pub enum WellKnownObjectKind {
3606    GlobalObject,
3607    PathModule,
3608    PathModuleDefault,
3609    FsModule,
3610    FsModuleDefault,
3611    FsModulePromises,
3612    FsExtraModule,
3613    FsExtraModuleDefault,
3614    ModuleModule,
3615    ModuleModuleDefault,
3616    UrlModule,
3617    UrlModuleDefault,
3618    WorkerThreadsModule,
3619    WorkerThreadsModuleDefault,
3620    ChildProcessModule,
3621    ChildProcessModuleDefault,
3622    OsModule,
3623    OsModuleDefault,
3624    NodeProcessModule,
3625    NodeProcessArgv,
3626    NodeProcessEnv,
3627    NodePreGyp,
3628    NodeExpressApp,
3629    NodeProtobufLoader,
3630    NodeBuffer,
3631    RequireCache,
3632    ImportMeta,
3633    /// An iterator object, used to model generator return values.
3634    Generator,
3635    /// The `module.hot` object providing HMR API.
3636    ModuleHot,
3637}
3638
3639impl WellKnownObjectKind {
3640    pub fn as_define_name(&self) -> Option<&[&str]> {
3641        match self {
3642            Self::GlobalObject => Some(&["Object"]),
3643            Self::PathModule => Some(&["path"]),
3644            Self::FsModule => Some(&["fs"]),
3645            Self::UrlModule => Some(&["url"]),
3646            Self::ChildProcessModule => Some(&["child_process"]),
3647            Self::OsModule => Some(&["os"]),
3648            Self::WorkerThreadsModule => Some(&["worker_threads"]),
3649            Self::NodeProcessModule => Some(&["process"]),
3650            Self::NodeProcessArgv => Some(&["process", "argv"]),
3651            Self::NodeProcessEnv => Some(&["process", "env"]),
3652            Self::NodeBuffer => Some(&["Buffer"]),
3653            Self::RequireCache => Some(&["require", "cache"]),
3654            Self::ImportMeta => Some(&["import", "meta"]),
3655            _ => None,
3656        }
3657    }
3658}
3659
3660#[derive(Debug, Clone)]
3661pub struct RequireContextOptions {
3662    pub dir: RcStr,
3663    pub include_subdirs: bool,
3664    /// this is a regex (pattern, flags)
3665    pub filter: EsRegex,
3666}
3667
3668/// Parse the arguments passed to a require.context invocation, validate them
3669/// and convert them to the appropriate rust values.
3670pub fn parse_require_context(args: &[JsValue]) -> Result<RequireContextOptions> {
3671    if !(1..=3).contains(&args.len()) {
3672        // https://linear.app/vercel/issue/WEB-910/add-support-for-requirecontexts-mode-argument
3673        bail!("require.context() only supports 1-3 arguments (mode is not supported)");
3674    }
3675
3676    let Some(dir) = args[0].as_str().map(|s| s.into()) else {
3677        bail!("require.context(dir, ...) requires dir to be a constant string");
3678    };
3679
3680    let include_subdirs = if let Some(include_subdirs) = args.get(1) {
3681        if let Some(include_subdirs) = include_subdirs.as_bool() {
3682            include_subdirs
3683        } else {
3684            bail!(
3685                "require.context(..., includeSubdirs, ...) requires includeSubdirs to be a \
3686                 constant boolean",
3687            );
3688        }
3689    } else {
3690        true
3691    };
3692
3693    let filter = if let Some(filter) = args.get(2) {
3694        if let JsValue::Constant(ConstantValue::Regex(box (pattern, flags))) = filter {
3695            EsRegex::new(pattern, flags)?
3696        } else {
3697            bail!("require.context(..., ..., filter) requires filter to be a regex");
3698        }
3699    } else {
3700        // https://webpack.js.org/api/module-methods/#requirecontext
3701        // > optional, default /^\.\/.*$/, any file
3702        static DEFAULT_REGEX: LazyLock<EsRegex> =
3703            LazyLock::new(|| EsRegex::new(r"^\\./.*$", "").unwrap());
3704
3705        DEFAULT_REGEX.clone()
3706    };
3707
3708    Ok(RequireContextOptions {
3709        dir,
3710        include_subdirs,
3711        filter,
3712    })
3713}
3714
3715#[derive(Debug, Clone, Eq, PartialEq)]
3716pub struct RequireContextValue(FxIndexMap<RcStr, RcStr>);
3717
3718impl RequireContextValue {
3719    pub async fn from_context_map(map: Vc<RequireContextMap>) -> Result<Self> {
3720        let mut context_map = FxIndexMap::default();
3721
3722        for (key, entry) in map.await?.iter() {
3723            context_map.insert(key.clone(), entry.origin_relative.clone());
3724        }
3725
3726        Ok(RequireContextValue(context_map))
3727    }
3728}
3729
3730impl Hash for RequireContextValue {
3731    fn hash<H: Hasher>(&self, state: &mut H) {
3732        self.0.len().hash(state);
3733        for (i, (k, v)) in self.0.iter().enumerate() {
3734            i.hash(state);
3735            k.hash(state);
3736            v.hash(state);
3737        }
3738    }
3739}
3740
3741/// A list of well-known functions that have special meaning in the analysis.
3742#[derive(Debug, Clone, Hash, PartialEq, Eq)]
3743pub enum WellKnownFunctionKind {
3744    ArrayFilter,
3745    ArrayForEach,
3746    ArrayMap,
3747    ObjectAssign,
3748    PathJoin,
3749    PathDirname,
3750    /// `0` is the current working directory.
3751    PathResolve(Box<JsValue>),
3752    Import,
3753    Require,
3754    /// `0` is the path to resolve from (relative to the current module).
3755    RequireFrom(Box<ConstantString>),
3756    RequireResolve,
3757    RequireContext,
3758    // Boxed: `RequireContextValue` wraps a 56-byte `FxIndexMap`. Inlining it here dominates
3759    // `WellKnownFunctionKind`'s size (64 bytes) and by extension `JsValue`.
3760    RequireContextRequire(Box<RequireContextValue>),
3761    RequireContextRequireKeys(Box<RequireContextValue>),
3762    RequireContextRequireResolve(Box<RequireContextValue>),
3763    Define,
3764    FsReadMethod(Atom),
3765    FsReadDir,
3766    PathToFileUrl,
3767    CreateRequire,
3768    ChildProcessSpawnMethod(Atom),
3769    ChildProcessFork,
3770    OsArch,
3771    OsPlatform,
3772    OsEndianness,
3773    ProcessCwd,
3774    NodePreGypFind,
3775    NodeGypBuild,
3776    NodeBindings,
3777    NodeExpress,
3778    NodeExpressSet,
3779    NodeStrongGlobalize,
3780    NodeStrongGlobalizeSetRootDir,
3781    NodeResolveFrom,
3782    NodeProtobufLoad,
3783    WorkerConstructor,
3784    SharedWorkerConstructor,
3785    // The worker_threads Worker class
3786    NodeWorkerConstructor,
3787    URLConstructor,
3788    /// `module.hot.accept(deps, callback, errorHandler)` — accept HMR updates for dependencies.
3789    ModuleHotAccept,
3790    /// `module.hot.decline(deps)` — decline HMR updates for dependencies.
3791    ModuleHotDecline,
3792    /// `import.meta.glob(patterns, options?)` — Vite-compatible glob import.
3793    ImportMetaGlob,
3794}
3795
3796impl WellKnownFunctionKind {
3797    pub fn as_define_name(&self) -> Option<&[&str]> {
3798        match self {
3799            Self::Import { .. } => Some(&["import"]),
3800            Self::Require { .. } => Some(&["require"]),
3801            Self::RequireResolve => Some(&["require", "resolve"]),
3802            Self::RequireContext => Some(&["require", "context"]),
3803            Self::Define => Some(&["define"]),
3804            _ => None,
3805        }
3806    }
3807}
3808
3809fn is_unresolved(i: &Ident, unresolved_mark: Mark) -> bool {
3810    i.ctxt.outer() == unresolved_mark
3811}
3812
3813fn is_unresolved_id(i: &Id, unresolved_mark: Mark) -> bool {
3814    i.1.outer() == unresolved_mark
3815}
3816
3817#[doc(hidden)]
3818pub mod test_utils {
3819    use anyhow::Result;
3820    use turbo_rcstr::rcstr;
3821    use turbo_tasks::{FxIndexMap, PrettyPrintError, Vc};
3822    use turbopack_core::compile_time_info::CompileTimeInfo;
3823
3824    use super::{
3825        ConstantValue, JsValue, JsValueUrlKind, ModuleValue, WellKnownFunctionKind,
3826        WellKnownObjectKind, builtin::early_replace_builtin, well_known::replace_well_known,
3827    };
3828    use crate::{
3829        analyzer::{
3830            RequireContextValue, builtin::replace_builtin, imports::ImportAttributes,
3831            parse_require_context,
3832        },
3833        utils::module_value_to_well_known_object,
3834    };
3835
3836    pub async fn early_visitor(mut v: JsValue) -> Result<(JsValue, bool)> {
3837        let m = early_replace_builtin(&mut v);
3838        Ok((v, m))
3839    }
3840
3841    /// Visitor that replaces well known functions and objects with their
3842    /// corresponding values. Returns the new value and whether it was modified.
3843    pub async fn visitor(
3844        v: JsValue,
3845        compile_time_info: Vc<CompileTimeInfo>,
3846        attributes: &ImportAttributes,
3847    ) -> Result<(JsValue, bool)> {
3848        let ImportAttributes { ignore, .. } = *attributes;
3849        let mut new_value = match v {
3850            JsValue::Call(_, ref call)
3851                if matches!(
3852                    call.callee(),
3853                    JsValue::WellKnownFunction(WellKnownFunctionKind::Import)
3854                ) =>
3855            {
3856                match &call.args()[0] {
3857                    JsValue::Constant(ConstantValue::Str(v)) => {
3858                        JsValue::promise(JsValue::Module(ModuleValue {
3859                            module: v.as_atom().into_owned().into(),
3860                            annotations: None,
3861                        }))
3862                    }
3863                    _ => v.into_unknown(true, rcstr!("import() non constant")),
3864                }
3865            }
3866            JsValue::Call(_, ref call)
3867                if matches!(
3868                    call.callee(),
3869                    JsValue::WellKnownFunction(WellKnownFunctionKind::CreateRequire)
3870                ) =>
3871            {
3872                if let [
3873                    JsValue::Member(
3874                        _,
3875                        box JsValue::WellKnownObject(WellKnownObjectKind::ImportMeta),
3876                        box JsValue::Constant(ConstantValue::Str(prop)),
3877                    ),
3878                ] = call.args()
3879                    && prop.as_str() == "url"
3880                {
3881                    JsValue::WellKnownFunction(WellKnownFunctionKind::Require)
3882                } else {
3883                    v.into_unknown(true, rcstr!("createRequire() non constant"))
3884                }
3885            }
3886            JsValue::Call(_, ref call)
3887                if matches!(
3888                    call.callee(),
3889                    JsValue::WellKnownFunction(WellKnownFunctionKind::RequireResolve)
3890                ) =>
3891            {
3892                match &call.args()[0] {
3893                    JsValue::Constant(v) => (v.to_string() + "/resolved/lib/index.js").into(),
3894                    _ => v.into_unknown(true, rcstr!("require.resolve non constant")),
3895                }
3896            }
3897            JsValue::Call(_, ref call)
3898                if matches!(
3899                    call.callee(),
3900                    JsValue::WellKnownFunction(WellKnownFunctionKind::ImportMetaGlob)
3901                ) =>
3902            {
3903                v.into_unknown(false, rcstr!("import.meta.glob()"))
3904            }
3905            JsValue::Call(_, ref call)
3906                if matches!(
3907                    call.callee(),
3908                    JsValue::WellKnownFunction(WellKnownFunctionKind::RequireContext)
3909                ) =>
3910            {
3911                match parse_require_context(call.args()) {
3912                    Ok(options) => {
3913                        let mut map = FxIndexMap::default();
3914
3915                        map.insert(
3916                            rcstr!("./a"),
3917                            format!("[context: {}]/a", options.dir).into(),
3918                        );
3919                        map.insert(
3920                            rcstr!("./b"),
3921                            format!("[context: {}]/b", options.dir).into(),
3922                        );
3923                        map.insert(
3924                            rcstr!("./c"),
3925                            format!("[context: {}]/c", options.dir).into(),
3926                        );
3927
3928                        JsValue::WellKnownFunction(WellKnownFunctionKind::RequireContextRequire(
3929                            Box::new(RequireContextValue(map)),
3930                        ))
3931                    }
3932                    Err(err) => v.into_unknown(true, PrettyPrintError(&err).to_string().into()),
3933                }
3934            }
3935            JsValue::New(_, ref call)
3936                if matches!(
3937                    call.callee(),
3938                    JsValue::WellKnownFunction(WellKnownFunctionKind::URLConstructor)
3939                ) =>
3940            {
3941                if let [
3942                    JsValue::Constant(ConstantValue::Str(url)),
3943                    JsValue::Member(
3944                        _,
3945                        box JsValue::WellKnownObject(WellKnownObjectKind::ImportMeta),
3946                        box JsValue::Constant(ConstantValue::Str(prop)),
3947                    ),
3948                ] = call.args()
3949                {
3950                    if prop.as_str() == "url" {
3951                        // TODO avoid clone
3952                        JsValue::Url(url.clone(), JsValueUrlKind::Relative)
3953                    } else {
3954                        v.into_unknown(true, rcstr!("new non constant"))
3955                    }
3956                } else {
3957                    v.into_unknown(true, rcstr!("new non constant"))
3958                }
3959            }
3960            JsValue::FreeVar(ref var) => match &**var {
3961                "__dirname" => rcstr!("__dirname").into(),
3962                "__filename" => rcstr!("__filename").into(),
3963
3964                "require" => JsValue::unknown_if(
3965                    ignore,
3966                    JsValue::WellKnownFunction(WellKnownFunctionKind::Require),
3967                    true,
3968                    rcstr!("ignored require"),
3969                ),
3970                "import" => JsValue::unknown_if(
3971                    ignore,
3972                    JsValue::WellKnownFunction(WellKnownFunctionKind::Import),
3973                    true,
3974                    rcstr!("ignored import"),
3975                ),
3976                "Worker" => JsValue::unknown_if(
3977                    ignore,
3978                    JsValue::WellKnownFunction(WellKnownFunctionKind::WorkerConstructor),
3979                    true,
3980                    rcstr!("ignored Worker constructor"),
3981                ),
3982                "define" => JsValue::WellKnownFunction(WellKnownFunctionKind::Define),
3983                "URL" => JsValue::WellKnownFunction(WellKnownFunctionKind::URLConstructor),
3984                "process" => JsValue::WellKnownObject(WellKnownObjectKind::NodeProcessModule),
3985                "Object" => JsValue::WellKnownObject(WellKnownObjectKind::GlobalObject),
3986                "Buffer" => JsValue::WellKnownObject(WellKnownObjectKind::NodeBuffer),
3987                _ => v.into_unknown(true, rcstr!("unknown global")),
3988            },
3989            JsValue::Module(ref mv) => {
3990                if let Some(wko) = module_value_to_well_known_object(mv) {
3991                    wko
3992                } else {
3993                    return Ok((v, false));
3994                }
3995            }
3996            _ => {
3997                let (mut v, m1) = replace_well_known(v, compile_time_info, true).await?;
3998                let m2 = replace_builtin(&mut v);
3999                let m = m1 || m2 || v.make_nested_operations_unknown();
4000                return Ok((v, m));
4001            }
4002        };
4003        new_value.normalize_shallow();
4004        Ok((new_value, true))
4005    }
4006}
4007
4008#[cfg(test)]
4009mod tests {
4010    use std::{mem::take, path::PathBuf, time::Instant};
4011
4012    use parking_lot::Mutex;
4013    use rustc_hash::FxHashMap;
4014    use swc_core::{
4015        common::{Mark, comments::SingleThreadedComments},
4016        ecma::{
4017            ast::{EsVersion, Id},
4018            parser::parse_file_as_program,
4019            transforms::base::resolver,
4020            visit::VisitMutWith,
4021        },
4022        testing::{NormalizedOutput, fixture, run_test},
4023    };
4024    use turbo_rcstr::rcstr;
4025    use turbo_tasks::{ResolvedVc, util::FormatDuration};
4026    use turbopack_core::{
4027        compile_time_info::CompileTimeInfo,
4028        environment::{Environment, ExecutionEnvironment, NodeJsEnvironment, NodeJsVersion},
4029        target::{Arch, CompileTarget, Endianness, Libc, Platform},
4030    };
4031
4032    use super::{
4033        JsValue,
4034        graph::{ConditionalKind, Effect, EffectArg, EvalContext, VarGraph, create_graph},
4035        linker::link,
4036    };
4037    use crate::{
4038        AnalyzeMode,
4039        analyzer::{graph::AssignmentScopes, imports::ImportAttributes},
4040    };
4041
4042    #[fixture("tests/analyzer/graph/**/input.js")]
4043    fn fixture(input: PathBuf) {
4044        let graph_snapshot_path = input.with_file_name("graph.snapshot");
4045        let graph_explained_snapshot_path = input.with_file_name("graph-explained.snapshot");
4046        let graph_effects_snapshot_path = input.with_file_name("graph-effects.snapshot");
4047        let resolved_explained_snapshot_path = input.with_file_name("resolved-explained.snapshot");
4048        let resolved_effects_snapshot_path = input.with_file_name("resolved-effects.snapshot");
4049        let large_marker = input.with_file_name("large");
4050
4051        run_test(false, |cm, handler| {
4052            let r = tokio::runtime::Builder::new_current_thread()
4053                .build()
4054                .unwrap();
4055            r.block_on(async move {
4056                let fm = cm.load_file(&input).unwrap();
4057
4058                let comments = SingleThreadedComments::default();
4059                let mut m = parse_file_as_program(
4060                    &fm,
4061                    Default::default(),
4062                    EsVersion::latest(),
4063                    Some(&comments),
4064                    &mut vec![],
4065                )
4066                .map_err(|err| err.into_diagnostic(handler).emit())?;
4067
4068                let unresolved_mark = Mark::new();
4069                let top_level_mark = Mark::new();
4070                m.visit_mut_with(&mut resolver(unresolved_mark, top_level_mark, false));
4071
4072                let eval_context = EvalContext::new(
4073                    Some(&m),
4074                    unresolved_mark,
4075                    top_level_mark,
4076                    Default::default(),
4077                    Some(&comments),
4078                );
4079
4080                let mut var_graph = create_graph(
4081                    &m,
4082                    &eval_context,
4083                    AnalyzeMode::CodeGenerationAndTracing,
4084                    true,
4085                );
4086                let var_cache = Default::default();
4087
4088                let mut named_values = var_graph
4089                    .values
4090                    .clone()
4091                    .into_iter()
4092                    .map(|((id, ctx), value)| {
4093                        let unique = var_graph.values.keys().filter(|(i, _)| &id == i).count() == 1;
4094                        if unique {
4095                            (id.to_string(), ((id, ctx), value))
4096                        } else {
4097                            (format!("{id}{ctx:?}"), ((id, ctx), value))
4098                        }
4099                    })
4100                    .collect::<Vec<_>>();
4101                named_values.sort_by(|a, b| a.0.cmp(&b.0));
4102
4103                fn explain_all<'a>(
4104                    values: impl IntoIterator<
4105                        Item = (&'a String, &'a JsValue, Option<AssignmentScopes>),
4106                    >,
4107                ) -> String {
4108                    values
4109                        .into_iter()
4110                        .map(|(id, value, assignment_scopes)| {
4111                            let non_root_assignments = match assignment_scopes {
4112                                Some(AssignmentScopes::AllInModuleEvalScope) => {
4113                                    " (const after eval)"
4114                                }
4115                                _ => "",
4116                            };
4117                            let (explainer, hints) = value.explain(10, 5);
4118                            format!("{id}{non_root_assignments} = {explainer}{hints}")
4119                        })
4120                        .collect::<Vec<_>>()
4121                        .join("\n\n")
4122                }
4123
4124                {
4125                    // Dump snapshot of graph
4126
4127                    let large = large_marker.exists();
4128
4129                    if !large {
4130                        NormalizedOutput::from(format!(
4131                            "{:#?}",
4132                            named_values
4133                                .iter()
4134                                .map(|(name, (_, value))| (name, value))
4135                                .collect::<Vec<_>>()
4136                        ))
4137                        .compare_to_file(&graph_snapshot_path)
4138                        .unwrap();
4139                    }
4140                    NormalizedOutput::from(explain_all(named_values.iter().map(
4141                        |(name, (id, value))| {
4142                            (
4143                                name,
4144                                value,
4145                                eval_context.imports.assignment_scopes.get(id).copied(),
4146                            )
4147                        },
4148                    )))
4149                    .compare_to_file(&graph_explained_snapshot_path)
4150                    .unwrap();
4151                    if !large {
4152                        NormalizedOutput::from(format!("{:#?}", var_graph.effects))
4153                            .compare_to_file(&graph_effects_snapshot_path)
4154                            .unwrap();
4155                    }
4156                }
4157
4158                {
4159                    // Dump snapshot of resolved
4160
4161                    let start = Instant::now();
4162                    let mut resolved = Vec::new();
4163                    for (name, (id, _)) in named_values.iter().cloned() {
4164                        let start = Instant::now();
4165                        // Ideally this would use eval_context.imports.get_attributes(span), but the
4166                        // span isn't available here
4167                        let (res, steps) = resolve(
4168                            &var_graph,
4169                            JsValue::Variable(id),
4170                            ImportAttributes::empty_ref(),
4171                            &var_cache,
4172                        )
4173                        .await;
4174                        let time = start.elapsed();
4175                        if time.as_millis() > 1 {
4176                            println!(
4177                                "linking {} {name} took {} in {} steps",
4178                                input.display(),
4179                                FormatDuration(time),
4180                                steps
4181                            );
4182                        }
4183
4184                        resolved.push((name, res));
4185                    }
4186                    let time = start.elapsed();
4187                    if time.as_millis() > 1 {
4188                        println!("linking {} took {}", input.display(), FormatDuration(time));
4189                    }
4190
4191                    let start = Instant::now();
4192                    let explainer =
4193                        explain_all(resolved.iter().map(|(name, value)| (name, value, None)));
4194                    let time = start.elapsed();
4195                    if time.as_millis() > 1 {
4196                        println!(
4197                            "explaining {} took {}",
4198                            input.display(),
4199                            FormatDuration(time)
4200                        );
4201                    }
4202
4203                    NormalizedOutput::from(explainer)
4204                        .compare_to_file(&resolved_explained_snapshot_path)
4205                        .unwrap();
4206                }
4207
4208                {
4209                    // Dump snapshot of resolved effects
4210
4211                    let start = Instant::now();
4212                    let mut resolved = Vec::new();
4213                    let mut queue = take(&mut var_graph.effects)
4214                        .into_iter()
4215                        .map(|effect| (0, effect))
4216                        .rev()
4217                        .collect::<Vec<_>>();
4218                    let mut i = 0;
4219                    while let Some((parent, effect)) = queue.pop() {
4220                        i += 1;
4221                        let start = Instant::now();
4222                        async fn handle_args(
4223                            args: Vec<EffectArg>,
4224                            queue: &mut Vec<(usize, Effect)>,
4225                            var_graph: &VarGraph,
4226                            var_cache: &Mutex<FxHashMap<Id, JsValue>>,
4227                            i: usize,
4228                        ) -> Vec<JsValue> {
4229                            let mut new_args = Vec::with_capacity(args.len());
4230                            for arg in args {
4231                                match arg {
4232                                    EffectArg::Value(v) => {
4233                                        new_args.push(
4234                                            resolve(
4235                                                var_graph,
4236                                                v,
4237                                                ImportAttributes::empty_ref(),
4238                                                var_cache,
4239                                            )
4240                                            .await
4241                                            .0,
4242                                        );
4243                                    }
4244                                    EffectArg::Closure(v, effects) => {
4245                                        new_args.push(
4246                                            resolve(
4247                                                var_graph,
4248                                                v,
4249                                                ImportAttributes::empty_ref(),
4250                                                var_cache,
4251                                            )
4252                                            .await
4253                                            .0,
4254                                        );
4255                                        queue.extend(
4256                                            effects.effects.into_iter().rev().map(|e| (i, e)),
4257                                        );
4258                                    }
4259                                    EffectArg::Spread => {
4260                                        new_args
4261                                            .push(JsValue::unknown_empty(true, rcstr!("spread")));
4262                                    }
4263                                }
4264                            }
4265                            new_args
4266                        }
4267                        let steps = match effect {
4268                            Effect::Conditional {
4269                                condition, kind, ..
4270                            } => {
4271                                let (condition, steps) = resolve(
4272                                    &var_graph,
4273                                    *condition,
4274                                    ImportAttributes::empty_ref(),
4275                                    &var_cache,
4276                                )
4277                                .await;
4278                                resolved.push((format!("{parent} -> {i} conditional"), condition));
4279                                match *kind {
4280                                    ConditionalKind::If { then } => {
4281                                        queue
4282                                            .extend(then.effects.into_iter().rev().map(|e| (i, e)));
4283                                    }
4284                                    ConditionalKind::Else { r#else } => {
4285                                        queue.extend(
4286                                            r#else.effects.into_iter().rev().map(|e| (i, e)),
4287                                        );
4288                                    }
4289                                    ConditionalKind::IfElse { then, r#else }
4290                                    | ConditionalKind::Ternary { then, r#else } => {
4291                                        queue.extend(
4292                                            r#else.effects.into_iter().rev().map(|e| (i, e)),
4293                                        );
4294                                        queue
4295                                            .extend(then.effects.into_iter().rev().map(|e| (i, e)));
4296                                    }
4297                                    ConditionalKind::IfElseMultiple { then, r#else } => {
4298                                        for then in then {
4299                                            queue.extend(
4300                                                then.effects.into_iter().rev().map(|e| (i, e)),
4301                                            );
4302                                        }
4303                                        for r#else in r#else {
4304                                            queue.extend(
4305                                                r#else.effects.into_iter().rev().map(|e| (i, e)),
4306                                            );
4307                                        }
4308                                    }
4309                                    ConditionalKind::And { expr }
4310                                    | ConditionalKind::Or { expr }
4311                                    | ConditionalKind::NullishCoalescing { expr }
4312                                    | ConditionalKind::Labeled { body: expr } => {
4313                                        queue
4314                                            .extend(expr.effects.into_iter().rev().map(|e| (i, e)));
4315                                    }
4316                                };
4317                                steps
4318                            }
4319                            Effect::Call {
4320                                func,
4321                                args,
4322                                new,
4323                                span,
4324                                ..
4325                            } => {
4326                                let (func, steps) = resolve(
4327                                    &var_graph,
4328                                    *func,
4329                                    eval_context.imports.get_attributes(span),
4330                                    &var_cache,
4331                                )
4332                                .await;
4333                                let new_args =
4334                                    handle_args(args, &mut queue, &var_graph, &var_cache, i).await;
4335                                resolved.push((
4336                                    format!("{parent} -> {i} call"),
4337                                    if new {
4338                                        JsValue::new_from_iter(func, new_args)
4339                                    } else {
4340                                        JsValue::call_from_iter(func, new_args)
4341                                    },
4342                                ));
4343                                steps
4344                            }
4345                            Effect::FreeVar { var, .. } => {
4346                                resolved.push((
4347                                    format!("{parent} -> {i} free var"),
4348                                    JsValue::FreeVar(var),
4349                                ));
4350                                0
4351                            }
4352                            Effect::TypeOf { arg, .. } => {
4353                                let (arg, steps) = resolve(
4354                                    &var_graph,
4355                                    *arg,
4356                                    ImportAttributes::empty_ref(),
4357                                    &var_cache,
4358                                )
4359                                .await;
4360                                resolved.push((
4361                                    format!("{parent} -> {i} typeof"),
4362                                    JsValue::type_of(Box::new(arg)),
4363                                ));
4364                                steps
4365                            }
4366                            Effect::MemberCall {
4367                                obj, prop, args, ..
4368                            } => {
4369                                let (obj, obj_steps) = resolve(
4370                                    &var_graph,
4371                                    *obj,
4372                                    ImportAttributes::empty_ref(),
4373                                    &var_cache,
4374                                )
4375                                .await;
4376                                let (prop, prop_steps) = resolve(
4377                                    &var_graph,
4378                                    *prop,
4379                                    ImportAttributes::empty_ref(),
4380                                    &var_cache,
4381                                )
4382                                .await;
4383                                let new_args =
4384                                    handle_args(args, &mut queue, &var_graph, &var_cache, i).await;
4385                                resolved.push((
4386                                    format!("{parent} -> {i} member call"),
4387                                    JsValue::member_call_from_iter(obj, prop, new_args),
4388                                ));
4389                                obj_steps + prop_steps
4390                            }
4391                            Effect::DynamicImport { args, .. } => {
4392                                let new_args =
4393                                    handle_args(args, &mut queue, &var_graph, &var_cache, i).await;
4394                                resolved.push((
4395                                    format!("{parent} -> {i} dynamic import"),
4396                                    JsValue::call_from_iter(
4397                                        JsValue::FreeVar("import".into()),
4398                                        new_args,
4399                                    ),
4400                                ));
4401                                0
4402                            }
4403                            Effect::Unreachable { .. } => {
4404                                resolved.push((
4405                                    format!("{parent} -> {i} unreachable"),
4406                                    JsValue::unknown_empty(true, rcstr!("unreachable")),
4407                                ));
4408                                0
4409                            }
4410                            Effect::ImportMeta { .. }
4411                            | Effect::ImportedBinding { .. }
4412                            | Effect::Member { .. } => 0,
4413                        };
4414                        let time = start.elapsed();
4415                        if time.as_millis() > 1 {
4416                            println!(
4417                                "linking effect {} took {} in {} steps",
4418                                input.display(),
4419                                FormatDuration(time),
4420                                steps
4421                            );
4422                        }
4423                    }
4424                    let time = start.elapsed();
4425                    if time.as_millis() > 1 {
4426                        println!(
4427                            "linking effects {} took {}",
4428                            input.display(),
4429                            FormatDuration(time)
4430                        );
4431                    }
4432
4433                    let start = Instant::now();
4434                    let explainer =
4435                        explain_all(resolved.iter().map(|(name, value)| (name, value, None)));
4436                    let time = start.elapsed();
4437                    if time.as_millis() > 1 {
4438                        println!(
4439                            "explaining effects {} took {}",
4440                            input.display(),
4441                            FormatDuration(time)
4442                        );
4443                    }
4444
4445                    NormalizedOutput::from(explainer)
4446                        .compare_to_file(&resolved_effects_snapshot_path)
4447                        .unwrap();
4448                }
4449
4450                Ok(())
4451            })
4452        })
4453        .unwrap();
4454    }
4455
4456    async fn resolve(
4457        var_graph: &VarGraph,
4458        val: JsValue,
4459        attributes: &ImportAttributes,
4460        var_cache: &Mutex<FxHashMap<Id, JsValue>>,
4461    ) -> (JsValue, u32) {
4462        turbo_tasks_testing::VcStorage::with(async {
4463            let compile_time_info = CompileTimeInfo::builder(
4464                Environment::new(ExecutionEnvironment::NodeJsLambda(
4465                    NodeJsEnvironment {
4466                        compile_target: CompileTarget {
4467                            arch: Arch::X64,
4468                            platform: Platform::Linux,
4469                            endianness: Endianness::Little,
4470                            libc: Libc::Glibc,
4471                        }
4472                        .resolved_cell(),
4473                        node_version: NodeJsVersion::default().resolved_cell(),
4474                        cwd: ResolvedVc::cell(None),
4475                    }
4476                    .resolved_cell(),
4477                ))
4478                .to_resolved()
4479                .await?,
4480            )
4481            .cell()
4482            .await?;
4483            link(
4484                var_graph,
4485                val,
4486                &super::test_utils::early_visitor,
4487                &(|val| {
4488                    Box::pin(super::test_utils::visitor(
4489                        val,
4490                        compile_time_info,
4491                        attributes,
4492                    ))
4493                }),
4494                &Default::default(),
4495                var_cache,
4496            )
4497            .await
4498        })
4499        .await
4500        .unwrap()
4501    }
4502
4503    #[test]
4504    #[cfg(target_pointer_width = "64")]
4505    fn jsvalue_size() {
4506        assert_eq!(32, size_of::<JsValue>());
4507    }
4508}