turbopack_core/source_map/
mod.rs

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