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