sourcemap/
types.rs

1use std::borrow::Cow;
2use std::cmp::Ordering;
3use std::collections::BTreeSet;
4use std::fmt;
5use std::io::{Read, Write};
6use std::path::Path;
7use std::sync::Arc;
8
9use crate::builder::SourceMapBuilder;
10use crate::decoder::{decode, decode_slice};
11use crate::encoder::encode;
12use crate::errors::{Error, Result};
13use crate::hermes::SourceMapHermes;
14use crate::sourceview::SourceView;
15use crate::utils::{find_common_prefix, greatest_lower_bound};
16
17use debugid::DebugId;
18
19/// Controls the `SourceMap::rewrite` behavior
20///
21/// Default configuration:
22///
23/// * `with_names`: true
24/// * `with_source_contents`: true
25/// * `load_local_source_contents`: false
26#[derive(Debug, Clone)]
27pub struct RewriteOptions<'a> {
28    /// If enabled, names are kept in the rewritten sourcemap.
29    pub with_names: bool,
30    /// If enabled source contents are kept in the sourcemap.
31    pub with_source_contents: bool,
32    /// If enabled local source contents that are not in the
33    /// file are automatically inlined.
34    #[cfg(any(unix, windows, target_os = "redox"))]
35    pub load_local_source_contents: bool,
36    /// The base path to the used for source reference resolving
37    /// when loading local source contents is used.
38    pub base_path: Option<&'a Path>,
39    /// Optionally strips common prefixes from the sources.  If
40    /// an item in the list is set to `~` then the common prefix
41    /// of all sources is stripped.
42    pub strip_prefixes: &'a [&'a str],
43}
44
45impl<'a> Default for RewriteOptions<'a> {
46    fn default() -> RewriteOptions<'a> {
47        RewriteOptions {
48            with_names: true,
49            with_source_contents: true,
50            #[cfg(any(unix, windows, target_os = "redox"))]
51            load_local_source_contents: false,
52            base_path: None,
53            strip_prefixes: &[][..],
54        }
55    }
56}
57
58/// Represents the result of a decode operation
59///
60/// This represents either an actual sourcemap or a source map index.
61/// Usually the two things are too distinct to provide a common
62/// interface however for token lookup and writing back into a writer
63/// general methods are provided.
64#[derive(Debug, Clone, PartialEq)]
65pub enum DecodedMap {
66    /// Indicates a regular sourcemap
67    Regular(SourceMap),
68    /// Indicates a sourcemap index
69    Index(SourceMapIndex),
70    /// Indicates a sourcemap as generated by Metro+Hermes, as used by react-native
71    Hermes(SourceMapHermes),
72}
73
74impl DecodedMap {
75    /// Alias for `decode`.
76    pub fn from_reader<R: Read>(rdr: R) -> Result<DecodedMap> {
77        decode(rdr)
78    }
79
80    /// Writes a decoded sourcemap to a writer.
81    pub fn to_writer<W: Write>(&self, w: W) -> Result<()> {
82        match *self {
83            DecodedMap::Regular(ref sm) => encode(sm, w),
84            DecodedMap::Index(ref smi) => encode(smi, w),
85            DecodedMap::Hermes(ref smh) => encode(smh, w),
86        }
87    }
88
89    /// Shortcut to look up a token on either an index or a
90    /// regular sourcemap.  This method can only be used if
91    /// the contained index actually contains embedded maps
92    /// or it will not be able to look up anything.
93    pub fn lookup_token(&self, line: u32, col: u32) -> Option<Token<'_>> {
94        match *self {
95            DecodedMap::Regular(ref sm) => sm.lookup_token(line, col),
96            DecodedMap::Index(ref smi) => smi.lookup_token(line, col),
97            DecodedMap::Hermes(ref smh) => smh.lookup_token(line, col),
98        }
99    }
100
101    /// Returns the original function name.
102    ///
103    /// `minified_name` and `source_view` are not always necessary.  For
104    /// instance hermes source maps can provide this information without
105    /// access to the original sources.
106    pub fn get_original_function_name(
107        &self,
108        line: u32,
109        col: u32,
110        minified_name: Option<&str>,
111        source_view: Option<&SourceView>,
112    ) -> Option<&str> {
113        match *self {
114            DecodedMap::Regular(ref sm) => {
115                sm.get_original_function_name(line, col, minified_name?, source_view?)
116            }
117            DecodedMap::Index(ref smi) => {
118                smi.get_original_function_name(line, col, minified_name?, source_view?)
119            }
120            DecodedMap::Hermes(ref smh) => {
121                if line != 0 {
122                    return None;
123                }
124                smh.get_original_function_name(col)
125            }
126        }
127    }
128
129    /// Returns the debug ID of the sourcemap, if it exists.
130    pub fn debug_id(&self) -> Option<DebugId> {
131        match self {
132            DecodedMap::Regular(sm) => sm.get_debug_id(),
133            DecodedMap::Index(smi) => smi.debug_id(),
134            DecodedMap::Hermes(smh) => smh.get_debug_id(),
135        }
136    }
137
138    /// Sets the debug ID of the sourcemap.
139    pub fn set_debug_id(&mut self, debug_id: Option<DebugId>) {
140        match self {
141            DecodedMap::Regular(sm) => sm.set_debug_id(debug_id),
142            DecodedMap::Index(smi) => smi.set_debug_id(debug_id),
143            DecodedMap::Hermes(smh) => smh.set_debug_id(debug_id),
144        }
145    }
146}
147
148/// Represents a raw token
149///
150/// Raw tokens are used internally to represent the sourcemap
151/// in a memory efficient way.  If you construct sourcemaps yourself
152/// then you need to create these objects, otherwise they are invisible
153/// to you as a user.
154#[derive(PartialEq, Eq, Copy, Clone, Debug)]
155pub struct RawToken {
156    /// the destination (minified) line number (0-indexed)
157    pub dst_line: u32,
158    /// the destination (minified) column number (0-indexed)
159    pub dst_col: u32,
160    /// the source line number (0-indexed)
161    pub src_line: u32,
162    /// the source line column (0-indexed)
163    pub src_col: u32,
164    /// source identifier
165    pub src_id: u32,
166    /// name identifier (`!0` in case there is no associated name)
167    pub name_id: u32,
168
169    /// If true, this token is a range token.
170    ///
171    /// See <https://github.com/tc39/source-map-rfc/blob/main/proposals/range-mappings.md>
172    pub is_range: bool,
173}
174
175/// Represents a token from a sourcemap
176#[derive(Copy, Clone)]
177pub struct Token<'a> {
178    raw: &'a RawToken,
179    pub(crate) sm: &'a SourceMap,
180    pub(crate) idx: usize,
181    offset: u32,
182}
183
184impl<'a> Token<'a> {
185    /// The sourcemap this token is linked to.
186    pub fn sourcemap(&self) -> &'a SourceMap {
187        self.sm
188    }
189}
190
191impl PartialEq for Token<'_> {
192    fn eq(&self, other: &Token<'_>) -> bool {
193        self.raw == other.raw
194    }
195}
196
197impl Eq for Token<'_> {}
198
199impl PartialOrd for Token<'_> {
200    fn partial_cmp(&self, other: &Token<'_>) -> Option<Ordering> {
201        Some(self.cmp(other))
202    }
203}
204
205impl Ord for Token<'_> {
206    fn cmp(&self, other: &Token<'_>) -> Ordering {
207        macro_rules! try_cmp {
208            ($a:expr, $b:expr) => {
209                match $a.cmp(&$b) {
210                    Ordering::Equal => {}
211                    x => {
212                        return x;
213                    }
214                }
215            };
216        }
217        try_cmp!(self.get_dst_line(), other.get_dst_line());
218        try_cmp!(self.get_dst_col(), other.get_dst_col());
219        try_cmp!(self.get_source(), other.get_source());
220        try_cmp!(self.get_src_line(), other.get_src_line());
221        try_cmp!(self.get_src_col(), other.get_src_col());
222        try_cmp!(self.get_name(), other.get_name());
223        try_cmp!(self.is_range(), other.is_range());
224
225        Ordering::Equal
226    }
227}
228
229impl<'a> Token<'a> {
230    /// get the destination (minified) line number
231    pub fn get_dst_line(&self) -> u32 {
232        self.raw.dst_line
233    }
234
235    /// get the destination (minified) column number
236    pub fn get_dst_col(&self) -> u32 {
237        self.raw.dst_col
238    }
239
240    /// get the destination line and column
241    pub fn get_dst(&self) -> (u32, u32) {
242        (self.get_dst_line(), self.get_dst_col())
243    }
244
245    /// Get the source line number.
246    ///
247    /// `u32::MAX` is a sentinel value meaning
248    /// this token is unmapped.
249    pub fn get_src_line(&self) -> u32 {
250        self.raw.src_line
251    }
252
253    /// Get the source column number.
254    ///
255    /// `u32::MAX` is a sentinel value meaning
256    /// this token is unmapped.
257    pub fn get_src_col(&self) -> u32 {
258        self.raw.src_col.saturating_add(self.offset)
259    }
260
261    /// get the source line and column
262    pub fn get_src(&self) -> (u32, u32) {
263        (self.get_src_line(), self.get_src_col())
264    }
265
266    /// Return the source ID of the token
267    pub fn get_src_id(&self) -> u32 {
268        self.raw.src_id
269    }
270
271    /// get the source if it exists as string
272    pub fn get_source(&self) -> Option<&'a str> {
273        if self.raw.src_id == !0 {
274            None
275        } else {
276            self.sm.get_source(self.raw.src_id)
277        }
278    }
279
280    /// Is there a source for this token?
281    pub fn has_source(&self) -> bool {
282        self.raw.src_id != !0
283    }
284
285    /// get the name if it exists as string
286    pub fn get_name(&self) -> Option<&'a str> {
287        if self.raw.name_id == !0 {
288            None
289        } else {
290            self.sm.get_name(self.raw.name_id)
291        }
292    }
293
294    /// returns `true` if a name exists, `false` otherwise
295    pub fn has_name(&self) -> bool {
296        self.get_name().is_some()
297    }
298
299    /// Return the name ID of the token
300    pub fn get_name_id(&self) -> u32 {
301        self.raw.name_id
302    }
303
304    /// Converts the token into a debug tuple in the form
305    /// `(source, src_line, src_col, name)`
306    pub fn to_tuple(&self) -> (&'a str, u32, u32, Option<&'a str>) {
307        (
308            self.get_source().unwrap_or(""),
309            self.get_src_line(),
310            self.get_src_col(),
311            self.get_name(),
312        )
313    }
314
315    /// Get the underlying raw token
316    pub fn get_raw_token(&self) -> RawToken {
317        *self.raw
318    }
319
320    /// Returns the referenced source view.
321    pub fn get_source_view(&self) -> Option<&SourceView> {
322        self.sm.get_source_view(self.get_src_id())
323    }
324
325    /// If true, this token is a range token.
326    ///
327    /// See <https://github.com/tc39/source-map-rfc/blob/main/proposals/range-mappings.md>
328    pub fn is_range(&self) -> bool {
329        self.raw.is_range
330    }
331}
332
333/// Iterates over all tokens in a sourcemap
334pub struct TokenIter<'a> {
335    i: &'a SourceMap,
336    next_idx: usize,
337}
338
339impl TokenIter<'_> {
340    pub fn seek(&mut self, line: u32, col: u32) -> bool {
341        let token = self.i.lookup_token(line, col);
342        match token {
343            Some(token) => {
344                self.next_idx = token.idx + 1;
345                true
346            }
347            None => false,
348        }
349    }
350}
351
352impl<'a> Iterator for TokenIter<'a> {
353    type Item = Token<'a>;
354
355    fn next(&mut self) -> Option<Token<'a>> {
356        self.i.get_token(self.next_idx).inspect(|_| {
357            self.next_idx += 1;
358        })
359    }
360}
361
362/// Iterates over all sources in a sourcemap
363pub struct SourceIter<'a> {
364    i: &'a SourceMap,
365    next_idx: u32,
366}
367
368impl<'a> Iterator for SourceIter<'a> {
369    type Item = &'a str;
370
371    fn next(&mut self) -> Option<&'a str> {
372        self.i.get_source(self.next_idx).inspect(|_| {
373            self.next_idx += 1;
374        })
375    }
376}
377
378/// Iterates over all source contents in a sourcemap
379pub struct SourceContentsIter<'a> {
380    i: &'a SourceMap,
381    next_idx: u32,
382}
383
384impl<'a> Iterator for SourceContentsIter<'a> {
385    type Item = Option<&'a str>;
386
387    fn next(&mut self) -> Option<Option<&'a str>> {
388        if self.next_idx >= self.i.get_source_count() {
389            None
390        } else {
391            let rv = Some(self.i.get_source_contents(self.next_idx));
392            self.next_idx += 1;
393            rv
394        }
395    }
396}
397
398/// Iterates over all tokens in a sourcemap
399pub struct NameIter<'a> {
400    i: &'a SourceMap,
401    next_idx: u32,
402}
403
404impl<'a> Iterator for NameIter<'a> {
405    type Item = &'a str;
406
407    fn next(&mut self) -> Option<&'a str> {
408        self.i.get_name(self.next_idx).inspect(|_| {
409            self.next_idx += 1;
410        })
411    }
412}
413
414impl fmt::Debug for Token<'_> {
415    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
416        write!(f, "<Token {self:#}>")
417    }
418}
419
420impl fmt::Display for Token<'_> {
421    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
422        write!(
423            f,
424            "{}:{}:{}{}",
425            self.get_source().unwrap_or("<unknown>"),
426            self.get_src_line(),
427            self.get_src_col(),
428            self.get_name()
429                .map(|x| format!(" name={x}"))
430                .unwrap_or_default()
431        )?;
432        if f.alternate() {
433            write!(
434                f,
435                " ({}:{}){}",
436                self.get_dst_line(),
437                self.get_dst_col(),
438                if self.is_range() { " (range)" } else { "" }
439            )?;
440        }
441        Ok(())
442    }
443}
444
445/// Represents a section in a sourcemap index
446#[derive(Debug, Clone, PartialEq)]
447pub struct SourceMapSection {
448    offset: (u32, u32),
449    url: Option<String>,
450    map: Option<Box<DecodedMap>>,
451}
452
453/// Iterates over all sections in a sourcemap index
454pub struct SourceMapSectionIter<'a> {
455    i: &'a SourceMapIndex,
456    next_idx: u32,
457}
458
459impl<'a> Iterator for SourceMapSectionIter<'a> {
460    type Item = &'a SourceMapSection;
461
462    fn next(&mut self) -> Option<&'a SourceMapSection> {
463        self.i.get_section(self.next_idx).inspect(|_| {
464            self.next_idx += 1;
465        })
466    }
467}
468
469/// Represents a sourcemap index in memory
470#[derive(Debug, Clone, PartialEq)]
471pub struct SourceMapIndex {
472    file: Option<String>,
473    sections: Vec<SourceMapSection>,
474    x_facebook_offsets: Option<Vec<Option<u32>>>,
475    x_metro_module_paths: Option<Vec<String>>,
476    debug_id: Option<DebugId>,
477}
478
479/// Represents a sourcemap in memory
480///
481/// This is always represents a regular "non-indexed" sourcemap.  Particularly
482/// in case the `from_reader` method is used an index sourcemap will be
483/// rejected with an error on reading.
484#[derive(Clone, Debug, PartialEq)]
485pub struct SourceMap {
486    pub(crate) file: Option<Arc<str>>,
487    pub(crate) tokens: Vec<RawToken>,
488    pub(crate) names: Vec<Arc<str>>,
489    pub(crate) source_root: Option<Arc<str>>,
490    pub(crate) sources: Vec<Arc<str>>,
491    pub(crate) sources_prefixed: Option<Vec<Arc<str>>>,
492    pub(crate) sources_content: Vec<Option<SourceView>>,
493    pub(crate) ignore_list: BTreeSet<u32>,
494    pub(crate) debug_id: Option<DebugId>,
495}
496
497impl SourceMap {
498    /// Creates a sourcemap from a reader over a JSON stream in UTF-8
499    /// format.  Optionally a "garbage header" as defined by the
500    /// sourcemap draft specification is supported.  In case an indexed
501    /// sourcemap is encountered an error is returned.
502    ///
503    /// ```rust
504    /// use sourcemap::SourceMap;
505    /// let input: &[_] = b"{
506    ///     \"version\":3,
507    ///     \"sources\":[\"coolstuff.js\"],
508    ///     \"names\":[\"x\",\"alert\"],
509    ///     \"mappings\":\"AAAA,GAAIA,GAAI,EACR,IAAIA,GAAK,EAAG,CACVC,MAAM\"
510    /// }";
511    /// let sm = SourceMap::from_reader(input).unwrap();
512    /// ```
513    ///
514    /// While sourcemaps objects permit some modifications, it's generally
515    /// not possible to modify tokens after they have been added.  For
516    /// creating sourcemaps from scratch or for general operations for
517    /// modifying a sourcemap have a look at the `SourceMapBuilder`.
518    pub fn from_reader<R: Read>(rdr: R) -> Result<SourceMap> {
519        match decode(rdr)? {
520            DecodedMap::Regular(sm) => Ok(sm),
521            _ => Err(Error::IncompatibleSourceMap),
522        }
523    }
524
525    /// Writes a sourcemap into a writer.
526    ///
527    /// Note that this operation will generate an equivalent sourcemap to the
528    /// one that was generated on load however there might be small differences
529    /// in the generated JSON and layout. For instance `sourceRoot` will not
530    /// be set as upon parsing of the sourcemap the sources will already be
531    /// expanded.
532    ///
533    /// ```rust
534    /// # use sourcemap::SourceMap;
535    /// # let input: &[_] = b"{
536    /// #     \"version\":3,
537    /// #     \"sources\":[\"coolstuff.js\"],
538    /// #     \"names\":[\"x\",\"alert\"],
539    /// #     \"mappings\":\"AAAA,GAAIA,GAAI,EACR,IAAIA,GAAK,EAAG,CACVC,MAAM\"
540    /// # }";
541    /// let sm = SourceMap::from_reader(input).unwrap();
542    /// let mut output : Vec<u8> = vec![];
543    /// sm.to_writer(&mut output).unwrap();
544    /// ```
545    pub fn to_writer<W: Write>(&self, w: W) -> Result<()> {
546        encode(self, w)
547    }
548
549    /// Encode a sourcemap into a data url.
550    ///
551    /// ```rust
552    /// # use sourcemap::SourceMap;
553    /// # let input: &[_] = b"{
554    /// #     \"version\":3,
555    /// #     \"sources\":[\"coolstuff.js\"],
556    /// #     \"names\":[\"x\",\"alert\"],
557    /// #     \"mappings\":\"AAAA,GAAIA,GAAI,EACR,IAAIA,GAAK,EAAG,CACVC,MAAM\"
558    /// # }";
559    /// let sm = SourceMap::from_reader(input).unwrap();
560    /// sm.to_data_url().unwrap();
561    /// ```
562    pub fn to_data_url(&self) -> Result<String> {
563        let mut buf = vec![];
564        encode(self, &mut buf)?;
565        let b64 = base64_simd::STANDARD.encode_to_string(&buf);
566        Ok(format!("data:application/json;charset=utf-8;base64,{b64}"))
567    }
568
569    /// Creates a sourcemap from a reader over a JSON byte slice in UTF-8
570    /// format.  Optionally a "garbage header" as defined by the
571    /// sourcemap draft specification is supported.  In case an indexed
572    /// sourcemap is encountered an error is returned.
573    ///
574    /// ```rust
575    /// use sourcemap::SourceMap;
576    /// let input: &[_] = b"{
577    ///     \"version\":3,
578    ///     \"sources\":[\"coolstuff.js\"],
579    ///     \"names\":[\"x\",\"alert\"],
580    ///     \"mappings\":\"AAAA,GAAIA,GAAI,EACR,IAAIA,GAAK,EAAG,CACVC,MAAM\"
581    /// }";
582    /// let sm = SourceMap::from_slice(input).unwrap();
583    /// ```
584    pub fn from_slice(slice: &[u8]) -> Result<SourceMap> {
585        match decode_slice(slice)? {
586            DecodedMap::Regular(sm) => Ok(sm),
587            _ => Err(Error::IncompatibleSourceMap),
588        }
589    }
590
591    /// Constructs a new sourcemap from raw components.
592    ///
593    /// - `file`: an optional filename of the sourcemap
594    /// - `tokens`: a list of raw tokens
595    /// - `names`: a vector of names
596    /// - `sources` a vector of source filenames
597    /// - `sources_content` optional source contents
598    /// - `ignore_list` optional list of source indexes for devtools to ignore
599    pub fn new(
600        file: Option<Arc<str>>,
601        mut tokens: Vec<RawToken>,
602        names: Vec<Arc<str>>,
603        sources: Vec<Arc<str>>,
604        sources_content: Option<Vec<Option<Arc<str>>>>,
605    ) -> SourceMap {
606        tokens.sort_unstable_by_key(|t| (t.dst_line, t.dst_col));
607        SourceMap {
608            file,
609            tokens,
610            names,
611            source_root: None,
612            sources,
613            sources_prefixed: None,
614            sources_content: sources_content
615                .unwrap_or_default()
616                .into_iter()
617                .map(|opt| opt.map(SourceView::new))
618                .collect(),
619            ignore_list: BTreeSet::default(),
620            debug_id: None,
621        }
622    }
623
624    /// Returns the embedded debug id.
625    pub fn get_debug_id(&self) -> Option<DebugId> {
626        self.debug_id
627    }
628
629    /// Sets a new value for the debug id.
630    pub fn set_debug_id(&mut self, debug_id: Option<DebugId>) {
631        self.debug_id = debug_id
632    }
633
634    /// Returns the embedded filename in case there is one.
635    pub fn get_file(&self) -> Option<&str> {
636        self.file.as_deref()
637    }
638
639    /// Sets a new value for the file.
640    pub fn set_file<T: Into<Arc<str>>>(&mut self, value: Option<T>) {
641        self.file = value.map(Into::into);
642    }
643
644    /// Returns the embedded source_root in case there is one.
645    pub fn get_source_root(&self) -> Option<&str> {
646        self.source_root.as_deref()
647    }
648
649    fn prefix_source(source_root: &str, source: &str) -> Arc<str> {
650        let source_root = source_root.strip_suffix('/').unwrap_or(source_root);
651        let is_valid = !source.is_empty()
652            && (source.starts_with('/')
653                || source.starts_with("http:")
654                || source.starts_with("https:"));
655
656        if is_valid {
657            source.into()
658        } else {
659            format!("{source_root}/{source}").into()
660        }
661    }
662
663    /// Sets a new value for the source_root.
664    pub fn set_source_root<T: Into<Arc<str>>>(&mut self, value: Option<T>) {
665        self.source_root = value.map(Into::into);
666
667        match self.source_root.as_deref().filter(|rs| !rs.is_empty()) {
668            Some(source_root) => {
669                let sources_prefixed = self
670                    .sources
671                    .iter()
672                    .map(|source| Self::prefix_source(source_root, source))
673                    .collect();
674                self.sources_prefixed = Some(sources_prefixed)
675            }
676            None => self.sources_prefixed = None,
677        }
678    }
679
680    pub fn add_to_ignore_list(&mut self, src_id: u32) {
681        self.ignore_list.insert(src_id);
682    }
683
684    pub fn ignore_list(&self) -> impl Iterator<Item = &u32> {
685        self.ignore_list.iter()
686    }
687
688    /// Looks up a token by its index.
689    pub fn get_token(&self, idx: usize) -> Option<Token<'_>> {
690        self.tokens.get(idx).map(|raw| Token {
691            raw,
692            sm: self,
693            idx,
694            offset: 0,
695        })
696    }
697
698    /// Returns the number of tokens in the sourcemap.
699    pub fn get_token_count(&self) -> u32 {
700        self.tokens.len() as u32
701    }
702
703    /// Returns an iterator over the tokens.
704    pub fn tokens(&self) -> TokenIter<'_> {
705        TokenIter {
706            i: self,
707            next_idx: 0,
708        }
709    }
710
711    /// Looks up the closest token to a given 0-indexed line and column.
712    pub fn lookup_token(&self, line: u32, col: u32) -> Option<Token<'_>> {
713        let (idx, raw) =
714            greatest_lower_bound(&self.tokens, &(line, col), |t| (t.dst_line, t.dst_col))?;
715
716        let mut token = Token {
717            raw,
718            sm: self,
719            idx,
720            offset: 0,
721        };
722
723        if token.is_range() {
724            token.offset = col - token.get_dst_col();
725        }
726
727        Some(token)
728    }
729
730    /// Given a location, name and minified source file resolve a minified
731    /// name to an original function name.
732    ///
733    /// This invokes some guesswork and requires access to the original minified
734    /// source.  This will not yield proper results for anonymous functions or
735    /// functions that do not have clear function names.  (For instance it's
736    /// recommended that dotted function names are not passed to this
737    /// function).
738    pub fn get_original_function_name(
739        &self,
740        line: u32,
741        col: u32,
742        minified_name: &str,
743        sv: &SourceView,
744    ) -> Option<&str> {
745        self.lookup_token(line, col)
746            .and_then(|token| sv.get_original_function_name(token, minified_name))
747    }
748
749    /// Returns the number of sources in the sourcemap.
750    pub fn get_source_count(&self) -> u32 {
751        self.sources.len() as u32
752    }
753
754    /// Looks up a source for a specific index.
755    pub fn get_source(&self, idx: u32) -> Option<&str> {
756        let sources = self.sources_prefixed.as_deref().unwrap_or(&self.sources);
757        sources.get(idx as usize).map(|x| &x[..])
758    }
759
760    /// Sets a new source value for an index.  This cannot add new
761    /// sources.
762    ///
763    /// This panics if a source is set that does not exist.
764    pub fn set_source(&mut self, idx: u32, value: &str) {
765        self.sources[idx as usize] = value.into();
766
767        if let Some(sources_prefixed) = self.sources_prefixed.as_mut() {
768            // If sources_prefixed is `Some`, we must have a nonempty `source_root`.
769            sources_prefixed[idx as usize] =
770                Self::prefix_source(self.source_root.as_deref().unwrap(), value);
771        }
772    }
773
774    /// Iterates over all sources
775    pub fn sources(&self) -> SourceIter<'_> {
776        SourceIter {
777            i: self,
778            next_idx: 0,
779        }
780    }
781
782    /// Returns the sources content as source view.
783    pub fn get_source_view(&self, idx: u32) -> Option<&SourceView> {
784        self.sources_content
785            .get(idx as usize)
786            .and_then(Option::as_ref)
787    }
788
789    /// Looks up the content for a source.
790    pub fn get_source_contents(&self, idx: u32) -> Option<&str> {
791        self.sources_content
792            .get(idx as usize)
793            .and_then(Option::as_ref)
794            .map(SourceView::source)
795    }
796
797    /// Sets source contents for a source.
798    pub fn set_source_contents(&mut self, idx: u32, value: Option<&str>) {
799        if self.sources_content.len() != self.sources.len() {
800            self.sources_content.resize(self.sources.len(), None);
801        }
802        self.sources_content[idx as usize] = value.map(|x| SourceView::from_string(x.to_string()));
803    }
804
805    /// Iterates over all source contents
806    pub fn source_contents(&self) -> SourceContentsIter<'_> {
807        SourceContentsIter {
808            i: self,
809            next_idx: 0,
810        }
811    }
812
813    /// Returns an iterator over the names.
814    pub fn names(&self) -> NameIter<'_> {
815        NameIter {
816            i: self,
817            next_idx: 0,
818        }
819    }
820
821    /// Returns the number of names in the sourcemap.
822    pub fn get_name_count(&self) -> u32 {
823        self.names.len() as u32
824    }
825
826    /// Returns true if there are any names in the map.
827    pub fn has_names(&self) -> bool {
828        !self.names.is_empty()
829    }
830
831    /// Looks up a name for a specific index.
832    pub fn get_name(&self, idx: u32) -> Option<&str> {
833        self.names.get(idx as usize).map(|x| &x[..])
834    }
835
836    /// Removes all names from the sourcemap.
837    pub fn remove_names(&mut self) {
838        self.names.clear();
839    }
840
841    /// This rewrites the sourcemap according to the provided rewrite
842    /// options.
843    ///
844    /// The default behavior is to just deduplicate the sourcemap, something
845    /// that automatically takes place.  This for instance can be used to
846    /// slightly compress sourcemaps if certain data is not wanted.
847    ///
848    /// ```rust
849    /// use sourcemap::{SourceMap, RewriteOptions};
850    /// # let input: &[_] = b"{
851    /// #     \"version\":3,
852    /// #     \"sources\":[\"coolstuff.js\"],
853    /// #     \"names\":[\"x\",\"alert\"],
854    /// #     \"mappings\":\"AAAA,GAAIA,GAAI,EACR,IAAIA,GAAK,EAAG,CACVC,MAAM\"
855    /// # }";
856    /// let sm = SourceMap::from_slice(input).unwrap();
857    /// let new_sm = sm.rewrite(&RewriteOptions {
858    ///     with_names: false,
859    ///     ..Default::default()
860    /// });
861    /// ```
862    pub fn rewrite(self, options: &RewriteOptions<'_>) -> Result<SourceMap> {
863        Ok(self.rewrite_with_mapping(options)?.0)
864    }
865
866    /// Same as `rewrite`, except also returns a remapping index for deduplicated `sources`.
867    pub(crate) fn rewrite_with_mapping(
868        self,
869        options: &RewriteOptions<'_>,
870    ) -> Result<(SourceMap, Vec<u32>)> {
871        let mut builder = SourceMapBuilder::new(self.get_file());
872        builder.set_debug_id(self.debug_id);
873
874        for token in self.tokens() {
875            let raw = builder.add_token(&token, options.with_names);
876            if raw.src_id != !0
877                && options.with_source_contents
878                && !builder.has_source_contents(raw.src_id)
879            {
880                builder
881                    .set_source_contents(raw.src_id, self.get_source_contents(token.get_src_id()));
882            }
883        }
884
885        #[cfg(any(unix, windows, target_os = "redox"))]
886        {
887            if options.load_local_source_contents {
888                builder.load_local_source_contents(options.base_path)?;
889            }
890        }
891
892        let mut prefixes = vec![];
893        let mut need_common_prefix = false;
894        for &prefix in options.strip_prefixes.iter() {
895            if prefix == "~" {
896                need_common_prefix = true;
897            } else {
898                prefixes.push(prefix.to_string());
899            }
900        }
901        if need_common_prefix {
902            if let Some(prefix) = find_common_prefix(self.sources.iter().map(AsRef::as_ref)) {
903                prefixes.push(prefix);
904            }
905        }
906        if !prefixes.is_empty() {
907            builder.strip_prefixes(&prefixes);
908        }
909
910        let mapping = builder.take_mapping();
911
912        let sm = builder.into_sourcemap();
913
914        Ok((sm, mapping))
915    }
916
917    /// Adjusts the mappings in `self` using the mappings in `adjustment`.
918    ///
919    /// Here is the intended use case for this function:
920    /// * You have a source file (for example, minified JS) `foo.js` and a
921    ///   corresponding sourcemap `foo.js.map`.
922    /// * You modify `foo.js` in some way and generate a sourcemap `transform.js.map`
923    ///   representing this modification. This can be done using `magic-string`, for example.
924    /// * You want a sourcemap that is "like" `foo.js.map`, but takes the changes you made to `foo.js` into account.
925    ///
926    /// Then `foo.js.map.adjust_mappings(transform.js.map)` is the desired sourcemap.
927    ///
928    /// This function assumes that `adjustment` contains no relevant information except for mappings.
929    ///  All information about sources and names is copied from `self`.
930    ///
931    /// Note that the resulting sourcemap will be at most as fine-grained as `self.`.
932    pub fn adjust_mappings(&mut self, adjustment: &Self) {
933        // The algorithm works by going through the tokens in `self` in order and adjusting
934        // them depending on the token in `adjustment` they're "covered" by.
935        // For example:
936        // Let `l` be a token in `adjustment` mapping `(17, 23)` to `(8, 30)` and let
937        // `r₁ : (8, 28) -> (102, 35)`, `r₂ : (8, 40) -> (102, 50)`, and
938        // `r₃ : (9, 10) -> (103, 12)` be the tokens in `self` that fall in the range of `l`.
939        // `l` offsets these tokens by `(+9, -7)`, so `r₁, … , r₃` must be offset by the same
940        // amount. Thus, the adjusted sourcemap will contain the tokens
941        // `c₁ : (17, 23) -> (102, 35)`, `c₂ : (17, 33) -> (102, 50)`, and
942        // `c3 : (18, 3) -> (103, 12)`.
943        //
944        // Or, in diagram form:
945        //
946        //    (17, 23)                                    (position in the edited source file)
947        //    ↓ l
948        //    (8, 30)
949        // (8, 28)        (8, 40)        (9, 10)          (positions in the original source file)
950        // ↓ r₁           ↓ r₂           ↓ r₃
951        // (102, 35)      (102, 50)      (103, 12)        (positions in the target file)
952        //
953        // becomes
954        //
955        //    (17, 23)       (17, 33)       (18, 3)       (positions in the edited source file)
956        //    ↓ c₁           ↓ c₂           ↓ c₃
957        //    (102, 35)      (102, 50)      (103, 12)     (positions in the target file)
958
959        // Helper struct that makes it easier to compare tokens by the start and end
960        // of the range they cover.
961        #[derive(Debug, Clone, Copy)]
962        struct Range<'a> {
963            start: (u32, u32),
964            end: (u32, u32),
965            value: &'a RawToken,
966        }
967
968        /// Turns a list of tokens into a list of ranges, using the provided `key` function to determine the order of the tokens.
969        #[allow(clippy::ptr_arg)]
970        fn create_ranges(
971            tokens: &mut [RawToken],
972            key: fn(&RawToken) -> (u32, u32),
973        ) -> Vec<Range<'_>> {
974            tokens.sort_unstable_by_key(key);
975
976            let mut token_iter = tokens.iter().peekable();
977            let mut ranges = Vec::new();
978
979            while let Some(t) = token_iter.next() {
980                let start = key(t);
981                let next_start = token_iter.peek().map_or((u32::MAX, u32::MAX), |t| key(t));
982                // A token extends either to the start of the next token or the end of the line, whichever comes sooner
983                let end = std::cmp::min(next_start, (start.0, u32::MAX));
984                ranges.push(Range {
985                    start,
986                    end,
987                    value: t,
988                });
989            }
990
991            ranges
992        }
993
994        // Turn `self.tokens` and `adjustment.tokens` into vectors of ranges so we have easy access to
995        // both start and end.
996        // We want to compare `self` and `adjustment` tokens by line/column numbers in the "original source" file.
997        // These line/column numbers are the `dst_line/col` for
998        // the `self` tokens and `src_line/col` for the `adjustment` tokens.
999        let mut self_tokens = std::mem::take(&mut self.tokens);
1000        let original_ranges = create_ranges(&mut self_tokens, |t| (t.dst_line, t.dst_col));
1001        let mut adjustment_tokens = adjustment.tokens.clone();
1002        let adjustment_ranges = create_ranges(&mut adjustment_tokens, |t| (t.src_line, t.src_col));
1003
1004        let mut original_ranges_iter = original_ranges.iter();
1005
1006        let mut original_range = match original_ranges_iter.next() {
1007            Some(r) => r,
1008            None => return,
1009        };
1010
1011        // Iterate over `adjustment_ranges` (sorted by `src_line/col`). For each such range, consider
1012        // all `original_ranges` which overlap with it.
1013        'outer: for &adjustment_range in &adjustment_ranges {
1014            // The `adjustment_range` offsets lines and columns by a certain amount. All `original_ranges`
1015            // it covers will get the same offset.
1016            let (line_diff, col_diff) = (
1017                adjustment_range.value.dst_line as i32 - adjustment_range.value.src_line as i32,
1018                adjustment_range.value.dst_col as i32 - adjustment_range.value.src_col as i32,
1019            );
1020
1021            // Skip `original_ranges` that are entirely before the `adjustment_range`.
1022            while original_range.end <= adjustment_range.start {
1023                match original_ranges_iter.next() {
1024                    Some(r) => original_range = r,
1025                    None => break 'outer,
1026                }
1027            }
1028
1029            // At this point `original_range.end` > `adjustment_range.start`
1030
1031            // Iterate over `original_ranges` that fall at least partially within the `adjustment_range`.
1032            while original_range.start < adjustment_range.end {
1033                // If `original_range` started before `adjustment_range`, cut off the token's start.
1034                let (dst_line, dst_col) =
1035                    std::cmp::max(original_range.start, adjustment_range.start);
1036                let mut token = RawToken {
1037                    dst_line,
1038                    dst_col,
1039                    ..*original_range.value
1040                };
1041
1042                token.dst_line = (token.dst_line as i32 + line_diff) as u32;
1043                token.dst_col = (token.dst_col as i32 + col_diff) as u32;
1044
1045                self.tokens.push(token);
1046
1047                if original_range.end >= adjustment_range.end {
1048                    // There are surely no more `original_ranges` for this `adjustment_range`.
1049                    // Break the loop without advancing the `original_range`.
1050                    break;
1051                } else {
1052                    //  Advance the `original_range`.
1053                    match original_ranges_iter.next() {
1054                        Some(r) => original_range = r,
1055                        None => break 'outer,
1056                    }
1057                }
1058            }
1059        }
1060
1061        self.tokens
1062            .sort_unstable_by_key(|t| (t.dst_line, t.dst_col));
1063    }
1064}
1065
1066impl SourceMapIndex {
1067    /// Creates a sourcemap index from a reader over a JSON stream in UTF-8
1068    /// format.  Optionally a "garbage header" as defined by the
1069    /// sourcemap draft specification is supported.  In case a regular
1070    /// sourcemap is encountered an error is returned.
1071    pub fn from_reader<R: Read>(rdr: R) -> Result<SourceMapIndex> {
1072        match decode(rdr)? {
1073            DecodedMap::Index(smi) => Ok(smi),
1074            _ => Err(Error::IncompatibleSourceMap),
1075        }
1076    }
1077
1078    /// Writes a sourcemap index into a writer.
1079    pub fn to_writer<W: Write>(&self, w: W) -> Result<()> {
1080        encode(self, w)
1081    }
1082
1083    /// Creates a sourcemap index from a reader over a JSON byte slice in UTF-8
1084    /// format.  Optionally a "garbage header" as defined by the
1085    /// sourcemap draft specification is supported.  In case a regular
1086    /// sourcemap is encountered an error is returned.
1087    pub fn from_slice(slice: &[u8]) -> Result<SourceMapIndex> {
1088        match decode_slice(slice)? {
1089            DecodedMap::Index(smi) => Ok(smi),
1090            _ => Err(Error::IncompatibleSourceMap),
1091        }
1092    }
1093
1094    /// Constructs a new sourcemap index from raw components.
1095    ///
1096    /// - `file`: an optional filename of the index
1097    /// - `sections`: a vector of source map index sections
1098    pub fn new(file: Option<String>, sections: Vec<SourceMapSection>) -> SourceMapIndex {
1099        SourceMapIndex {
1100            file,
1101            sections,
1102            x_facebook_offsets: None,
1103            x_metro_module_paths: None,
1104            debug_id: None,
1105        }
1106    }
1107
1108    /// Constructs a new sourcemap index from raw components including the
1109    /// facebook RAM bundle extensions.
1110    ///
1111    /// - `file`: an optional filename of the index
1112    /// - `sections`: a vector of source map index sections
1113    /// - `x_facebook_offsets`: a vector of facebook offsets
1114    /// - `x_metro_module_paths`: a vector of metro module paths
1115    pub fn new_ram_bundle_compatible(
1116        file: Option<String>,
1117        sections: Vec<SourceMapSection>,
1118        x_facebook_offsets: Option<Vec<Option<u32>>>,
1119        x_metro_module_paths: Option<Vec<String>>,
1120    ) -> SourceMapIndex {
1121        SourceMapIndex {
1122            file,
1123            sections,
1124            x_facebook_offsets,
1125            x_metro_module_paths,
1126            debug_id: None,
1127        }
1128    }
1129
1130    /// Returns the debug ID.
1131    pub(crate) fn debug_id(&self) -> Option<DebugId> {
1132        self.debug_id
1133    }
1134
1135    fn set_debug_id(&mut self, debug_id: Option<DebugId>) {
1136        self.debug_id = debug_id;
1137    }
1138
1139    /// Adds the given debug id to the sourcemap index.
1140    pub(crate) fn with_debug_id(mut self, debug_id: Option<DebugId>) -> Self {
1141        self.set_debug_id(debug_id);
1142        self
1143    }
1144
1145    /// Returns the embedded filename in case there is one.
1146    pub fn get_file(&self) -> Option<&str> {
1147        self.file.as_ref().map(|x| &x[..])
1148    }
1149
1150    /// Sets a new value for the file.
1151    pub fn set_file(&mut self, value: Option<&str>) {
1152        self.file = value.map(str::to_owned);
1153    }
1154
1155    /// Returns the number of sections in this index
1156    pub fn get_section_count(&self) -> u32 {
1157        self.sections.len() as u32
1158    }
1159
1160    /// Looks up a single section and returns it
1161    pub fn get_section(&self, idx: u32) -> Option<&SourceMapSection> {
1162        self.sections.get(idx as usize)
1163    }
1164
1165    /// Looks up a single section and returns it as a mutable ref
1166    pub fn get_section_mut(&mut self, idx: u32) -> Option<&mut SourceMapSection> {
1167        self.sections.get_mut(idx as usize)
1168    }
1169
1170    /// Iterates over all sections
1171    pub fn sections(&self) -> SourceMapSectionIter<'_> {
1172        SourceMapSectionIter {
1173            i: self,
1174            next_idx: 0,
1175        }
1176    }
1177
1178    /// Given a location, name and minified source file resolve a minified
1179    /// name to an original function name.
1180    ///
1181    /// This invokes some guesswork and requires access to the original minified
1182    /// source.  This will not yield proper results for anonymous functions or
1183    /// functions that do not have clear function names.  (For instance it's
1184    /// recommended that dotted function names are not passed to this
1185    /// function).
1186    pub fn get_original_function_name(
1187        &self,
1188        line: u32,
1189        col: u32,
1190        minified_name: &str,
1191        sv: &SourceView,
1192    ) -> Option<&str> {
1193        self.lookup_token(line, col)
1194            .and_then(|token| sv.get_original_function_name(token, minified_name))
1195    }
1196
1197    /// Looks up the closest token to a given line and column.
1198    ///
1199    /// This requires that the referenced sourcemaps are actually loaded.
1200    /// If a sourcemap is encountered that is not embedded but just
1201    /// externally referenced it is silently skipped.
1202    pub fn lookup_token(&self, line: u32, col: u32) -> Option<Token<'_>> {
1203        let (_section_idx, section) =
1204            greatest_lower_bound(&self.sections, &(line, col), SourceMapSection::get_offset)?;
1205        let map = section.get_sourcemap()?;
1206        let (off_line, off_col) = section.get_offset();
1207        map.lookup_token(
1208            line - off_line,
1209            if line == off_line { col - off_col } else { col },
1210        )
1211    }
1212
1213    /// Flattens an indexed sourcemap into a regular one.  This requires
1214    /// that all referenced sourcemaps are attached.
1215    pub fn flatten(&self) -> Result<SourceMap> {
1216        let mut builder = SourceMapBuilder::new(self.get_file());
1217
1218        for section in self.sections() {
1219            let (off_line, off_col) = section.get_offset();
1220            let map = match section.get_sourcemap() {
1221                Some(map) => match map {
1222                    DecodedMap::Regular(sm) => Cow::Borrowed(sm),
1223                    DecodedMap::Index(idx) => Cow::Owned(idx.flatten()?),
1224                    DecodedMap::Hermes(smh) => Cow::Borrowed(&smh.sm),
1225                },
1226                None => {
1227                    return Err(Error::CannotFlatten(format!(
1228                        "Section has an unresolved \
1229                         sourcemap: {}",
1230                        section.get_url().unwrap_or("<unknown url>")
1231                    )));
1232                }
1233            };
1234
1235            let mut src_id_map = Vec::<u32>::with_capacity(map.sources().count());
1236
1237            for (original_id, (source, contents)) in
1238                map.sources().zip(map.source_contents()).enumerate()
1239            {
1240                debug_assert_eq!(original_id, src_id_map.len());
1241                let src_id = builder.add_source(source);
1242
1243                src_id_map.push(src_id);
1244
1245                if let Some(contents) = contents {
1246                    builder.set_source_contents(src_id, Some(contents));
1247                }
1248            }
1249
1250            let mut name_id_map = Vec::<u32>::with_capacity(map.names().count());
1251
1252            for (original_id, name) in map.names().enumerate() {
1253                debug_assert_eq!(original_id, name_id_map.len());
1254                let name_id = builder.add_name(name);
1255                name_id_map.push(name_id);
1256            }
1257
1258            for token in map.tokens() {
1259                let dst_col = if token.get_dst_line() == 0 {
1260                    token.get_dst_col() + off_col
1261                } else {
1262                    token.get_dst_col()
1263                };
1264
1265                // Use u32 -> u32 map instead of using the hash map in SourceMapBuilder for better performance
1266                let original_src_id = token.raw.src_id;
1267                let src_id = if original_src_id == !0 {
1268                    None
1269                } else {
1270                    src_id_map.get(original_src_id as usize).copied()
1271                };
1272
1273                let original_name_id = token.raw.name_id;
1274                let name_id = if original_name_id == !0 {
1275                    None
1276                } else {
1277                    name_id_map.get(original_name_id as usize).copied()
1278                };
1279
1280                let raw = builder.add_raw(
1281                    token.get_dst_line() + off_line,
1282                    dst_col,
1283                    token.get_src_line(),
1284                    token.get_src_col(),
1285                    src_id,
1286                    name_id,
1287                    token.is_range(),
1288                );
1289
1290                if map.ignore_list.contains(&token.get_src_id()) {
1291                    builder.add_to_ignore_list(raw.src_id);
1292                }
1293            }
1294        }
1295
1296        Ok(builder.into_sourcemap())
1297    }
1298
1299    /// Flattens an indexed sourcemap into a regular one and automatically
1300    /// rewrites it.  This is more useful than plain flattening as this will
1301    /// cause the sourcemap to be properly deduplicated.
1302    pub fn flatten_and_rewrite(self, options: &RewriteOptions<'_>) -> Result<SourceMap> {
1303        self.flatten()?.rewrite(options)
1304    }
1305
1306    /// Returns `true` if this sourcemap is for a RAM bundle.
1307    pub fn is_for_ram_bundle(&self) -> bool {
1308        self.x_facebook_offsets.is_some() && self.x_metro_module_paths.is_some()
1309    }
1310
1311    /// Returns embeded x-facebook-offset values.
1312    pub fn x_facebook_offsets(&self) -> Option<&[Option<u32>]> {
1313        self.x_facebook_offsets.as_ref().map(|x| &x[..])
1314    }
1315
1316    /// Returns embedded metro module paths.
1317    pub fn x_metro_module_paths(&self) -> Option<&[String]> {
1318        self.x_metro_module_paths.as_ref().map(|x| &x[..])
1319    }
1320
1321    /// Adjusts all of the sections' offset rows by the given amount.
1322    /// Returns a boolean indicating whether the adjustment was successful
1323    /// (false indicating that not all of the sections could be adjusted
1324    /// because we overflowed the u32, true if adjustment was successful).
1325    /// If false is returned, then the sourcemap index is unchanged.
1326    pub fn adjust_sections_offset_rows(&mut self, amount: u32) -> bool {
1327        let adjusted_rows: Vec<_> = self
1328            .sections
1329            .iter()
1330            // Filter map will filter out adjustments that overflow
1331            .filter_map(|section| section.offset.0.checked_add(amount))
1332            .collect();
1333
1334        if adjusted_rows.len() != self.sections.len() {
1335            // We overflowed at least one section
1336            return false;
1337        }
1338
1339        for (section, adjustment) in self.sections.iter_mut().zip(adjusted_rows) {
1340            section.offset.0 = adjustment;
1341        }
1342
1343        true
1344    }
1345}
1346
1347impl SourceMapSection {
1348    /// Create a new sourcemap index section
1349    ///
1350    /// - `offset`: offset as line and column
1351    /// - `url`: optional URL of where the sourcemap is located
1352    /// - `map`: an optional already resolved internal sourcemap
1353    pub fn new(
1354        offset: (u32, u32),
1355        url: Option<String>,
1356        map: Option<DecodedMap>,
1357    ) -> SourceMapSection {
1358        SourceMapSection {
1359            offset,
1360            url,
1361            map: map.map(Box::new),
1362        }
1363    }
1364
1365    /// Returns the offset line
1366    pub fn get_offset_line(&self) -> u32 {
1367        self.offset.0
1368    }
1369
1370    /// Returns the offset column
1371    pub fn get_offset_col(&self) -> u32 {
1372        self.offset.1
1373    }
1374
1375    /// Returns the offset as tuple
1376    pub fn get_offset(&self) -> (u32, u32) {
1377        self.offset
1378    }
1379
1380    /// Returns the URL of the referenced map if available
1381    pub fn get_url(&self) -> Option<&str> {
1382        self.url.as_deref()
1383    }
1384
1385    /// Updates the URL for this section.
1386    pub fn set_url(&mut self, value: Option<&str>) {
1387        self.url = value.map(str::to_owned);
1388    }
1389
1390    /// Returns a reference to the embedded sourcemap if available
1391    pub fn get_sourcemap(&self) -> Option<&DecodedMap> {
1392        self.map.as_ref().map(Box::as_ref)
1393    }
1394
1395    /// Returns a reference to the embedded sourcemap if available
1396    pub fn get_sourcemap_mut(&mut self) -> Option<&mut DecodedMap> {
1397        self.map.as_mut().map(Box::as_mut)
1398    }
1399
1400    /// Replaces the embedded sourcemap
1401    pub fn set_sourcemap(&mut self, sm: Option<DecodedMap>) {
1402        self.map = sm.map(Box::new);
1403    }
1404}
1405
1406#[cfg(test)]
1407mod tests {
1408    use std::collections::BTreeSet;
1409
1410    use super::{DecodedMap, RewriteOptions, SourceMap, SourceMapIndex, SourceMapSection};
1411    use debugid::DebugId;
1412
1413    #[test]
1414    fn test_rewrite_debugid() {
1415        let input: &[_] = br#"{
1416         "version":3,
1417         "sources":["coolstuff.js"],
1418         "names":["x","alert"],
1419         "mappings":"AAAA,GAAIA,GAAI,EACR,IAAIA,GAAK,EAAG,CACVC,MAAM",
1420         "debug_id":"00000000-0000-0000-0000-000000000000"
1421     }"#;
1422
1423        let sm = SourceMap::from_slice(input).unwrap();
1424
1425        assert_eq!(sm.debug_id, Some(DebugId::default()));
1426
1427        let new_sm = sm
1428            .rewrite(&RewriteOptions {
1429                with_names: false,
1430                ..Default::default()
1431            })
1432            .unwrap();
1433
1434        assert_eq!(new_sm.debug_id, Some(DebugId::default()));
1435    }
1436
1437    #[test]
1438    fn test_debugid_alias() {
1439        let input: &[_] = br#"{
1440         "version":3,
1441         "sources":["coolstuff.js"],
1442         "names":["x","alert"],
1443         "mappings":"AAAA,GAAIA,GAAI,EACR,IAAIA,GAAK,EAAG,CACVC,MAAM",
1444         "debug_id": "11111111-1111-1111-1111-111111111111",
1445         "debugId":"00000000-0000-0000-0000-000000000000"
1446     }"#;
1447
1448        let sm = SourceMap::from_slice(input).unwrap();
1449
1450        assert_eq!(sm.debug_id, Some(DebugId::default()));
1451    }
1452
1453    #[test]
1454    fn test_adjust_mappings_injection() {
1455        // A test that `adjust_mappings` does what it's supposed to for debug id injection.
1456        //
1457        // For each bundler:
1458        // * `bundle.js` and `bundle.js.map` are taken from https://github.com/kamilogorek/sourcemaps-playground/.
1459        // * `injected.bundle.js` and `injected.bundle.js.map` were created using the function`fixup_js_file` in `sentry-cli`.
1460        //   `injected.bundle.js.map` maps from `injected.bundle.js` to `bundle.js`.
1461        // * `composed.bundle.js.map` is the result of calling `adjust_mappings` on `bundle.js.map` and `injected.bundle.js.map`.
1462        //
1463        // If everything is working as intended, `composed.bundle.js.map` is a (good) sourcemap from `injected.bundle.js` to
1464        // the original sources. To verify that this is indeed the case, you can compare `bundle.js` / `bundle.js.map` with
1465        // `injected.bundle.js` / `composed.bundle.js.map` using https://sokra.github.io/source-map-visualization/#custom.
1466        //
1467        // NB: In the case of `rspack`, the sourcemap generated by the bundler is *horrible*. It's probably not useful, but
1468        // `adjust_mappings` preserves it as far as it goes.
1469        for bundler in ["esbuild", "rollup", "vite", "webpack", "rspack"] {
1470            let original_map_file = std::fs::File::open(format!(
1471                "tests/fixtures/adjust_mappings/{bundler}.bundle.js.map"
1472            ))
1473            .unwrap();
1474
1475            let injected_map_file = std::fs::File::open(format!(
1476                "tests/fixtures/adjust_mappings/{bundler}-injected.bundle.js.map"
1477            ))
1478            .unwrap();
1479
1480            let composed_map_file = std::fs::File::open(format!(
1481                "tests/fixtures/adjust_mappings/{bundler}-composed.bundle.js.map"
1482            ))
1483            .unwrap();
1484
1485            let mut original_map = SourceMap::from_reader(original_map_file).unwrap();
1486            let injected_map = SourceMap::from_reader(injected_map_file).unwrap();
1487            let composed_map = SourceMap::from_reader(composed_map_file).unwrap();
1488            original_map.adjust_mappings(&injected_map);
1489
1490            assert_eq!(
1491                original_map.tokens, composed_map.tokens,
1492                "bundler = {bundler}"
1493            );
1494        }
1495    }
1496
1497    #[test]
1498    fn test_roundtrip() {
1499        let sm = br#"{
1500            "version": 3,
1501            "file": "foo.js",
1502            "sources": [
1503                "./bar.js",
1504                "./baz.js"
1505            ],
1506            "sourceRoot": "webpack:///",
1507            "sourcesContent": [null, null],
1508            "names": [],
1509            "mappings": ""
1510        }"#;
1511
1512        let sm = SourceMap::from_slice(sm).unwrap();
1513        let mut out = Vec::new();
1514        sm.to_writer(&mut out).unwrap();
1515
1516        let sm_new = SourceMap::from_slice(&out).unwrap();
1517        assert_eq!(sm_new.sources, sm.sources);
1518    }
1519
1520    #[test]
1521    fn test_sourcemap_index_default_debug_id() {
1522        let sm = SourceMapIndex::new(None, vec![]);
1523        assert!(sm.debug_id().is_none());
1524    }
1525
1526    #[test]
1527    fn test_sourcemap_index_debug_id() {
1528        const DEBUG_ID: &str = "0123456789abcdef0123456789abcdef";
1529
1530        let sm = SourceMapIndex::new(None, vec![])
1531            .with_debug_id(Some(DEBUG_ID.parse().expect("valid debug id")));
1532
1533        assert_eq!(
1534            sm.debug_id(),
1535            Some(DEBUG_ID.parse().expect("valid debug id"))
1536        );
1537    }
1538
1539    #[test]
1540    fn test_decoded_map_regular_debug_id() {
1541        const DEBUG_ID: &str = "0123456789abcdef0123456789abcdef";
1542
1543        let mut decoded_map = DecodedMap::Regular(SourceMap {
1544            file: None,
1545            tokens: vec![],
1546            names: vec![],
1547            source_root: None,
1548            sources: vec![],
1549            sources_prefixed: None,
1550            sources_content: vec![],
1551            ignore_list: BTreeSet::new(),
1552            debug_id: None,
1553        });
1554
1555        assert!(decoded_map.debug_id().is_none());
1556
1557        decoded_map.set_debug_id(Some(DEBUG_ID.parse().expect("valid debug id")));
1558
1559        assert_eq!(
1560            decoded_map,
1561            DecodedMap::Regular(SourceMap {
1562                file: None,
1563                tokens: vec![],
1564                names: vec![],
1565                source_root: None,
1566                sources: vec![],
1567                sources_prefixed: None,
1568                sources_content: vec![],
1569                ignore_list: BTreeSet::new(),
1570                debug_id: Some(DEBUG_ID.parse().expect("valid debug id")),
1571            })
1572        );
1573
1574        assert_eq!(
1575            decoded_map.debug_id(),
1576            Some(DEBUG_ID.parse().expect("valid debug id"))
1577        );
1578    }
1579
1580    #[test]
1581    fn test_decoded_map_index_debug_id() {
1582        const DEBUG_ID: &str = "0123456789abcdef0123456789abcdef";
1583
1584        let mut decoded_map = DecodedMap::Index(SourceMapIndex {
1585            file: None,
1586            sections: vec![],
1587            x_facebook_offsets: None,
1588            x_metro_module_paths: None,
1589            debug_id: None,
1590        });
1591
1592        assert!(decoded_map.debug_id().is_none());
1593
1594        decoded_map.set_debug_id(Some(DEBUG_ID.parse().expect("valid debug id")));
1595
1596        assert_eq!(
1597            decoded_map,
1598            DecodedMap::Index(SourceMapIndex {
1599                file: None,
1600                sections: vec![],
1601                x_facebook_offsets: None,
1602                x_metro_module_paths: None,
1603                debug_id: Some(DEBUG_ID.parse().expect("valid debug id")),
1604            })
1605        );
1606
1607        assert_eq!(
1608            decoded_map.debug_id(),
1609            Some(DEBUG_ID.parse().expect("valid debug id"))
1610        );
1611    }
1612
1613    #[test]
1614    fn test_adjust_sections_offset_rows_basic() {
1615        // Create a sourcemap index with sections starting at (0, 0) and (10, 0)
1616        let mut smi = SourceMapIndex::new(
1617            Some("test.js".to_string()),
1618            vec![
1619                SourceMapSection::new((0, 0), None, None),
1620                SourceMapSection::new((10, 0), None, None),
1621            ],
1622        );
1623
1624        // Adjust by 1
1625        assert!(smi.adjust_sections_offset_rows(1));
1626
1627        // Check that the entire SourceMapIndex was adjusted correctly
1628        assert_eq!(
1629            smi,
1630            SourceMapIndex::new(
1631                Some("test.js".to_string()),
1632                vec![
1633                    SourceMapSection::new((1, 0), None, None),
1634                    SourceMapSection::new((11, 0), None, None),
1635                ],
1636            )
1637        );
1638    }
1639
1640    #[test]
1641    fn test_adjust_sections_offset_rows_zero() {
1642        // Create a sourcemap index with sections starting at (0, 0) and (10, 0)
1643        let mut smi = SourceMapIndex::new(
1644            Some("test.js".to_string()),
1645            vec![
1646                SourceMapSection::new((0, 0), None, None),
1647                SourceMapSection::new((10, 0), None, None),
1648            ],
1649        );
1650
1651        // Adjust by zero
1652        assert!(smi.adjust_sections_offset_rows(0));
1653
1654        // Check that the entire SourceMapIndex remained unchanged
1655        assert_eq!(
1656            smi,
1657            SourceMapIndex::new(
1658                Some("test.js".to_string()),
1659                vec![
1660                    SourceMapSection::new((0, 0), None, None),
1661                    SourceMapSection::new((10, 0), None, None),
1662                ],
1663            )
1664        );
1665    }
1666
1667    #[test]
1668    fn test_adjust_sections_offset_rows_multiple_sections() {
1669        // Create a sourcemap index with multiple sections
1670        let mut smi = SourceMapIndex::new(
1671            Some("test.js".to_string()),
1672            vec![
1673                SourceMapSection::new((0, 0), None, None),
1674                SourceMapSection::new((10, 0), None, None),
1675                SourceMapSection::new((20, 10), None, None),
1676                SourceMapSection::new((30, 40), None, None),
1677            ],
1678        );
1679
1680        // Adjust by 1
1681        assert!(smi.adjust_sections_offset_rows(1));
1682
1683        // Check that the entire SourceMapIndex was adjusted correctly
1684        assert_eq!(
1685            smi,
1686            SourceMapIndex::new(
1687                Some("test.js".to_string()),
1688                vec![
1689                    SourceMapSection::new((1, 0), None, None),
1690                    SourceMapSection::new((11, 0), None, None),
1691                    SourceMapSection::new((21, 10), None, None),
1692                    SourceMapSection::new((31, 40), None, None),
1693                ],
1694            )
1695        );
1696    }
1697
1698    #[test]
1699    fn test_adjust_sections_offset_rows_overflow() {
1700        // Create a sourcemap index with a section at u32::MAX
1701        let mut smi = SourceMapIndex::new(
1702            Some("test.js".to_string()),
1703            vec![
1704                SourceMapSection::new((0, 0), None, None),
1705                SourceMapSection::new((u32::MAX, 0), None, None),
1706            ],
1707        );
1708
1709        // Store the original state
1710        let original_smi = smi.clone();
1711
1712        // An adjustment of 1 would overflow
1713        assert!(!smi.adjust_sections_offset_rows(1));
1714
1715        // Verify the sourcemap index remains unchanged
1716        assert_eq!(smi, original_smi);
1717    }
1718
1719    #[test]
1720    fn test_adjust_sections_offset_rows_partial_overflow() {
1721        // Create a sourcemap index with multiple sections, one at u32::MAX
1722        let mut smi = SourceMapIndex::new(
1723            Some("test.js".to_string()),
1724            vec![
1725                SourceMapSection::new((0, 0), None, None),
1726                SourceMapSection::new((10, 0), None, None),
1727                SourceMapSection::new((20, 0), None, None),
1728                SourceMapSection::new((u32::MAX, 0), None, None),
1729            ],
1730        );
1731
1732        // Store the original state
1733        let original_smi = smi.clone();
1734
1735        // Try to adjust by an amount that would cause overflow for one section
1736        assert!(!smi.adjust_sections_offset_rows(1));
1737
1738        // Verify the sourcemap index remains unchanged
1739        assert_eq!(smi, original_smi);
1740    }
1741
1742    #[test]
1743    fn test_adjust_sections_offset_rows_large_amount() {
1744        // Create a sourcemap index with sections
1745        let mut smi = SourceMapIndex::new(
1746            Some("test.js".to_string()),
1747            vec![
1748                SourceMapSection::new((0, 0), None, None),
1749                SourceMapSection::new((10, 0), None, None),
1750            ],
1751        );
1752
1753        assert!(smi.adjust_sections_offset_rows(1_000_000));
1754
1755        // Check that the entire SourceMapIndex was adjusted correctly
1756        assert_eq!(
1757            smi,
1758            SourceMapIndex::new(
1759                Some("test.js".to_string()),
1760                vec![
1761                    SourceMapSection::new((1_000_000, 0), None, None),
1762                    SourceMapSection::new((1_000_010, 0), None, None),
1763                ],
1764            )
1765        );
1766    }
1767
1768    #[test]
1769    fn adjust_sections_offset_rows_large_amount_overflow() {
1770        // Create a sourcemap index with a section at a positive amount
1771        let mut smi = SourceMapIndex::new(
1772            Some("test.js".to_string()),
1773            vec![
1774                SourceMapSection::new((0, 0), None, None),
1775                SourceMapSection::new((10, 0), None, None),
1776            ],
1777        );
1778
1779        // Store the original state
1780        let original_smi = smi.clone();
1781
1782        // An adjustment of u32::MAX would overflow
1783        assert!(!smi.adjust_sections_offset_rows(u32::MAX));
1784
1785        // Verify the sourcemap index remains unchanged
1786        assert_eq!(smi, original_smi);
1787    }
1788
1789    #[test]
1790    fn adjust_sections_offset_rows_no_sections() {
1791        // Create a sourcemap index with no sections
1792        let mut smi = SourceMapIndex::new(Some("test.js".to_string()), vec![]);
1793
1794        // An adjustment by 1 should return true and no-op
1795        assert!(smi.adjust_sections_offset_rows(1));
1796
1797        // The sourcemap index should remain unchanged
1798        assert_eq!(
1799            smi,
1800            SourceMapIndex::new(Some("test.js".to_string()), vec![])
1801        );
1802    }
1803
1804    mod prop {
1805        //! This module exists to test the following property:
1806        //!
1807        //! Let `s` be a string.
1808        //! 1. Edit `s` with `magic-string` in such a way that edits (insertions, deletions) only happen *within* lines.
1809        //!    Call the resulting string `t` and the sourcemap relating the two `m₁`.
1810        //! 2. Further edit `t` with `magic-string` so that only *whole* lines are edited (inserted, deleted, prepended, appended).
1811        //!    Call the resulting string `u` and the sourcemap relating `u` to `t` `m₂`.
1812        //! 3. Do (1) and (2) in one go. The resulting string should still be `u`. Call the sourcemap
1813        //!    relating `u` and `s` `m₃`.
1814        //!
1815        //! Then `SourceMap::adjust_mappings(m₁, m₂) = m₃`.
1816        //!
1817        //! Or, in diagram form:
1818        //!
1819        //! u  -----m₂--------> t  -----m₁--------> s
1820        //! | -----------------m₃-----------------> |
1821        //!
1822        //! For the sake of simplicty, all input strings are 10 lines by 10 columns of the characters a-z.
1823        use magic_string::MagicString;
1824        use proptest::prelude::*;
1825
1826        use crate::SourceMap;
1827
1828        /// An edit in the first batch (only within a line).
1829        #[derive(Debug, Clone)]
1830        enum FirstEdit {
1831            /// Insert a string at a column.
1832            Insert(u32, String),
1833            /// Delete from one column to the other.
1834            Delete(i64, i64),
1835        }
1836
1837        impl FirstEdit {
1838            /// Applies an edit to the given line in the given `MagicString`.
1839            fn apply(&self, line: usize, ms: &mut MagicString) {
1840                // Every line is 11 bytes long, counting the newline.
1841                let line_offset = line * 11;
1842                match self {
1843                    FirstEdit::Insert(col, s) => {
1844                        ms.append_left(line_offset as u32 + *col, s).unwrap();
1845                    }
1846                    FirstEdit::Delete(start, end) => {
1847                        ms.remove(line_offset as i64 + *start, line_offset as i64 + *end)
1848                            .unwrap();
1849                    }
1850                }
1851            }
1852        }
1853
1854        /// Find the start and end index of the n'th line in the given string
1855        /// (including the terminating newline, if there is one).
1856        fn nth_line_start_end(n: usize, s: &str) -> (usize, usize) {
1857            let line = s.lines().nth(n).unwrap();
1858            let start = line.as_ptr() as usize - s.as_ptr() as usize;
1859            // All lines except line 9 have a final newline.
1860            let end = if n == 9 {
1861                start + line.len()
1862            } else {
1863                start + line.len() + 1
1864            };
1865            (start, end)
1866        }
1867
1868        /// An edit in the second batch (only whole lines).
1869        #[derive(Debug, Clone)]
1870        enum SecondEdit {
1871            /// Prepends a string.
1872            Prepend(String),
1873            /// Appends a string.
1874            Append(String),
1875            /// Inserts a string at a given line.
1876            Insert(usize, String),
1877            /// Deletes a a line.
1878            Delete(usize),
1879        }
1880
1881        impl SecondEdit {
1882            /// Applies an edit to a `MagicString`.
1883            ///
1884            /// This must know the original string (which unfortunately can't be extracted from a `MagicString`)
1885            /// to find line boundaries.
1886            fn apply(&self, orig: &str, ms: &mut MagicString) {
1887                match self {
1888                    SecondEdit::Prepend(s) => {
1889                        ms.prepend(s).unwrap();
1890                    }
1891                    SecondEdit::Append(s) => {
1892                        ms.append(s).unwrap();
1893                    }
1894                    SecondEdit::Insert(line, s) => {
1895                        let (start, _) = nth_line_start_end(*line, orig);
1896                        ms.prepend_left(start as u32, s).unwrap();
1897                    }
1898                    SecondEdit::Delete(line) => {
1899                        let (start, end) = nth_line_start_end(*line, orig);
1900                        ms.remove(start as i64, end as i64).unwrap();
1901                    }
1902                }
1903            }
1904        }
1905
1906        /// Produces a random 10x10 grid of the characters a-z.
1907        fn starting_string() -> impl Strategy<Value = String> {
1908            (vec!["[a-z]{10}"; 10]).prop_map(|v| v.join("\n"))
1909        }
1910
1911        /// Produces a random first-batch edit.
1912        fn first_edit() -> impl Strategy<Value = FirstEdit> {
1913            prop_oneof![
1914                (1u32..9, "[a-z]{5}").prop_map(|(c, s)| FirstEdit::Insert(c, s)),
1915                (1i64..10)
1916                    .prop_flat_map(|end| (0..end, Just(end)))
1917                    .prop_map(|(a, b)| FirstEdit::Delete(a, b))
1918            ]
1919        }
1920
1921        /// Produces a random sequence of first-batch edits, one per line.
1922        ///
1923        /// Thus, each line will either have an insertion or a deletion.
1924        fn first_edit_sequence() -> impl Strategy<Value = Vec<FirstEdit>> {
1925            let mut vec = Vec::with_capacity(10);
1926
1927            for _ in 0..10 {
1928                vec.push(first_edit())
1929            }
1930
1931            vec
1932        }
1933
1934        /// Produces a random sequence of second-batch edits, one per line.
1935        ///
1936        /// Each edit may delete a line, insert a line, or prepend or append something
1937        /// to the whole string. No two edits operate on the same line. The order of the edits is random.
1938        fn second_edit_sequence() -> impl Strategy<Value = Vec<SecondEdit>> {
1939            let edits = (0..10)
1940                .map(|i| {
1941                    prop_oneof![
1942                        "[a-z\n]{12}".prop_map(SecondEdit::Prepend),
1943                        "[a-z\n]{12}".prop_map(SecondEdit::Append),
1944                        "[a-z\n]{11}\n".prop_map(move |s| SecondEdit::Insert(i, s)),
1945                        Just(SecondEdit::Delete(i)),
1946                    ]
1947                })
1948                .collect::<Vec<_>>();
1949
1950            edits.prop_shuffle()
1951        }
1952
1953        proptest! {
1954            #[test]
1955            fn test_composition_identity(
1956                input in starting_string(),
1957                first_edits in first_edit_sequence(),
1958                second_edits in second_edit_sequence(),
1959            ) {
1960
1961                // Do edits in two batches and generate two sourcemaps
1962
1963                let mut ms1 = MagicString::new(&input);
1964
1965                for (line, first_edit) in first_edits.iter().enumerate() {
1966                    first_edit.apply(line, &mut ms1);
1967                }
1968
1969                let first_map = ms1.generate_map(Default::default()).unwrap().to_string().unwrap();
1970                let mut first_map = SourceMap::from_slice(first_map.as_bytes()).unwrap();
1971
1972                let transformed_input = ms1.to_string();
1973
1974                let mut ms2 = MagicString::new(&transformed_input);
1975
1976                for second_edit in second_edits.iter() {
1977                    second_edit.apply(&transformed_input, &mut ms2);
1978                }
1979
1980                let output_1 = ms2.to_string();
1981
1982                let second_map = ms2.generate_map(Default::default()).unwrap().to_string().unwrap();
1983                let second_map = SourceMap::from_slice(second_map.as_bytes()).unwrap();
1984
1985                // Do edits again in one batch and generate one big sourcemap
1986
1987                let mut ms3 = MagicString::new(&input);
1988
1989                for (line, first_edit) in first_edits.iter().enumerate() {
1990                    first_edit.apply(line, &mut ms3);
1991                }
1992
1993                for second_edit in second_edits.iter() {
1994                    second_edit.apply(&input, &mut ms3);
1995                }
1996
1997                let output_2 = ms3.to_string();
1998
1999                let third_map = ms3.generate_map(Default::default()).unwrap().to_string().unwrap();
2000                let third_map = SourceMap::from_slice(third_map.as_bytes()).unwrap();
2001
2002                // Both methods must produce the same output
2003                assert_eq!(output_1, output_2);
2004
2005                first_map.adjust_mappings(&second_map);
2006
2007                assert_eq!(first_map.tokens, third_map.tokens);
2008            }
2009        }
2010    }
2011}