Skip to main content

swc_sourcemap/
types.rs

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