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