1use std::{borrow::Cow, io::Write, ops::Deref, sync::Arc};
2
3use anyhow::Result;
4use bytes_str::BytesStr;
5use once_cell::sync::Lazy;
6use ref_cast::RefCast;
7use regex::Regex;
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9use swc_sourcemap::{DecodedMap, SourceMap as RegularMap, SourceMapBuilder, SourceMapIndex};
10use turbo_rcstr::{RcStr, rcstr};
11use turbo_tasks::{ResolvedVc, TryJoinIterExt, Vc};
12use turbo_tasks_fs::{
13 File, FileContent, FileSystem, FileSystemPath, VirtualFileSystem,
14 rope::{Rope, RopeBuilder},
15};
16
17use crate::{
18 SOURCE_URL_PROTOCOL, asset::AssetContent, source::Source,
19 source_map::utils::add_default_ignore_list, source_pos::SourcePos,
20 virtual_source::VirtualSource,
21};
22
23pub(crate) mod source_map_asset;
24pub mod utils;
25
26pub use source_map_asset::SourceMapAsset;
27
28static SOURCEMAP_CRATE_NONE_U32: u32 = !0;
30
31#[turbo_tasks::value(transparent)]
32pub struct OptionStringifiedSourceMap(Option<Rope>);
33
34#[turbo_tasks::value_impl]
35impl OptionStringifiedSourceMap {
36 #[turbo_tasks::function]
37 pub fn none() -> Vc<Self> {
38 Vc::cell(None)
39 }
40}
41
42#[turbo_tasks::value_trait]
44pub trait GenerateSourceMap {
45 #[turbo_tasks::function]
47 fn generate_source_map(self: Vc<Self>) -> Vc<OptionStringifiedSourceMap>;
48
49 #[turbo_tasks::function]
51 fn by_section(self: Vc<Self>, _section: RcStr) -> Vc<OptionStringifiedSourceMap> {
52 Vc::cell(None)
53 }
54}
55
56#[turbo_tasks::value(shared, cell = "new", eq = "manual")]
67#[derive(Debug)]
68pub struct SourceMap {
69 #[turbo_tasks(trace_ignore)]
71 map: Arc<CrateMapWrapper>,
72}
73impl Eq for SourceMap {}
74impl PartialEq for SourceMap {
75 fn eq(&self, other: &Self) -> bool {
76 Arc::ptr_eq(&self.map, &other.map)
77 }
78}
79
80#[turbo_tasks::value(transparent)]
81pub struct OptionSourceMap(Option<SourceMap>);
82
83#[turbo_tasks::value_impl]
84impl OptionSourceMap {
85 #[turbo_tasks::function]
86 pub fn none() -> Vc<Self> {
87 Vc::cell(None)
88 }
89}
90
91impl OptionSourceMap {
92 pub fn none_resolved() -> ResolvedVc<Self> {
93 ResolvedVc::cell(None)
94 }
95}
96#[turbo_tasks::value(transparent)]
97#[derive(Clone, Debug)]
98pub struct Tokens(Vec<Token>);
99
100#[turbo_tasks::value]
106#[derive(Clone, Debug)]
107pub enum Token {
108 Synthetic(SyntheticToken),
109 Original(OriginalToken),
110}
111
112#[turbo_tasks::value]
113#[derive(Clone, Debug)]
114pub struct TokenWithSource {
115 pub token: Token,
116 pub source_content: Option<ResolvedVc<Box<dyn Source>>>,
117}
118
119#[turbo_tasks::value]
122#[derive(Clone, Debug)]
123pub struct SyntheticToken {
124 pub generated_line: u32,
125 pub generated_column: u32,
126 pub guessed_original_file: Option<RcStr>,
127}
128
129#[turbo_tasks::value]
132#[derive(Clone, Debug)]
133pub struct OriginalToken {
134 pub generated_line: u32,
135 pub generated_column: u32,
136 pub original_file: RcStr,
137 pub original_line: u32,
138 pub original_column: u32,
139 pub name: Option<RcStr>,
140}
141
142impl Token {
143 pub fn generated_line(&self) -> u32 {
144 match self {
145 Self::Original(t) => t.generated_line,
146 Self::Synthetic(t) => t.generated_line,
147 }
148 }
149
150 pub fn generated_column(&self) -> u32 {
151 match self {
152 Self::Original(t) => t.generated_column,
153 Self::Synthetic(t) => t.generated_column,
154 }
155 }
156}
157
158impl From<swc_sourcemap::Token<'_>> for Token {
159 fn from(t: swc_sourcemap::Token) -> Self {
160 if t.has_source() {
161 Token::Original(OriginalToken {
162 generated_line: t.get_dst_line(),
163 generated_column: t.get_dst_col(),
164 original_file: RcStr::from(
165 t.get_source()
166 .expect("already checked token has source")
167 .clone(),
168 ),
169 original_line: t.get_src_line(),
170 original_column: t.get_src_col(),
171 name: t.get_name().cloned().map(RcStr::from),
172 })
173 } else {
174 Token::Synthetic(SyntheticToken {
175 generated_line: t.get_dst_line(),
176 generated_column: t.get_dst_col(),
177 guessed_original_file: None,
178 })
179 }
180 }
181}
182
183impl TryInto<swc_sourcemap::RawToken> for Token {
184 type Error = std::num::ParseIntError;
185
186 fn try_into(self) -> Result<swc_sourcemap::RawToken, Self::Error> {
187 Ok(match self {
188 Self::Original(t) => swc_sourcemap::RawToken {
189 dst_col: t.generated_column,
190 dst_line: t.generated_line,
191 name_id: match t.name {
192 None => SOURCEMAP_CRATE_NONE_U32,
193 Some(name) => name.parse()?,
194 },
195 src_col: t.original_column,
196 src_line: t.original_line,
197 src_id: t.original_file.parse()?,
198 is_range: false,
199 },
200 Self::Synthetic(t) => swc_sourcemap::RawToken {
201 dst_col: t.generated_column,
202 dst_line: t.generated_line,
203 name_id: SOURCEMAP_CRATE_NONE_U32,
204 src_col: SOURCEMAP_CRATE_NONE_U32,
205 src_line: SOURCEMAP_CRATE_NONE_U32,
206 src_id: SOURCEMAP_CRATE_NONE_U32,
207 is_range: false,
208 },
209 })
210 }
211}
212
213impl SourceMap {
214 fn new_regular(map: RegularMap) -> Self {
216 Self::new_decoded(DecodedMap::Regular(map))
217 }
218
219 fn new_decoded(map: DecodedMap) -> Self {
221 SourceMap {
222 map: Arc::new(CrateMapWrapper(map)),
223 }
224 }
225
226 pub fn new_from_rope(content: &Rope) -> Result<Option<Self>> {
227 let Ok(map) = DecodedMap::from_reader(content.read()) else {
228 return Ok(None);
229 };
230 Ok(Some(SourceMap::new_decoded(map)))
231 }
232}
233
234#[turbo_tasks::value_impl]
235impl SourceMap {
236 #[turbo_tasks::function]
239 pub async fn new_from_rope_cached(
240 content: Vc<OptionStringifiedSourceMap>,
241 ) -> Result<Vc<OptionSourceMap>> {
242 let Some(content) = &*content.await? else {
243 return Ok(OptionSourceMap::none());
244 };
245 Ok(Vc::cell(SourceMap::new_from_rope(content)?))
246 }
247}
248
249impl SourceMap {
250 pub fn to_source_map(&self) -> Arc<CrateMapWrapper> {
251 self.map.clone()
252 }
253}
254
255static EMPTY_SOURCE_MAP_ROPE: Lazy<Rope> =
256 Lazy::new(|| Rope::from(r#"{"version":3,"sources":[],"names":[],"mappings":"A"}"#));
257
258impl SourceMap {
259 pub fn empty() -> Self {
264 let mut builder = SourceMapBuilder::new(None);
265 builder.add(0, 0, 0, 0, None, None, false);
266 SourceMap::new_regular(builder.into_sourcemap())
267 }
268
269 pub fn empty_rope() -> Rope {
274 EMPTY_SOURCE_MAP_ROPE.clone()
275 }
276
277 pub fn sections_to_rope(
278 sections: impl IntoIterator<Item = (SourcePos, Rope)>,
279 debug_id: Option<RcStr>,
280 ) -> Rope {
281 let mut sections = sections.into_iter().peekable();
282
283 let mut first = sections.next();
284 if let Some((offset, map)) = &mut first
285 && sections.peek().is_none()
286 && *offset == (0, 0)
287 && debug_id.is_none()
288 {
289 return std::mem::take(map);
291 }
292
293 let mut rope = RopeBuilder::from(
297 r#"{
298 "version": 3,
299 "sources": [],
300"#,
301 );
302 if let Some(debug_id) = debug_id {
303 writeln!(rope, r#" "debugId": "{debug_id}","#).unwrap();
304 }
305 rope += " \"sections\": [";
306
307 let mut first_section = true;
308 for (offset, section_map) in first.into_iter().chain(sections) {
309 if !first_section {
310 rope += ",";
311 }
312 first_section = false;
313
314 write!(
315 rope,
316 r#"
317 {{"offset": {{"line": {}, "column": {}}}, "map": "#,
318 offset.line, offset.column,
319 )
320 .unwrap();
321
322 rope += §ion_map;
323
324 rope += "}";
325 }
326
327 rope += "]";
328
329 rope += "\n}";
330
331 rope.build()
332 }
333
334 pub fn to_rope(&self) -> Result<Rope> {
336 let mut bytes = vec![];
337 self.map.0.to_writer(&mut bytes)?;
338 Ok(Rope::from(bytes))
339 }
340
341 pub fn lookup_token(&self, line: u32, column: u32) -> Token {
344 let (token, _) = self.lookup_token_and_source_internal(line, column, true);
345 token
346 }
347
348 pub async fn lookup_token_and_source(&self, line: u32, column: u32) -> Result<TokenWithSource> {
351 let (token, content) = self.lookup_token_and_source_internal(line, column, true);
352 Ok(TokenWithSource {
353 token,
354 source_content: match content {
355 Some(v) => Some(v.to_resolved().await?),
356 None => None,
357 },
358 })
359 }
360
361 pub async fn with_resolved_sources(&self, origin: FileSystemPath) -> Result<Self> {
362 async fn resolve_source(
363 source_request: BytesStr,
364 source_content: Option<BytesStr>,
365 origin: FileSystemPath,
366 ) -> Result<(BytesStr, BytesStr)> {
367 Ok(
368 if let Some(path) = origin.parent().try_join(&source_request)? {
369 let path_str = path.value_to_string().await?;
370 let source = format!("{SOURCE_URL_PROTOCOL}///{path_str}");
371 let source_content = if let Some(source_content) = source_content {
372 source_content
373 } else if let FileContent::Content(file) = &*path.read().await? {
374 let text = file.content().to_str()?;
375 text.to_string().into()
376 } else {
377 format!("unable to read source {path_str}").into()
378 };
379 (source.into(), source_content)
380 } else {
381 let origin_str = origin.value_to_string().await?;
382 static INVALID_REGEX: Lazy<Regex> =
383 Lazy::new(|| Regex::new(r#"(?:^|/)(?:\.\.?(?:/|$))+"#).unwrap());
384 let source = INVALID_REGEX
385 .replace_all(&source_request, |s: ®ex::Captures<'_>| {
386 s[0].replace('.', "_")
387 });
388 let source = format!("{SOURCE_URL_PROTOCOL}///{origin_str}/{source}");
389 let source_content = source_content.unwrap_or_else(|| {
390 format!(
391 "unable to access {source_request} in {origin_str} (it's leaving the \
392 filesystem root)"
393 )
394 .into()
395 });
396 (source.into(), source_content)
397 },
398 )
399 }
400 async fn regular_map_with_resolved_sources(
401 map: &RegularMapWrapper,
402 origin: FileSystemPath,
403 ) -> Result<RegularMap> {
404 let map = &map.0;
405 let file = map.get_file().cloned();
406 let tokens = map.tokens().map(|t| t.get_raw_token()).collect();
407 let names = map.names().cloned().collect();
408 let count = map.get_source_count() as usize;
409 let sources = map.sources().cloned().collect::<Vec<_>>();
410 let source_contents = map
411 .source_contents()
412 .map(|s| s.cloned())
413 .collect::<Vec<_>>();
414 let mut new_sources = Vec::with_capacity(count);
415 let mut new_source_contents = Vec::with_capacity(count);
416 for (source, source_content) in sources.into_iter().zip(source_contents.into_iter()) {
417 let (source, source_content) =
418 resolve_source(source, source_content, origin.clone()).await?;
419 new_sources.push(source);
420 new_source_contents.push(Some(source_content));
421 }
422 let mut map =
423 RegularMap::new(file, tokens, names, new_sources, Some(new_source_contents));
424
425 add_default_ignore_list(&mut map);
426
427 Ok(map)
428 }
429 async fn decoded_map_with_resolved_sources(
430 map: &CrateMapWrapper,
431 origin: FileSystemPath,
432 ) -> Result<CrateMapWrapper> {
433 Ok(CrateMapWrapper(match &map.0 {
434 DecodedMap::Regular(map) => {
435 let map = RegularMapWrapper::ref_cast(map);
436 DecodedMap::Regular(regular_map_with_resolved_sources(map, origin).await?)
437 }
438 DecodedMap::Index(map) => {
439 let count = map.get_section_count() as usize;
440 let file = map.get_file().cloned();
441 let sections = map
442 .sections()
443 .filter_map(|section| {
444 section
445 .get_sourcemap()
446 .map(|s| (section.get_offset(), CrateMapWrapper::ref_cast(s)))
447 })
448 .collect::<Vec<_>>();
449 let sections = sections
450 .into_iter()
451 .map(|(offset, map)| {
452 let origin = origin.clone();
453 async move {
454 Ok((
455 offset,
456 Box::pin(decoded_map_with_resolved_sources(
457 map,
458 origin.clone(),
459 ))
460 .await?,
461 ))
462 }
463 })
464 .try_join()
465 .await?;
466 let mut new_sections = Vec::with_capacity(count);
467 for (offset, map) in sections {
468 new_sections.push(swc_sourcemap::SourceMapSection::new(
469 offset,
470 None,
472 Some(map.0),
473 ));
474 }
475 DecodedMap::Index(SourceMapIndex::new(file, new_sections))
476 }
477 DecodedMap::Hermes(_) => {
478 todo!("hermes source maps are not implemented");
479 }
480 }))
481 }
482
483 let map = Box::pin(decoded_map_with_resolved_sources(&self.map, origin)).await?;
484 Ok(Self::new_decoded(map.0))
485 }
486}
487
488#[turbo_tasks::function]
489fn sourcemap_content_fs_root() -> Vc<FileSystemPath> {
490 VirtualFileSystem::new_with_name(rcstr!("sourcemap-content")).root()
491}
492
493#[turbo_tasks::function]
494async fn sourcemap_content_source(path: RcStr, content: RcStr) -> Result<Vc<Box<dyn Source>>> {
495 let path = sourcemap_content_fs_root().await?.join(&path)?;
496 let content = AssetContent::file(FileContent::new(File::from(content)).cell());
497 Ok(Vc::upcast(VirtualSource::new(path, content)))
498}
499
500impl SourceMap {
501 fn lookup_token_and_source_internal(
502 &self,
503 line: u32,
504 column: u32,
505 need_source_content: bool,
506 ) -> (Token, Option<Vc<Box<dyn Source>>>) {
507 let mut content: Option<Vc<Box<dyn Source>>> = None;
508
509 let token: Token = {
510 let map = &self.map;
511
512 let tok = map.lookup_token(line, column);
513 let mut token = tok.map(Token::from).unwrap_or_else(|| {
514 Token::Synthetic(SyntheticToken {
515 generated_line: line,
516 generated_column: column,
517 guessed_original_file: None,
518 })
519 });
520
521 if let Token::Synthetic(SyntheticToken {
522 guessed_original_file,
523 ..
524 }) = &mut token
525 && let DecodedMap::Regular(map) = &map.0
526 && map.get_source_count() == 1
527 {
528 let source = map.sources().next().unwrap().clone();
529 *guessed_original_file = Some(RcStr::from(source));
530 }
531
532 if need_source_content
533 && content.is_none()
534 && let Some(map) = map.as_regular_source_map()
535 {
536 content = tok.and_then(|tok| {
537 let src_id = tok.get_src_id();
538
539 let name = map.get_source(src_id);
540 let content = map.get_source_contents(src_id);
541
542 let (name, content) = name.zip(content)?;
543 Some(sourcemap_content_source(
544 name.clone().into(),
545 content.clone().into(),
546 ))
547 });
548 }
549
550 token
551 };
552
553 (token, content)
554 }
555}
556
557#[turbo_tasks::value_impl]
558impl GenerateSourceMap for SourceMap {
559 #[turbo_tasks::function]
560 fn generate_source_map(&self) -> Result<Vc<OptionStringifiedSourceMap>> {
561 Ok(Vc::cell(Some(self.to_rope()?)))
562 }
563}
564
565#[derive(Debug, RefCast)]
571#[repr(transparent)]
572pub struct CrateMapWrapper(DecodedMap);
573
574unsafe impl Send for CrateMapWrapper {}
577unsafe impl Sync for CrateMapWrapper {}
578
579#[derive(Debug, RefCast)]
585#[repr(transparent)]
586pub struct RegularMapWrapper(RegularMap);
587
588unsafe impl Send for RegularMapWrapper {}
591unsafe impl Sync for RegularMapWrapper {}
592
593impl CrateMapWrapper {
594 pub fn as_regular_source_map(&self) -> Option<Cow<'_, RegularMap>> {
595 match &self.0 {
596 DecodedMap::Regular(m) => Some(Cow::Borrowed(m)),
597 DecodedMap::Index(m) => m.flatten().map(Cow::Owned).ok(),
598 _ => None,
599 }
600 }
601}
602
603impl Deref for CrateMapWrapper {
604 type Target = DecodedMap;
605
606 fn deref(&self) -> &Self::Target {
607 &self.0
608 }
609}
610
611impl Serialize for CrateMapWrapper {
612 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
613 use serde::ser::Error;
614 let mut bytes = vec![];
615 self.0.to_writer(&mut bytes).map_err(Error::custom)?;
616 serializer.serialize_bytes(bytes.as_slice())
617 }
618}
619
620impl<'de> Deserialize<'de> for CrateMapWrapper {
621 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
622 use serde::de::Error;
623 let bytes = <&[u8]>::deserialize(deserializer)?;
624 let map = DecodedMap::from_reader(bytes).map_err(Error::custom)?;
625 Ok(CrateMapWrapper(map))
626 }
627}