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