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