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