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