1use std::{
2 borrow::Cow,
3 io::Write,
4 ops::Deref,
5 sync::{Arc, LazyLock},
6};
7
8use anyhow::Result;
9use bincode::{
10 Decode, Encode,
11 de::Decoder,
12 enc::Encoder,
13 error::{DecodeError, EncodeError},
14};
15use bytes_str::BytesStr;
16use either::Either;
17use ref_cast::RefCast;
18use regex::Regex;
19use swc_sourcemap::{DecodedMap, SourceMap as RegularMap, SourceMapBuilder, SourceMapIndex};
20use turbo_rcstr::{RcStr, rcstr};
21use turbo_tasks::{ResolvedVc, TryJoinIterExt, ValueToStringRef, Vc};
22use turbo_tasks_fs::{
23 File, FileContent, FileSystem, FileSystemPath, VirtualFileSystem,
24 rope::{Rope, RopeBuilder},
25};
26
27use crate::{
28 SOURCE_URL_PROTOCOL, asset::AssetContent, source::Source,
29 source_map::utils::add_default_ignore_list, source_pos::SourcePos,
30 virtual_source::VirtualSource,
31};
32
33pub(crate) mod source_map_asset;
34pub mod structured;
35pub mod utils;
36
37pub use source_map_asset::SourceMapAsset;
38
39static SOURCEMAP_CRATE_NONE_U32: u32 = !0;
41
42#[turbo_tasks::value_trait]
44pub trait GenerateSourceMap {
45 #[turbo_tasks::function]
47 fn generate_source_map(self: Vc<Self>) -> Vc<FileContent>;
48
49 #[turbo_tasks::function]
51 fn by_section(self: Vc<Self>, _section: RcStr) -> Vc<FileContent> {
52 FileContent::NotFound.cell()
53 }
54}
55
56#[turbo_tasks::value(shared, cell = "new", eq = "manual")]
59#[derive(Debug)]
60pub struct SourceMap {
61 #[turbo_tasks(trace_ignore)]
63 map: Arc<CrateMapWrapper>,
64}
65impl Eq for SourceMap {}
66impl PartialEq for SourceMap {
67 fn eq(&self, other: &Self) -> bool {
68 Arc::ptr_eq(&self.map, &other.map)
69 }
70}
71
72#[turbo_tasks::value(transparent)]
73pub struct OptionSourceMap(Option<SourceMap>);
74
75#[turbo_tasks::value_impl]
76impl OptionSourceMap {
77 #[turbo_tasks::function]
78 pub fn none() -> Vc<Self> {
79 Vc::cell(None)
80 }
81}
82
83impl OptionSourceMap {
84 pub fn none_resolved() -> ResolvedVc<Self> {
85 ResolvedVc::cell(None)
86 }
87}
88
89#[turbo_tasks::value]
95#[derive(Clone, Debug)]
96pub enum Token {
97 Synthetic(SyntheticToken),
98 Original(OriginalToken),
99}
100
101#[turbo_tasks::value]
102#[derive(Clone, Debug)]
103pub struct TokenWithSource {
104 pub token: Token,
105 pub source_content: Option<ResolvedVc<Box<dyn Source>>>,
106}
107
108#[turbo_tasks::value]
111#[derive(Clone, Debug)]
112pub struct SyntheticToken {
113 pub generated_line: u32,
114 pub generated_column: u32,
115 pub guessed_original_file: Option<RcStr>,
116}
117
118#[turbo_tasks::value]
121#[derive(Clone, Debug)]
122pub struct OriginalToken {
123 pub generated_line: u32,
124 pub generated_column: u32,
125 pub original_file: RcStr,
126 pub original_line: u32,
127 pub original_column: u32,
128 pub name: Option<RcStr>,
129 pub is_ignored: bool,
131}
132
133impl Token {
134 pub fn generated_line(&self) -> u32 {
135 match self {
136 Self::Original(t) => t.generated_line,
137 Self::Synthetic(t) => t.generated_line,
138 }
139 }
140
141 pub fn generated_column(&self) -> u32 {
142 match self {
143 Self::Original(t) => t.generated_column,
144 Self::Synthetic(t) => t.generated_column,
145 }
146 }
147
148 pub fn with_offset(&self, line_offset: u32, column_offset: u32) -> Self {
149 match self {
150 Self::Original(t) => Self::Original(OriginalToken {
151 generated_line: t.generated_line + line_offset,
152 generated_column: if t.generated_line == 0 {
153 t.generated_column + column_offset
154 } else {
155 t.generated_column
156 },
157 original_file: t.original_file.clone(),
158 original_line: t.original_line,
159 original_column: t.original_column,
160 name: t.name.clone(),
161 is_ignored: t.is_ignored,
162 }),
163 Self::Synthetic(t) => Self::Synthetic(SyntheticToken {
164 generated_line: t.generated_line + line_offset,
165 generated_column: if t.generated_line == 0 {
166 t.generated_column + column_offset
167 } else {
168 t.generated_column
169 },
170 guessed_original_file: t.guessed_original_file.clone(),
171 }),
172 }
173 }
174}
175
176impl From<swc_sourcemap::Token<'_>> for Token {
177 fn from(t: swc_sourcemap::Token) -> Self {
178 if t.has_source() {
179 Token::Original(OriginalToken {
180 generated_line: t.get_dst_line(),
181 generated_column: t.get_dst_col(),
182 original_file: RcStr::from(
183 t.get_source()
184 .expect("already checked token has source")
185 .clone(),
186 ),
187 original_line: t.get_src_line(),
188 original_column: t.get_src_col(),
189 name: t.get_name().cloned().map(RcStr::from),
190 is_ignored: false,
194 })
195 } else {
196 Token::Synthetic(SyntheticToken {
197 generated_line: t.get_dst_line(),
198 generated_column: t.get_dst_col(),
199 guessed_original_file: None,
200 })
201 }
202 }
203}
204
205impl TryInto<swc_sourcemap::RawToken> for Token {
206 type Error = std::num::ParseIntError;
207
208 fn try_into(self) -> Result<swc_sourcemap::RawToken, Self::Error> {
209 Ok(match self {
210 Self::Original(t) => swc_sourcemap::RawToken {
211 dst_col: t.generated_column,
212 dst_line: t.generated_line,
213 name_id: match t.name {
214 None => SOURCEMAP_CRATE_NONE_U32,
215 Some(name) => name.parse()?,
216 },
217 src_col: t.original_column,
218 src_line: t.original_line,
219 src_id: t.original_file.parse()?,
220 is_range: false,
221 },
222 Self::Synthetic(t) => swc_sourcemap::RawToken {
223 dst_col: t.generated_column,
224 dst_line: t.generated_line,
225 name_id: SOURCEMAP_CRATE_NONE_U32,
226 src_col: SOURCEMAP_CRATE_NONE_U32,
227 src_line: SOURCEMAP_CRATE_NONE_U32,
228 src_id: SOURCEMAP_CRATE_NONE_U32,
229 is_range: false,
230 },
231 })
232 }
233}
234
235impl SourceMap {
236 fn new_regular(map: RegularMap) -> Self {
237 Self::new_decoded(DecodedMap::Regular(map))
238 }
239
240 fn new_decoded(map: DecodedMap) -> Self {
241 SourceMap {
242 map: Arc::new(CrateMapWrapper(map)),
243 }
244 }
245
246 pub fn new_from_rope(content: &Rope) -> Result<Option<Self>> {
247 let Ok(map) = DecodedMap::from_reader(content.read()) else {
248 return Ok(None);
249 };
250 Ok(Some(SourceMap::new_decoded(map)))
251 }
252}
253
254#[turbo_tasks::value_impl]
255impl SourceMap {
256 #[turbo_tasks::function]
259 pub async fn new_from_rope_cached(content: Vc<FileContent>) -> Result<Vc<OptionSourceMap>> {
260 let content = content.await?;
261 let Some(content) = content.as_content() else {
262 return Ok(OptionSourceMap::none());
263 };
264 Ok(Vc::cell(SourceMap::new_from_rope(content.content())?))
265 }
266}
267
268impl SourceMap {
269 pub fn to_source_map(&self) -> Arc<CrateMapWrapper> {
270 self.map.clone()
271 }
272}
273
274static EMPTY_SOURCE_MAP_ROPE: LazyLock<Rope> =
275 LazyLock::new(|| Rope::from(r#"{"version":3,"sources":[],"names":[],"mappings":"A"}"#));
276
277impl SourceMap {
278 pub fn empty() -> Self {
283 let mut builder = SourceMapBuilder::new(None);
284 builder.add(0, 0, 0, 0, None, None, false);
285 SourceMap::new_regular(builder.into_sourcemap())
286 }
287
288 pub fn empty_rope() -> Rope {
293 EMPTY_SOURCE_MAP_ROPE.clone()
294 }
295
296 pub fn sections_to_rope(
297 sections: impl IntoIterator<Item = (SourcePos, Rope)>,
298 debug_id: Option<RcStr>,
299 ) -> Rope {
300 let mut sections = sections.into_iter().peekable();
301
302 let mut first = sections.next();
303 if let Some((offset, map)) = &mut first
304 && sections.peek().is_none()
305 && *offset == (0, 0)
306 && debug_id.is_none()
307 {
308 return std::mem::take(map);
310 }
311
312 let mut rope = RopeBuilder::from(
316 r#"{
317 "version": 3,
318 "sources": [],
319"#,
320 );
321 if let Some(debug_id) = debug_id {
322 writeln!(rope, r#" "debugId": "{debug_id}","#).unwrap();
323 }
324 rope += " \"sections\": [";
325
326 let mut first_section = true;
327 for (offset, section_map) in first.into_iter().chain(sections) {
328 if !first_section {
329 rope += ",";
330 }
331 first_section = false;
332
333 write!(
334 rope,
335 r#"
336 {{"offset": {{"line": {}, "column": {}}}, "map": "#,
337 offset.line, offset.column,
338 )
339 .unwrap();
340
341 rope += §ion_map;
342
343 rope += "}";
344 }
345
346 rope += "]";
347
348 rope += "\n}";
349
350 rope.build()
351 }
352
353 pub fn to_rope(&self) -> Result<Rope> {
355 let mut bytes = vec![];
356 self.map.0.to_writer(&mut bytes)?;
357 Ok(Rope::from(bytes))
358 }
359
360 pub fn lookup_token(&self, line: u32, column: u32) -> Token {
363 let (token, _) = self.lookup_token_and_source_internal(line, column, true);
364 token
365 }
366
367 pub async fn lookup_token_and_source(&self, line: u32, column: u32) -> Result<TokenWithSource> {
370 let (token, content) = self.lookup_token_and_source_internal(line, column, true);
371 Ok(TokenWithSource {
372 token,
373 source_content: match content {
374 Some(v) => Some(v.to_resolved().await?),
375 None => None,
376 },
377 })
378 }
379
380 pub async fn with_resolved_sources(&self, origin: FileSystemPath) -> Result<Self> {
381 async fn resolve_source(
382 source_request: BytesStr,
383 source_content: Option<BytesStr>,
384 origin: FileSystemPath,
385 ) -> Result<(BytesStr, BytesStr)> {
386 Ok(
387 if let Some(path) = origin.parent().try_join(&source_request) {
388 let path_str = path.to_string_ref().await?;
389 let source = format!("{SOURCE_URL_PROTOCOL}///{path_str}");
390 let source_content = if let Some(source_content) = source_content {
391 source_content
392 } else if let FileContent::Content(file) = &*path.read().await? {
393 let text = file.content().to_str()?;
394 text.to_string().into()
395 } else {
396 format!("unable to read source {path_str}").into()
397 };
398 (source.into(), source_content)
399 } else {
400 let origin_str = origin.to_string_ref().await?;
401 static INVALID_REGEX: LazyLock<Regex> =
402 LazyLock::new(|| Regex::new(r#"(?:^|/)(?:\.\.?(?:/|$))+"#).unwrap());
403 let source = INVALID_REGEX
404 .replace_all(&source_request, |s: ®ex::Captures<'_>| {
405 s[0].replace('.', "_")
406 });
407 let source = format!("{SOURCE_URL_PROTOCOL}///{origin_str}/{source}");
408 let source_content = source_content.unwrap_or_else(|| {
409 format!(
410 "unable to access {source_request} in {origin_str} (it's leaving the \
411 filesystem root)"
412 )
413 .into()
414 });
415 (source.into(), source_content)
416 },
417 )
418 }
419 async fn regular_map_with_resolved_sources(
420 map: &RegularMapWrapper,
421 origin: FileSystemPath,
422 ) -> Result<RegularMap> {
423 let map = &map.0;
424 let file = map.get_file().cloned();
425 let tokens = map.tokens().map(|t| t.get_raw_token()).collect();
426 let names = map.names().cloned().collect();
427 let count = map.get_source_count() as usize;
428 let sources = map.sources().cloned().collect::<Vec<_>>();
429 let source_contents = map
430 .source_contents()
431 .map(|s| s.cloned())
432 .collect::<Vec<_>>();
433 let mut new_sources = Vec::with_capacity(count);
434 let mut new_source_contents = Vec::with_capacity(count);
435 for (source, source_content) in sources.into_iter().zip(source_contents) {
436 let (source, source_content) =
437 resolve_source(source, source_content, origin.clone()).await?;
438 new_sources.push(source);
439 new_source_contents.push(Some(source_content));
440 }
441 let mut map =
442 RegularMap::new(file, tokens, names, new_sources, Some(new_source_contents));
443
444 add_default_ignore_list(&mut map);
445
446 Ok(map)
447 }
448 async fn decoded_map_with_resolved_sources(
449 map: &CrateMapWrapper,
450 origin: FileSystemPath,
451 ) -> Result<CrateMapWrapper> {
452 Ok(CrateMapWrapper(match &map.0 {
453 DecodedMap::Regular(map) => {
454 let map = RegularMapWrapper::ref_cast(map);
455 DecodedMap::Regular(regular_map_with_resolved_sources(map, origin).await?)
456 }
457 DecodedMap::Index(map) => {
458 let count = map.get_section_count() as usize;
459 let file = map.get_file().cloned();
460 let sections = map
461 .sections()
462 .filter_map(|section| {
463 section
464 .get_sourcemap()
465 .map(|s| (section.get_offset(), CrateMapWrapper::ref_cast(s)))
466 })
467 .collect::<Vec<_>>();
468 let sections = sections
469 .into_iter()
470 .map(|(offset, map)| {
471 let origin = origin.clone();
472 async move {
473 Ok((
474 offset,
475 Box::pin(decoded_map_with_resolved_sources(
476 map,
477 origin.clone(),
478 ))
479 .await?,
480 ))
481 }
482 })
483 .try_join()
484 .await?;
485 let mut new_sections = Vec::with_capacity(count);
486 for (offset, map) in sections {
487 new_sections.push(swc_sourcemap::SourceMapSection::new(
488 offset,
489 None,
491 Some(map.0),
492 ));
493 }
494 DecodedMap::Index(SourceMapIndex::new(file, new_sections))
495 }
496 DecodedMap::Hermes(_) => {
497 todo!("hermes source maps are not implemented");
498 }
499 }))
500 }
501
502 let map = Box::pin(decoded_map_with_resolved_sources(&self.map, origin)).await?;
503 Ok(Self::new_decoded(map.0))
504 }
505}
506
507#[turbo_tasks::function]
508fn sourcemap_content_fs_root() -> Vc<FileSystemPath> {
509 VirtualFileSystem::new_with_name(rcstr!("sourcemap-content")).root()
510}
511
512#[turbo_tasks::function]
513async fn sourcemap_content_source(path: RcStr, content: RcStr) -> Result<Vc<Box<dyn Source>>> {
514 let path = sourcemap_content_fs_root().await?.join(&path)?;
515 let content = AssetContent::file(FileContent::new(File::from(content)).cell());
516 Ok(Vc::upcast(VirtualSource::new(path, content)))
517}
518
519impl SourceMap {
520 fn lookup_token_and_source_internal(
521 &self,
522 line: u32,
523 column: u32,
524 need_source_content: bool,
525 ) -> (Token, Option<Vc<Box<dyn Source>>>) {
526 let mut content: Option<Vc<Box<dyn Source>>> = None;
527
528 let token: Token = {
529 let map = &self.map;
530
531 let tok = map.lookup_token(line, column);
532 let mut token = tok.map(Token::from).unwrap_or_else(|| {
533 Token::Synthetic(SyntheticToken {
534 generated_line: line,
535 generated_column: column,
536 guessed_original_file: None,
537 })
538 });
539
540 if let Token::Synthetic(SyntheticToken {
541 guessed_original_file,
542 ..
543 }) = &mut token
544 && let DecodedMap::Regular(map) = &map.0
545 && map.get_source_count() == 1
546 {
547 let source = map.sources().next().unwrap().clone();
548 *guessed_original_file = Some(RcStr::from(source));
549 }
550
551 if let Some(flat_map) = map.as_regular_source_map() {
556 if let Token::Original(ref mut orig) = token
557 && let Some(source_name) = tok.and_then(|t| t.get_source().cloned())
558 && let Some(idx) = flat_map.sources().position(|s| *s == *source_name)
559 {
560 orig.is_ignored = flat_map.ignore_list().any(|id| *id == idx as u32);
561 }
562
563 if need_source_content && content.is_none() {
564 content = tok.and_then(|tok| {
565 let src_id = tok.get_src_id();
566
567 let name = flat_map.get_source(src_id);
568 let content = flat_map.get_source_contents(src_id);
569
570 let (name, content) = name.zip(content)?;
571 Some(sourcemap_content_source(
572 name.clone().into(),
573 content.clone().into(),
574 ))
575 });
576 }
577 }
578
579 token
580 };
581
582 (token, content)
583 }
584}
585
586impl SourceMap {
587 pub fn tokens(&self) -> impl Iterator<Item = Token> + '_ {
588 let map = &self.map;
589
590 fn regular_map_to_tokens(
591 map: &RegularMap,
592 offset_line: u32,
593 offset_column: u32,
594 ) -> impl Iterator<Item = Token> + '_ {
595 map.tokens()
596 .map(move |t| Token::from(t).with_offset(offset_line, offset_column))
597 }
598
599 fn index_map_to_tokens(
600 map: &SourceMapIndex,
601 offset_line: u32,
602 offset_column: u32,
603 ) -> impl Iterator<Item = Token> + '_ {
604 map.sections().flat_map(move |section| {
605 let (line, col) = section.get_offset();
606 let offset_line = offset_line + line;
607 let offset_column = if line == 0 { offset_column + col } else { col };
608 if let Some(source_map) = section.get_sourcemap() {
609 Either::Left(Box::new(decoded_map_to_tokens(
610 source_map,
611 offset_line,
612 offset_column,
613 )) as Box<dyn Iterator<Item = Token>>)
614 } else {
615 Either::Right(std::iter::empty())
616 }
617 })
618 }
619
620 fn decoded_map_to_tokens(
621 map: &DecodedMap,
622 offset_line: u32,
623 offset_column: u32,
624 ) -> impl Iterator<Item = Token> + '_ {
625 match map {
626 DecodedMap::Regular(map) => {
627 Either::Left(regular_map_to_tokens(map, offset_line, offset_column))
628 }
629 DecodedMap::Index(map) => {
630 Either::Right(index_map_to_tokens(map, offset_line, offset_column))
631 }
632 DecodedMap::Hermes(_) => {
633 todo!("hermes source maps are not implemented");
634 }
635 }
636 }
637
638 decoded_map_to_tokens(&map.0, 0, 0)
639 }
640}
641
642#[turbo_tasks::value_impl]
643impl GenerateSourceMap for SourceMap {
644 #[turbo_tasks::function]
645 fn generate_source_map(&self) -> Result<Vc<FileContent>> {
646 Ok(FileContent::Content(File::from(self.to_rope()?)).cell())
647 }
648}
649
650#[derive(Debug, RefCast)]
656#[repr(transparent)]
657pub struct CrateMapWrapper(DecodedMap);
658
659unsafe impl Send for CrateMapWrapper {}
662unsafe impl Sync for CrateMapWrapper {}
663
664#[derive(Debug, RefCast)]
670#[repr(transparent)]
671pub struct RegularMapWrapper(RegularMap);
672
673unsafe impl Send for RegularMapWrapper {}
676unsafe impl Sync for RegularMapWrapper {}
677
678impl CrateMapWrapper {
679 pub fn as_regular_source_map(&self) -> Option<Cow<'_, RegularMap>> {
680 match &self.0 {
681 DecodedMap::Regular(m) => Some(Cow::Borrowed(m)),
682 DecodedMap::Index(m) => m.flatten().map(Cow::Owned).ok(),
683 _ => None,
684 }
685 }
686}
687
688impl Deref for CrateMapWrapper {
689 type Target = DecodedMap;
690
691 fn deref(&self) -> &Self::Target {
692 &self.0
693 }
694}
695
696impl Encode for CrateMapWrapper {
697 fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
698 let mut bytes = Vec::new();
699 self.0
700 .to_writer(&mut bytes)
701 .map_err(|e| EncodeError::OtherString(e.to_string()))?;
702 bytes.encode(encoder)
703 }
704}
705
706impl<Context> Decode<Context> for CrateMapWrapper {
707 fn decode<D: Decoder<Context = Context>>(decoder: &mut D) -> Result<Self, DecodeError> {
708 let bytes = Vec::<u8>::decode(decoder)?;
709 let map = DecodedMap::from_reader(&*bytes)
710 .map_err(|e| DecodeError::OtherString(e.to_string()))?;
711 Ok(CrateMapWrapper(map))
712 }
713}
714
715bincode::impl_borrow_decode!(CrateMapWrapper);