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