turbopack_ecmascript/analyzer/
mod.rs

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