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