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#[derive(Debug, Clone)]
32pub struct RewriteOptions<'a> {
33 pub with_names: bool,
35 pub with_source_contents: bool,
37 #[cfg(any(unix, windows, target_os = "redox"))]
40 pub load_local_source_contents: bool,
41 pub base_path: Option<&'a Path>,
44 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#[derive(Debug, Clone, PartialEq)]
70pub enum DecodedMap {
71 Regular(SourceMap),
73 Index(SourceMapIndex),
75 Hermes(SourceMapHermes),
78}
79
80impl DecodedMap {
81 pub fn from_reader<R: Read>(rdr: R) -> Result<DecodedMap> {
83 decode(rdr)
84 }
85
86 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 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 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 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 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#[derive(PartialEq, Eq, Copy, Clone, Debug)]
161pub struct RawToken {
162 pub dst_line: u32,
164 pub dst_col: u32,
166 pub src_line: u32,
168 pub src_col: u32,
170 pub src_id: u32,
172 pub name_id: u32,
174
175 pub is_range: bool,
179}
180
181#[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 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 pub fn get_dst_line(&self) -> u32 {
238 self.raw.dst_line
239 }
240
241 pub fn get_dst_col(&self) -> u32 {
243 self.raw.dst_col
244 }
245
246 pub fn get_dst(&self) -> (u32, u32) {
248 (self.get_dst_line(), self.get_dst_col())
249 }
250
251 pub fn get_src_line(&self) -> u32 {
256 self.raw.src_line
257 }
258
259 pub fn get_src_col(&self) -> u32 {
264 self.raw.src_col.saturating_add(self.offset)
265 }
266
267 pub fn get_src(&self) -> (u32, u32) {
269 (self.get_src_line(), self.get_src_col())
270 }
271
272 pub fn get_src_id(&self) -> u32 {
274 self.raw.src_id
275 }
276
277 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 pub fn has_source(&self) -> bool {
288 self.raw.src_id != !0
289 }
290
291 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 pub fn has_name(&self) -> bool {
302 self.get_name().is_some()
303 }
304
305 pub fn get_name_id(&self) -> u32 {
307 self.raw.name_id
308 }
309
310 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 pub fn get_raw_token(&self) -> RawToken {
323 *self.raw
324 }
325
326 pub fn get_source_view(&self) -> Option<&SourceView> {
328 self.sm.get_source_view(self.get_src_id())
329 }
330
331 pub fn is_range(&self) -> bool {
335 self.raw.is_range
336 }
337}
338
339pub 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
370pub 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
388pub 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
408pub 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#[derive(Debug, Clone, PartialEq)]
459pub struct SourceMapSection {
460 offset: (u32, u32),
461 url: Option<String>,
462 map: Option<Box<DecodedMap>>,
463}
464
465pub 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#[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#[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 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 pub fn to_writer<W: Write>(&self, w: W) -> Result<()> {
561 encode(self, w)
562 }
563
564 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 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 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 pub fn get_debug_id(&self) -> Option<DebugId> {
642 self.debug_id
643 }
644
645 pub fn set_debug_id(&mut self, debug_id: Option<DebugId>) {
647 self.debug_id = debug_id
648 }
649
650 pub fn get_scopes(&self) -> Option<&BytesStr> {
652 self.scopes.as_ref()
653 }
654
655 pub fn set_scopes<T: Into<BytesStr>>(&mut self, value: Option<T>) {
657 self.scopes = value.map(Into::into);
658 }
659
660 pub fn get_file(&self) -> Option<&BytesStr> {
662 self.file.as_ref()
663 }
664
665 pub fn set_file<T: Into<BytesStr>>(&mut self, value: Option<T>) {
667 self.file = value.map(Into::into);
668 }
669
670 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 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 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 pub fn get_token_count(&self) -> u32 {
726 self.tokens.len() as u32
727 }
728
729 pub fn tokens(&self) -> TokenIter<'_> {
731 TokenIter {
732 i: self,
733 next_idx: 0,
734 }
735 }
736
737 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 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 pub fn get_source_count(&self) -> u32 {
777 self.sources.len() as u32
778 }
779
780 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 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 sources_prefixed[idx as usize] =
796 Self::prefix_source(self.source_root.as_ref().unwrap(), &value);
797 }
798 }
799
800 pub fn sources(&self) -> SourceIter<'_> {
802 SourceIter {
803 i: self,
804 next_idx: 0,
805 }
806 }
807
808 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 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 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 pub fn source_contents(&self) -> SourceContentsIter<'_> {
833 SourceContentsIter {
834 i: self,
835 next_idx: 0,
836 }
837 }
838
839 pub fn names(&self) -> NameIter<'_> {
841 NameIter {
842 i: self,
843 next_idx: 0,
844 }
845 }
846
847 pub fn get_name_count(&self) -> u32 {
849 self.names.len() as u32
850 }
851
852 pub fn has_names(&self) -> bool {
854 !self.names.is_empty()
855 }
856
857 pub fn get_name(&self, idx: u32) -> Option<&BytesStr> {
859 self.names.get(idx as usize)
860 }
861
862 pub fn remove_names(&mut self) {
864 self.names.clear();
865 self.scopes = None;
866 }
867
868 pub fn rewrite(self, options: &RewriteOptions<'_>) -> Result<SourceMap> {
890 Ok(self.rewrite_with_mapping(options)?.0)
891 }
892
893 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 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 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 #[derive(Debug, Clone, Copy)]
1019 struct Range<'a> {
1020 start: (u32, u32),
1021 end: (u32, u32),
1022 value: &'a RawToken,
1023 }
1024
1025 #[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 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 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 'outer: for &adjustment_range in &adjustment_ranges {
1072 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 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 while original_range.start < adjustment_range.end {
1092 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 break;
1110 } else {
1111 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 #[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 #[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 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 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 for &self_range in &self_ranges {
1246 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 while input_range.is_some_and(|input_range| input_range.end <= self_range.start) {
1255 input_range = input_ranges_iter.next();
1256 }
1257 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 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 while input_range_value.start < self_range.end {
1290 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 break;
1308 } else {
1309 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 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 pub fn to_writer<W: Write>(&self, w: W) -> Result<()> {
1348 encode(self, w)
1349 }
1350
1351 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 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 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 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 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 pub fn get_file(&self) -> Option<&BytesStr> {
1415 self.file.as_ref()
1416 }
1417
1418 pub fn set_file(&mut self, value: Option<BytesStr>) {
1420 self.file = value;
1421 }
1422
1423 pub fn get_section_count(&self) -> u32 {
1425 self.sections.len() as u32
1426 }
1427
1428 pub fn get_section(&self, idx: u32) -> Option<&SourceMapSection> {
1430 self.sections.get(idx as usize)
1431 }
1432
1433 pub fn get_section_mut(&mut self, idx: u32) -> Option<&mut SourceMapSection> {
1435 self.sections.get_mut(idx as usize)
1436 }
1437
1438 pub fn sections(&self) -> SourceMapSectionIter<'_> {
1440 SourceMapSectionIter {
1441 i: self,
1442 next_idx: 0,
1443 }
1444 }
1445
1446 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 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 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 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 pub fn flatten_and_rewrite(self, options: &RewriteOptions<'_>) -> Result<SourceMap> {
1571 self.flatten()?.rewrite(options)
1572 }
1573
1574 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 pub fn x_facebook_offsets(&self) -> Option<&[Option<u32>]> {
1581 self.x_facebook_offsets.as_ref().map(|x| &x[..])
1582 }
1583
1584 pub fn x_metro_module_paths(&self) -> Option<&[String]> {
1586 self.x_metro_module_paths.as_ref().map(|x| &x[..])
1587 }
1588
1589 pub fn adjust_sections_offset_rows(&mut self, amount: u32) -> bool {
1595 let adjusted_rows: Vec<_> = self
1596 .sections
1597 .iter()
1598 .filter_map(|section| section.offset.0.checked_add(amount))
1600 .collect();
1601
1602 if adjusted_rows.len() != self.sections.len() {
1603 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 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 pub fn get_offset_line(&self) -> u32 {
1635 self.offset.0
1636 }
1637
1638 pub fn get_offset_col(&self) -> u32 {
1640 self.offset.1
1641 }
1642
1643 pub fn get_offset(&self) -> (u32, u32) {
1645 self.offset
1646 }
1647
1648 pub fn get_url(&self) -> Option<&str> {
1650 self.url.as_deref()
1651 }
1652
1653 pub fn set_url(&mut self, value: Option<&str>) {
1655 self.url = value.map(str::to_owned);
1656 }
1657
1658 pub fn get_sourcemap(&self) -> Option<&DecodedMap> {
1660 self.map.as_ref().map(Box::as_ref)
1661 }
1662
1663 pub fn get_sourcemap_mut(&mut self) -> Option<&mut DecodedMap> {
1665 self.map.as_mut().map(Box::as_mut)
1666 }
1667
1668 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 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 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 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 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 assert!(smi.adjust_sections_offset_rows(1));
1988
1989 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 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 assert!(smi.adjust_sections_offset_rows(0));
2015
2016 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 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 assert!(smi.adjust_sections_offset_rows(1));
2044
2045 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 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 let original_smi = smi.clone();
2073
2074 assert!(!smi.adjust_sections_offset_rows(1));
2076
2077 assert_eq!(smi, original_smi);
2079 }
2080
2081 #[test]
2082 fn test_adjust_sections_offset_rows_partial_overflow() {
2083 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 let original_smi = smi.clone();
2096
2097 assert!(!smi.adjust_sections_offset_rows(1));
2099
2100 assert_eq!(smi, original_smi);
2102 }
2103
2104 #[test]
2105 fn test_adjust_sections_offset_rows_large_amount() {
2106 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 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 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 let original_smi = smi.clone();
2143
2144 assert!(!smi.adjust_sections_offset_rows(u32::MAX));
2146
2147 assert_eq!(smi, original_smi);
2149 }
2150
2151 #[test]
2152 fn adjust_sections_offset_rows_no_sections() {
2153 let mut smi = SourceMapIndex::new(Some("test.js".into()), vec![]);
2155
2156 assert!(smi.adjust_sections_offset_rows(1));
2158
2159 assert_eq!(smi, SourceMapIndex::new(Some("test.js".into()), vec![]));
2161 }
2162
2163 mod prop {
2164 use magic_string::MagicString;
2186 use proptest::prelude::*;
2187
2188 use crate::SourceMap;
2189
2190 #[derive(Debug, Clone)]
2192 enum FirstEdit {
2193 Insert(u32, String),
2195 Delete(i64, i64),
2197 }
2198
2199 impl FirstEdit {
2200 fn apply(&self, line: usize, ms: &mut MagicString) {
2202 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 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 let end = if n == 9 {
2223 start + line.len()
2224 } else {
2225 start + line.len() + 1
2226 };
2227 (start, end)
2228 }
2229
2230 #[derive(Debug, Clone)]
2232 enum SecondEdit {
2233 Prepend(String),
2235 Append(String),
2237 Insert(usize, String),
2239 Delete(usize),
2241 }
2242
2243 impl SecondEdit {
2244 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 fn starting_string() -> impl Strategy<Value = String> {
2271 (vec!["[a-z]{10}"; 10]).prop_map(|v| v.join("\n"))
2272 }
2273
2274 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 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 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 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 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 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}