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