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