1use std::borrow::Cow;
2use std::cmp::Ordering;
3use std::collections::BTreeSet;
4use std::fmt;
5use std::io::{Read, Write};
6use std::path::Path;
7use std::sync::Arc;
8
9use crate::builder::SourceMapBuilder;
10use crate::decoder::{decode, decode_slice};
11use crate::encoder::encode;
12use crate::errors::{Error, Result};
13use crate::hermes::SourceMapHermes;
14use crate::sourceview::SourceView;
15use crate::utils::{find_common_prefix, greatest_lower_bound};
16
17use debugid::DebugId;
18
19#[derive(Debug, Clone)]
27pub struct RewriteOptions<'a> {
28 pub with_names: bool,
30 pub with_source_contents: bool,
32 #[cfg(any(unix, windows, target_os = "redox"))]
35 pub load_local_source_contents: bool,
36 pub base_path: Option<&'a Path>,
39 pub strip_prefixes: &'a [&'a str],
43}
44
45impl<'a> Default for RewriteOptions<'a> {
46 fn default() -> RewriteOptions<'a> {
47 RewriteOptions {
48 with_names: true,
49 with_source_contents: true,
50 #[cfg(any(unix, windows, target_os = "redox"))]
51 load_local_source_contents: false,
52 base_path: None,
53 strip_prefixes: &[][..],
54 }
55 }
56}
57
58#[derive(Debug, Clone)]
65pub enum DecodedMap {
66 Regular(SourceMap),
68 Index(SourceMapIndex),
70 Hermes(SourceMapHermes),
72}
73
74impl DecodedMap {
75 pub fn from_reader<R: Read>(rdr: R) -> Result<DecodedMap> {
77 decode(rdr)
78 }
79
80 pub fn to_writer<W: Write>(&self, w: W) -> Result<()> {
82 match *self {
83 DecodedMap::Regular(ref sm) => encode(sm, w),
84 DecodedMap::Index(ref smi) => encode(smi, w),
85 DecodedMap::Hermes(ref smh) => encode(smh, w),
86 }
87 }
88
89 pub fn lookup_token(&self, line: u32, col: u32) -> Option<Token<'_>> {
94 match *self {
95 DecodedMap::Regular(ref sm) => sm.lookup_token(line, col),
96 DecodedMap::Index(ref smi) => smi.lookup_token(line, col),
97 DecodedMap::Hermes(ref smh) => smh.lookup_token(line, col),
98 }
99 }
100
101 pub fn get_original_function_name(
107 &self,
108 line: u32,
109 col: u32,
110 minified_name: Option<&str>,
111 source_view: Option<&SourceView>,
112 ) -> Option<&str> {
113 match *self {
114 DecodedMap::Regular(ref sm) => {
115 sm.get_original_function_name(line, col, minified_name?, source_view?)
116 }
117 DecodedMap::Index(ref smi) => {
118 smi.get_original_function_name(line, col, minified_name?, source_view?)
119 }
120 DecodedMap::Hermes(ref smh) => {
121 if line != 0 {
122 return None;
123 }
124 smh.get_original_function_name(col)
125 }
126 }
127 }
128}
129
130#[derive(PartialEq, Eq, Copy, Clone, Debug)]
137pub struct RawToken {
138 pub dst_line: u32,
140 pub dst_col: u32,
142 pub src_line: u32,
144 pub src_col: u32,
146 pub src_id: u32,
148 pub name_id: u32,
150
151 pub is_range: bool,
155}
156
157#[derive(Copy, Clone)]
159pub struct Token<'a> {
160 raw: &'a RawToken,
161 pub(crate) sm: &'a SourceMap,
162 pub(crate) idx: usize,
163 offset: u32,
164}
165
166impl<'a> Token<'a> {
167 pub fn sourcemap(&self) -> &'a SourceMap {
169 self.sm
170 }
171}
172
173impl PartialEq for Token<'_> {
174 fn eq(&self, other: &Token<'_>) -> bool {
175 self.raw == other.raw
176 }
177}
178
179impl Eq for Token<'_> {}
180
181impl PartialOrd for Token<'_> {
182 fn partial_cmp(&self, other: &Token<'_>) -> Option<Ordering> {
183 Some(self.cmp(other))
184 }
185}
186
187impl Ord for Token<'_> {
188 fn cmp(&self, other: &Token<'_>) -> Ordering {
189 macro_rules! try_cmp {
190 ($a:expr, $b:expr) => {
191 match $a.cmp(&$b) {
192 Ordering::Equal => {}
193 x => {
194 return x;
195 }
196 }
197 };
198 }
199 try_cmp!(self.get_dst_line(), other.get_dst_line());
200 try_cmp!(self.get_dst_col(), other.get_dst_col());
201 try_cmp!(self.get_source(), other.get_source());
202 try_cmp!(self.get_src_line(), other.get_src_line());
203 try_cmp!(self.get_src_col(), other.get_src_col());
204 try_cmp!(self.get_name(), other.get_name());
205 try_cmp!(self.is_range(), other.is_range());
206
207 Ordering::Equal
208 }
209}
210
211impl<'a> Token<'a> {
212 pub fn get_dst_line(&self) -> u32 {
214 self.raw.dst_line
215 }
216
217 pub fn get_dst_col(&self) -> u32 {
219 self.raw.dst_col
220 }
221
222 pub fn get_dst(&self) -> (u32, u32) {
224 (self.get_dst_line(), self.get_dst_col())
225 }
226
227 pub fn get_src_line(&self) -> u32 {
229 self.raw.src_line
230 }
231
232 pub fn get_src_col(&self) -> u32 {
234 self.raw.src_col.saturating_add(self.offset)
235 }
236
237 pub fn get_src(&self) -> (u32, u32) {
239 (self.get_src_line(), self.get_src_col())
240 }
241
242 pub fn get_src_id(&self) -> u32 {
244 self.raw.src_id
245 }
246
247 pub fn get_source(&self) -> Option<&'a str> {
249 if self.raw.src_id == !0 {
250 None
251 } else {
252 self.sm.get_source(self.raw.src_id)
253 }
254 }
255
256 pub fn has_source(&self) -> bool {
258 self.raw.src_id != !0
259 }
260
261 pub fn get_name(&self) -> Option<&'a str> {
263 if self.raw.name_id == !0 {
264 None
265 } else {
266 self.sm.get_name(self.raw.name_id)
267 }
268 }
269
270 pub fn has_name(&self) -> bool {
272 self.get_name().is_some()
273 }
274
275 pub fn get_name_id(&self) -> u32 {
277 self.raw.name_id
278 }
279
280 pub fn to_tuple(&self) -> (&'a str, u32, u32, Option<&'a str>) {
283 (
284 self.get_source().unwrap_or(""),
285 self.get_src_line(),
286 self.get_src_col(),
287 self.get_name(),
288 )
289 }
290
291 pub fn get_raw_token(&self) -> RawToken {
293 *self.raw
294 }
295
296 pub fn get_source_view(&self) -> Option<&SourceView> {
298 self.sm.get_source_view(self.get_src_id())
299 }
300
301 pub fn is_range(&self) -> bool {
305 self.raw.is_range
306 }
307}
308
309pub struct TokenIter<'a> {
311 i: &'a SourceMap,
312 next_idx: usize,
313}
314
315impl TokenIter<'_> {
316 pub fn seek(&mut self, line: u32, col: u32) -> bool {
317 let token = self.i.lookup_token(line, col);
318 match token {
319 Some(token) => {
320 self.next_idx = token.idx + 1;
321 true
322 }
323 None => false,
324 }
325 }
326}
327
328impl<'a> Iterator for TokenIter<'a> {
329 type Item = Token<'a>;
330
331 fn next(&mut self) -> Option<Token<'a>> {
332 self.i.get_token(self.next_idx).inspect(|_| {
333 self.next_idx += 1;
334 })
335 }
336}
337
338pub struct SourceIter<'a> {
340 i: &'a SourceMap,
341 next_idx: u32,
342}
343
344impl<'a> Iterator for SourceIter<'a> {
345 type Item = &'a str;
346
347 fn next(&mut self) -> Option<&'a str> {
348 self.i.get_source(self.next_idx).inspect(|_| {
349 self.next_idx += 1;
350 })
351 }
352}
353
354pub struct SourceContentsIter<'a> {
356 i: &'a SourceMap,
357 next_idx: u32,
358}
359
360impl<'a> Iterator for SourceContentsIter<'a> {
361 type Item = Option<&'a str>;
362
363 fn next(&mut self) -> Option<Option<&'a str>> {
364 if self.next_idx >= self.i.get_source_count() {
365 None
366 } else {
367 let rv = Some(self.i.get_source_contents(self.next_idx));
368 self.next_idx += 1;
369 rv
370 }
371 }
372}
373
374pub struct NameIter<'a> {
376 i: &'a SourceMap,
377 next_idx: u32,
378}
379
380impl<'a> Iterator for NameIter<'a> {
381 type Item = &'a str;
382
383 fn next(&mut self) -> Option<&'a str> {
384 self.i.get_name(self.next_idx).inspect(|_| {
385 self.next_idx += 1;
386 })
387 }
388}
389
390impl fmt::Debug for Token<'_> {
391 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
392 write!(f, "<Token {self:#}>")
393 }
394}
395
396impl fmt::Display for Token<'_> {
397 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
398 write!(
399 f,
400 "{}:{}:{}{}",
401 self.get_source().unwrap_or("<unknown>"),
402 self.get_src_line(),
403 self.get_src_col(),
404 self.get_name()
405 .map(|x| format!(" name={x}"))
406 .unwrap_or_default()
407 )?;
408 if f.alternate() {
409 write!(
410 f,
411 " ({}:{}){}",
412 self.get_dst_line(),
413 self.get_dst_col(),
414 if self.is_range() { " (range)" } else { "" }
415 )?;
416 }
417 Ok(())
418 }
419}
420
421#[derive(Debug, Clone)]
423pub struct SourceMapSection {
424 offset: (u32, u32),
425 url: Option<String>,
426 map: Option<Box<DecodedMap>>,
427}
428
429pub struct SourceMapSectionIter<'a> {
431 i: &'a SourceMapIndex,
432 next_idx: u32,
433}
434
435impl<'a> Iterator for SourceMapSectionIter<'a> {
436 type Item = &'a SourceMapSection;
437
438 fn next(&mut self) -> Option<&'a SourceMapSection> {
439 self.i.get_section(self.next_idx).inspect(|_| {
440 self.next_idx += 1;
441 })
442 }
443}
444
445#[derive(Debug, Clone)]
447pub struct SourceMapIndex {
448 file: Option<String>,
449 sections: Vec<SourceMapSection>,
450 x_facebook_offsets: Option<Vec<Option<u32>>>,
451 x_metro_module_paths: Option<Vec<String>>,
452}
453
454#[derive(Clone, Debug)]
460pub struct SourceMap {
461 pub(crate) file: Option<Arc<str>>,
462 pub(crate) tokens: Vec<RawToken>,
463 pub(crate) names: Vec<Arc<str>>,
464 pub(crate) source_root: Option<Arc<str>>,
465 pub(crate) sources: Vec<Arc<str>>,
466 pub(crate) sources_prefixed: Option<Vec<Arc<str>>>,
467 pub(crate) sources_content: Vec<Option<SourceView>>,
468 pub(crate) ignore_list: BTreeSet<u32>,
469 pub(crate) debug_id: Option<DebugId>,
470}
471
472impl SourceMap {
473 pub fn from_reader<R: Read>(rdr: R) -> Result<SourceMap> {
494 match decode(rdr)? {
495 DecodedMap::Regular(sm) => Ok(sm),
496 _ => Err(Error::IncompatibleSourceMap),
497 }
498 }
499
500 pub fn to_writer<W: Write>(&self, w: W) -> Result<()> {
521 encode(self, w)
522 }
523
524 pub fn to_data_url(&self) -> Result<String> {
538 let mut buf = vec![];
539 encode(self, &mut buf)?;
540 let b64 = base64_simd::Base64::STANDARD.encode_to_boxed_str(&buf);
541 Ok(format!(
542 "data:application/json;charset=utf-8;base64,{}",
543 b64
544 ))
545 }
546
547 pub fn from_slice(slice: &[u8]) -> Result<SourceMap> {
563 match decode_slice(slice)? {
564 DecodedMap::Regular(sm) => Ok(sm),
565 _ => Err(Error::IncompatibleSourceMap),
566 }
567 }
568
569 pub fn new(
578 file: Option<Arc<str>>,
579 mut tokens: Vec<RawToken>,
580 names: Vec<Arc<str>>,
581 sources: Vec<Arc<str>>,
582 sources_content: Option<Vec<Option<Arc<str>>>>,
583 ) -> SourceMap {
584 tokens.sort_unstable_by_key(|t| (t.dst_line, t.dst_col));
585 SourceMap {
586 file,
587 tokens,
588 names,
589 source_root: None,
590 sources,
591 sources_prefixed: None,
592 sources_content: sources_content
593 .unwrap_or_default()
594 .into_iter()
595 .map(|opt| opt.map(SourceView::new))
596 .collect(),
597 ignore_list: BTreeSet::default(),
598 debug_id: None,
599 }
600 }
601
602 pub fn get_debug_id(&self) -> Option<DebugId> {
604 self.debug_id
605 }
606
607 pub fn set_debug_id(&mut self, debug_id: Option<DebugId>) {
609 self.debug_id = debug_id
610 }
611
612 pub fn get_file(&self) -> Option<&str> {
614 self.file.as_deref()
615 }
616
617 pub fn set_file<T: Into<Arc<str>>>(&mut self, value: Option<T>) {
619 self.file = value.map(Into::into);
620 }
621
622 pub fn get_source_root(&self) -> Option<&str> {
624 self.source_root.as_deref()
625 }
626
627 fn prefix_source(source_root: &str, source: &str) -> Arc<str> {
628 let source_root = source_root.strip_suffix('/').unwrap_or(source_root);
629 let is_valid = !source.is_empty()
630 && (source.starts_with('/')
631 || source.starts_with("http:")
632 || source.starts_with("https:"));
633
634 if is_valid {
635 source.into()
636 } else {
637 format!("{source_root}/{source}").into()
638 }
639 }
640
641 pub fn set_source_root<T: Into<Arc<str>>>(&mut self, value: Option<T>) {
643 self.source_root = value.map(Into::into);
644
645 match self.source_root.as_deref().filter(|rs| !rs.is_empty()) {
646 Some(source_root) => {
647 let sources_prefixed = self
648 .sources
649 .iter()
650 .map(|source| Self::prefix_source(source_root, source))
651 .collect();
652 self.sources_prefixed = Some(sources_prefixed)
653 }
654 None => self.sources_prefixed = None,
655 }
656 }
657
658 pub fn add_to_ignore_list(&mut self, src_id: u32) {
659 self.ignore_list.insert(src_id);
660 }
661
662 pub fn ignore_list(&self) -> impl Iterator<Item = &u32> {
663 self.ignore_list.iter()
664 }
665
666 pub fn get_token(&self, idx: usize) -> Option<Token<'_>> {
668 self.tokens.get(idx).map(|raw| Token {
669 raw,
670 sm: self,
671 idx,
672 offset: 0,
673 })
674 }
675
676 pub fn get_token_count(&self) -> u32 {
678 self.tokens.len() as u32
679 }
680
681 pub fn tokens(&self) -> TokenIter<'_> {
683 TokenIter {
684 i: self,
685 next_idx: 0,
686 }
687 }
688
689 pub fn lookup_token(&self, line: u32, col: u32) -> Option<Token<'_>> {
691 let (idx, raw) =
692 greatest_lower_bound(&self.tokens, &(line, col), |t| (t.dst_line, t.dst_col))?;
693
694 let mut token = Token {
695 raw,
696 sm: self,
697 idx,
698 offset: 0,
699 };
700
701 if token.is_range() {
702 token.offset = col - token.get_dst_col();
703 }
704
705 Some(token)
706 }
707
708 pub fn get_original_function_name(
717 &self,
718 line: u32,
719 col: u32,
720 minified_name: &str,
721 sv: &SourceView,
722 ) -> Option<&str> {
723 self.lookup_token(line, col)
724 .and_then(|token| sv.get_original_function_name(token, minified_name))
725 }
726
727 pub fn get_source_count(&self) -> u32 {
729 self.sources.len() as u32
730 }
731
732 pub fn get_source(&self, idx: u32) -> Option<&str> {
734 let sources = self.sources_prefixed.as_deref().unwrap_or(&self.sources);
735 sources.get(idx as usize).map(|x| &x[..])
736 }
737
738 pub fn set_source(&mut self, idx: u32, value: &str) {
743 self.sources[idx as usize] = value.into();
744
745 if let Some(sources_prefixed) = self.sources_prefixed.as_mut() {
746 sources_prefixed[idx as usize] =
748 Self::prefix_source(self.source_root.as_deref().unwrap(), value);
749 }
750 }
751
752 pub fn sources(&self) -> SourceIter<'_> {
754 SourceIter {
755 i: self,
756 next_idx: 0,
757 }
758 }
759
760 pub fn get_source_view(&self, idx: u32) -> Option<&SourceView> {
762 self.sources_content
763 .get(idx as usize)
764 .and_then(Option::as_ref)
765 }
766
767 pub fn get_source_contents(&self, idx: u32) -> Option<&str> {
769 self.sources_content
770 .get(idx as usize)
771 .and_then(Option::as_ref)
772 .map(SourceView::source)
773 }
774
775 pub fn set_source_contents(&mut self, idx: u32, value: Option<&str>) {
777 if self.sources_content.len() != self.sources.len() {
778 self.sources_content.resize(self.sources.len(), None);
779 }
780 self.sources_content[idx as usize] = value.map(|x| SourceView::from_string(x.to_string()));
781 }
782
783 pub fn source_contents(&self) -> SourceContentsIter<'_> {
785 SourceContentsIter {
786 i: self,
787 next_idx: 0,
788 }
789 }
790
791 pub fn names(&self) -> NameIter<'_> {
793 NameIter {
794 i: self,
795 next_idx: 0,
796 }
797 }
798
799 pub fn get_name_count(&self) -> u32 {
801 self.names.len() as u32
802 }
803
804 pub fn has_names(&self) -> bool {
806 !self.names.is_empty()
807 }
808
809 pub fn get_name(&self, idx: u32) -> Option<&str> {
811 self.names.get(idx as usize).map(|x| &x[..])
812 }
813
814 pub fn remove_names(&mut self) {
816 self.names.clear();
817 }
818
819 pub fn rewrite(self, options: &RewriteOptions<'_>) -> Result<SourceMap> {
841 Ok(self.rewrite_with_mapping(options)?.0)
842 }
843
844 pub(crate) fn rewrite_with_mapping(
846 self,
847 options: &RewriteOptions<'_>,
848 ) -> Result<(SourceMap, Vec<u32>)> {
849 let mut builder = SourceMapBuilder::new(self.get_file());
850 builder.set_debug_id(self.debug_id);
851
852 for token in self.tokens() {
853 let raw = builder.add_token(&token, options.with_names);
854 if raw.src_id != !0
855 && options.with_source_contents
856 && !builder.has_source_contents(raw.src_id)
857 {
858 builder
859 .set_source_contents(raw.src_id, self.get_source_contents(token.get_src_id()));
860 }
861 }
862
863 #[cfg(any(unix, windows, target_os = "redox"))]
864 {
865 if options.load_local_source_contents {
866 builder.load_local_source_contents(options.base_path)?;
867 }
868 }
869
870 let mut prefixes = vec![];
871 let mut need_common_prefix = false;
872 for &prefix in options.strip_prefixes.iter() {
873 if prefix == "~" {
874 need_common_prefix = true;
875 } else {
876 prefixes.push(prefix.to_string());
877 }
878 }
879 if need_common_prefix {
880 if let Some(prefix) = find_common_prefix(self.sources.iter().map(AsRef::as_ref)) {
881 prefixes.push(prefix);
882 }
883 }
884 if !prefixes.is_empty() {
885 builder.strip_prefixes(&prefixes);
886 }
887
888 let mapping = builder.take_mapping();
889
890 let sm = builder.into_sourcemap();
891
892 Ok((sm, mapping))
893 }
894
895 pub fn adjust_mappings(&mut self, adjustment: &Self) {
911 #[derive(Debug, Clone, Copy)]
940 struct Range {
941 start: (u32, u32),
942 end: (u32, u32),
943 value: RawToken,
944 }
945
946 fn create_ranges(
948 mut tokens: Vec<RawToken>,
949 key: fn(&RawToken) -> (u32, u32),
950 ) -> Vec<Range> {
951 tokens.sort_unstable_by_key(key);
952
953 let mut token_iter = tokens.into_iter().peekable();
954 let mut ranges = Vec::new();
955
956 while let Some(t) = token_iter.next() {
957 let start = key(&t);
958 let next_start = token_iter.peek().map_or((u32::MAX, u32::MAX), key);
959 let end = std::cmp::min(next_start, (start.0, u32::MAX));
961 ranges.push(Range {
962 start,
963 end,
964 value: t,
965 });
966 }
967
968 ranges
969 }
970
971 let self_tokens = std::mem::take(&mut self.tokens);
977 let original_ranges = create_ranges(self_tokens, |t| (t.dst_line, t.dst_col));
978 let adjustment_ranges =
979 create_ranges(adjustment.tokens.clone(), |t| (t.src_line, t.src_col));
980
981 let mut original_ranges_iter = original_ranges.iter();
982
983 let mut original_range = match original_ranges_iter.next() {
984 Some(r) => r,
985 None => return,
986 };
987
988 'outer: for &adjustment_range in &adjustment_ranges {
991 let (line_diff, col_diff) = (
994 adjustment_range.value.dst_line as i32 - adjustment_range.value.src_line as i32,
995 adjustment_range.value.dst_col as i32 - adjustment_range.value.src_col as i32,
996 );
997
998 while original_range.end <= adjustment_range.start {
1000 match original_ranges_iter.next() {
1001 Some(r) => original_range = r,
1002 None => break 'outer,
1003 }
1004 }
1005
1006 while original_range.start < adjustment_range.end {
1010 let (dst_line, dst_col) =
1012 std::cmp::max(original_range.start, adjustment_range.start);
1013 let mut token = RawToken {
1014 dst_line,
1015 dst_col,
1016 ..original_range.value
1017 };
1018
1019 token.dst_line = (token.dst_line as i32 + line_diff) as u32;
1020 token.dst_col = (token.dst_col as i32 + col_diff) as u32;
1021
1022 self.tokens.push(token);
1023
1024 if original_range.end >= adjustment_range.end {
1025 break;
1028 } else {
1029 match original_ranges_iter.next() {
1031 Some(r) => original_range = r,
1032 None => break 'outer,
1033 }
1034 }
1035 }
1036 }
1037
1038 self.tokens
1039 .sort_unstable_by_key(|t| (t.dst_line, t.dst_col));
1040 }
1041}
1042
1043impl SourceMapIndex {
1044 pub fn from_reader<R: Read>(rdr: R) -> Result<SourceMapIndex> {
1049 match decode(rdr)? {
1050 DecodedMap::Index(smi) => Ok(smi),
1051 _ => Err(Error::IncompatibleSourceMap),
1052 }
1053 }
1054
1055 pub fn to_writer<W: Write>(&self, w: W) -> Result<()> {
1057 encode(self, w)
1058 }
1059
1060 pub fn from_slice(slice: &[u8]) -> Result<SourceMapIndex> {
1065 match decode_slice(slice)? {
1066 DecodedMap::Index(smi) => Ok(smi),
1067 _ => Err(Error::IncompatibleSourceMap),
1068 }
1069 }
1070
1071 pub fn new(file: Option<String>, sections: Vec<SourceMapSection>) -> SourceMapIndex {
1076 SourceMapIndex {
1077 file,
1078 sections,
1079 x_facebook_offsets: None,
1080 x_metro_module_paths: None,
1081 }
1082 }
1083
1084 pub fn new_ram_bundle_compatible(
1092 file: Option<String>,
1093 sections: Vec<SourceMapSection>,
1094 x_facebook_offsets: Option<Vec<Option<u32>>>,
1095 x_metro_module_paths: Option<Vec<String>>,
1096 ) -> SourceMapIndex {
1097 SourceMapIndex {
1098 file,
1099 sections,
1100 x_facebook_offsets,
1101 x_metro_module_paths,
1102 }
1103 }
1104
1105 pub fn get_file(&self) -> Option<&str> {
1107 self.file.as_ref().map(|x| &x[..])
1108 }
1109
1110 pub fn set_file(&mut self, value: Option<&str>) {
1112 self.file = value.map(str::to_owned);
1113 }
1114
1115 pub fn get_section_count(&self) -> u32 {
1117 self.sections.len() as u32
1118 }
1119
1120 pub fn get_section(&self, idx: u32) -> Option<&SourceMapSection> {
1122 self.sections.get(idx as usize)
1123 }
1124
1125 pub fn get_section_mut(&mut self, idx: u32) -> Option<&mut SourceMapSection> {
1127 self.sections.get_mut(idx as usize)
1128 }
1129
1130 pub fn sections(&self) -> SourceMapSectionIter<'_> {
1132 SourceMapSectionIter {
1133 i: self,
1134 next_idx: 0,
1135 }
1136 }
1137
1138 pub fn get_original_function_name(
1147 &self,
1148 line: u32,
1149 col: u32,
1150 minified_name: &str,
1151 sv: &SourceView,
1152 ) -> Option<&str> {
1153 self.lookup_token(line, col)
1154 .and_then(|token| sv.get_original_function_name(token, minified_name))
1155 }
1156
1157 pub fn lookup_token(&self, line: u32, col: u32) -> Option<Token<'_>> {
1163 let (_section_idx, section) =
1164 greatest_lower_bound(&self.sections, &(line, col), SourceMapSection::get_offset)?;
1165 let map = section.get_sourcemap()?;
1166 let (off_line, off_col) = section.get_offset();
1167 map.lookup_token(
1168 line - off_line,
1169 if line == off_line { col - off_col } else { col },
1170 )
1171 }
1172
1173 pub fn flatten(&self) -> Result<SourceMap> {
1176 let mut builder = SourceMapBuilder::new(self.get_file());
1177
1178 for section in self.sections() {
1179 let (off_line, off_col) = section.get_offset();
1180 let map = match section.get_sourcemap() {
1181 Some(map) => match map {
1182 DecodedMap::Regular(sm) => Cow::Borrowed(sm),
1183 DecodedMap::Index(idx) => Cow::Owned(idx.flatten()?),
1184 DecodedMap::Hermes(smh) => Cow::Borrowed(&smh.sm),
1185 },
1186 None => {
1187 return Err(Error::CannotFlatten(format!(
1188 "Section has an unresolved \
1189 sourcemap: {}",
1190 section.get_url().unwrap_or("<unknown url>")
1191 )));
1192 }
1193 };
1194
1195 for token in map.tokens() {
1196 let dst_col = if token.get_dst_line() == 0 {
1197 token.get_dst_col() + off_col
1198 } else {
1199 token.get_dst_col()
1200 };
1201 let raw = builder.add(
1202 token.get_dst_line() + off_line,
1203 dst_col,
1204 token.get_src_line(),
1205 token.get_src_col(),
1206 token.get_source(),
1207 token.get_name(),
1208 token.is_range(),
1209 );
1210 if token.get_source().is_some() && !builder.has_source_contents(raw.src_id) {
1211 builder.set_source_contents(
1212 raw.src_id,
1213 map.get_source_contents(token.get_src_id()),
1214 );
1215 }
1216 if map.ignore_list.contains(&token.get_src_id()) {
1217 builder.add_to_ignore_list(raw.src_id);
1218 }
1219 }
1220 }
1221
1222 Ok(builder.into_sourcemap())
1223 }
1224
1225 pub fn flatten_and_rewrite(self, options: &RewriteOptions<'_>) -> Result<SourceMap> {
1229 self.flatten()?.rewrite(options)
1230 }
1231
1232 pub fn is_for_ram_bundle(&self) -> bool {
1234 self.x_facebook_offsets.is_some() && self.x_metro_module_paths.is_some()
1235 }
1236
1237 pub fn x_facebook_offsets(&self) -> Option<&[Option<u32>]> {
1239 self.x_facebook_offsets.as_ref().map(|x| &x[..])
1240 }
1241
1242 pub fn x_metro_module_paths(&self) -> Option<&[String]> {
1244 self.x_metro_module_paths.as_ref().map(|x| &x[..])
1245 }
1246}
1247
1248impl SourceMapSection {
1249 pub fn new(
1255 offset: (u32, u32),
1256 url: Option<String>,
1257 map: Option<DecodedMap>,
1258 ) -> SourceMapSection {
1259 SourceMapSection {
1260 offset,
1261 url,
1262 map: map.map(Box::new),
1263 }
1264 }
1265
1266 pub fn get_offset_line(&self) -> u32 {
1268 self.offset.0
1269 }
1270
1271 pub fn get_offset_col(&self) -> u32 {
1273 self.offset.1
1274 }
1275
1276 pub fn get_offset(&self) -> (u32, u32) {
1278 self.offset
1279 }
1280
1281 pub fn get_url(&self) -> Option<&str> {
1283 self.url.as_deref()
1284 }
1285
1286 pub fn set_url(&mut self, value: Option<&str>) {
1288 self.url = value.map(str::to_owned);
1289 }
1290
1291 pub fn get_sourcemap(&self) -> Option<&DecodedMap> {
1293 self.map.as_ref().map(Box::as_ref)
1294 }
1295
1296 pub fn get_sourcemap_mut(&mut self) -> Option<&mut DecodedMap> {
1298 self.map.as_mut().map(Box::as_mut)
1299 }
1300
1301 pub fn set_sourcemap(&mut self, sm: Option<DecodedMap>) {
1303 self.map = sm.map(Box::new);
1304 }
1305}
1306
1307#[cfg(test)]
1308mod tests {
1309 use super::{RewriteOptions, SourceMap};
1310 use debugid::DebugId;
1311
1312 #[test]
1313 fn test_rewrite_debugid() {
1314 let input: &[_] = br#"{
1315 "version":3,
1316 "sources":["coolstuff.js"],
1317 "names":["x","alert"],
1318 "mappings":"AAAA,GAAIA,GAAI,EACR,IAAIA,GAAK,EAAG,CACVC,MAAM",
1319 "debug_id":"00000000-0000-0000-0000-000000000000"
1320 }"#;
1321
1322 let sm = SourceMap::from_slice(input).unwrap();
1323
1324 assert_eq!(sm.debug_id, Some(DebugId::default()));
1325
1326 let new_sm = sm
1327 .rewrite(&RewriteOptions {
1328 with_names: false,
1329 ..Default::default()
1330 })
1331 .unwrap();
1332
1333 assert_eq!(new_sm.debug_id, Some(DebugId::default()));
1334 }
1335
1336 #[test]
1337 fn test_debugid_alias() {
1338 let input: &[_] = br#"{
1339 "version":3,
1340 "sources":["coolstuff.js"],
1341 "names":["x","alert"],
1342 "mappings":"AAAA,GAAIA,GAAI,EACR,IAAIA,GAAK,EAAG,CACVC,MAAM",
1343 "debug_id":"00000000-0000-0000-0000-000000000000",
1344 "debugId": "11111111-1111-1111-1111-111111111111"
1345 }"#;
1346
1347 let sm = SourceMap::from_slice(input).unwrap();
1348
1349 assert_eq!(sm.debug_id, Some(DebugId::default()));
1350 }
1351
1352 #[test]
1353 fn test_adjust_mappings_injection() {
1354 for bundler in ["esbuild", "rollup", "vite", "webpack", "rspack"] {
1369 let original_map_file = std::fs::File::open(format!(
1370 "tests/fixtures/adjust_mappings/{bundler}.bundle.js.map"
1371 ))
1372 .unwrap();
1373
1374 let injected_map_file = std::fs::File::open(format!(
1375 "tests/fixtures/adjust_mappings/{bundler}-injected.bundle.js.map"
1376 ))
1377 .unwrap();
1378
1379 let composed_map_file = std::fs::File::open(format!(
1380 "tests/fixtures/adjust_mappings/{bundler}-composed.bundle.js.map"
1381 ))
1382 .unwrap();
1383
1384 let mut original_map = SourceMap::from_reader(original_map_file).unwrap();
1385 let injected_map = SourceMap::from_reader(injected_map_file).unwrap();
1386 let composed_map = SourceMap::from_reader(composed_map_file).unwrap();
1387 original_map.adjust_mappings(&injected_map);
1388
1389 assert_eq!(
1390 original_map.tokens, composed_map.tokens,
1391 "bundler = {bundler}"
1392 );
1393 }
1394 }
1395
1396 #[test]
1397 fn test_roundtrip() {
1398 let sm = br#"{
1399 "version": 3,
1400 "file": "foo.js",
1401 "sources": [
1402 "./bar.js",
1403 "./baz.js"
1404 ],
1405 "sourceRoot": "webpack:///",
1406 "sourcesContent": [null, null],
1407 "names": [],
1408 "mappings": ""
1409 }"#;
1410
1411 let sm = SourceMap::from_slice(sm).unwrap();
1412 let mut out = Vec::new();
1413 sm.to_writer(&mut out).unwrap();
1414
1415 let sm_new = SourceMap::from_slice(&out).unwrap();
1416 assert_eq!(sm_new.sources, sm.sources);
1417 }
1418
1419 mod prop {
1420 use magic_string::MagicString;
1439 use proptest::prelude::*;
1440
1441 use crate::SourceMap;
1442
1443 #[derive(Debug, Clone)]
1445 enum FirstEdit {
1446 Insert(u32, String),
1448 Delete(i64, i64),
1450 }
1451
1452 impl FirstEdit {
1453 fn apply(&self, line: usize, ms: &mut MagicString) {
1455 let line_offset = line * 11;
1457 match self {
1458 FirstEdit::Insert(col, s) => {
1459 ms.append_left(line_offset as u32 + *col, s).unwrap();
1460 }
1461 FirstEdit::Delete(start, end) => {
1462 ms.remove(line_offset as i64 + *start, line_offset as i64 + *end)
1463 .unwrap();
1464 }
1465 }
1466 }
1467 }
1468
1469 fn nth_line_start_end(n: usize, s: &str) -> (usize, usize) {
1472 let line = s.lines().nth(n).unwrap();
1473 let start = line.as_ptr() as usize - s.as_ptr() as usize;
1474 let end = if n == 9 {
1476 start + line.len()
1477 } else {
1478 start + line.len() + 1
1479 };
1480 (start, end)
1481 }
1482
1483 #[derive(Debug, Clone)]
1485 enum SecondEdit {
1486 Prepend(String),
1488 Append(String),
1490 Insert(usize, String),
1492 Delete(usize),
1494 }
1495
1496 impl SecondEdit {
1497 fn apply(&self, orig: &str, ms: &mut MagicString) {
1502 match self {
1503 SecondEdit::Prepend(s) => {
1504 ms.prepend(s).unwrap();
1505 }
1506 SecondEdit::Append(s) => {
1507 ms.append(s).unwrap();
1508 }
1509 SecondEdit::Insert(line, s) => {
1510 let (start, _) = nth_line_start_end(*line, orig);
1511 ms.prepend_left(start as u32, s).unwrap();
1512 }
1513 SecondEdit::Delete(line) => {
1514 let (start, end) = nth_line_start_end(*line, orig);
1515 ms.remove(start as i64, end as i64).unwrap();
1516 }
1517 }
1518 }
1519 }
1520
1521 fn starting_string() -> impl Strategy<Value = String> {
1523 (vec!["[a-z]{10}"; 10]).prop_map(|v| v.join("\n"))
1524 }
1525
1526 fn first_edit() -> impl Strategy<Value = FirstEdit> {
1528 prop_oneof![
1529 (1u32..9, "[a-z]{5}").prop_map(|(c, s)| FirstEdit::Insert(c, s)),
1530 (1i64..10)
1531 .prop_flat_map(|end| (0..end, Just(end)))
1532 .prop_map(|(a, b)| FirstEdit::Delete(a, b))
1533 ]
1534 }
1535
1536 fn first_edit_sequence() -> impl Strategy<Value = Vec<FirstEdit>> {
1540 let mut vec = Vec::with_capacity(10);
1541
1542 for _ in 0..10 {
1543 vec.push(first_edit())
1544 }
1545
1546 vec
1547 }
1548
1549 fn second_edit_sequence() -> impl Strategy<Value = Vec<SecondEdit>> {
1554 let edits = (0..10)
1555 .map(|i| {
1556 prop_oneof![
1557 "[a-z\n]{12}".prop_map(SecondEdit::Prepend),
1558 "[a-z\n]{12}".prop_map(SecondEdit::Append),
1559 "[a-z\n]{11}\n".prop_map(move |s| SecondEdit::Insert(i, s)),
1560 Just(SecondEdit::Delete(i)),
1561 ]
1562 })
1563 .collect::<Vec<_>>();
1564
1565 edits.prop_shuffle()
1566 }
1567
1568 proptest! {
1569 #[test]
1570 fn test_composition_identity(
1571 input in starting_string(),
1572 first_edits in first_edit_sequence(),
1573 second_edits in second_edit_sequence(),
1574 ) {
1575
1576 let mut ms1 = MagicString::new(&input);
1579
1580 for (line, first_edit) in first_edits.iter().enumerate() {
1581 first_edit.apply(line, &mut ms1);
1582 }
1583
1584 let first_map = ms1.generate_map(Default::default()).unwrap().to_string().unwrap();
1585 let mut first_map = SourceMap::from_slice(first_map.as_bytes()).unwrap();
1586
1587 let transformed_input = ms1.to_string();
1588
1589 let mut ms2 = MagicString::new(&transformed_input);
1590
1591 for second_edit in second_edits.iter() {
1592 second_edit.apply(&transformed_input, &mut ms2);
1593 }
1594
1595 let output_1 = ms2.to_string();
1596
1597 let second_map = ms2.generate_map(Default::default()).unwrap().to_string().unwrap();
1598 let second_map = SourceMap::from_slice(second_map.as_bytes()).unwrap();
1599
1600 let mut ms3 = MagicString::new(&input);
1603
1604 for (line, first_edit) in first_edits.iter().enumerate() {
1605 first_edit.apply(line, &mut ms3);
1606 }
1607
1608 for second_edit in second_edits.iter() {
1609 second_edit.apply(&input, &mut ms3);
1610 }
1611
1612 let output_2 = ms3.to_string();
1613
1614 let third_map = ms3.generate_map(Default::default()).unwrap().to_string().unwrap();
1615 let third_map = SourceMap::from_slice(third_map.as_bytes()).unwrap();
1616
1617 assert_eq!(output_1, output_2);
1619
1620 first_map.adjust_mappings(&second_map);
1621
1622 assert_eq!(first_map.tokens, third_map.tokens);
1623 }
1624 }
1625 }
1626}