turbopack_core/source_map/
mod.rs

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;
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
28/// Represents an empty value in a u32 variable in the sourcemap crate.
29static 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/// Allows callers to generate source maps.
43#[turbo_tasks::value_trait]
44pub trait GenerateSourceMap {
45    /// Generates a usable source map, capable of both tracing and stringifying.
46    #[turbo_tasks::function]
47    fn generate_source_map(self: Vc<Self>) -> Vc<OptionStringifiedSourceMap>;
48
49    /// Returns an individual section of the larger source map, if found.
50    #[turbo_tasks::function]
51    fn by_section(self: Vc<Self>, _section: RcStr) -> Vc<OptionStringifiedSourceMap> {
52        Vc::cell(None)
53    }
54}
55
56/// Implements the source map specification as either a decoded or sectioned sourcemap.
57///
58/// - A "decoded" map represents a source map as if it was came out of a JSON
59/// decode.
60/// - A "sectioned" source map is a tree of many [SourceMap]
61/// covering regions of an output file.
62///
63/// The distinction between the source map spec's [sourcemap::Index] and our
64/// [SourceMap::Sectioned] is whether the sections are represented with Vcs
65/// pointers.
66#[turbo_tasks::value(shared, cell = "new")]
67#[derive(Debug)]
68pub enum SourceMap {
69    /// A decoded source map contains no Vcs.
70    Decoded(#[turbo_tasks(trace_ignore)] InnerSourceMap),
71    /// A sectioned source map contains many (possibly recursive) maps covering
72    /// different regions of the file.
73    Sectioned(#[turbo_tasks(trace_ignore)] SectionedSourceMap),
74}
75
76#[turbo_tasks::value(transparent)]
77pub struct OptionSourceMap(Option<SourceMap>);
78
79#[turbo_tasks::value_impl]
80impl OptionSourceMap {
81    #[turbo_tasks::function]
82    pub fn none() -> Vc<Self> {
83        Vc::cell(None)
84    }
85}
86
87impl OptionSourceMap {
88    pub fn none_resolved() -> ResolvedVc<Self> {
89        ResolvedVc::cell(None)
90    }
91}
92#[turbo_tasks::value(transparent)]
93#[derive(Clone, Debug)]
94pub struct Tokens(Vec<Token>);
95
96/// A token represents a mapping in a source map.
97///
98/// It may either be Synthetic, meaning it was generated by some build tool and doesn't
99/// represent a location in a user-authored source file, or it is Original, meaning it represents a
100/// real location in source file.
101#[turbo_tasks::value]
102#[derive(Clone, Debug)]
103pub enum Token {
104    Synthetic(SyntheticToken),
105    Original(OriginalToken),
106}
107
108#[turbo_tasks::value]
109#[derive(Clone, Debug)]
110pub struct TokenWithSource {
111    pub token: Token,
112    pub source_content: Option<ResolvedVc<Box<dyn Source>>>,
113}
114
115/// A SyntheticToken represents a region of the generated file that was created
116/// by some build tool.
117#[turbo_tasks::value]
118#[derive(Clone, Debug)]
119pub struct SyntheticToken {
120    pub generated_line: u32,
121    pub generated_column: u32,
122    pub guessed_original_file: Option<RcStr>,
123}
124
125/// An OriginalToken represents a region of the generated file that exists in
126/// user-authored source file.
127#[turbo_tasks::value]
128#[derive(Clone, Debug)]
129pub struct OriginalToken {
130    pub generated_line: u32,
131    pub generated_column: u32,
132    pub original_file: RcStr,
133    pub original_line: u32,
134    pub original_column: u32,
135    pub name: Option<RcStr>,
136}
137
138impl Token {
139    pub fn generated_line(&self) -> u32 {
140        match self {
141            Self::Original(t) => t.generated_line,
142            Self::Synthetic(t) => t.generated_line,
143        }
144    }
145
146    pub fn generated_column(&self) -> u32 {
147        match self {
148            Self::Original(t) => t.generated_column,
149            Self::Synthetic(t) => t.generated_column,
150        }
151    }
152}
153
154impl From<swc_sourcemap::Token<'_>> for Token {
155    fn from(t: swc_sourcemap::Token) -> Self {
156        if t.has_source() {
157            Token::Original(OriginalToken {
158                generated_line: t.get_dst_line(),
159                generated_column: t.get_dst_col(),
160                original_file: RcStr::from(
161                    t.get_source()
162                        .expect("already checked token has source")
163                        .clone(),
164                ),
165                original_line: t.get_src_line(),
166                original_column: t.get_src_col(),
167                name: t.get_name().cloned().map(RcStr::from),
168            })
169        } else {
170            Token::Synthetic(SyntheticToken {
171                generated_line: t.get_dst_line(),
172                generated_column: t.get_dst_col(),
173                guessed_original_file: None,
174            })
175        }
176    }
177}
178
179impl TryInto<swc_sourcemap::RawToken> for Token {
180    type Error = std::num::ParseIntError;
181
182    fn try_into(self) -> Result<swc_sourcemap::RawToken, Self::Error> {
183        Ok(match self {
184            Self::Original(t) => swc_sourcemap::RawToken {
185                dst_col: t.generated_column,
186                dst_line: t.generated_line,
187                name_id: match t.name {
188                    None => SOURCEMAP_CRATE_NONE_U32,
189                    Some(name) => name.parse()?,
190                },
191                src_col: t.original_column,
192                src_line: t.original_line,
193                src_id: t.original_file.parse()?,
194                is_range: false,
195            },
196            Self::Synthetic(t) => swc_sourcemap::RawToken {
197                dst_col: t.generated_column,
198                dst_line: t.generated_line,
199                name_id: SOURCEMAP_CRATE_NONE_U32,
200                src_col: SOURCEMAP_CRATE_NONE_U32,
201                src_line: SOURCEMAP_CRATE_NONE_U32,
202                src_id: SOURCEMAP_CRATE_NONE_U32,
203                is_range: false,
204            },
205        })
206    }
207}
208
209impl SourceMap {
210    pub fn empty_uncelled() -> Self {
211        let mut builder = SourceMapBuilder::new(None);
212        builder.add(0, 0, 0, 0, None, None, false);
213        SourceMap::new_regular(builder.into_sourcemap())
214    }
215
216    /// Creates a new SourceMap::Decoded Vc out of a [RegularMap] instance.
217    pub fn new_regular(map: RegularMap) -> Self {
218        Self::new_decoded(DecodedMap::Regular(map))
219    }
220
221    /// Creates a new SourceMap::Decoded Vc out of a [DecodedMap] instance.
222    pub fn new_decoded(map: DecodedMap) -> Self {
223        SourceMap::Decoded(InnerSourceMap::new(map))
224    }
225
226    /// Creates a new SourceMap::Sectioned Vc out of a collection of source map
227    /// sections.
228    pub fn new_sectioned(sections: Vec<SourceMapSection>) -> Self {
229        SourceMap::Sectioned(SectionedSourceMap::new(sections))
230    }
231
232    pub fn new_from_rope(content: &Rope) -> Result<Option<Self>> {
233        let Ok(map) = DecodedMap::from_reader(content.read()) else {
234            return Ok(None);
235        };
236        Ok(Some(SourceMap::Decoded(InnerSourceMap::new(map))))
237    }
238
239    pub async fn new_from_file(file: FileSystemPath) -> Result<Option<Self>> {
240        let read = file.read();
241        Self::new_from_file_content(read).await
242    }
243
244    pub async fn new_from_file_content(content: Vc<FileContent>) -> Result<Option<Self>> {
245        let content = &content.await?;
246        let Some(contents) = content.as_content() else {
247            return Ok(None);
248        };
249        let Ok(map) = DecodedMap::from_reader(contents.read()) else {
250            return Ok(None);
251        };
252        Ok(Some(SourceMap::Decoded(InnerSourceMap::new(map))))
253    }
254}
255
256#[turbo_tasks::value_impl]
257impl SourceMap {
258    /// This function should be used sparingly to reduce memory usage, only in cold code paths
259    /// (issue resolving, etc).
260    #[turbo_tasks::function]
261    pub async fn new_from_rope_cached(
262        content: Vc<OptionStringifiedSourceMap>,
263    ) -> Result<Vc<OptionSourceMap>> {
264        let Some(content) = &*content.await? else {
265            return Ok(OptionSourceMap::none());
266        };
267        Ok(Vc::cell(SourceMap::new_from_rope(content)?))
268    }
269}
270
271impl SourceMap {
272    pub async fn to_source_map(&self) -> Result<Arc<CrateMapWrapper>> {
273        Ok(match self {
274            Self::Decoded(m) => m.map.clone(),
275            Self::Sectioned(m) => {
276                let wrapped = m.to_crate_wrapper().await?;
277                let sections = wrapped
278                    .sections
279                    .iter()
280                    .map(|s| {
281                        swc_sourcemap::SourceMapSection::new(
282                            (s.offset.line, s.offset.column),
283                            None,
284                            Some(s.map.0.clone()),
285                        )
286                    })
287                    .collect::<Vec<swc_sourcemap::SourceMapSection>>();
288                Arc::new(CrateMapWrapper(DecodedMap::Index(SourceMapIndex::new(
289                    None, sections,
290                ))))
291            }
292        })
293    }
294}
295
296static EMPTY_SOURCE_MAP_ROPE: Lazy<Rope> =
297    Lazy::new(|| Rope::from(r#"{"version":3,"sources":[],"names":[],"mappings":"A"}"#));
298
299impl SourceMap {
300    /// A source map that contains no actual source location information (no
301    /// `sources`, no mappings that point into a source). This is used to tell
302    /// Chrome that the generated code starting at a particular offset is no
303    /// longer part of the previous section's mappings.
304    pub fn empty() -> Self {
305        let mut builder = SourceMapBuilder::new(None);
306        builder.add(0, 0, 0, 0, None, None, false);
307        SourceMap::new_regular(builder.into_sourcemap())
308    }
309
310    /// A source map that contains no actual source location information (no
311    /// `sources`, no mappings that point into a source). This is used to tell
312    /// Chrome that the generated code starting at a particular offset is no
313    /// longer part of the previous section's mappings.
314    pub fn empty_rope() -> Rope {
315        EMPTY_SOURCE_MAP_ROPE.clone()
316    }
317
318    pub fn sections_to_rope(sections: impl IntoIterator<Item = (SourcePos, Rope)>) -> Result<Rope> {
319        let mut sections = sections.into_iter().peekable();
320
321        let mut first = sections.next();
322        if let Some((offset, map)) = &mut first
323            && sections.peek().is_none()
324            && *offset == (0, 0)
325        {
326            // There is just a single sourcemap that starts at the beginning of the file.
327            return Ok(std::mem::take(map));
328        }
329
330        // My kingdom for a decent dedent macro with interpolation!
331        // NOTE: The empty `sources` array is technically incorrect, but there is a bug
332        // in Node.js that requires sectioned source maps to have a `sources` array.
333        let mut rope = RopeBuilder::from(
334            r#"{
335  "version": 3,
336  "sources": [],
337  "sections": ["#,
338        );
339
340        let mut first_section = true;
341        for (offset, section_map) in first.into_iter().chain(sections) {
342            if !first_section {
343                rope += ",";
344            }
345            first_section = false;
346
347            write!(
348                rope,
349                r#"
350    {{"offset": {{"line": {}, "column": {}}}, "map": "#,
351                offset.line, offset.column,
352            )?;
353
354            rope += &section_map;
355
356            rope += "}";
357        }
358
359        rope += "]
360}";
361
362        Ok(rope.build())
363    }
364
365    /// Stringifies the source map into JSON bytes.
366    pub async fn to_rope(&self) -> Result<Rope> {
367        let rope = match self {
368            SourceMap::Decoded(r) => {
369                let mut bytes = vec![];
370                r.0.to_writer(&mut bytes)?;
371                Rope::from(bytes)
372            }
373
374            SourceMap::Sectioned(s) => {
375                let sections = s
376                    .sections
377                    .iter()
378                    .map(async |s| Ok((s.offset, s.map.to_rope().await?)))
379                    .try_join()
380                    .await?;
381
382                Self::sections_to_rope(sections)?
383            }
384        };
385        Ok(rope)
386    }
387
388    /// Traces a generated line/column into an mapping token representing either
389    /// synthetic code or user-authored original code.
390    pub async fn lookup_token(&self, line: u32, column: u32) -> Result<Token> {
391        let (token, _) = self
392            .lookup_token_and_source_internal(line, column, true)
393            .await?;
394        Ok(token)
395    }
396
397    /// Traces a generated line/column into an mapping token representing either
398    /// synthetic code or user-authored original code.
399    pub async fn lookup_token_and_source(&self, line: u32, column: u32) -> Result<TokenWithSource> {
400        let (token, content) = self
401            .lookup_token_and_source_internal(line, column, true)
402            .await?;
403        Ok(TokenWithSource {
404            token,
405            source_content: match content {
406                Some(v) => Some(v.to_resolved().await?),
407                None => None,
408            },
409        })
410    }
411
412    pub async fn with_resolved_sources(&self, origin: FileSystemPath) -> Result<Self> {
413        async fn resolve_source(
414            source_request: BytesStr,
415            source_content: Option<BytesStr>,
416            origin: FileSystemPath,
417        ) -> Result<(BytesStr, BytesStr)> {
418            Ok(
419                if let Some(path) = origin.parent().try_join(&source_request)? {
420                    let path_str = path.value_to_string().await?;
421                    let source = format!("{SOURCE_URL_PROTOCOL}///{path_str}");
422                    let source_content = if let Some(source_content) = source_content {
423                        source_content
424                    } else if let FileContent::Content(file) = &*path.read().await? {
425                        let text = file.content().to_str()?;
426                        text.to_string().into()
427                    } else {
428                        format!("unable to read source {path_str}").into()
429                    };
430                    (source.into(), source_content)
431                } else {
432                    let origin_str = origin.value_to_string().await?;
433                    static INVALID_REGEX: Lazy<Regex> =
434                        Lazy::new(|| Regex::new(r#"(?:^|/)(?:\.\.?(?:/|$))+"#).unwrap());
435                    let source = INVALID_REGEX
436                        .replace_all(&source_request, |s: &regex::Captures<'_>| {
437                            s[0].replace('.', "_")
438                        });
439                    let source = format!("{SOURCE_URL_PROTOCOL}///{origin_str}/{source}");
440                    let source_content = source_content.unwrap_or_else(|| {
441                        format!(
442                            "unable to access {source_request} in {origin_str} (it's leaving the \
443                             filesystem root)"
444                        )
445                        .into()
446                    });
447                    (source.into(), source_content)
448                },
449            )
450        }
451        async fn regular_map_with_resolved_sources(
452            map: &RegularMapWrapper,
453            origin: FileSystemPath,
454        ) -> Result<RegularMap> {
455            let map = &map.0;
456            let file = map.get_file().cloned();
457            let tokens = map.tokens().map(|t| t.get_raw_token()).collect();
458            let names = map.names().cloned().collect();
459            let count = map.get_source_count() as usize;
460            let sources = map.sources().cloned().collect::<Vec<_>>();
461            let source_contents = map
462                .source_contents()
463                .map(|s| s.cloned())
464                .collect::<Vec<_>>();
465            let mut new_sources = Vec::with_capacity(count);
466            let mut new_source_contents = Vec::with_capacity(count);
467            for (source, source_content) in sources.into_iter().zip(source_contents.into_iter()) {
468                let (source, name) = resolve_source(source, source_content, origin.clone()).await?;
469                new_sources.push(source);
470                new_source_contents.push(Some(name));
471            }
472            let mut map =
473                RegularMap::new(file, tokens, names, new_sources, Some(new_source_contents));
474
475            add_default_ignore_list(&mut map);
476
477            Ok(map)
478        }
479        async fn decoded_map_with_resolved_sources(
480            map: &CrateMapWrapper,
481            origin: FileSystemPath,
482        ) -> Result<CrateMapWrapper> {
483            Ok(CrateMapWrapper(match &map.0 {
484                DecodedMap::Regular(map) => {
485                    let map = RegularMapWrapper::ref_cast(map);
486                    DecodedMap::Regular(regular_map_with_resolved_sources(map, origin).await?)
487                }
488                DecodedMap::Index(map) => {
489                    let count = map.get_section_count() as usize;
490                    let file = map.get_file().cloned();
491                    let sections = map
492                        .sections()
493                        .filter_map(|section| {
494                            section
495                                .get_sourcemap()
496                                .map(|s| (section.get_offset(), CrateMapWrapper::ref_cast(s)))
497                        })
498                        .collect::<Vec<_>>();
499                    let sections = sections
500                        .into_iter()
501                        .map(|(offset, map)| {
502                            let origin = origin.clone();
503                            async move {
504                                Ok((
505                                    offset,
506                                    Box::pin(decoded_map_with_resolved_sources(
507                                        map,
508                                        origin.clone(),
509                                    ))
510                                    .await?,
511                                ))
512                            }
513                        })
514                        .try_join()
515                        .await?;
516                    let mut new_sections = Vec::with_capacity(count);
517                    for (offset, map) in sections {
518                        new_sections.push(swc_sourcemap::SourceMapSection::new(
519                            offset,
520                            // Urls are deprecated and we don't accept them
521                            None,
522                            Some(map.0),
523                        ));
524                    }
525                    DecodedMap::Index(SourceMapIndex::new(file, new_sections))
526                }
527                DecodedMap::Hermes(_) => {
528                    todo!("hermes source maps are not implemented");
529                }
530            }))
531        }
532        Ok(match self {
533            Self::Decoded(m) => {
534                let map = Box::pin(decoded_map_with_resolved_sources(&m.map, origin)).await?;
535                Self::Decoded(InnerSourceMap::new(map.0))
536            }
537            Self::Sectioned(m) => {
538                let mut sections = Vec::with_capacity(m.sections.len());
539                for section in &m.sections {
540                    let map = Box::pin(section.map.with_resolved_sources(origin.clone())).await?;
541                    sections.push(SourceMapSection::new(section.offset, map));
542                }
543                SourceMap::new_sectioned(sections)
544            }
545        })
546    }
547}
548
549#[turbo_tasks::function]
550fn sourcemap_content_fs_root() -> Vc<FileSystemPath> {
551    VirtualFileSystem::new_with_name("sourcemap-content".into()).root()
552}
553
554#[turbo_tasks::function]
555async fn sourcemap_content_source(path: RcStr, content: RcStr) -> Result<Vc<Box<dyn Source>>> {
556    let path = sourcemap_content_fs_root().await?.join(&path)?;
557    let content = AssetContent::file(FileContent::new(File::from(content)).cell());
558    Ok(Vc::upcast(VirtualSource::new(path, content)))
559}
560
561impl SourceMap {
562    async fn lookup_token_and_source_internal(
563        &self,
564        line: u32,
565        column: u32,
566        need_source_content: bool,
567    ) -> Result<(Token, Option<Vc<Box<dyn Source>>>)> {
568        let mut content: Option<Vc<Box<dyn Source>>> = None;
569
570        let token: Token = match self {
571            SourceMap::Decoded(map) => {
572                let tok = map.lookup_token(line, column);
573                let mut token = tok.map(Token::from).unwrap_or_else(|| {
574                    Token::Synthetic(SyntheticToken {
575                        generated_line: line,
576                        generated_column: column,
577                        guessed_original_file: None,
578                    })
579                });
580
581                if let Token::Synthetic(SyntheticToken {
582                    guessed_original_file,
583                    ..
584                }) = &mut token
585                    && let DecodedMap::Regular(map) = &map.map.0
586                    && map.get_source_count() == 1
587                {
588                    let source = map.sources().next().unwrap().clone();
589                    *guessed_original_file = Some(RcStr::from(source));
590                }
591
592                if need_source_content
593                    && content.is_none()
594                    && let Some(map) = map.map.as_regular_source_map()
595                {
596                    content = tok.and_then(|tok| {
597                        let src_id = tok.get_src_id();
598
599                        let name = map.get_source(src_id);
600                        let content = map.get_source_contents(src_id);
601
602                        let (name, content) = name.zip(content)?;
603                        Some(sourcemap_content_source(
604                            name.clone().into(),
605                            content.clone().into(),
606                        ))
607                    });
608                }
609
610                token
611            }
612
613            SourceMap::Sectioned(map) => {
614                let len = map.sections.len();
615                let mut low = 0;
616                let mut high = len;
617                let pos = SourcePos { line, column };
618
619                // A "greatest lower bound" binary search. We're looking for the closest section
620                // offset <= to our line/col.
621                while low < high {
622                    let mid = (low + high) / 2;
623                    if pos < map.sections[mid].offset {
624                        high = mid;
625                    } else {
626                        low = mid + 1;
627                    }
628                }
629
630                // Our GLB search will return the section immediately to the right of the
631                // section we actually want to recurse into, because the binary search does not
632                // early exit on an exact match (it'll `low = mid + 1`).
633                if low > 0 && low <= len {
634                    let SourceMapSection { map, offset } = &map.sections[low - 1];
635                    // We're looking for the position `l` lines into region covered by this
636                    // sourcemap's section.
637                    let l = line - offset.line;
638                    // The source map starts offset by the section's column only on its first line.
639                    // On the 2nd+ line, the source map covers starting at column 0.
640                    let c = if line == offset.line {
641                        column - offset.column
642                    } else {
643                        column
644                    };
645
646                    if need_source_content {
647                        let result = Box::pin(map.lookup_token_and_source(l, c)).await?;
648                        return Ok((result.token, result.source_content.map(|v| *v)));
649                    } else {
650                        return Ok((Box::pin(map.lookup_token(l, c)).await?, None));
651                    }
652                }
653                Token::Synthetic(SyntheticToken {
654                    generated_line: line,
655                    generated_column: column,
656                    guessed_original_file: None,
657                })
658            }
659        };
660
661        Ok((token, content))
662    }
663}
664
665#[turbo_tasks::value_impl]
666impl GenerateSourceMap for SourceMap {
667    #[turbo_tasks::function]
668    async fn generate_source_map(self: ResolvedVc<Self>) -> Result<Vc<OptionStringifiedSourceMap>> {
669        Ok(Vc::cell(Some(self.await?.to_rope().await?)))
670    }
671}
672
673/// A regular source map covers an entire file.
674#[derive(Clone, Debug, Serialize, Deserialize)]
675pub struct InnerSourceMap {
676    map: Arc<CrateMapWrapper>,
677}
678
679impl InnerSourceMap {
680    pub fn new(map: DecodedMap) -> Self {
681        InnerSourceMap {
682            map: Arc::new(CrateMapWrapper(map)),
683        }
684    }
685}
686
687impl Deref for InnerSourceMap {
688    type Target = Arc<CrateMapWrapper>;
689
690    fn deref(&self) -> &Self::Target {
691        &self.map
692    }
693}
694
695impl Eq for InnerSourceMap {}
696impl PartialEq for InnerSourceMap {
697    fn eq(&self, other: &Self) -> bool {
698        Arc::ptr_eq(&self.map, &other.map)
699    }
700}
701
702/// Wraps the DecodedMap struct so that it is Sync and Send.
703///
704/// # Safety
705///
706/// Must not use per line access to the SourceMap, as it is not thread safe.
707#[derive(Debug, RefCast)]
708#[repr(transparent)]
709pub struct CrateMapWrapper(DecodedMap);
710
711// Safety: DecodedMap contains a raw pointer, which isn't Send, which is
712// required to cache in a Vc. So, we have wrap it in 4 layers of cruft to do it.
713unsafe impl Send for CrateMapWrapper {}
714unsafe impl Sync for CrateMapWrapper {}
715
716/// Wraps the RegularMap struct so that it is Sync and Send.
717///
718/// # Safety
719///
720/// Must not use per line access to the SourceMap, as it is not thread safe.
721#[derive(Debug, RefCast)]
722#[repr(transparent)]
723pub struct RegularMapWrapper(RegularMap);
724
725// Safety: RegularMap contains a raw pointer, which isn't Send, which is
726// required to cache in a Vc. So, we have wrap it in 4 layers of cruft to do it.
727unsafe impl Send for RegularMapWrapper {}
728unsafe impl Sync for RegularMapWrapper {}
729
730#[derive(Debug)]
731pub struct CrateIndexWrapper {
732    pub sections: Vec<CrateSectionWrapper>,
733}
734
735#[derive(Debug)]
736pub struct CrateSectionWrapper {
737    pub offset: SourcePos,
738    pub map: Arc<CrateMapWrapper>,
739}
740
741impl CrateMapWrapper {
742    pub fn as_regular_source_map(&self) -> Option<Cow<'_, RegularMap>> {
743        match &self.0 {
744            DecodedMap::Regular(m) => Some(Cow::Borrowed(m)),
745            DecodedMap::Index(m) => m.flatten().map(Cow::Owned).ok(),
746            _ => None,
747        }
748    }
749}
750
751impl Deref for CrateMapWrapper {
752    type Target = DecodedMap;
753
754    fn deref(&self) -> &Self::Target {
755        &self.0
756    }
757}
758
759impl Serialize for CrateMapWrapper {
760    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
761        use serde::ser::Error;
762        let mut bytes = vec![];
763        self.0.to_writer(&mut bytes).map_err(Error::custom)?;
764        serializer.serialize_bytes(bytes.as_slice())
765    }
766}
767
768impl<'de> Deserialize<'de> for CrateMapWrapper {
769    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
770        use serde::de::Error;
771        let bytes = <&[u8]>::deserialize(deserializer)?;
772        let map = DecodedMap::from_reader(bytes).map_err(Error::custom)?;
773        Ok(CrateMapWrapper(map))
774    }
775}
776
777/// A sectioned source map contains many (possibly recursive) maps covering
778/// different regions of the file.
779#[derive(Eq, PartialEq, Debug, Serialize, Deserialize)]
780pub struct SectionedSourceMap {
781    sections: Vec<SourceMapSection>,
782}
783
784impl SectionedSourceMap {
785    pub fn new(sections: Vec<SourceMapSection>) -> Self {
786        Self { sections }
787    }
788
789    pub async fn to_crate_wrapper(&self) -> Result<CrateIndexWrapper> {
790        let mut sections = Vec::with_capacity(self.sections.len());
791        for section in &self.sections {
792            sections.push(section.to_crate_wrapper().await?);
793        }
794        Ok(CrateIndexWrapper { sections })
795    }
796}
797
798/// A section of a larger sectioned source map, which applies at source
799/// positions >= the offset (until the next section starts).
800#[derive(Eq, PartialEq, Debug, Serialize, Deserialize)]
801pub struct SourceMapSection {
802    offset: SourcePos,
803    map: SourceMap,
804}
805
806impl SourceMapSection {
807    pub fn new(offset: SourcePos, map: SourceMap) -> Self {
808        Self { offset, map }
809    }
810
811    pub async fn to_crate_wrapper(&self) -> Result<CrateSectionWrapper> {
812        let map = Box::pin(self.map.to_source_map()).await?;
813        Ok(CrateSectionWrapper {
814            offset: self.offset,
815            map,
816        })
817    }
818}