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