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::WorkerThreadsModule | WellKnownObjectKind::WorkerThreadsModuleDefault => (
1792 "worker_threads",
1793 "The Node.js `worker_threads` module: https://nodejs.org/api/worker_threads.html",
1794 ),
1795 WellKnownObjectKind::ChildProcessModule | WellKnownObjectKind::ChildProcessModuleDefault => (
1796 "child_process",
1797 "The Node.js child_process module: https://nodejs.org/api/child_process.html",
1798 ),
1799 WellKnownObjectKind::OsModule | WellKnownObjectKind::OsModuleDefault => (
1800 "os",
1801 "The Node.js os module: https://nodejs.org/api/os.html",
1802 ),
1803 WellKnownObjectKind::NodeProcessModule => (
1804 "process",
1805 "The Node.js process module: https://nodejs.org/api/process.html",
1806 ),
1807 WellKnownObjectKind::NodeProcessArgv => (
1808 "process.argv",
1809 "The Node.js process.argv property: https://nodejs.org/api/process.html#processargv",
1810 ),
1811 WellKnownObjectKind::NodeProcessEnv => (
1812 "process.env",
1813 "The Node.js process.env property: https://nodejs.org/api/process.html#processenv",
1814 ),
1815 WellKnownObjectKind::NodePreGyp => (
1816 "@mapbox/node-pre-gyp",
1817 "The Node.js @mapbox/node-pre-gyp module: https://github.com/mapbox/node-pre-gyp",
1818 ),
1819 WellKnownObjectKind::NodeExpressApp => (
1820 "express",
1821 "The Node.js express package: https://github.com/expressjs/express"
1822 ),
1823 WellKnownObjectKind::NodeProtobufLoader => (
1824 "@grpc/proto-loader",
1825 "The Node.js @grpc/proto-loader package: https://github.com/grpc/grpc-node"
1826 ),
1827 WellKnownObjectKind::NodeBuffer => (
1828 "Buffer",
1829 "The Node.js Buffer object: https://nodejs.org/api/buffer.html#class-buffer"
1830 ),
1831 WellKnownObjectKind::RequireCache => (
1832 "require.cache",
1833 "The CommonJS require.cache object: https://nodejs.org/api/modules.html#requirecache"
1834 ),
1835 WellKnownObjectKind::ImportMeta => (
1836 "import.meta",
1837 "The import.meta object"
1838 ),
1839 };
1840 if depth > 0 {
1841 let i = hints.len();
1842 hints.push(format!("- *{i}* {name}: {explainer}"));
1843 format!("{name}*{i}*")
1844 } else {
1845 name.to_string()
1846 }
1847 }
1848 JsValue::WellKnownFunction(func) => {
1849 let (name, explainer) = match func {
1850 WellKnownFunctionKind::ArrayFilter => (
1851 "Array.prototype.filter".to_string(),
1852 "The standard Array.prototype.filter method: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter"
1853 ),
1854 WellKnownFunctionKind::ArrayForEach => (
1855 "Array.prototype.forEach".to_string(),
1856 "The standard Array.prototype.forEach method: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach"
1857 ),
1858 WellKnownFunctionKind::ArrayMap => (
1859 "Array.prototype.map".to_string(),
1860 "The standard Array.prototype.map method: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map"
1861 ),
1862 WellKnownFunctionKind::ObjectAssign => (
1863 "Object.assign".to_string(),
1864 "Object.assign method: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/assign",
1865 ),
1866 WellKnownFunctionKind::PathJoin => (
1867 "path.join".to_string(),
1868 "The Node.js path.join method: https://nodejs.org/api/path.html#pathjoinpaths",
1869 ),
1870 WellKnownFunctionKind::PathDirname => (
1871 "path.dirname".to_string(),
1872 "The Node.js path.dirname method: https://nodejs.org/api/path.html#pathdirnamepath",
1873 ),
1874 WellKnownFunctionKind::PathResolve(cwd) => (
1875 format!("path.resolve({cwd})"),
1876 "The Node.js path.resolve method: https://nodejs.org/api/path.html#pathresolvepaths",
1877 ),
1878 WellKnownFunctionKind::Import => (
1879 "import".to_string(),
1880 "The dynamic import() method from the ESM specification: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#dynamic_imports"
1881 ),
1882 WellKnownFunctionKind::Require => ("require".to_string(), "The require method from CommonJS"),
1883 WellKnownFunctionKind::RequireResolve => ("require.resolve".to_string(), "The require.resolve method from CommonJS"),
1884 WellKnownFunctionKind::RequireContext => ("require.context".to_string(), "The require.context method from webpack"),
1885 WellKnownFunctionKind::RequireContextRequire(..) => ("require.context(...)".to_string(), "The require.context(...) method from webpack: https://webpack.js.org/api/module-methods/#requirecontext"),
1886 WellKnownFunctionKind::RequireContextRequireKeys(..) => ("require.context(...).keys".to_string(), "The require.context(...).keys method from webpack: https://webpack.js.org/guides/dependency-management/#requirecontext"),
1887 WellKnownFunctionKind::RequireContextRequireResolve(..) => ("require.context(...).resolve".to_string(), "The require.context(...).resolve method from webpack: https://webpack.js.org/guides/dependency-management/#requirecontext"),
1888 WellKnownFunctionKind::Define => ("define".to_string(), "The define method from AMD"),
1889 WellKnownFunctionKind::FsReadMethod(name) => (
1890 format!("fs.{name}"),
1891 "A file reading method from the Node.js fs module: https://nodejs.org/api/fs.html",
1892 ),
1893 WellKnownFunctionKind::PathToFileUrl => (
1894 "url.pathToFileURL".to_string(),
1895 "The Node.js url.pathToFileURL method: https://nodejs.org/api/url.html#urlpathtofileurlpath",
1896 ),
1897 WellKnownFunctionKind::CreateRequire => (
1898 "module.createRequire".to_string(),
1899 "The Node.js module.createRequire method: https://nodejs.org/api/module.html#modulecreaterequirefilename",
1900 ),
1901 WellKnownFunctionKind::ChildProcessSpawnMethod(name) => (
1902 format!("child_process.{name}"),
1903 "A process spawning method from the Node.js child_process module: https://nodejs.org/api/child_process.html",
1904 ),
1905 WellKnownFunctionKind::ChildProcessFork => (
1906 "child_process.fork".to_string(),
1907 "The Node.js child_process.fork method: https://nodejs.org/api/child_process.html#child_processforkmodulepath-args-options",
1908 ),
1909 WellKnownFunctionKind::OsArch => (
1910 "os.arch".to_string(),
1911 "The Node.js os.arch method: https://nodejs.org/api/os.html#os_os_arch",
1912 ),
1913 WellKnownFunctionKind::OsPlatform => (
1914 "os.process".to_string(),
1915 "The Node.js os.process method: https://nodejs.org/api/os.html#os_os_process",
1916 ),
1917 WellKnownFunctionKind::OsEndianness => (
1918 "os.endianness".to_string(),
1919 "The Node.js os.endianness method: https://nodejs.org/api/os.html#os_os_endianness",
1920 ),
1921 WellKnownFunctionKind::ProcessCwd => (
1922 "process.cwd".to_string(),
1923 "The Node.js process.cwd method: https://nodejs.org/api/process.html#processcwd",
1924 ),
1925 WellKnownFunctionKind::NodePreGypFind => (
1926 "binary.find".to_string(),
1927 "The Node.js @mapbox/node-pre-gyp module: https://github.com/mapbox/node-pre-gyp",
1928 ),
1929 WellKnownFunctionKind::NodeGypBuild => (
1930 "node-gyp-build".to_string(),
1931 "The Node.js node-gyp-build module: https://github.com/prebuild/node-gyp-build"
1932 ),
1933 WellKnownFunctionKind::NodeBindings => (
1934 "bindings".to_string(),
1935 "The Node.js bindings module: https://github.com/TooTallNate/node-bindings"
1936 ),
1937 WellKnownFunctionKind::NodeExpress => (
1938 "express".to_string(),
1939 "require('express')() : https://github.com/expressjs/express"
1940 ),
1941 WellKnownFunctionKind::NodeExpressSet => (
1942 "set".to_string(),
1943 "require('express')().set('view engine', 'jade') https://github.com/expressjs/express"
1944 ),
1945 WellKnownFunctionKind::NodeStrongGlobalize => (
1946 "SetRootDir".to_string(),
1947 "require('strong-globalize')() https://github.com/strongloop/strong-globalize"
1948 ),
1949 WellKnownFunctionKind::NodeStrongGlobalizeSetRootDir => (
1950 "SetRootDir".to_string(),
1951 "require('strong-globalize').SetRootDir(__dirname) https://github.com/strongloop/strong-globalize"
1952 ),
1953 WellKnownFunctionKind::NodeResolveFrom => (
1954 "resolveFrom".to_string(),
1955 "require('resolve-from')(__dirname, 'node-gyp/bin/node-gyp') https://github.com/sindresorhus/resolve-from"
1956 ),
1957 WellKnownFunctionKind::NodeProtobufLoad => (
1958 "load/loadSync".to_string(),
1959 "require('@grpc/proto-loader').load(filepath, { includeDirs: [root] }) https://github.com/grpc/grpc-node"
1960 ),
1961 WellKnownFunctionKind::NodeWorkerConstructor => (
1962 "Worker".to_string(),
1963 "The Node.js worker_threads Worker constructor: https://nodejs.org/api/worker_threads.html#worker_threads_class_worker"
1964 ),
1965 WellKnownFunctionKind::WorkerConstructor => (
1966 "Worker".to_string(),
1967 "The standard Worker constructor: https://developer.mozilla.org/en-US/docs/Web/API/Worker/Worker"
1968 ),
1969 WellKnownFunctionKind::URLConstructor => (
1970 "URL".to_string(),
1971 "The standard URL constructor: https://developer.mozilla.org/en-US/docs/Web/API/URL/URL"
1972 ),
1973 };
1974 if depth > 0 {
1975 let i = hints.len();
1976 hints.push(format!("- *{i}* {name}: {explainer}"));
1977 format!("{name}*{i}*")
1978 } else {
1979 name
1980 }
1981 }
1982 JsValue::Function(_, _, return_value) => {
1983 if depth > 0 {
1984 format!(
1985 "(...) => {}",
1986 return_value.explain_internal(
1987 hints,
1988 indent_depth,
1989 depth - 1,
1990 unknown_depth
1991 )
1992 )
1993 } else {
1994 "(...) => ...".to_string()
1995 }
1996 }
1997 }
1998 }
1999}
2000
2001impl JsValue {
2003 pub fn make_unknown(&mut self, side_effects: bool, reason: impl Into<Cow<'static, str>>) {
2005 *self = JsValue::unknown(take(self), side_effects || self.has_side_effects(), reason);
2006 }
2007
2008 pub fn into_unknown(
2010 mut self,
2011 side_effects: bool,
2012 reason: impl Into<Cow<'static, str>>,
2013 ) -> Self {
2014 self.make_unknown(side_effects, reason);
2015 self
2016 }
2017
2018 pub fn make_unknown_without_content(
2021 &mut self,
2022 side_effects: bool,
2023 reason: impl Into<Cow<'static, str>>,
2024 ) {
2025 *self = JsValue::unknown_empty(side_effects || self.has_side_effects(), reason);
2026 }
2027
2028 pub fn make_nested_operations_unknown(&mut self) -> bool {
2030 fn inner(this: &mut JsValue) -> bool {
2031 if matches!(this.meta_type(), JsValueMetaKind::Operation) {
2032 this.make_unknown(false, "nested operation");
2033 true
2034 } else {
2035 this.for_each_children_mut(&mut inner)
2036 }
2037 }
2038 if matches!(self.meta_type(), JsValueMetaKind::Operation) {
2039 self.for_each_children_mut(&mut inner)
2040 } else {
2041 false
2042 }
2043 }
2044
2045 pub fn add_unknown_mutations(&mut self, side_effects: bool) {
2046 self.add_alt(JsValue::unknown_empty(side_effects, "unknown mutation"));
2047 }
2048}
2049
2050impl JsValue {
2052 pub fn get_definable_name_len(&self) -> Option<usize> {
2061 match self {
2062 JsValue::FreeVar(_) => Some(1),
2063 JsValue::Member(_, obj, prop) if prop.as_str().is_some() => {
2064 Some(obj.get_definable_name_len()? + 1)
2065 }
2066 JsValue::WellKnownObject(obj) => obj.as_define_name().map(|d| d.len()),
2067 JsValue::WellKnownFunction(func) => func.as_define_name().map(|d| d.len()),
2068 JsValue::MemberCall(_, callee, prop, args)
2069 if args.is_empty() && prop.as_str().is_some() =>
2070 {
2071 Some(callee.get_definable_name_len()? + 1)
2072 }
2073 JsValue::TypeOf(_, arg) => Some(arg.get_definable_name_len()? + 1),
2074
2075 _ => None,
2076 }
2077 }
2078
2079 pub fn iter_definable_name_rev(&self) -> DefinableNameIter<'_> {
2086 DefinableNameIter {
2087 next: Some(self),
2088 index: 0,
2089 }
2090 }
2091
2092 pub fn match_free_var_reference<'a, T>(
2098 &self,
2099 var_graph: &VarGraph,
2100 free_var_references: &'a FxIndexMap<
2101 DefinableNameSegment,
2102 FxIndexMap<Vec<DefinableNameSegment>, T>,
2103 >,
2104 prop: &DefinableNameSegment,
2105 ) -> Option<&'a T> {
2106 if let Some(def_name_len) = self.get_definable_name_len()
2107 && let Some(references) = free_var_references.get(prop)
2108 {
2109 for (name, value) in references {
2110 if name.len() != def_name_len {
2111 continue;
2112 }
2113
2114 let name_rev_it = name.iter().map(Cow::Borrowed).rev();
2115 if name_rev_it.eq(self.iter_definable_name_rev()) {
2116 if let DefinableNameSegment::Name(first_str) = name.first().unwrap() {
2117 let first_str: &str = first_str;
2118 if var_graph
2119 .free_var_ids
2120 .get(&first_str.into())
2121 .is_some_and(|id| var_graph.values.contains_key(id))
2122 {
2123 return None;
2125 }
2126 }
2127
2128 return Some(value);
2129 }
2130 }
2131 }
2132
2133 None
2134 }
2135
2136 pub fn match_define<'a, T>(
2138 &self,
2139 defines: &'a FxIndexMap<Vec<DefinableNameSegment>, T>,
2140 ) -> Option<&'a T> {
2141 if let Some(def_name_len) = self.get_definable_name_len() {
2142 for (name, value) in defines.iter() {
2143 if name.len() != def_name_len {
2144 continue;
2145 }
2146
2147 if name
2148 .iter()
2149 .map(Cow::Borrowed)
2150 .rev()
2151 .eq(self.iter_definable_name_rev())
2152 {
2153 return Some(value);
2154 }
2155 }
2156 }
2157
2158 None
2159 }
2160}
2161
2162pub struct DefinableNameIter<'a> {
2163 next: Option<&'a JsValue>,
2164 index: usize,
2165}
2166
2167impl<'a> Iterator for DefinableNameIter<'a> {
2168 type Item = Cow<'a, DefinableNameSegment>;
2169
2170 fn next(&mut self) -> Option<Self::Item> {
2171 let value = self.next.take()?;
2172 Some(Cow::Owned(match value {
2173 JsValue::FreeVar(kind) => (&**kind).into(),
2174 JsValue::Member(_, obj, prop) => {
2175 self.next = Some(obj);
2176 prop.as_str()?.into()
2177 }
2178 JsValue::WellKnownObject(obj) => {
2179 let name = obj.as_define_name()?;
2180 let i = self.index;
2181 self.index += 1;
2182 if self.index < name.len() {
2183 self.next = Some(value);
2184 }
2185 name[name.len() - i - 1].into()
2186 }
2187 JsValue::WellKnownFunction(func) => {
2188 let name = func.as_define_name()?;
2189 let i = self.index;
2190 self.index += 1;
2191 if self.index < name.len() {
2192 self.next = Some(value);
2193 }
2194 name[name.len() - i - 1].into()
2195 }
2196 JsValue::MemberCall(_, callee, prop, args) if args.is_empty() => {
2197 self.next = Some(callee);
2198 format!("{}()", prop.as_str()?).into()
2199 }
2200 JsValue::TypeOf(_, arg) => {
2201 self.next = Some(arg);
2202 DefinableNameSegment::TypeOf
2203 }
2204
2205 _ => return None,
2206 }))
2207 }
2208}
2209
2210impl JsValue {
2212 pub fn as_str(&self) -> Option<&str> {
2214 match self {
2215 JsValue::Constant(c) => c.as_str(),
2216 _ => None,
2217 }
2218 }
2219
2220 pub fn as_bool(&self) -> Option<bool> {
2222 match self {
2223 JsValue::Constant(c) => c.as_bool(),
2224 _ => None,
2225 }
2226 }
2227
2228 pub fn has_side_effects(&self) -> bool {
2229 match self {
2230 JsValue::Constant(_) => false,
2231 JsValue::Concat(_, values)
2232 | JsValue::Add(_, values)
2233 | JsValue::Logical(_, _, values)
2234 | JsValue::Alternatives {
2235 total_nodes: _,
2236 values,
2237 logical_property: _,
2238 } => values.iter().any(JsValue::has_side_effects),
2239 JsValue::Binary(_, a, _, b) => a.has_side_effects() || b.has_side_effects(),
2240 JsValue::Tenary(_, test, cons, alt) => {
2241 test.has_side_effects() || cons.has_side_effects() || alt.has_side_effects()
2242 }
2243 JsValue::Not(_, value) => value.has_side_effects(),
2244 JsValue::Array { items, .. } => items.iter().any(JsValue::has_side_effects),
2245 JsValue::Object { parts, .. } => parts.iter().any(|v| match v {
2246 ObjectPart::KeyValue(k, v) => k.has_side_effects() || v.has_side_effects(),
2247 ObjectPart::Spread(v) => v.has_side_effects(),
2248 }),
2249 JsValue::New(_, _callee, _args) => true,
2255 JsValue::Call(_, _callee, _args) => true,
2256 JsValue::SuperCall(_, _args) => true,
2257 JsValue::MemberCall(_, _obj, _prop, _args) => true,
2258 JsValue::Member(_, obj, prop) => obj.has_side_effects() || prop.has_side_effects(),
2259 JsValue::Function(_, _, _) => false,
2260 JsValue::Url(_, _) => false,
2261 JsValue::Variable(_) => false,
2262 JsValue::Module(_) => false,
2263 JsValue::WellKnownObject(_) => false,
2264 JsValue::WellKnownFunction(_) => false,
2265 JsValue::FreeVar(_) => false,
2266 JsValue::Unknown {
2267 has_side_effects, ..
2268 } => *has_side_effects,
2269 JsValue::Argument(_, _) => false,
2270 JsValue::Iterated(_, iterable) => iterable.has_side_effects(),
2271 JsValue::TypeOf(_, operand) => operand.has_side_effects(),
2272 JsValue::Promise(_, operand) => operand.has_side_effects(),
2273 JsValue::Awaited(_, operand) => operand.has_side_effects(),
2274 }
2275 }
2276
2277 pub fn is_truthy(&self) -> Option<bool> {
2280 match self {
2281 JsValue::Constant(c) => Some(c.is_truthy()),
2282 JsValue::Concat(..) => self.is_empty_string().map(|x| !x),
2283 JsValue::Url(..)
2284 | JsValue::Array { .. }
2285 | JsValue::Object { .. }
2286 | JsValue::WellKnownObject(..)
2287 | JsValue::WellKnownFunction(..)
2288 | JsValue::Function(..) => Some(true),
2289 JsValue::Alternatives {
2290 total_nodes: _,
2291 values,
2292 logical_property,
2293 } => match logical_property {
2294 Some(LogicalProperty::Truthy) => Some(true),
2295 Some(LogicalProperty::Falsy) => Some(false),
2296 Some(LogicalProperty::Nullish) => Some(false),
2297 _ => merge_if_known(values, JsValue::is_truthy),
2298 },
2299 JsValue::Not(_, value) => value.is_truthy().map(|x| !x),
2300 JsValue::Logical(_, op, list) => match op {
2301 LogicalOperator::And => all_if_known(list, JsValue::is_truthy),
2302 LogicalOperator::Or => any_if_known(list, JsValue::is_truthy),
2303 LogicalOperator::NullishCoalescing => {
2304 shortcircuit_if_known(list, JsValue::is_not_nullish, JsValue::is_truthy)
2305 }
2306 },
2307 JsValue::Binary(_, box a, op, box b) => {
2308 let (positive_op, negate) = op.positive_op();
2309 match (positive_op, a, b) {
2310 (
2311 PositiveBinaryOperator::StrictEqual,
2312 JsValue::Constant(a),
2313 JsValue::Constant(b),
2314 ) if a.is_value_type() => Some(a == b),
2315 (
2316 PositiveBinaryOperator::StrictEqual,
2317 JsValue::Constant(a),
2318 JsValue::Constant(b),
2319 ) if a.is_value_type() => {
2320 let same_type = {
2321 use ConstantValue::*;
2322 matches!(
2323 (a, b),
2324 (Num(_), Num(_))
2325 | (Str(_), Str(_))
2326 | (BigInt(_), BigInt(_))
2327 | (True | False, True | False)
2328 | (Undefined, Undefined)
2329 | (Null, Null)
2330 )
2331 };
2332 if same_type { Some(a == b) } else { None }
2333 }
2334 (
2335 PositiveBinaryOperator::Equal,
2336 JsValue::Constant(ConstantValue::Str(a)),
2337 JsValue::Constant(ConstantValue::Str(b)),
2338 ) => Some(a == b),
2339 (
2340 PositiveBinaryOperator::Equal,
2341 JsValue::Constant(ConstantValue::Num(a)),
2342 JsValue::Constant(ConstantValue::Num(b)),
2343 ) => Some(a == b),
2344 _ => None,
2345 }
2346 .map(|x| x ^ negate)
2347 }
2348 _ => None,
2349 }
2350 }
2351
2352 pub fn is_falsy(&self) -> Option<bool> {
2355 self.is_truthy().map(|x| !x)
2356 }
2357
2358 pub fn is_nullish(&self) -> Option<bool> {
2361 match self {
2362 JsValue::Constant(c) => Some(c.is_nullish()),
2363 JsValue::Concat(..)
2364 | JsValue::Url(..)
2365 | JsValue::Array { .. }
2366 | JsValue::Object { .. }
2367 | JsValue::WellKnownObject(..)
2368 | JsValue::WellKnownFunction(..)
2369 | JsValue::Not(..)
2370 | JsValue::Binary(..)
2371 | JsValue::Function(..) => Some(false),
2372 JsValue::Alternatives {
2373 total_nodes: _,
2374 values,
2375 logical_property,
2376 } => match logical_property {
2377 Some(LogicalProperty::Nullish) => Some(true),
2378 _ => merge_if_known(values, JsValue::is_nullish),
2379 },
2380 JsValue::Logical(_, op, list) => match op {
2381 LogicalOperator::And => {
2382 shortcircuit_if_known(list, JsValue::is_truthy, JsValue::is_nullish)
2383 }
2384 LogicalOperator::Or => {
2385 shortcircuit_if_known(list, JsValue::is_falsy, JsValue::is_nullish)
2386 }
2387 LogicalOperator::NullishCoalescing => all_if_known(list, JsValue::is_nullish),
2388 },
2389 _ => None,
2390 }
2391 }
2392
2393 pub fn is_not_nullish(&self) -> Option<bool> {
2397 self.is_nullish().map(|x| !x)
2398 }
2399
2400 pub fn is_empty_string(&self) -> Option<bool> {
2404 match self {
2405 JsValue::Constant(c) => Some(c.is_empty_string()),
2406 JsValue::Concat(_, list) => all_if_known(list, JsValue::is_empty_string),
2407 JsValue::Alternatives {
2408 total_nodes: _,
2409 values,
2410 logical_property: _,
2411 } => merge_if_known(values, JsValue::is_empty_string),
2412 JsValue::Logical(_, op, list) => match op {
2413 LogicalOperator::And => {
2414 shortcircuit_if_known(list, JsValue::is_truthy, JsValue::is_empty_string)
2415 }
2416 LogicalOperator::Or => {
2417 shortcircuit_if_known(list, JsValue::is_falsy, JsValue::is_empty_string)
2418 }
2419 LogicalOperator::NullishCoalescing => {
2420 shortcircuit_if_known(list, JsValue::is_not_nullish, JsValue::is_empty_string)
2421 }
2422 },
2423 JsValue::Not(..) | JsValue::Binary(..) => Some(false),
2425 JsValue::Url(..)
2427 | JsValue::Array { .. }
2428 | JsValue::Object { .. }
2429 | JsValue::WellKnownObject(..)
2430 | JsValue::WellKnownFunction(..)
2431 | JsValue::Function(..) => Some(false),
2432 _ => None,
2433 }
2434 }
2435
2436 pub fn is_unknown(&self) -> bool {
2439 match self {
2440 JsValue::Unknown { .. } => true,
2441 JsValue::Alternatives {
2442 total_nodes: _,
2443 values,
2444 logical_property: _,
2445 } => values.iter().any(|x| x.is_unknown()),
2446 _ => false,
2447 }
2448 }
2449
2450 pub fn is_string(&self) -> Option<bool> {
2453 match self {
2454 JsValue::Constant(ConstantValue::Str(..))
2455 | JsValue::Concat(..)
2456 | JsValue::TypeOf(..) => Some(true),
2457
2458 JsValue::Constant(..)
2460 | JsValue::Array { .. }
2461 | JsValue::Object { .. }
2462 | JsValue::Url(..)
2463 | JsValue::Module(..)
2464 | JsValue::Function(..)
2465 | JsValue::WellKnownObject(_)
2466 | JsValue::WellKnownFunction(_)
2467 | JsValue::Promise(_, _) => Some(false),
2468
2469 JsValue::Not(..) | JsValue::Binary(..) => Some(false),
2471
2472 JsValue::Add(_, list) => any_if_known(list, JsValue::is_string),
2473 JsValue::Logical(_, op, list) => match op {
2474 LogicalOperator::And => {
2475 shortcircuit_if_known(list, JsValue::is_truthy, JsValue::is_string)
2476 }
2477 LogicalOperator::Or => {
2478 shortcircuit_if_known(list, JsValue::is_falsy, JsValue::is_string)
2479 }
2480 LogicalOperator::NullishCoalescing => {
2481 shortcircuit_if_known(list, JsValue::is_not_nullish, JsValue::is_string)
2482 }
2483 },
2484
2485 JsValue::Alternatives {
2486 total_nodes: _,
2487 values,
2488 logical_property: _,
2489 } => merge_if_known(values, JsValue::is_string),
2490
2491 JsValue::Call(
2492 _,
2493 box JsValue::WellKnownFunction(
2494 WellKnownFunctionKind::RequireResolve
2495 | WellKnownFunctionKind::PathJoin
2496 | WellKnownFunctionKind::PathResolve(..)
2497 | WellKnownFunctionKind::OsArch
2498 | WellKnownFunctionKind::OsPlatform
2499 | WellKnownFunctionKind::PathDirname
2500 | WellKnownFunctionKind::PathToFileUrl
2501 | WellKnownFunctionKind::ProcessCwd,
2502 ),
2503 _,
2504 ) => Some(true),
2505
2506 JsValue::Awaited(_, operand) => match &**operand {
2507 JsValue::Promise(_, v) => v.is_string(),
2508 v => v.is_string(),
2509 },
2510
2511 JsValue::FreeVar(..)
2512 | JsValue::Variable(_)
2513 | JsValue::Unknown { .. }
2514 | JsValue::Argument(..)
2515 | JsValue::New(..)
2516 | JsValue::Call(..)
2517 | JsValue::MemberCall(..)
2518 | JsValue::Member(..)
2519 | JsValue::Tenary(..)
2520 | JsValue::SuperCall(..)
2521 | JsValue::Iterated(..) => None,
2522 }
2523 }
2524
2525 pub fn starts_with(&self, str: &str) -> Option<bool> {
2529 if let Some(s) = self.as_str() {
2530 return Some(s.starts_with(str));
2531 }
2532 match self {
2533 JsValue::Alternatives {
2534 total_nodes: _,
2535 values,
2536 logical_property: _,
2537 } => merge_if_known(values, |a| a.starts_with(str)),
2538 JsValue::Concat(_, list) => {
2539 if let Some(item) = list.iter().next() {
2540 if item.starts_with(str) == Some(true) {
2541 Some(true)
2542 } else if let Some(s) = item.as_str() {
2543 if str.starts_with(s) {
2544 None
2545 } else {
2546 Some(false)
2547 }
2548 } else {
2549 None
2550 }
2551 } else {
2552 Some(false)
2553 }
2554 }
2555
2556 _ => None,
2557 }
2558 }
2559
2560 pub fn ends_with(&self, str: &str) -> Option<bool> {
2564 if let Some(s) = self.as_str() {
2565 return Some(s.ends_with(str));
2566 }
2567 match self {
2568 JsValue::Alternatives {
2569 total_nodes: _,
2570 values,
2571 logical_property: _,
2572 } => merge_if_known(values, |alt| alt.ends_with(str)),
2573 JsValue::Concat(_, list) => {
2574 if let Some(item) = list.last() {
2575 if item.ends_with(str) == Some(true) {
2576 Some(true)
2577 } else if let Some(s) = item.as_str() {
2578 if str.ends_with(s) { None } else { Some(false) }
2579 } else {
2580 None
2581 }
2582 } else {
2583 Some(false)
2584 }
2585 }
2586
2587 _ => None,
2588 }
2589 }
2590}
2591
2592fn merge_if_known<T: Copy>(
2595 list: impl IntoIterator<Item = T>,
2596 func: impl Fn(T) -> Option<bool>,
2597) -> Option<bool> {
2598 let mut current = None;
2599 for item in list.into_iter().map(func) {
2600 if item.is_some() {
2601 if current.is_none() {
2602 current = item;
2603 } else if current != item {
2604 return None;
2605 }
2606 } else {
2607 return None;
2608 }
2609 }
2610 current
2611}
2612
2613fn all_if_known<T: Copy>(
2617 list: impl IntoIterator<Item = T>,
2618 func: impl Fn(T) -> Option<bool>,
2619) -> Option<bool> {
2620 let mut unknown = false;
2621 for item in list.into_iter().map(func) {
2622 match item {
2623 Some(false) => return Some(false),
2624 None => unknown = true,
2625 _ => {}
2626 }
2627 }
2628 if unknown { None } else { Some(true) }
2629}
2630
2631fn any_if_known<T: Copy>(
2635 list: impl IntoIterator<Item = T>,
2636 func: impl Fn(T) -> Option<bool>,
2637) -> Option<bool> {
2638 all_if_known(list, |x| func(x).map(|x| !x)).map(|x| !x)
2639}
2640
2641fn shortcircuit_if_known<T: Copy>(
2644 list: impl IntoIterator<Item = T>,
2645 use_item: impl Fn(T) -> Option<bool>,
2646 item_value: impl FnOnce(T) -> Option<bool>,
2647) -> Option<bool> {
2648 let mut it = list.into_iter().peekable();
2649 while let Some(item) = it.next() {
2650 if it.peek().is_none() {
2651 return item_value(item);
2652 } else {
2653 match use_item(item) {
2654 Some(true) => return item_value(item),
2655 None => return None,
2656 _ => {}
2657 }
2658 }
2659 }
2660 None
2661}
2662
2663impl JsValue {
2665 pub fn for_each_children_mut(
2668 &mut self,
2669 visitor: &mut impl FnMut(&mut JsValue) -> bool,
2670 ) -> bool {
2671 match self {
2672 JsValue::Alternatives {
2673 total_nodes: _,
2674 values: list,
2675 logical_property: _,
2676 }
2677 | JsValue::Concat(_, list)
2678 | JsValue::Add(_, list)
2679 | JsValue::Logical(_, _, list)
2680 | JsValue::Array { items: list, .. } => {
2681 let mut modified = false;
2682 for item in list.iter_mut() {
2683 if visitor(item) {
2684 modified = true
2685 }
2686 }
2687 if modified {
2688 self.update_total_nodes();
2689 }
2690 modified
2691 }
2692 JsValue::Not(_, value) => {
2693 let modified = visitor(value);
2694 if modified {
2695 self.update_total_nodes();
2696 }
2697 modified
2698 }
2699 JsValue::Object { parts, .. } => {
2700 let mut modified = false;
2701 for item in parts.iter_mut() {
2702 match item {
2703 ObjectPart::KeyValue(key, value) => {
2704 if visitor(key) {
2705 modified = true
2706 }
2707 if visitor(value) {
2708 modified = true
2709 }
2710 }
2711 ObjectPart::Spread(value) => {
2712 if visitor(value) {
2713 modified = true
2714 }
2715 }
2716 }
2717 }
2718 if modified {
2719 self.update_total_nodes();
2720 }
2721 modified
2722 }
2723 JsValue::New(_, callee, list) => {
2724 let mut modified = visitor(callee);
2725 for item in list.iter_mut() {
2726 if visitor(item) {
2727 modified = true
2728 }
2729 }
2730 if modified {
2731 self.update_total_nodes();
2732 }
2733 modified
2734 }
2735 JsValue::Call(_, callee, list) => {
2736 let mut modified = visitor(callee);
2737 for item in list.iter_mut() {
2738 if visitor(item) {
2739 modified = true
2740 }
2741 }
2742 if modified {
2743 self.update_total_nodes();
2744 }
2745 modified
2746 }
2747 JsValue::SuperCall(_, list) => {
2748 let mut modified = false;
2749 for item in list.iter_mut() {
2750 if visitor(item) {
2751 modified = true
2752 }
2753 }
2754 if modified {
2755 self.update_total_nodes();
2756 }
2757 modified
2758 }
2759 JsValue::MemberCall(_, obj, prop, list) => {
2760 let m1 = visitor(obj);
2761 let m2 = visitor(prop);
2762 let mut modified = m1 || m2;
2763 for item in list.iter_mut() {
2764 if visitor(item) {
2765 modified = true
2766 }
2767 }
2768 if modified {
2769 self.update_total_nodes();
2770 }
2771 modified
2772 }
2773 JsValue::Function(_, _, return_value) => {
2774 let modified = visitor(return_value);
2775
2776 if modified {
2777 self.update_total_nodes();
2778 }
2779 modified
2780 }
2781 JsValue::Binary(_, a, _, b) => {
2782 let m1 = visitor(a);
2783 let m2 = visitor(b);
2784 let modified = m1 || m2;
2785 if modified {
2786 self.update_total_nodes();
2787 }
2788 modified
2789 }
2790 JsValue::Tenary(_, test, cons, alt) => {
2791 let m1 = visitor(test);
2792 let m2 = visitor(cons);
2793 let m3 = visitor(alt);
2794 let modified = m1 || m2 || m3;
2795 if modified {
2796 self.update_total_nodes();
2797 }
2798 modified
2799 }
2800 JsValue::Member(_, obj, prop) => {
2801 let m1 = visitor(obj);
2802 let m2 = visitor(prop);
2803 let modified = m1 || m2;
2804 if modified {
2805 self.update_total_nodes();
2806 }
2807 modified
2808 }
2809
2810 JsValue::Iterated(_, operand)
2811 | JsValue::TypeOf(_, operand)
2812 | JsValue::Promise(_, operand)
2813 | JsValue::Awaited(_, operand) => {
2814 let modified = visitor(operand);
2815 if modified {
2816 self.update_total_nodes();
2817 }
2818 modified
2819 }
2820
2821 JsValue::Constant(_)
2822 | JsValue::FreeVar(_)
2823 | JsValue::Variable(_)
2824 | JsValue::Module(..)
2825 | JsValue::Url(_, _)
2826 | JsValue::WellKnownObject(_)
2827 | JsValue::WellKnownFunction(_)
2828 | JsValue::Unknown { .. }
2829 | JsValue::Argument(..) => false,
2830 }
2831 }
2832
2833 pub fn for_each_early_children_mut(
2836 &mut self,
2837 visitor: &mut impl FnMut(&mut JsValue) -> bool,
2838 ) -> bool {
2839 match self {
2840 JsValue::New(_, callee, list) if !list.is_empty() => {
2841 let m = visitor(callee);
2842 if m {
2843 self.update_total_nodes();
2844 }
2845 m
2846 }
2847 JsValue::Call(_, callee, list) if !list.is_empty() => {
2848 let m = visitor(callee);
2849 if m {
2850 self.update_total_nodes();
2851 }
2852 m
2853 }
2854 JsValue::MemberCall(_, obj, prop, list) if !list.is_empty() => {
2855 let m1 = visitor(obj);
2856 let m2 = visitor(prop);
2857 let modified = m1 || m2;
2858 if modified {
2859 self.update_total_nodes();
2860 }
2861 modified
2862 }
2863 JsValue::Member(_, obj, _) => {
2864 let m = visitor(obj);
2865 if m {
2866 self.update_total_nodes();
2867 }
2868 m
2869 }
2870 _ => false,
2871 }
2872 }
2873
2874 pub fn for_each_late_children_mut(
2877 &mut self,
2878 visitor: &mut impl FnMut(&mut JsValue) -> bool,
2879 ) -> bool {
2880 match self {
2881 JsValue::New(_, _, list) if !list.is_empty() => {
2882 let mut modified = false;
2883 for item in list.iter_mut() {
2884 if visitor(item) {
2885 modified = true
2886 }
2887 }
2888 if modified {
2889 self.update_total_nodes();
2890 }
2891 modified
2892 }
2893 JsValue::Call(_, _, list) if !list.is_empty() => {
2894 let mut modified = false;
2895 for item in list.iter_mut() {
2896 if visitor(item) {
2897 modified = true
2898 }
2899 }
2900 if modified {
2901 self.update_total_nodes();
2902 }
2903 modified
2904 }
2905 JsValue::MemberCall(_, _, _, list) if !list.is_empty() => {
2906 let mut modified = false;
2907 for item in list.iter_mut() {
2908 if visitor(item) {
2909 modified = true
2910 }
2911 }
2912 if modified {
2913 self.update_total_nodes();
2914 }
2915 modified
2916 }
2917 JsValue::Member(_, _, prop) => {
2918 let m = visitor(prop);
2919 if m {
2920 self.update_total_nodes();
2921 }
2922 m
2923 }
2924 _ => self.for_each_children_mut(visitor),
2925 }
2926 }
2927
2928 pub fn visit(&self, visitor: &mut impl FnMut(&JsValue)) {
2930 self.for_each_children(&mut |value| value.visit(visitor));
2931 visitor(self);
2932 }
2933
2934 pub fn for_each_children(&self, visitor: &mut impl FnMut(&JsValue)) {
2936 match self {
2937 JsValue::Alternatives {
2938 total_nodes: _,
2939 values: list,
2940 logical_property: _,
2941 }
2942 | JsValue::Concat(_, list)
2943 | JsValue::Add(_, list)
2944 | JsValue::Logical(_, _, list)
2945 | JsValue::Array { items: list, .. } => {
2946 for item in list.iter() {
2947 visitor(item);
2948 }
2949 }
2950 JsValue::Not(_, value) => {
2951 visitor(value);
2952 }
2953 JsValue::Object { parts, .. } => {
2954 for item in parts.iter() {
2955 match item {
2956 ObjectPart::KeyValue(key, value) => {
2957 visitor(key);
2958 visitor(value);
2959 }
2960 ObjectPart::Spread(value) => {
2961 visitor(value);
2962 }
2963 }
2964 }
2965 }
2966 JsValue::New(_, callee, list) => {
2967 visitor(callee);
2968 for item in list.iter() {
2969 visitor(item);
2970 }
2971 }
2972 JsValue::Call(_, callee, list) => {
2973 visitor(callee);
2974 for item in list.iter() {
2975 visitor(item);
2976 }
2977 }
2978 JsValue::SuperCall(_, list) => {
2979 for item in list.iter() {
2980 visitor(item);
2981 }
2982 }
2983 JsValue::MemberCall(_, obj, prop, list) => {
2984 visitor(obj);
2985 visitor(prop);
2986 for item in list.iter() {
2987 visitor(item);
2988 }
2989 }
2990 JsValue::Function(_, _, return_value) => {
2991 visitor(return_value);
2992 }
2993 JsValue::Member(_, obj, prop) => {
2994 visitor(obj);
2995 visitor(prop);
2996 }
2997 JsValue::Binary(_, a, _, b) => {
2998 visitor(a);
2999 visitor(b);
3000 }
3001 JsValue::Tenary(_, test, cons, alt) => {
3002 visitor(test);
3003 visitor(cons);
3004 visitor(alt);
3005 }
3006
3007 JsValue::Iterated(_, operand)
3008 | JsValue::TypeOf(_, operand)
3009 | JsValue::Promise(_, operand)
3010 | JsValue::Awaited(_, operand) => {
3011 visitor(operand);
3012 }
3013
3014 JsValue::Constant(_)
3015 | JsValue::FreeVar(_)
3016 | JsValue::Variable(_)
3017 | JsValue::Module(..)
3018 | JsValue::Url(_, _)
3019 | JsValue::WellKnownObject(_)
3020 | JsValue::WellKnownFunction(_)
3021 | JsValue::Unknown { .. }
3022 | JsValue::Argument(..) => {}
3023 }
3024 }
3025}
3026
3027impl JsValue {
3029 fn add_alt(&mut self, v: Self) {
3033 if self == &v {
3034 return;
3035 }
3036
3037 if let JsValue::Alternatives {
3038 total_nodes: c,
3039 values,
3040 logical_property: _,
3041 } = self
3042 {
3043 if !values.contains(&v) {
3044 *c += v.total_nodes();
3045 values.push(v);
3046 }
3047 } else {
3048 let l = take(self);
3049 *self = JsValue::Alternatives {
3050 total_nodes: 1 + l.total_nodes() + v.total_nodes(),
3051 values: vec![l, v],
3052 logical_property: None,
3053 };
3054 }
3055 }
3056}
3057
3058impl JsValue {
3060 pub fn normalize_shallow(&mut self) {
3063 match self {
3064 JsValue::Alternatives {
3065 total_nodes: _,
3066 values,
3067 logical_property: _,
3068 } => {
3069 if values.len() == 1 {
3070 *self = take(&mut values[0]);
3071 } else {
3072 let mut set = FxIndexSet::with_capacity_and_hasher(
3073 values.len(),
3074 BuildHasherDefault::<FxHasher>::default(),
3075 );
3076 for v in take(values) {
3077 match v {
3078 JsValue::Alternatives {
3079 total_nodes: _,
3080 values,
3081 logical_property: _,
3082 } => {
3083 for v in values {
3084 set.insert(SimilarJsValue(v));
3085 }
3086 }
3087 v => {
3088 set.insert(SimilarJsValue(v));
3089 }
3090 }
3091 }
3092 if set.len() == 1 {
3093 *self = set.into_iter().next().unwrap().0;
3094 } else {
3095 *values = set.into_iter().map(|v| v.0).collect();
3096 self.update_total_nodes();
3097 }
3098 }
3099 }
3100 JsValue::Concat(_, v) => {
3101 v.retain(|v| v.as_str() != Some(""));
3103
3104 let mut new: Vec<JsValue> = vec![];
3106 for v in take(v) {
3107 if let Some(str) = v.as_str() {
3108 if let Some(last) = new.last_mut() {
3109 if let Some(last_str) = last.as_str() {
3110 *last = [last_str, str].concat().into();
3111 } else {
3112 new.push(v);
3113 }
3114 } else {
3115 new.push(v);
3116 }
3117 } else if let JsValue::Concat(_, v) = v {
3118 new.extend(v);
3119 } else {
3120 new.push(v);
3121 }
3122 }
3123 if new.len() == 1 {
3124 *self = new.into_iter().next().unwrap();
3125 } else {
3126 *v = new;
3127 self.update_total_nodes();
3128 }
3129 }
3130 JsValue::Add(_, v) => {
3131 let mut added: Vec<JsValue> = Vec::new();
3132 let mut iter = take(v).into_iter();
3133 while let Some(item) = iter.next() {
3134 if item.is_string() == Some(true) {
3135 let mut concat = match added.len() {
3136 0 => Vec::new(),
3137 1 => vec![added.into_iter().next().unwrap()],
3138 _ => vec![JsValue::Add(
3139 1 + added.iter().map(|v| v.total_nodes()).sum::<u32>(),
3140 added,
3141 )],
3142 };
3143 concat.push(item);
3144 for item in iter.by_ref() {
3145 concat.push(item);
3146 }
3147 *self = JsValue::Concat(
3148 1 + concat.iter().map(|v| v.total_nodes()).sum::<u32>(),
3149 concat,
3150 );
3151 return;
3152 } else {
3153 added.push(item);
3154 }
3155 }
3156 if added.len() == 1 {
3157 *self = added.into_iter().next().unwrap();
3158 } else {
3159 *v = added;
3160 self.update_total_nodes();
3161 }
3162 }
3163 JsValue::Logical(_, op, list) => {
3164 if list.iter().any(|v| {
3167 if let JsValue::Logical(_, inner_op, _) = v {
3168 inner_op == op
3169 } else {
3170 false
3171 }
3172 }) {
3173 for mut v in take(list).into_iter() {
3175 if let JsValue::Logical(_, inner_op, inner_list) = &mut v {
3176 if inner_op == op {
3177 list.append(inner_list);
3178 } else {
3179 list.push(v);
3180 }
3181 } else {
3182 list.push(v);
3183 }
3184 }
3185 self.update_total_nodes();
3186 }
3187 }
3188 _ => {}
3189 }
3190 }
3191
3192 pub fn normalize(&mut self) {
3194 self.for_each_children_mut(&mut |child| {
3195 child.normalize();
3196 true
3197 });
3198 self.normalize_shallow();
3199 }
3200}
3201
3202impl JsValue {
3205 fn similar(&self, other: &JsValue, depth: usize) -> bool {
3208 if depth == 0 {
3209 return false;
3210 }
3211 fn all_similar(a: &[JsValue], b: &[JsValue], depth: usize) -> bool {
3212 if a.len() != b.len() {
3213 return false;
3214 }
3215 a.iter().zip(b.iter()).all(|(a, b)| a.similar(b, depth))
3216 }
3217 fn all_parts_similar(a: &[ObjectPart], b: &[ObjectPart], depth: usize) -> bool {
3218 if a.len() != b.len() {
3219 return false;
3220 }
3221 a.iter().zip(b.iter()).all(|(a, b)| match (a, b) {
3222 (ObjectPart::KeyValue(lk, lv), ObjectPart::KeyValue(rk, rv)) => {
3223 lk.similar(rk, depth) && lv.similar(rv, depth)
3224 }
3225 (ObjectPart::Spread(l), ObjectPart::Spread(r)) => l.similar(r, depth),
3226 _ => false,
3227 })
3228 }
3229 match (self, other) {
3230 (JsValue::Constant(l), JsValue::Constant(r)) => l == r,
3231 (
3232 JsValue::Array {
3233 total_nodes: lc,
3234 items: li,
3235 mutable: lm,
3236 },
3237 JsValue::Array {
3238 total_nodes: rc,
3239 items: ri,
3240 mutable: rm,
3241 },
3242 ) => lc == rc && lm == rm && all_similar(li, ri, depth - 1),
3243 (
3244 JsValue::Object {
3245 total_nodes: lc,
3246 parts: lp,
3247 mutable: lm,
3248 },
3249 JsValue::Object {
3250 total_nodes: rc,
3251 parts: rp,
3252 mutable: rm,
3253 },
3254 ) => lc == rc && lm == rm && all_parts_similar(lp, rp, depth - 1),
3255 (JsValue::Url(l, kl), JsValue::Url(r, kr)) => l == r && kl == kr,
3256 (
3257 JsValue::Alternatives {
3258 total_nodes: lc,
3259 values: l,
3260 logical_property: lp,
3261 },
3262 JsValue::Alternatives {
3263 total_nodes: rc,
3264 values: r,
3265 logical_property: rp,
3266 },
3267 ) => lc == rc && all_similar(l, r, depth - 1) && lp == rp,
3268 (JsValue::FreeVar(l), JsValue::FreeVar(r)) => l == r,
3269 (JsValue::Variable(l), JsValue::Variable(r)) => l == r,
3270 (JsValue::Concat(lc, l), JsValue::Concat(rc, r)) => {
3271 lc == rc && all_similar(l, r, depth - 1)
3272 }
3273 (JsValue::Add(lc, l), JsValue::Add(rc, r)) => lc == rc && all_similar(l, r, depth - 1),
3274 (JsValue::Logical(lc, lo, l), JsValue::Logical(rc, ro, r)) => {
3275 lc == rc && lo == ro && all_similar(l, r, depth - 1)
3276 }
3277 (JsValue::Not(lc, l), JsValue::Not(rc, r)) => lc == rc && l.similar(r, depth - 1),
3278 (JsValue::New(lc, lf, la), JsValue::New(rc, rf, ra)) => {
3279 lc == rc && lf.similar(rf, depth - 1) && all_similar(la, ra, depth - 1)
3280 }
3281 (JsValue::Call(lc, lf, la), JsValue::Call(rc, rf, ra)) => {
3282 lc == rc && lf.similar(rf, depth - 1) && all_similar(la, ra, depth - 1)
3283 }
3284 (JsValue::MemberCall(lc, lo, lp, la), JsValue::MemberCall(rc, ro, rp, ra)) => {
3285 lc == rc
3286 && lo.similar(ro, depth - 1)
3287 && lp.similar(rp, depth - 1)
3288 && all_similar(la, ra, depth - 1)
3289 }
3290 (JsValue::Member(lc, lo, lp), JsValue::Member(rc, ro, rp)) => {
3291 lc == rc && lo.similar(ro, depth - 1) && lp.similar(rp, depth - 1)
3292 }
3293 (JsValue::Binary(lc, la, lo, lb), JsValue::Binary(rc, ra, ro, rb)) => {
3294 lc == rc && lo == ro && la.similar(ra, depth - 1) && lb.similar(rb, depth - 1)
3295 }
3296 (
3297 JsValue::Module(ModuleValue {
3298 module: l,
3299 annotations: la,
3300 }),
3301 JsValue::Module(ModuleValue {
3302 module: r,
3303 annotations: ra,
3304 }),
3305 ) => l == r && la == ra,
3306 (JsValue::WellKnownObject(l), JsValue::WellKnownObject(r)) => l == r,
3307 (JsValue::WellKnownFunction(l), JsValue::WellKnownFunction(r)) => l == r,
3308 (
3309 JsValue::Unknown {
3310 original_value: _,
3311 reason: l,
3312 has_side_effects: ls,
3313 },
3314 JsValue::Unknown {
3315 original_value: _,
3316 reason: r,
3317 has_side_effects: rs,
3318 },
3319 ) => l == r && ls == rs,
3320 (JsValue::Function(lc, _, l), JsValue::Function(rc, _, r)) => {
3321 lc == rc && l.similar(r, depth - 1)
3322 }
3323 (JsValue::Argument(li, l), JsValue::Argument(ri, r)) => li == ri && l == r,
3324 _ => false,
3325 }
3326 }
3327
3328 fn similar_hash<H: std::hash::Hasher>(&self, state: &mut H, depth: usize) {
3330 if depth == 0 {
3331 self.total_nodes().hash(state);
3332 return;
3333 }
3334
3335 fn all_similar_hash<H: std::hash::Hasher>(slice: &[JsValue], state: &mut H, depth: usize) {
3336 for item in slice {
3337 item.similar_hash(state, depth);
3338 }
3339 }
3340
3341 fn all_parts_similar_hash<H: std::hash::Hasher>(
3342 slice: &[ObjectPart],
3343 state: &mut H,
3344 depth: usize,
3345 ) {
3346 for item in slice {
3347 match item {
3348 ObjectPart::KeyValue(key, value) => {
3349 key.similar_hash(state, depth);
3350 value.similar_hash(state, depth);
3351 }
3352 ObjectPart::Spread(value) => {
3353 value.similar_hash(state, depth);
3354 }
3355 }
3356 }
3357 }
3358
3359 match self {
3360 JsValue::Constant(v) => Hash::hash(v, state),
3361 JsValue::Object { parts, .. } => all_parts_similar_hash(parts, state, depth - 1),
3362 JsValue::Url(v, kind) => {
3363 Hash::hash(v, state);
3364 Hash::hash(kind, state);
3365 }
3366 JsValue::FreeVar(v) => Hash::hash(v, state),
3367 JsValue::Variable(v) => Hash::hash(v, state),
3368 JsValue::Array { items: v, .. }
3369 | JsValue::Alternatives {
3370 total_nodes: _,
3371 values: v,
3372 logical_property: _,
3373 }
3374 | JsValue::Concat(_, v)
3375 | JsValue::Add(_, v)
3376 | JsValue::Logical(_, _, v) => all_similar_hash(v, state, depth - 1),
3377 JsValue::Not(_, v) => v.similar_hash(state, depth - 1),
3378 JsValue::New(_, a, b) => {
3379 a.similar_hash(state, depth - 1);
3380 all_similar_hash(b, state, depth - 1);
3381 }
3382 JsValue::Call(_, a, b) => {
3383 a.similar_hash(state, depth - 1);
3384 all_similar_hash(b, state, depth - 1);
3385 }
3386 JsValue::SuperCall(_, a) => {
3387 all_similar_hash(a, state, depth - 1);
3388 }
3389 JsValue::MemberCall(_, a, b, c) => {
3390 a.similar_hash(state, depth - 1);
3391 b.similar_hash(state, depth - 1);
3392 all_similar_hash(c, state, depth - 1);
3393 }
3394 JsValue::Member(_, o, p) => {
3395 o.similar_hash(state, depth - 1);
3396 p.similar_hash(state, depth - 1);
3397 }
3398 JsValue::Binary(_, a, o, b) => {
3399 a.similar_hash(state, depth - 1);
3400 o.hash(state);
3401 b.similar_hash(state, depth - 1);
3402 }
3403 JsValue::Tenary(_, test, cons, alt) => {
3404 test.similar_hash(state, depth - 1);
3405 cons.similar_hash(state, depth - 1);
3406 alt.similar_hash(state, depth - 1);
3407 }
3408 JsValue::Iterated(_, operand)
3409 | JsValue::TypeOf(_, operand)
3410 | JsValue::Promise(_, operand)
3411 | JsValue::Awaited(_, operand) => {
3412 operand.similar_hash(state, depth - 1);
3413 }
3414 JsValue::Module(ModuleValue {
3415 module: v,
3416 annotations: a,
3417 }) => {
3418 Hash::hash(v, state);
3419 Hash::hash(a, state);
3420 }
3421 JsValue::WellKnownObject(v) => Hash::hash(v, state),
3422 JsValue::WellKnownFunction(v) => Hash::hash(v, state),
3423 JsValue::Unknown {
3424 original_value: _,
3425 reason: v,
3426 has_side_effects,
3427 } => {
3428 Hash::hash(v, state);
3429 Hash::hash(has_side_effects, state);
3430 }
3431 JsValue::Function(_, _, v) => v.similar_hash(state, depth - 1),
3432 JsValue::Argument(i, v) => {
3433 Hash::hash(i, state);
3434 Hash::hash(v, state);
3435 }
3436 }
3437 }
3438}
3439
3440const SIMILAR_EQ_DEPTH: usize = 3;
3442const SIMILAR_HASH_DEPTH: usize = 2;
3444
3445struct SimilarJsValue(JsValue);
3449
3450impl PartialEq for SimilarJsValue {
3451 fn eq(&self, other: &Self) -> bool {
3452 self.0.similar(&other.0, SIMILAR_EQ_DEPTH)
3453 }
3454}
3455
3456impl Eq for SimilarJsValue {}
3457
3458impl Hash for SimilarJsValue {
3459 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
3460 self.0.similar_hash(state, SIMILAR_HASH_DEPTH)
3461 }
3462}
3463
3464#[derive(Debug, Clone, Hash, PartialEq, Eq)]
3466pub enum WellKnownObjectKind {
3467 GlobalObject,
3468 PathModule,
3469 PathModuleDefault,
3470 FsModule,
3471 FsModuleDefault,
3472 FsModulePromises,
3473 FsExtraModule,
3474 FsExtraModuleDefault,
3475 ModuleModule,
3476 ModuleModuleDefault,
3477 UrlModule,
3478 UrlModuleDefault,
3479 WorkerThreadsModule,
3480 WorkerThreadsModuleDefault,
3481 ChildProcessModule,
3482 ChildProcessModuleDefault,
3483 OsModule,
3484 OsModuleDefault,
3485 NodeProcessModule,
3486 NodeProcessArgv,
3487 NodeProcessEnv,
3488 NodePreGyp,
3489 NodeExpressApp,
3490 NodeProtobufLoader,
3491 NodeBuffer,
3492 RequireCache,
3493 ImportMeta,
3494 Generator,
3496}
3497
3498impl WellKnownObjectKind {
3499 pub fn as_define_name(&self) -> Option<&[&str]> {
3500 match self {
3501 Self::GlobalObject => Some(&["Object"]),
3502 Self::PathModule => Some(&["path"]),
3503 Self::FsModule => Some(&["fs"]),
3504 Self::UrlModule => Some(&["url"]),
3505 Self::ChildProcessModule => Some(&["child_process"]),
3506 Self::OsModule => Some(&["os"]),
3507 Self::WorkerThreadsModule => Some(&["worker_threads"]),
3508 Self::NodeProcessModule => Some(&["process"]),
3509 Self::NodeProcessArgv => Some(&["process", "argv"]),
3510 Self::NodeProcessEnv => Some(&["process", "env"]),
3511 Self::NodeBuffer => Some(&["Buffer"]),
3512 Self::RequireCache => Some(&["require", "cache"]),
3513 Self::ImportMeta => Some(&["import", "meta"]),
3514 _ => None,
3515 }
3516 }
3517}
3518
3519#[derive(Debug, Clone)]
3520pub struct RequireContextOptions {
3521 pub dir: RcStr,
3522 pub include_subdirs: bool,
3523 pub filter: EsRegex,
3525}
3526
3527pub fn parse_require_context(args: &[JsValue]) -> Result<RequireContextOptions> {
3530 if !(1..=3).contains(&args.len()) {
3531 bail!("require.context() only supports 1-3 arguments (mode is not supported)");
3533 }
3534
3535 let Some(dir) = args[0].as_str().map(|s| s.into()) else {
3536 bail!("require.context(dir, ...) requires dir to be a constant string");
3537 };
3538
3539 let include_subdirs = if let Some(include_subdirs) = args.get(1) {
3540 if let Some(include_subdirs) = include_subdirs.as_bool() {
3541 include_subdirs
3542 } else {
3543 bail!(
3544 "require.context(..., includeSubdirs, ...) requires includeSubdirs to be a \
3545 constant boolean",
3546 );
3547 }
3548 } else {
3549 true
3550 };
3551
3552 let filter = if let Some(filter) = args.get(2) {
3553 if let JsValue::Constant(ConstantValue::Regex(box (pattern, flags))) = filter {
3554 EsRegex::new(pattern, flags)?
3555 } else {
3556 bail!("require.context(..., ..., filter) requires filter to be a regex");
3557 }
3558 } else {
3559 static DEFAULT_REGEX: Lazy<EsRegex> = Lazy::new(|| EsRegex::new(r"^\\./.*$", "").unwrap());
3562
3563 DEFAULT_REGEX.clone()
3564 };
3565
3566 Ok(RequireContextOptions {
3567 dir,
3568 include_subdirs,
3569 filter,
3570 })
3571}
3572
3573#[derive(Debug, Clone, Eq, PartialEq)]
3574pub struct RequireContextValue(FxIndexMap<RcStr, RcStr>);
3575
3576impl RequireContextValue {
3577 pub async fn from_context_map(map: Vc<RequireContextMap>) -> Result<Self> {
3578 let mut context_map = FxIndexMap::default();
3579
3580 for (key, entry) in map.await?.iter() {
3581 context_map.insert(key.clone(), entry.origin_relative.clone());
3582 }
3583
3584 Ok(RequireContextValue(context_map))
3585 }
3586}
3587
3588impl Hash for RequireContextValue {
3589 fn hash<H: Hasher>(&self, state: &mut H) {
3590 self.0.len().hash(state);
3591 for (i, (k, v)) in self.0.iter().enumerate() {
3592 i.hash(state);
3593 k.hash(state);
3594 v.hash(state);
3595 }
3596 }
3597}
3598
3599#[derive(Debug, Clone, Hash, PartialEq, Eq)]
3601pub enum WellKnownFunctionKind {
3602 ArrayFilter,
3603 ArrayForEach,
3604 ArrayMap,
3605 ObjectAssign,
3606 PathJoin,
3607 PathDirname,
3608 PathResolve(Box<JsValue>),
3610 Import,
3611 Require,
3612 RequireResolve,
3613 RequireContext,
3614 RequireContextRequire(RequireContextValue),
3615 RequireContextRequireKeys(RequireContextValue),
3616 RequireContextRequireResolve(RequireContextValue),
3617 Define,
3618 FsReadMethod(Atom),
3619 PathToFileUrl,
3620 CreateRequire,
3621 ChildProcessSpawnMethod(Atom),
3622 ChildProcessFork,
3623 OsArch,
3624 OsPlatform,
3625 OsEndianness,
3626 ProcessCwd,
3627 NodePreGypFind,
3628 NodeGypBuild,
3629 NodeBindings,
3630 NodeExpress,
3631 NodeExpressSet,
3632 NodeStrongGlobalize,
3633 NodeStrongGlobalizeSetRootDir,
3634 NodeResolveFrom,
3635 NodeProtobufLoad,
3636 WorkerConstructor,
3637 NodeWorkerConstructor,
3639 URLConstructor,
3640}
3641
3642impl WellKnownFunctionKind {
3643 pub fn as_define_name(&self) -> Option<&[&str]> {
3644 match self {
3645 Self::Import { .. } => Some(&["import"]),
3646 Self::Require { .. } => Some(&["require"]),
3647 Self::RequireResolve => Some(&["require", "resolve"]),
3648 Self::RequireContext => Some(&["require", "context"]),
3649 Self::Define => Some(&["define"]),
3650 _ => None,
3651 }
3652 }
3653}
3654
3655fn is_unresolved(i: &Ident, unresolved_mark: Mark) -> bool {
3656 i.ctxt.outer() == unresolved_mark
3657}
3658
3659fn is_unresolved_id(i: &Id, unresolved_mark: Mark) -> bool {
3660 i.1.outer() == unresolved_mark
3661}
3662
3663#[doc(hidden)]
3664pub mod test_utils {
3665 use anyhow::Result;
3666 use turbo_rcstr::rcstr;
3667 use turbo_tasks::{FxIndexMap, Vc};
3668 use turbopack_core::{compile_time_info::CompileTimeInfo, error::PrettyPrintError};
3669
3670 use super::{
3671 ConstantValue, JsValue, JsValueUrlKind, ModuleValue, WellKnownFunctionKind,
3672 WellKnownObjectKind, builtin::early_replace_builtin, well_known::replace_well_known,
3673 };
3674 use crate::{
3675 analyzer::{
3676 RequireContextValue,
3677 builtin::replace_builtin,
3678 imports::{ImportAnnotations, ImportAttributes},
3679 parse_require_context,
3680 },
3681 utils::module_value_to_well_known_object,
3682 };
3683
3684 pub async fn early_visitor(mut v: JsValue) -> Result<(JsValue, bool)> {
3685 let m = early_replace_builtin(&mut v);
3686 Ok((v, m))
3687 }
3688
3689 pub async fn visitor(
3692 v: JsValue,
3693 compile_time_info: Vc<CompileTimeInfo>,
3694 attributes: &ImportAttributes,
3695 ) -> Result<(JsValue, bool)> {
3696 let ImportAttributes { ignore, .. } = *attributes;
3697 let mut new_value = match v {
3698 JsValue::Call(
3699 _,
3700 box JsValue::WellKnownFunction(WellKnownFunctionKind::Import),
3701 ref args,
3702 ) => match &args[0] {
3703 JsValue::Constant(ConstantValue::Str(v)) => {
3704 JsValue::promise(JsValue::Module(ModuleValue {
3705 module: v.as_atom().into_owned(),
3706 annotations: ImportAnnotations::default(),
3707 }))
3708 }
3709 _ => v.into_unknown(true, "import() non constant"),
3710 },
3711 JsValue::Call(
3712 _,
3713 box JsValue::WellKnownFunction(WellKnownFunctionKind::CreateRequire),
3714 ref args,
3715 ) => {
3716 if let [
3717 JsValue::Member(
3718 _,
3719 box JsValue::WellKnownObject(WellKnownObjectKind::ImportMeta),
3720 box JsValue::Constant(ConstantValue::Str(prop)),
3721 ),
3722 ] = &args[..]
3723 && prop.as_str() == "url"
3724 {
3725 JsValue::WellKnownFunction(WellKnownFunctionKind::Require)
3726 } else {
3727 v.into_unknown(true, "createRequire() non constant")
3728 }
3729 }
3730 JsValue::Call(
3731 _,
3732 box JsValue::WellKnownFunction(WellKnownFunctionKind::RequireResolve),
3733 ref args,
3734 ) => match &args[0] {
3735 JsValue::Constant(v) => (v.to_string() + "/resolved/lib/index.js").into(),
3736 _ => v.into_unknown(true, "require.resolve non constant"),
3737 },
3738 JsValue::Call(
3739 _,
3740 box JsValue::WellKnownFunction(WellKnownFunctionKind::RequireContext),
3741 ref args,
3742 ) => match parse_require_context(args) {
3743 Ok(options) => {
3744 let mut map = FxIndexMap::default();
3745
3746 map.insert(
3747 rcstr!("./a"),
3748 format!("[context: {}]/a", options.dir).into(),
3749 );
3750 map.insert(
3751 rcstr!("./b"),
3752 format!("[context: {}]/b", options.dir).into(),
3753 );
3754 map.insert(
3755 rcstr!("./c"),
3756 format!("[context: {}]/c", options.dir).into(),
3757 );
3758
3759 JsValue::WellKnownFunction(WellKnownFunctionKind::RequireContextRequire(
3760 RequireContextValue(map),
3761 ))
3762 }
3763 Err(err) => v.into_unknown(true, PrettyPrintError(&err).to_string()),
3764 },
3765 JsValue::New(
3766 _,
3767 box JsValue::WellKnownFunction(WellKnownFunctionKind::URLConstructor),
3768 ref args,
3769 ) => {
3770 if let [
3771 JsValue::Constant(ConstantValue::Str(url)),
3772 JsValue::Member(
3773 _,
3774 box JsValue::WellKnownObject(WellKnownObjectKind::ImportMeta),
3775 box JsValue::Constant(ConstantValue::Str(prop)),
3776 ),
3777 ] = &args[..]
3778 {
3779 if prop.as_str() == "url" {
3780 JsValue::Url(url.clone(), JsValueUrlKind::Relative)
3782 } else {
3783 v.into_unknown(true, "new non constant")
3784 }
3785 } else {
3786 v.into_unknown(true, "new non constant")
3787 }
3788 }
3789 JsValue::FreeVar(ref var) => match &**var {
3790 "__dirname" => rcstr!("__dirname").into(),
3791 "__filename" => rcstr!("__filename").into(),
3792
3793 "require" => JsValue::unknown_if(
3794 ignore,
3795 JsValue::WellKnownFunction(WellKnownFunctionKind::Require),
3796 true,
3797 "ignored require",
3798 ),
3799 "import" => JsValue::unknown_if(
3800 ignore,
3801 JsValue::WellKnownFunction(WellKnownFunctionKind::Import),
3802 true,
3803 "ignored import",
3804 ),
3805 "Worker" => JsValue::unknown_if(
3806 ignore,
3807 JsValue::WellKnownFunction(WellKnownFunctionKind::WorkerConstructor),
3808 true,
3809 "ignored Worker constructor",
3810 ),
3811 "define" => JsValue::WellKnownFunction(WellKnownFunctionKind::Define),
3812 "URL" => JsValue::WellKnownFunction(WellKnownFunctionKind::URLConstructor),
3813 "process" => JsValue::WellKnownObject(WellKnownObjectKind::NodeProcessModule),
3814 "Object" => JsValue::WellKnownObject(WellKnownObjectKind::GlobalObject),
3815 "Buffer" => JsValue::WellKnownObject(WellKnownObjectKind::NodeBuffer),
3816 _ => v.into_unknown(true, "unknown global"),
3817 },
3818 JsValue::Module(ref mv) => {
3819 if let Some(wko) = module_value_to_well_known_object(mv) {
3820 wko
3821 } else {
3822 return Ok((v, false));
3823 }
3824 }
3825 _ => {
3826 let (mut v, m1) = replace_well_known(v, compile_time_info, true).await?;
3827 let m2 = replace_builtin(&mut v);
3828 let m = m1 || m2 || v.make_nested_operations_unknown();
3829 return Ok((v, m));
3830 }
3831 };
3832 new_value.normalize_shallow();
3833 Ok((new_value, true))
3834 }
3835}
3836
3837#[cfg(test)]
3838mod tests {
3839 use std::{mem::take, path::PathBuf, time::Instant};
3840
3841 use parking_lot::Mutex;
3842 use rustc_hash::FxHashMap;
3843 use swc_core::{
3844 common::{Mark, comments::SingleThreadedComments},
3845 ecma::{
3846 ast::{EsVersion, Id},
3847 parser::parse_file_as_program,
3848 transforms::base::resolver,
3849 visit::VisitMutWith,
3850 },
3851 testing::{NormalizedOutput, fixture, run_test},
3852 };
3853 use turbo_tasks::{ResolvedVc, util::FormatDuration};
3854 use turbopack_core::{
3855 compile_time_info::CompileTimeInfo,
3856 environment::{Environment, ExecutionEnvironment, NodeJsEnvironment, NodeJsVersion},
3857 target::{Arch, CompileTarget, Endianness, Libc, Platform},
3858 };
3859
3860 use super::{
3861 JsValue,
3862 graph::{ConditionalKind, Effect, EffectArg, EvalContext, VarGraph, create_graph},
3863 linker::link,
3864 };
3865 use crate::{
3866 AnalyzeMode,
3867 analyzer::{
3868 graph::{AssignmentScopes, VarMeta},
3869 imports::ImportAttributes,
3870 },
3871 };
3872
3873 #[fixture("tests/analyzer/graph/**/input.js")]
3874 fn fixture(input: PathBuf) {
3875 let graph_snapshot_path = input.with_file_name("graph.snapshot");
3876 let graph_explained_snapshot_path = input.with_file_name("graph-explained.snapshot");
3877 let graph_effects_snapshot_path = input.with_file_name("graph-effects.snapshot");
3878 let resolved_explained_snapshot_path = input.with_file_name("resolved-explained.snapshot");
3879 let resolved_effects_snapshot_path = input.with_file_name("resolved-effects.snapshot");
3880 let large_marker = input.with_file_name("large");
3881
3882 run_test(false, |cm, handler| {
3883 let r = tokio::runtime::Builder::new_current_thread()
3884 .build()
3885 .unwrap();
3886 r.block_on(async move {
3887 let fm = cm.load_file(&input).unwrap();
3888
3889 let comments = SingleThreadedComments::default();
3890 let mut m = parse_file_as_program(
3891 &fm,
3892 Default::default(),
3893 EsVersion::latest(),
3894 Some(&comments),
3895 &mut vec![],
3896 )
3897 .map_err(|err| err.into_diagnostic(handler).emit())?;
3898
3899 let unresolved_mark = Mark::new();
3900 let top_level_mark = Mark::new();
3901 m.visit_mut_with(&mut resolver(unresolved_mark, top_level_mark, false));
3902
3903 let eval_context = EvalContext::new(
3904 Some(&m),
3905 unresolved_mark,
3906 top_level_mark,
3907 Default::default(),
3908 Some(&comments),
3909 None,
3910 );
3911
3912 let mut var_graph =
3913 create_graph(&m, &eval_context, AnalyzeMode::CodeGenerationAndTracing);
3914 let var_cache = Default::default();
3915
3916 let mut named_values = var_graph
3917 .values
3918 .clone()
3919 .into_iter()
3920 .map(|((id, ctx), value)| {
3921 let unique = var_graph.values.keys().filter(|(i, _)| &id == i).count() == 1;
3922 if unique {
3923 (id.to_string(), ((id, ctx), value))
3924 } else {
3925 (format!("{id}{ctx:?}"), ((id, ctx), value))
3926 }
3927 })
3928 .collect::<Vec<_>>();
3929 named_values.sort_by(|a, b| a.0.cmp(&b.0));
3930
3931 fn explain_all<'a>(
3932 values: impl IntoIterator<
3933 Item = (&'a String, &'a JsValue, Option<AssignmentScopes>),
3934 >,
3935 ) -> String {
3936 values
3937 .into_iter()
3938 .map(|(id, value, assignment_scopes)| {
3939 let non_root_assignments = match assignment_scopes {
3940 Some(AssignmentScopes::AllInModuleEvalScope) => {
3941 " (const after eval)"
3942 }
3943 _ => "",
3944 };
3945 let (explainer, hints) = value.explain(10, 5);
3946 format!("{id}{non_root_assignments} = {explainer}{hints}")
3947 })
3948 .collect::<Vec<_>>()
3949 .join("\n\n")
3950 }
3951
3952 {
3953 let large = large_marker.exists();
3956
3957 if !large {
3958 NormalizedOutput::from(format!(
3959 "{:#?}",
3960 named_values
3961 .iter()
3962 .map(|(name, (_, VarMeta { value, .. }))| (name, value))
3963 .collect::<Vec<_>>()
3964 ))
3965 .compare_to_file(&graph_snapshot_path)
3966 .unwrap();
3967 }
3968 NormalizedOutput::from(explain_all(named_values.iter().map(
3969 |(
3970 name,
3971 (
3972 _,
3973 VarMeta {
3974 value,
3975 assignment_scopes,
3976 },
3977 ),
3978 )| (name, value, Some(*assignment_scopes)),
3979 )))
3980 .compare_to_file(&graph_explained_snapshot_path)
3981 .unwrap();
3982 if !large {
3983 NormalizedOutput::from(format!("{:#?}", var_graph.effects))
3984 .compare_to_file(&graph_effects_snapshot_path)
3985 .unwrap();
3986 }
3987 }
3988
3989 {
3990 let start = Instant::now();
3993 let mut resolved = Vec::new();
3994 for (name, (id, _)) in named_values.iter().cloned() {
3995 let start = Instant::now();
3996 let (res, steps) = resolve(
3999 &var_graph,
4000 JsValue::Variable(id),
4001 ImportAttributes::empty_ref(),
4002 &var_cache,
4003 )
4004 .await;
4005 let time = start.elapsed();
4006 if time.as_millis() > 1 {
4007 println!(
4008 "linking {} {name} took {} in {} steps",
4009 input.display(),
4010 FormatDuration(time),
4011 steps
4012 );
4013 }
4014
4015 resolved.push((name, res));
4016 }
4017 let time = start.elapsed();
4018 if time.as_millis() > 1 {
4019 println!("linking {} took {}", input.display(), FormatDuration(time));
4020 }
4021
4022 let start = Instant::now();
4023 let explainer =
4024 explain_all(resolved.iter().map(|(name, value)| (name, value, None)));
4025 let time = start.elapsed();
4026 if time.as_millis() > 1 {
4027 println!(
4028 "explaining {} took {}",
4029 input.display(),
4030 FormatDuration(time)
4031 );
4032 }
4033
4034 NormalizedOutput::from(explainer)
4035 .compare_to_file(&resolved_explained_snapshot_path)
4036 .unwrap();
4037 }
4038
4039 {
4040 let start = Instant::now();
4043 let mut resolved = Vec::new();
4044 let mut queue = take(&mut var_graph.effects)
4045 .into_iter()
4046 .map(|effect| (0, effect))
4047 .rev()
4048 .collect::<Vec<_>>();
4049 let mut i = 0;
4050 while let Some((parent, effect)) = queue.pop() {
4051 i += 1;
4052 let start = Instant::now();
4053 async fn handle_args(
4054 args: Vec<EffectArg>,
4055 queue: &mut Vec<(usize, Effect)>,
4056 var_graph: &VarGraph,
4057 var_cache: &Mutex<FxHashMap<Id, JsValue>>,
4058 i: usize,
4059 ) -> Vec<JsValue> {
4060 let mut new_args = Vec::new();
4061 for arg in args {
4062 match arg {
4063 EffectArg::Value(v) => {
4064 new_args.push(
4065 resolve(
4066 var_graph,
4067 v,
4068 ImportAttributes::empty_ref(),
4069 var_cache,
4070 )
4071 .await
4072 .0,
4073 );
4074 }
4075 EffectArg::Closure(v, effects) => {
4076 new_args.push(
4077 resolve(
4078 var_graph,
4079 v,
4080 ImportAttributes::empty_ref(),
4081 var_cache,
4082 )
4083 .await
4084 .0,
4085 );
4086 queue.extend(
4087 effects.effects.into_iter().rev().map(|e| (i, e)),
4088 );
4089 }
4090 EffectArg::Spread => {
4091 new_args.push(JsValue::unknown_empty(true, "spread"));
4092 }
4093 }
4094 }
4095 new_args
4096 }
4097 let steps = match effect {
4098 Effect::Conditional {
4099 condition, kind, ..
4100 } => {
4101 let (condition, steps) = resolve(
4102 &var_graph,
4103 *condition,
4104 ImportAttributes::empty_ref(),
4105 &var_cache,
4106 )
4107 .await;
4108 resolved.push((format!("{parent} -> {i} conditional"), condition));
4109 match *kind {
4110 ConditionalKind::If { then } => {
4111 queue
4112 .extend(then.effects.into_iter().rev().map(|e| (i, e)));
4113 }
4114 ConditionalKind::Else { r#else } => {
4115 queue.extend(
4116 r#else.effects.into_iter().rev().map(|e| (i, e)),
4117 );
4118 }
4119 ConditionalKind::IfElse { then, r#else }
4120 | ConditionalKind::Ternary { then, r#else } => {
4121 queue.extend(
4122 r#else.effects.into_iter().rev().map(|e| (i, e)),
4123 );
4124 queue
4125 .extend(then.effects.into_iter().rev().map(|e| (i, e)));
4126 }
4127 ConditionalKind::IfElseMultiple { then, r#else } => {
4128 for then in then {
4129 queue.extend(
4130 then.effects.into_iter().rev().map(|e| (i, e)),
4131 );
4132 }
4133 for r#else in r#else {
4134 queue.extend(
4135 r#else.effects.into_iter().rev().map(|e| (i, e)),
4136 );
4137 }
4138 }
4139 ConditionalKind::And { expr }
4140 | ConditionalKind::Or { expr }
4141 | ConditionalKind::NullishCoalescing { expr }
4142 | ConditionalKind::Labeled { body: expr } => {
4143 queue
4144 .extend(expr.effects.into_iter().rev().map(|e| (i, e)));
4145 }
4146 };
4147 steps
4148 }
4149 Effect::Call {
4150 func,
4151 args,
4152 new,
4153 span,
4154 ..
4155 } => {
4156 let (func, steps) = resolve(
4157 &var_graph,
4158 *func,
4159 eval_context.imports.get_attributes(span),
4160 &var_cache,
4161 )
4162 .await;
4163 let new_args =
4164 handle_args(args, &mut queue, &var_graph, &var_cache, i).await;
4165 resolved.push((
4166 format!("{parent} -> {i} call"),
4167 if new {
4168 JsValue::new(Box::new(func), new_args)
4169 } else {
4170 JsValue::call(Box::new(func), new_args)
4171 },
4172 ));
4173 steps
4174 }
4175 Effect::FreeVar { var, .. } => {
4176 resolved.push((
4177 format!("{parent} -> {i} free var"),
4178 JsValue::FreeVar(var),
4179 ));
4180 0
4181 }
4182 Effect::TypeOf { arg, .. } => {
4183 let (arg, steps) = resolve(
4184 &var_graph,
4185 *arg,
4186 ImportAttributes::empty_ref(),
4187 &var_cache,
4188 )
4189 .await;
4190 resolved.push((
4191 format!("{parent} -> {i} typeof"),
4192 JsValue::type_of(Box::new(arg)),
4193 ));
4194 steps
4195 }
4196 Effect::MemberCall {
4197 obj, prop, args, ..
4198 } => {
4199 let (obj, obj_steps) = resolve(
4200 &var_graph,
4201 *obj,
4202 ImportAttributes::empty_ref(),
4203 &var_cache,
4204 )
4205 .await;
4206 let (prop, prop_steps) = resolve(
4207 &var_graph,
4208 *prop,
4209 ImportAttributes::empty_ref(),
4210 &var_cache,
4211 )
4212 .await;
4213 let new_args =
4214 handle_args(args, &mut queue, &var_graph, &var_cache, i).await;
4215 resolved.push((
4216 format!("{parent} -> {i} member call"),
4217 JsValue::member_call(Box::new(obj), Box::new(prop), new_args),
4218 ));
4219 obj_steps + prop_steps
4220 }
4221 Effect::Unreachable { .. } => {
4222 resolved.push((
4223 format!("{parent} -> {i} unreachable"),
4224 JsValue::unknown_empty(true, "unreachable"),
4225 ));
4226 0
4227 }
4228 Effect::ImportMeta { .. }
4229 | Effect::ImportedBinding { .. }
4230 | Effect::Member { .. } => 0,
4231 };
4232 let time = start.elapsed();
4233 if time.as_millis() > 1 {
4234 println!(
4235 "linking effect {} took {} in {} steps",
4236 input.display(),
4237 FormatDuration(time),
4238 steps
4239 );
4240 }
4241 }
4242 let time = start.elapsed();
4243 if time.as_millis() > 1 {
4244 println!(
4245 "linking effects {} took {}",
4246 input.display(),
4247 FormatDuration(time)
4248 );
4249 }
4250
4251 let start = Instant::now();
4252 let explainer =
4253 explain_all(resolved.iter().map(|(name, value)| (name, value, None)));
4254 let time = start.elapsed();
4255 if time.as_millis() > 1 {
4256 println!(
4257 "explaining effects {} took {}",
4258 input.display(),
4259 FormatDuration(time)
4260 );
4261 }
4262
4263 NormalizedOutput::from(explainer)
4264 .compare_to_file(&resolved_effects_snapshot_path)
4265 .unwrap();
4266 }
4267
4268 Ok(())
4269 })
4270 })
4271 .unwrap();
4272 }
4273
4274 async fn resolve(
4275 var_graph: &VarGraph,
4276 val: JsValue,
4277 attributes: &ImportAttributes,
4278 var_cache: &Mutex<FxHashMap<Id, JsValue>>,
4279 ) -> (JsValue, u32) {
4280 turbo_tasks_testing::VcStorage::with(async {
4281 let compile_time_info = CompileTimeInfo::builder(
4282 Environment::new(ExecutionEnvironment::NodeJsLambda(
4283 NodeJsEnvironment {
4284 compile_target: CompileTarget {
4285 arch: Arch::X64,
4286 platform: Platform::Linux,
4287 endianness: Endianness::Little,
4288 libc: Libc::Glibc,
4289 }
4290 .resolved_cell(),
4291 node_version: NodeJsVersion::default().resolved_cell(),
4292 cwd: ResolvedVc::cell(None),
4293 }
4294 .resolved_cell(),
4295 ))
4296 .to_resolved()
4297 .await?,
4298 )
4299 .cell()
4300 .await?;
4301 link(
4302 var_graph,
4303 val,
4304 &super::test_utils::early_visitor,
4305 &(|val| {
4306 Box::pin(super::test_utils::visitor(
4307 val,
4308 compile_time_info,
4309 attributes,
4310 ))
4311 }),
4312 &Default::default(),
4313 var_cache,
4314 )
4315 .await
4316 })
4317 .await
4318 .unwrap()
4319 }
4320}