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