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