turbopack_ecmascript/analyzer/
mod.rs

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