1use std::ops::Deref;
2
3use bincode::{Decode, Encode};
4use serde::Serialize;
5use smallvec::SmallVec;
6use swc_core::{
7 common::{DUMMY_SP, SyntaxContext},
8 ecma::{
9 ast::{ComputedPropName, Expr, Lit, MemberProp, ObjectPatProp, Pat, PropName, Str},
10 visit::AstParentKind,
11 },
12};
13use turbo_rcstr::{RcStr, rcstr};
14use turbo_tasks::{NonLocalValue, TaskInput, trace::TraceRawVcs};
15use turbopack_core::{chunk::ModuleId, resolve::pattern::Pattern};
16
17use crate::analyzer::{
18 ConstantNumber, ConstantValue, JsValue, JsValueUrlKind, ModuleValue, WellKnownFunctionKind,
19 WellKnownObjectKind,
20};
21
22pub fn unparen(expr: &Expr) -> &Expr {
23 if let Some(expr) = expr.as_paren() {
24 return unparen(&expr.expr);
25 }
26 if let Expr::Seq(seq) = expr {
27 return unparen(seq.exprs.last().unwrap());
28 }
29 expr
30}
31
32pub(crate) fn extract_name_from_member_prop(prop: &MemberProp) -> Option<SmallVec<[RcStr; 1]>> {
35 match prop {
36 MemberProp::Ident(ident) => Some(SmallVec::from_buf([ident.sym.as_str().into()])),
37 MemberProp::Computed(ComputedPropName {
38 expr: box Expr::Lit(Lit::Str(s)),
39 ..
40 }) => s.value.as_str().map(|v| SmallVec::from_buf([v.into()])),
41 _ => None,
42 }
43}
44
45pub(crate) fn extract_names_from_object_pat(pat: &Pat) -> Option<SmallVec<[RcStr; 1]>> {
48 let Pat::Object(obj_pat) = pat else {
49 return None;
50 };
51 let mut names = SmallVec::new();
52 for prop in &obj_pat.props {
53 match prop {
54 ObjectPatProp::KeyValue(kv) => match &kv.key {
55 PropName::Ident(ident) => names.push(ident.sym.as_str().into()),
56 PropName::Str(s) => names.push(s.value.as_str()?.into()),
57 _ => return None, },
59 ObjectPatProp::Assign(assign) => {
60 names.push(assign.key.sym.as_str().into());
61 }
62 ObjectPatProp::Rest(_) => return None, }
64 }
65 Some(names)
66}
67
68pub fn js_value_to_pattern(value: &JsValue<'_>) -> Pattern {
70 match value {
71 JsValue::Constant(v) => Pattern::Constant(match v {
72 ConstantValue::Str(str) => {
73 if str.as_str().contains("\\") {
76 RcStr::from(str.to_string().replace('\\', "/"))
77 } else {
78 str.as_rcstr()
79 }
80 }
81 ConstantValue::True => rcstr!("true"),
82 ConstantValue::False => rcstr!("false"),
83 ConstantValue::Null => rcstr!("null"),
84 ConstantValue::Num(ConstantNumber(n)) => n.to_string().into(),
85 ConstantValue::BigInt(n) => n.to_string().into(),
86 ConstantValue::Regex(box (exp, flags)) => format!("/{exp}/{flags}").into(),
87 ConstantValue::Undefined => rcstr!("undefined"),
88 }),
89 JsValue::Url(v, JsValueUrlKind::Relative) => Pattern::Constant(v.as_rcstr()),
90 JsValue::Alternatives {
91 total_nodes: _,
92 values,
93 logical_property: _,
94 } => {
95 let mut alts = Pattern::Alternatives(values.iter().map(js_value_to_pattern).collect());
96 alts.normalize();
97 alts
98 }
99 JsValue::Concat(_, parts) => {
100 let mut concats =
101 Pattern::Concatenation(parts.iter().map(js_value_to_pattern).collect());
102 concats.normalize();
103 concats
104 }
105 JsValue::Add(..) => {
106 Pattern::Dynamic
109 }
110 _ => Pattern::Dynamic,
111 }
112}
113
114const JS_MAX_SAFE_INTEGER: u64 = (1u64 << 53) - 1;
115
116pub fn module_id_to_lit(module_id: &ModuleId) -> Expr {
117 Expr::Lit(match module_id {
118 ModuleId::Number(n) => {
119 if *n <= JS_MAX_SAFE_INTEGER {
120 Lit::Num((*n as f64).into())
121 } else {
122 Lit::Str(Str {
123 span: DUMMY_SP,
124 value: n.to_string().into(),
125 raw: None,
126 })
127 }
128 }
129 ModuleId::String(s) => Lit::Str(Str {
130 span: DUMMY_SP,
131 value: (s as &str).into(),
132 raw: None,
133 }),
134 })
135}
136
137pub struct StringifyModuleId<'a>(pub &'a ModuleId);
138
139impl std::fmt::Display for StringifyModuleId<'_> {
140 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141 match self.0 {
142 ModuleId::Number(n) => {
143 if *n <= JS_MAX_SAFE_INTEGER {
144 n.fmt(f)
145 } else {
146 write!(f, "\"{n}\"")
147 }
148 }
149 ModuleId::String(s) => StringifyJs(s).fmt(f),
150 }
151 }
152}
153
154pub struct StringifyJs<'a, T>(pub &'a T)
155where
156 T: ?Sized;
157
158impl<T> std::fmt::Display for StringifyJs<'_, T>
159where
160 T: Serialize + ?Sized,
161{
162 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163 struct DisplayWriter<'a, 'b> {
166 f: &'a mut std::fmt::Formatter<'b>,
167 }
168
169 impl std::io::Write for DisplayWriter<'_, '_> {
170 fn write(&mut self, bytes: &[u8]) -> std::result::Result<usize, std::io::Error> {
171 self.f
172 .write_str(std::str::from_utf8(bytes).map_err(std::io::Error::other)?)
173 .map_err(std::io::Error::other)?;
174 Ok(bytes.len())
175 }
176
177 fn flush(&mut self) -> std::result::Result<(), std::io::Error> {
178 unreachable!()
179 }
180 }
181
182 let to_writer = match f.alternate() {
183 true => serde_json::to_writer_pretty,
184 false => serde_json::to_writer,
185 };
186
187 to_writer(DisplayWriter { f }, self.0).map_err(|_err| std::fmt::Error)
188 }
189}
190
191pub struct FormatIter<T: Iterator, F: Fn() -> T>(pub F);
192
193macro_rules! format_iter {
194 ($trait:path) => {
195 impl<T: Iterator, F: Fn() -> T> $trait for FormatIter<T, F>
196 where
197 T::Item: $trait,
198 {
199 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
200 for item in self.0() {
201 item.fmt(f)?;
202 }
203 Ok(())
204 }
205 }
206 };
207}
208
209format_iter!(std::fmt::Binary);
210format_iter!(std::fmt::Debug);
211format_iter!(std::fmt::Display);
212format_iter!(std::fmt::LowerExp);
213format_iter!(std::fmt::LowerHex);
214format_iter!(std::fmt::Octal);
215format_iter!(std::fmt::Pointer);
216format_iter!(std::fmt::UpperExp);
217format_iter!(std::fmt::UpperHex);
218
219#[derive(Clone, PartialEq, Eq, TraceRawVcs, Debug, NonLocalValue, Hash, Encode, Decode)]
220pub enum AstPathRange {
221 Exact(
223 #[bincode(with_serde)]
224 #[turbo_tasks(trace_ignore)]
225 Vec<AstParentKind>,
226 ),
227 StartAfter(
230 #[bincode(with_serde)]
231 #[turbo_tasks(trace_ignore)]
232 Vec<AstParentKind>,
233 ),
234}
235
236pub fn module_value_to_well_known_object<'a>(module_value: &ModuleValue) -> Option<JsValue<'a>> {
239 Some(match module_value.module.as_bytes() {
240 b"node:path" | b"path" => JsValue::WellKnownObject(WellKnownObjectKind::PathModule),
241 b"node:fs/promises" | b"fs/promises" => {
242 JsValue::WellKnownObject(WellKnownObjectKind::FsModule)
243 }
244 b"node:fs" | b"fs" => JsValue::WellKnownObject(WellKnownObjectKind::FsModule),
245 b"node:child_process" | b"child_process" => {
246 JsValue::WellKnownObject(WellKnownObjectKind::ChildProcessModule)
247 }
248 b"node:os" | b"os" => JsValue::WellKnownObject(WellKnownObjectKind::OsModule),
249 b"node:process" | b"process" => {
250 JsValue::WellKnownObject(WellKnownObjectKind::NodeProcessModule)
251 }
252 b"node:url" | b"url" => JsValue::WellKnownObject(WellKnownObjectKind::UrlModule),
253 b"node:module" | b"module" => JsValue::WellKnownObject(WellKnownObjectKind::ModuleModule),
254 b"node:worker_threads" | b"worker_threads" => {
255 JsValue::WellKnownObject(WellKnownObjectKind::WorkerThreadsModule)
256 }
257 b"node-pre-gyp" | b"@mapbox/node-pre-gyp" => {
258 JsValue::WellKnownObject(WellKnownObjectKind::NodePreGyp)
259 }
260 b"node-gyp-build" => JsValue::WellKnownFunction(WellKnownFunctionKind::NodeGypBuild),
261 b"node:bindings" | b"bindings" => {
262 JsValue::WellKnownFunction(WellKnownFunctionKind::NodeBindings)
263 }
264 b"express" => JsValue::WellKnownFunction(WellKnownFunctionKind::NodeExpress),
265 b"strong-globalize" => {
266 JsValue::WellKnownFunction(WellKnownFunctionKind::NodeStrongGlobalize)
267 }
268 b"resolve-from" => JsValue::WellKnownFunction(WellKnownFunctionKind::NodeResolveFrom),
269 b"@grpc/proto-loader" => JsValue::WellKnownObject(WellKnownObjectKind::NodeProtobufLoader),
270 b"fs-extra" => JsValue::WellKnownObject(WellKnownObjectKind::FsExtraModule),
271 _ => return None,
272 })
273}
274
275#[derive(Hash, Debug, Clone, Copy, Eq, PartialEq, TraceRawVcs, Encode, Decode)]
276pub struct AstSyntaxContext(
277 #[turbo_tasks(trace_ignore)]
278 #[bincode(with_serde)]
279 SyntaxContext,
280);
281
282impl TaskInput for AstSyntaxContext {
283 fn is_transient(&self) -> bool {
284 false
285 }
286}
287unsafe impl NonLocalValue for AstSyntaxContext {}
288
289impl Deref for AstSyntaxContext {
290 type Target = SyntaxContext;
291
292 fn deref(&self) -> &Self::Target {
293 &self.0
294 }
295}
296
297impl From<SyntaxContext> for AstSyntaxContext {
298 fn from(v: SyntaxContext) -> Self {
299 Self(v)
300 }
301}
302
303pub fn inline_source_map_comment(original_path: &str, original_content: &str) -> String {
316 let source_map = serde_json::json!({
317 "version": 3,
318 "sources": [format!("turbopack:///{}", original_path)],
319 "sourcesContent": [original_content],
320 "names": [],
321 "mappings": "AAAA",
324 });
325
326 let source_map_base64 = data_encoding::BASE64.encode(source_map.to_string().as_bytes());
327
328 format!(
329 "//# sourceMappingURL=data:application/json;base64,{}",
330 source_map_base64
331 )
332}
333
334#[cfg(test)]
335mod tests {
336 use turbo_rcstr::rcstr;
337 use turbopack_core::resolve::pattern::Pattern;
338
339 use crate::{
340 analyzer::{BumpVec, ConstantString, ConstantValue, JsValue, ThreadLocal},
341 utils::js_value_to_pattern,
342 };
343
344 #[test]
345 fn test_path_normalization_in_pattern() {
346 let arena = ThreadLocal::new();
347 assert_eq!(
348 Pattern::Constant(rcstr!("hello/world")),
349 js_value_to_pattern(&JsValue::Constant(ConstantValue::Str(
350 ConstantString::RcStr(rcstr!("hello\\world"))
351 )))
352 );
353
354 assert_eq!(
355 Pattern::Constant(rcstr!("hello/world")),
356 js_value_to_pattern(&JsValue::Concat(
357 1,
358 BumpVec::from_iter_in(
359 arena.get_or_default(),
360 [
361 rcstr!("hello").into(),
362 rcstr!("\\").into(),
363 rcstr!("world").into()
364 ]
365 )
366 ))
367 );
368 }
369
370 #[test]
371 fn test_inline_source_map_comment() {
372 use super::inline_source_map_comment;
373
374 let comment = inline_source_map_comment("test.txt", "hello");
375
376 assert!(comment.starts_with("//# sourceMappingURL=data:application/json;base64,"));
377
378 let source_map_part = comment
380 .split("base64,")
381 .nth(1)
382 .expect("should have base64 part");
383 let decoded = data_encoding::BASE64
384 .decode(source_map_part.as_bytes())
385 .expect("should decode");
386 let json: serde_json::Value =
387 serde_json::from_slice(&decoded).expect("should be valid JSON");
388
389 assert_eq!(json["version"], 3);
390 assert_eq!(json["sources"][0], "turbopack:///test.txt");
391 assert_eq!(json["sourcesContent"][0], "hello");
392 assert_eq!(json["mappings"], "AAAA");
393 }
394}