Skip to main content

turbopack_ecmascript/analyzer/
mod.rs

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