1use std::ops::Deref;
2
3use serde::{Deserialize, Serialize};
4use swc_core::{
5 common::{DUMMY_SP, SyntaxContext},
6 ecma::{
7 ast::{Expr, Lit, Str},
8 visit::AstParentKind,
9 },
10};
11use turbo_rcstr::{RcStr, rcstr};
12use turbo_tasks::{NonLocalValue, TaskInput, trace::TraceRawVcs};
13use turbopack_core::{chunk::ModuleId, resolve::pattern::Pattern};
14
15use crate::analyzer::{
16 ConstantNumber, ConstantValue, JsValue, JsValueUrlKind, ModuleValue, WellKnownFunctionKind,
17 WellKnownObjectKind,
18};
19
20pub fn unparen(expr: &Expr) -> &Expr {
21 if let Some(expr) = expr.as_paren() {
22 return unparen(&expr.expr);
23 }
24 if let Expr::Seq(seq) = expr {
25 return unparen(seq.exprs.last().unwrap());
26 }
27 expr
28}
29
30pub fn js_value_to_pattern(value: &JsValue) -> Pattern {
32 match value {
33 JsValue::Constant(v) => Pattern::Constant(match v {
34 ConstantValue::Str(str) => {
35 if str.as_str().contains("\\") {
38 RcStr::from(str.to_string().replace('\\', "/"))
39 } else {
40 str.as_rcstr()
41 }
42 }
43 ConstantValue::True => rcstr!("true"),
44 ConstantValue::False => rcstr!("false"),
45 ConstantValue::Null => rcstr!("null"),
46 ConstantValue::Num(ConstantNumber(n)) => n.to_string().into(),
47 ConstantValue::BigInt(n) => n.to_string().into(),
48 ConstantValue::Regex(box (exp, flags)) => format!("/{exp}/{flags}").into(),
49 ConstantValue::Undefined => rcstr!("undefined"),
50 }),
51 JsValue::Url(v, JsValueUrlKind::Relative) => Pattern::Constant(v.as_rcstr()),
52 JsValue::Alternatives {
53 total_nodes: _,
54 values,
55 logical_property: _,
56 } => {
57 let mut alts = Pattern::Alternatives(values.iter().map(js_value_to_pattern).collect());
58 alts.normalize();
59 alts
60 }
61 JsValue::Concat(_, parts) => {
62 let mut concats =
63 Pattern::Concatenation(parts.iter().map(js_value_to_pattern).collect());
64 concats.normalize();
65 concats
66 }
67 JsValue::Add(..) => {
68 Pattern::Dynamic
71 }
72 _ => Pattern::Dynamic,
73 }
74}
75
76const JS_MAX_SAFE_INTEGER: u64 = (1u64 << 53) - 1;
77
78pub fn module_id_to_lit(module_id: &ModuleId) -> Expr {
79 Expr::Lit(match module_id {
80 ModuleId::Number(n) => {
81 if *n <= JS_MAX_SAFE_INTEGER {
82 Lit::Num((*n as f64).into())
83 } else {
84 Lit::Str(Str {
85 span: DUMMY_SP,
86 value: n.to_string().into(),
87 raw: None,
88 })
89 }
90 }
91 ModuleId::String(s) => Lit::Str(Str {
92 span: DUMMY_SP,
93 value: (s as &str).into(),
94 raw: None,
95 }),
96 })
97}
98
99pub struct StringifyModuleId<'a>(pub &'a ModuleId);
100
101impl std::fmt::Display for StringifyModuleId<'_> {
102 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103 match self.0 {
104 ModuleId::Number(n) => {
105 if *n <= JS_MAX_SAFE_INTEGER {
106 n.fmt(f)
107 } else {
108 write!(f, "\"{n}\"")
109 }
110 }
111 ModuleId::String(s) => StringifyJs(s).fmt(f),
112 }
113 }
114}
115
116pub struct StringifyJs<'a, T>(pub &'a T)
117where
118 T: ?Sized;
119
120impl<T> std::fmt::Display for StringifyJs<'_, T>
121where
122 T: Serialize + ?Sized,
123{
124 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125 struct DisplayWriter<'a, 'b> {
128 f: &'a mut std::fmt::Formatter<'b>,
129 }
130
131 impl std::io::Write for DisplayWriter<'_, '_> {
132 fn write(&mut self, bytes: &[u8]) -> std::result::Result<usize, std::io::Error> {
133 self.f
134 .write_str(std::str::from_utf8(bytes).map_err(std::io::Error::other)?)
135 .map_err(std::io::Error::other)?;
136 Ok(bytes.len())
137 }
138
139 fn flush(&mut self) -> std::result::Result<(), std::io::Error> {
140 unreachable!()
141 }
142 }
143
144 let to_writer = match f.alternate() {
145 true => serde_json::to_writer_pretty,
146 false => serde_json::to_writer,
147 };
148
149 to_writer(DisplayWriter { f }, self.0).map_err(|_err| std::fmt::Error)
150 }
151}
152
153pub struct FormatIter<T: Iterator, F: Fn() -> T>(pub F);
154
155macro_rules! format_iter {
156 ($trait:path) => {
157 impl<T: Iterator, F: Fn() -> T> $trait for FormatIter<T, F>
158 where
159 T::Item: $trait,
160 {
161 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162 for item in self.0() {
163 item.fmt(f)?;
164 }
165 Ok(())
166 }
167 }
168 };
169}
170
171format_iter!(std::fmt::Binary);
172format_iter!(std::fmt::Debug);
173format_iter!(std::fmt::Display);
174format_iter!(std::fmt::LowerExp);
175format_iter!(std::fmt::LowerHex);
176format_iter!(std::fmt::Octal);
177format_iter!(std::fmt::Pointer);
178format_iter!(std::fmt::UpperExp);
179format_iter!(std::fmt::UpperHex);
180
181#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, Debug, NonLocalValue)]
182pub enum AstPathRange {
183 Exact(#[turbo_tasks(trace_ignore)] Vec<AstParentKind>),
185 StartAfter(#[turbo_tasks(trace_ignore)] Vec<AstParentKind>),
188}
189
190pub fn module_value_to_well_known_object(module_value: &ModuleValue) -> Option<JsValue> {
193 Some(match &*module_value.module {
194 "node:path" | "path" => JsValue::WellKnownObject(WellKnownObjectKind::PathModule),
195 "node:fs/promises" | "fs/promises" => {
196 JsValue::WellKnownObject(WellKnownObjectKind::FsModule)
197 }
198 "node:fs" | "fs" => JsValue::WellKnownObject(WellKnownObjectKind::FsModule),
199 "node:child_process" | "child_process" => {
200 JsValue::WellKnownObject(WellKnownObjectKind::ChildProcess)
201 }
202 "node:os" | "os" => JsValue::WellKnownObject(WellKnownObjectKind::OsModule),
203 "node:process" | "process" => JsValue::WellKnownObject(WellKnownObjectKind::NodeProcess),
204 "node-pre-gyp" | "@mapbox/node-pre-gyp" => {
205 JsValue::WellKnownObject(WellKnownObjectKind::NodePreGyp)
206 }
207 "node-gyp-build" => JsValue::WellKnownFunction(WellKnownFunctionKind::NodeGypBuild),
208 "node:bindings" | "bindings" => {
209 JsValue::WellKnownFunction(WellKnownFunctionKind::NodeBindings)
210 }
211 "express" => JsValue::WellKnownFunction(WellKnownFunctionKind::NodeExpress),
212 "strong-globalize" => {
213 JsValue::WellKnownFunction(WellKnownFunctionKind::NodeStrongGlobalize)
214 }
215 "resolve-from" => JsValue::WellKnownFunction(WellKnownFunctionKind::NodeResolveFrom),
216 "@grpc/proto-loader" => JsValue::WellKnownObject(WellKnownObjectKind::NodeProtobufLoader),
217 _ => return None,
218 })
219}
220
221#[derive(Hash, Debug, Clone, Copy, Eq, Serialize, Deserialize, PartialEq, TraceRawVcs)]
222pub struct AstSyntaxContext(#[turbo_tasks(trace_ignore)] SyntaxContext);
223
224impl TaskInput for AstSyntaxContext {
225 fn is_transient(&self) -> bool {
226 false
227 }
228}
229unsafe impl NonLocalValue for AstSyntaxContext {}
230
231impl Deref for AstSyntaxContext {
232 type Target = SyntaxContext;
233
234 fn deref(&self) -> &Self::Target {
235 &self.0
236 }
237}
238
239impl From<SyntaxContext> for AstSyntaxContext {
240 fn from(v: SyntaxContext) -> Self {
241 Self(v)
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use turbo_rcstr::rcstr;
248 use turbopack_core::resolve::pattern::Pattern;
249
250 use crate::{
251 analyzer::{ConstantString, ConstantValue, JsValue},
252 utils::js_value_to_pattern,
253 };
254
255 #[test]
256 fn test_path_normalization_in_pattern() {
257 assert_eq!(
258 Pattern::Constant(rcstr!("hello/world")),
259 js_value_to_pattern(&JsValue::Constant(ConstantValue::Str(
260 ConstantString::RcStr(rcstr!("hello\\world"))
261 )))
262 );
263
264 assert_eq!(
265 Pattern::Constant(rcstr!("hello/world")),
266 js_value_to_pattern(&JsValue::Concat(
267 1,
268 vec!["hello".into(), "\\".into(), "world".into()]
269 ))
270 );
271 }
272}