1use std::cell::{OnceCell, RefCell};
27use std::collections::HashMap;
28use std::fmt;
29
30use serde::Deserialize;
31use srcmap_codec::{DecodeError, vlq_encode_unsigned};
32use srcmap_scopes::ScopeInfo;
33
34const NO_SOURCE: u32 = u32::MAX;
37const NO_NAME: u32 = u32::MAX;
38
39#[derive(Debug, Clone, Copy)]
47pub struct Mapping {
48 pub generated_line: u32,
50 pub generated_column: u32,
52 pub source: u32,
54 pub original_line: u32,
56 pub original_column: u32,
58 pub name: u32,
60 pub is_range_mapping: bool,
62}
63
64#[derive(Debug, Clone)]
69pub struct OriginalLocation {
70 pub source: u32,
72 pub line: u32,
74 pub column: u32,
76 pub name: Option<u32>,
78}
79
80#[derive(Debug, Clone)]
84pub struct GeneratedLocation {
85 pub line: u32,
87 pub column: u32,
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
97pub enum Bias {
98 #[default]
100 GreatestLowerBound,
101 LeastUpperBound,
103}
104
105#[derive(Debug, Clone)]
110pub struct MappedRange {
111 pub source: u32,
113 pub original_start_line: u32,
115 pub original_start_column: u32,
117 pub original_end_line: u32,
119 pub original_end_column: u32,
121}
122
123#[derive(Debug)]
125pub enum ParseError {
126 Json(serde_json::Error),
128 Vlq(DecodeError),
130 InvalidVersion(u32),
132 Scopes(srcmap_scopes::ScopesError),
134 NestedIndexMap,
136 SectionsNotOrdered,
138}
139
140impl fmt::Display for ParseError {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 match self {
143 Self::Json(e) => write!(f, "JSON parse error: {e}"),
144 Self::Vlq(e) => write!(f, "VLQ decode error: {e}"),
145 Self::InvalidVersion(v) => write!(f, "unsupported source map version: {v}"),
146 Self::Scopes(e) => write!(f, "scopes decode error: {e}"),
147 Self::NestedIndexMap => write!(f, "section map must not be an indexed source map"),
148 Self::SectionsNotOrdered => {
149 write!(f, "sections must be in ascending (line, column) order")
150 }
151 }
152 }
153}
154
155impl std::error::Error for ParseError {
156 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
157 match self {
158 Self::Json(e) => Some(e),
159 Self::Vlq(e) => Some(e),
160 Self::Scopes(e) => Some(e),
161 Self::InvalidVersion(_) | Self::NestedIndexMap | Self::SectionsNotOrdered => None,
162 }
163 }
164}
165
166impl From<serde_json::Error> for ParseError {
167 fn from(e: serde_json::Error) -> Self {
168 Self::Json(e)
169 }
170}
171
172impl From<DecodeError> for ParseError {
173 fn from(e: DecodeError) -> Self {
174 Self::Vlq(e)
175 }
176}
177
178impl From<srcmap_scopes::ScopesError> for ParseError {
179 fn from(e: srcmap_scopes::ScopesError) -> Self {
180 Self::Scopes(e)
181 }
182}
183
184fn resolve_sources(raw_sources: &[Option<String>], source_root: &str) -> Vec<String> {
188 raw_sources
189 .iter()
190 .map(|s| match s {
191 Some(s) if !source_root.is_empty() => format!("{source_root}{s}"),
192 Some(s) => s.clone(),
193 None => String::new(),
194 })
195 .collect()
196}
197
198fn build_source_map(sources: &[String]) -> HashMap<String, u32> {
200 sources
201 .iter()
202 .enumerate()
203 .map(|(i, s)| (s.clone(), i as u32))
204 .collect()
205}
206
207#[derive(Deserialize)]
210struct RawSourceMap<'a> {
211 version: u32,
212 #[serde(default)]
213 file: Option<String>,
214 #[serde(default, rename = "sourceRoot")]
215 source_root: Option<String>,
216 #[serde(default)]
217 sources: Vec<Option<String>>,
218 #[serde(default, rename = "sourcesContent")]
219 sources_content: Option<Vec<Option<String>>>,
220 #[serde(default)]
221 names: Vec<String>,
222 #[serde(default, borrow)]
223 mappings: &'a str,
224 #[serde(default, rename = "ignoreList")]
225 ignore_list: Option<Vec<u32>>,
226 #[serde(default, rename = "x_google_ignoreList")]
228 x_google_ignore_list: Option<Vec<u32>>,
229 #[serde(default, rename = "debugId", alias = "debug_id")]
232 debug_id: Option<String>,
233 #[serde(default, borrow)]
235 scopes: Option<&'a str>,
236 #[serde(default, borrow, rename = "rangeMappings")]
238 range_mappings: Option<&'a str>,
239 #[serde(default)]
241 sections: Option<Vec<RawSection>>,
242 #[serde(flatten)]
244 extensions: HashMap<String, serde_json::Value>,
245}
246
247#[derive(Deserialize)]
249struct RawSection {
250 offset: RawOffset,
251 map: Box<serde_json::value::RawValue>,
252}
253
254#[derive(Deserialize)]
255struct RawOffset {
256 line: u32,
257 column: u32,
258}
259
260#[derive(Debug, Clone)]
285pub struct SourceMap {
286 pub file: Option<String>,
287 pub source_root: Option<String>,
288 pub sources: Vec<String>,
289 pub sources_content: Vec<Option<String>>,
290 pub names: Vec<String>,
291 pub ignore_list: Vec<u32>,
292 pub extensions: HashMap<String, serde_json::Value>,
294 pub debug_id: Option<String>,
296 pub scopes: Option<ScopeInfo>,
298
299 mappings: Vec<Mapping>,
301
302 line_offsets: Vec<u32>,
305
306 reverse_index: OnceCell<Vec<u32>>,
309
310 source_map: HashMap<String, u32>,
312
313 has_range_mappings: bool,
315}
316
317impl SourceMap {
318 pub fn from_json(json: &str) -> Result<Self, ParseError> {
321 Self::from_json_inner(json, true)
322 }
323
324 fn from_json_inner(json: &str, allow_sections: bool) -> Result<Self, ParseError> {
326 let raw: RawSourceMap<'_> = serde_json::from_str(json)?;
327
328 if raw.version != 3 {
329 return Err(ParseError::InvalidVersion(raw.version));
330 }
331
332 if let Some(sections) = raw.sections {
334 if !allow_sections {
335 return Err(ParseError::NestedIndexMap);
336 }
337 return Self::from_sections(raw.file, sections);
338 }
339
340 Self::from_regular(raw)
341 }
342
343 fn from_regular(raw: RawSourceMap<'_>) -> Result<Self, ParseError> {
345 let source_root = raw.source_root.as_deref().unwrap_or("");
346 let sources = resolve_sources(&raw.sources, source_root);
347 let sources_content = raw.sources_content.unwrap_or_default();
348 let source_map = build_source_map(&sources);
349
350 let (mut mappings, line_offsets) = decode_mappings(raw.mappings)?;
352
353 if let Some(range_mappings_str) = raw.range_mappings
355 && !range_mappings_str.is_empty()
356 {
357 decode_range_mappings(range_mappings_str, &mut mappings, &line_offsets)?;
358 }
359
360 let num_sources = sources.len();
362 let scopes = match raw.scopes {
363 Some(scopes_str) if !scopes_str.is_empty() => Some(srcmap_scopes::decode_scopes(
364 scopes_str,
365 &raw.names,
366 num_sources,
367 )?),
368 _ => None,
369 };
370
371 let ignore_list = match raw.ignore_list {
373 Some(list) => list,
374 None => raw.x_google_ignore_list.unwrap_or_default(),
375 };
376
377 let extensions: HashMap<String, serde_json::Value> = raw
379 .extensions
380 .into_iter()
381 .filter(|(k, _)| k.starts_with("x_") || k.starts_with("x-"))
382 .collect();
383
384 let has_range_mappings = mappings.iter().any(|m| m.is_range_mapping);
385
386 Ok(Self {
387 file: raw.file,
388 source_root: raw.source_root,
389 sources,
390 sources_content,
391 names: raw.names,
392 ignore_list,
393 extensions,
394 debug_id: raw.debug_id,
395 scopes,
396 mappings,
397 line_offsets,
398 reverse_index: OnceCell::new(),
399 source_map,
400 has_range_mappings,
401 })
402 }
403
404 fn from_sections(file: Option<String>, sections: Vec<RawSection>) -> Result<Self, ParseError> {
406 let mut all_sources: Vec<String> = Vec::new();
407 let mut all_sources_content: Vec<Option<String>> = Vec::new();
408 let mut all_names: Vec<String> = Vec::new();
409 let mut all_mappings: Vec<Mapping> = Vec::new();
410 let mut all_ignore_list: Vec<u32> = Vec::new();
411 let mut max_line: u32 = 0;
412
413 let mut source_index_map: HashMap<String, u32> = HashMap::new();
415 let mut name_index_map: HashMap<String, u32> = HashMap::new();
416
417 for i in 1..sections.len() {
419 let prev = §ions[i - 1].offset;
420 let curr = §ions[i].offset;
421 if (curr.line, curr.column) <= (prev.line, prev.column) {
422 return Err(ParseError::SectionsNotOrdered);
423 }
424 }
425
426 for section in §ions {
427 let sub = Self::from_json_inner(section.map.get(), false)?;
429
430 let line_offset = section.offset.line;
431 let col_offset = section.offset.column;
432
433 let source_remap: Vec<u32> = sub
435 .sources
436 .iter()
437 .enumerate()
438 .map(|(i, s)| {
439 if let Some(&existing) = source_index_map.get(s) {
440 existing
441 } else {
442 let idx = all_sources.len() as u32;
443 all_sources.push(s.clone());
444 let content = sub.sources_content.get(i).cloned().unwrap_or(None);
446 all_sources_content.push(content);
447 source_index_map.insert(s.clone(), idx);
448 idx
449 }
450 })
451 .collect();
452
453 let name_remap: Vec<u32> = sub
455 .names
456 .iter()
457 .map(|n| {
458 if let Some(&existing) = name_index_map.get(n) {
459 existing
460 } else {
461 let idx = all_names.len() as u32;
462 all_names.push(n.clone());
463 name_index_map.insert(n.clone(), idx);
464 idx
465 }
466 })
467 .collect();
468
469 for &idx in &sub.ignore_list {
471 let global_idx = source_remap[idx as usize];
472 if !all_ignore_list.contains(&global_idx) {
473 all_ignore_list.push(global_idx);
474 }
475 }
476
477 for m in &sub.mappings {
479 let gen_line = m.generated_line + line_offset;
480 let gen_col = if m.generated_line == 0 {
481 m.generated_column + col_offset
482 } else {
483 m.generated_column
484 };
485
486 all_mappings.push(Mapping {
487 generated_line: gen_line,
488 generated_column: gen_col,
489 source: if m.source == NO_SOURCE {
490 NO_SOURCE
491 } else {
492 source_remap[m.source as usize]
493 },
494 original_line: m.original_line,
495 original_column: m.original_column,
496 name: if m.name == NO_NAME {
497 NO_NAME
498 } else {
499 name_remap[m.name as usize]
500 },
501 is_range_mapping: m.is_range_mapping,
502 });
503
504 if gen_line > max_line {
505 max_line = gen_line;
506 }
507 }
508 }
509
510 all_mappings.sort_unstable_by(|a, b| {
512 a.generated_line
513 .cmp(&b.generated_line)
514 .then(a.generated_column.cmp(&b.generated_column))
515 });
516
517 let line_count = if all_mappings.is_empty() {
519 0
520 } else {
521 max_line as usize + 1
522 };
523 let mut line_offsets: Vec<u32> = vec![0; line_count + 1];
524 let mut current_line: usize = 0;
525 for (i, m) in all_mappings.iter().enumerate() {
526 while current_line < m.generated_line as usize {
527 current_line += 1;
528 if current_line < line_offsets.len() {
529 line_offsets[current_line] = i as u32;
530 }
531 }
532 }
533 if !line_offsets.is_empty() {
535 let last = all_mappings.len() as u32;
536 for offset in line_offsets.iter_mut().skip(current_line + 1) {
537 *offset = last;
538 }
539 }
540
541 let source_map = build_source_map(&all_sources);
542 let has_range_mappings = all_mappings.iter().any(|m| m.is_range_mapping);
543
544 Ok(Self {
545 file,
546 source_root: None,
547 sources: all_sources,
548 sources_content: all_sources_content,
549 names: all_names,
550 ignore_list: all_ignore_list,
551 extensions: HashMap::new(),
552 debug_id: None,
553 scopes: None, mappings: all_mappings,
555 line_offsets,
556 reverse_index: OnceCell::new(),
557 source_map,
558 has_range_mappings,
559 })
560 }
561
562 pub fn original_position_for(&self, line: u32, column: u32) -> Option<OriginalLocation> {
567 self.original_position_for_with_bias(line, column, Bias::GreatestLowerBound)
568 }
569
570 pub fn original_position_for_with_bias(
576 &self,
577 line: u32,
578 column: u32,
579 bias: Bias,
580 ) -> Option<OriginalLocation> {
581 let line_idx = line as usize;
582 if line_idx + 1 >= self.line_offsets.len() {
583 return self.range_mapping_fallback(line, column);
584 }
585
586 let start = self.line_offsets[line_idx] as usize;
587 let end = self.line_offsets[line_idx + 1] as usize;
588
589 if start == end {
590 return self.range_mapping_fallback(line, column);
591 }
592
593 let line_mappings = &self.mappings[start..end];
594
595 let idx = match bias {
596 Bias::GreatestLowerBound => {
597 match line_mappings.binary_search_by_key(&column, |m| m.generated_column) {
598 Ok(i) => i,
599 Err(0) => return self.range_mapping_fallback(line, column),
600 Err(i) => i - 1,
601 }
602 }
603 Bias::LeastUpperBound => {
604 match line_mappings.binary_search_by_key(&column, |m| m.generated_column) {
605 Ok(i) => i,
606 Err(i) => {
607 if i >= line_mappings.len() {
608 return None;
609 }
610 i
611 }
612 }
613 }
614 };
615
616 let mapping = &line_mappings[idx];
617
618 if mapping.source == NO_SOURCE {
619 return None;
620 }
621
622 if mapping.is_range_mapping && column >= mapping.generated_column {
623 let column_delta = column - mapping.generated_column;
624 return Some(OriginalLocation {
625 source: mapping.source,
626 line: mapping.original_line,
627 column: mapping.original_column + column_delta,
628 name: if mapping.name == NO_NAME {
629 None
630 } else {
631 Some(mapping.name)
632 },
633 });
634 }
635
636 Some(OriginalLocation {
637 source: mapping.source,
638 line: mapping.original_line,
639 column: mapping.original_column,
640 name: if mapping.name == NO_NAME {
641 None
642 } else {
643 Some(mapping.name)
644 },
645 })
646 }
647
648 fn range_mapping_fallback(&self, line: u32, column: u32) -> Option<OriginalLocation> {
649 let line_idx = line as usize;
650 let search_end = if line_idx + 1 < self.line_offsets.len() {
651 self.line_offsets[line_idx] as usize
652 } else {
653 self.mappings.len()
654 };
655 if search_end == 0 {
656 return None;
657 }
658 let last_mapping = &self.mappings[search_end - 1];
659 if !last_mapping.is_range_mapping || last_mapping.source == NO_SOURCE {
660 return None;
661 }
662 let line_delta = line - last_mapping.generated_line;
663 let column_delta = if line_delta == 0 {
664 column - last_mapping.generated_column
665 } else {
666 0
667 };
668 Some(OriginalLocation {
669 source: last_mapping.source,
670 line: last_mapping.original_line + line_delta,
671 column: last_mapping.original_column + column_delta,
672 name: if last_mapping.name == NO_NAME {
673 None
674 } else {
675 Some(last_mapping.name)
676 },
677 })
678 }
679
680 pub fn generated_position_for(
685 &self,
686 source: &str,
687 line: u32,
688 column: u32,
689 ) -> Option<GeneratedLocation> {
690 self.generated_position_for_with_bias(source, line, column, Bias::LeastUpperBound)
691 }
692
693 pub fn generated_position_for_with_bias(
699 &self,
700 source: &str,
701 line: u32,
702 column: u32,
703 bias: Bias,
704 ) -> Option<GeneratedLocation> {
705 let &source_idx = self.source_map.get(source)?;
706
707 let reverse_index = self
708 .reverse_index
709 .get_or_init(|| build_reverse_index(&self.mappings));
710
711 let idx = reverse_index.partition_point(|&i| {
713 let m = &self.mappings[i as usize];
714 (m.source, m.original_line, m.original_column) < (source_idx, line, column)
715 });
716
717 match bias {
718 Bias::GreatestLowerBound => {
719 if idx < reverse_index.len() {
723 let mapping = &self.mappings[reverse_index[idx] as usize];
724 if mapping.source == source_idx
725 && mapping.original_line == line
726 && mapping.original_column == column
727 {
728 return Some(GeneratedLocation {
729 line: mapping.generated_line,
730 column: mapping.generated_column,
731 });
732 }
733 }
734 if idx == 0 {
736 return None;
737 }
738 let mapping = &self.mappings[reverse_index[idx - 1] as usize];
739 if mapping.source != source_idx {
740 return None;
741 }
742 Some(GeneratedLocation {
743 line: mapping.generated_line,
744 column: mapping.generated_column,
745 })
746 }
747 Bias::LeastUpperBound => {
748 if idx >= reverse_index.len() {
749 return None;
750 }
751 let mapping = &self.mappings[reverse_index[idx] as usize];
752 if mapping.source != source_idx {
753 return None;
754 }
755 Some(GeneratedLocation {
756 line: mapping.generated_line,
757 column: mapping.generated_column,
758 })
759 }
760 }
761 }
762
763 pub fn all_generated_positions_for(
768 &self,
769 source: &str,
770 line: u32,
771 column: u32,
772 ) -> Vec<GeneratedLocation> {
773 let Some(&source_idx) = self.source_map.get(source) else {
774 return Vec::new();
775 };
776
777 let reverse_index = self
778 .reverse_index
779 .get_or_init(|| build_reverse_index(&self.mappings));
780
781 let start = reverse_index.partition_point(|&i| {
783 let m = &self.mappings[i as usize];
784 (m.source, m.original_line, m.original_column) < (source_idx, line, column)
785 });
786
787 let mut results = Vec::new();
788
789 for &ri in &reverse_index[start..] {
790 let m = &self.mappings[ri as usize];
791 if m.source != source_idx || m.original_line != line || m.original_column != column {
792 break;
793 }
794 results.push(GeneratedLocation {
795 line: m.generated_line,
796 column: m.generated_column,
797 });
798 }
799
800 results
801 }
802
803 pub fn map_range(
809 &self,
810 start_line: u32,
811 start_column: u32,
812 end_line: u32,
813 end_column: u32,
814 ) -> Option<MappedRange> {
815 let start = self.original_position_for(start_line, start_column)?;
816 let end = self.original_position_for(end_line, end_column)?;
817
818 if start.source != end.source {
820 return None;
821 }
822
823 Some(MappedRange {
824 source: start.source,
825 original_start_line: start.line,
826 original_start_column: start.column,
827 original_end_line: end.line,
828 original_end_column: end.column,
829 })
830 }
831
832 #[inline]
839 pub fn source(&self, index: u32) -> &str {
840 &self.sources[index as usize]
841 }
842
843 #[inline]
845 pub fn get_source(&self, index: u32) -> Option<&str> {
846 self.sources.get(index as usize).map(|s| s.as_str())
847 }
848
849 #[inline]
856 pub fn name(&self, index: u32) -> &str {
857 &self.names[index as usize]
858 }
859
860 #[inline]
862 pub fn get_name(&self, index: u32) -> Option<&str> {
863 self.names.get(index as usize).map(|s| s.as_str())
864 }
865
866 #[inline]
868 pub fn source_index(&self, name: &str) -> Option<u32> {
869 self.source_map.get(name).copied()
870 }
871
872 #[inline]
874 pub fn mapping_count(&self) -> usize {
875 self.mappings.len()
876 }
877
878 #[inline]
880 pub fn line_count(&self) -> usize {
881 self.line_offsets.len().saturating_sub(1)
882 }
883
884 #[inline]
886 pub fn mappings_for_line(&self, line: u32) -> &[Mapping] {
887 let line_idx = line as usize;
888 if line_idx + 1 >= self.line_offsets.len() {
889 return &[];
890 }
891 let start = self.line_offsets[line_idx] as usize;
892 let end = self.line_offsets[line_idx + 1] as usize;
893 &self.mappings[start..end]
894 }
895
896 #[inline]
898 pub fn all_mappings(&self) -> &[Mapping] {
899 &self.mappings
900 }
901
902 pub fn to_json(&self) -> String {
907 self.to_json_with_options(false)
908 }
909
910 pub fn to_json_with_options(&self, exclude_content: bool) -> String {
914 let mappings = self.encode_mappings();
915
916 let scopes_encoded = if let Some(ref scopes_info) = self.scopes {
918 let mut names_clone = self.names.clone();
919 let s = srcmap_scopes::encode_scopes(scopes_info, &mut names_clone);
920 Some((s, names_clone))
921 } else {
922 None
923 };
924 let names_for_json = match &scopes_encoded {
925 Some((_, expanded_names)) => expanded_names,
926 None => &self.names,
927 };
928
929 let source_root_prefix = self.source_root.as_deref().unwrap_or("");
930
931 let mut json = String::with_capacity(256 + mappings.len());
932 json.push_str(r#"{"version":3"#);
933
934 if let Some(ref file) = self.file {
935 json.push_str(r#","file":"#);
936 json_quote_into(&mut json, file);
937 }
938
939 if let Some(ref root) = self.source_root {
940 json.push_str(r#","sourceRoot":"#);
941 json_quote_into(&mut json, root);
942 }
943
944 json.push_str(r#","sources":["#);
946 for (i, s) in self.sources.iter().enumerate() {
947 if i > 0 {
948 json.push(',');
949 }
950 let source_name = if !source_root_prefix.is_empty() {
951 s.strip_prefix(source_root_prefix).unwrap_or(s)
952 } else {
953 s
954 };
955 json_quote_into(&mut json, source_name);
956 }
957 json.push(']');
958
959 if !exclude_content
960 && !self.sources_content.is_empty()
961 && self.sources_content.iter().any(|c| c.is_some())
962 {
963 json.push_str(r#","sourcesContent":["#);
964 for (i, c) in self.sources_content.iter().enumerate() {
965 if i > 0 {
966 json.push(',');
967 }
968 match c {
969 Some(content) => json_quote_into(&mut json, content),
970 None => json.push_str("null"),
971 }
972 }
973 json.push(']');
974 }
975
976 json.push_str(r#","names":["#);
977 for (i, n) in names_for_json.iter().enumerate() {
978 if i > 0 {
979 json.push(',');
980 }
981 json_quote_into(&mut json, n);
982 }
983 json.push(']');
984
985 json.push_str(r#","mappings":""#);
987 json.push_str(&mappings);
988 json.push('"');
989
990 if let Some(range_mappings) = self.encode_range_mappings() {
991 json.push_str(r#","rangeMappings":""#);
993 json.push_str(&range_mappings);
994 json.push('"');
995 }
996
997 if !self.ignore_list.is_empty() {
998 use std::fmt::Write;
999 json.push_str(r#","ignoreList":["#);
1000 for (i, &idx) in self.ignore_list.iter().enumerate() {
1001 if i > 0 {
1002 json.push(',');
1003 }
1004 let _ = write!(json, "{idx}");
1005 }
1006 json.push(']');
1007 }
1008
1009 if let Some(ref id) = self.debug_id {
1010 json.push_str(r#","debugId":"#);
1011 json_quote_into(&mut json, id);
1012 }
1013
1014 if let Some((ref s, _)) = scopes_encoded {
1016 json.push_str(r#","scopes":"#);
1017 json_quote_into(&mut json, s);
1018 }
1019
1020 let mut ext_keys: Vec<&String> = self.extensions.keys().collect();
1022 ext_keys.sort();
1023 for key in ext_keys {
1024 if let Some(val) = self.extensions.get(key) {
1025 json.push(',');
1026 json_quote_into(&mut json, key);
1027 json.push(':');
1028 json.push_str(&serde_json::to_string(val).unwrap_or_default());
1029 }
1030 }
1031
1032 json.push('}');
1033 json
1034 }
1035
1036 #[allow(clippy::too_many_arguments)]
1042 pub fn from_parts(
1043 file: Option<String>,
1044 source_root: Option<String>,
1045 sources: Vec<String>,
1046 sources_content: Vec<Option<String>>,
1047 names: Vec<String>,
1048 mappings: Vec<Mapping>,
1049 ignore_list: Vec<u32>,
1050 debug_id: Option<String>,
1051 scopes: Option<ScopeInfo>,
1052 ) -> Self {
1053 let line_count = mappings.last().map_or(0, |m| m.generated_line as usize + 1);
1055 let mut line_offsets: Vec<u32> = vec![0; line_count + 1];
1056 let mut current_line: usize = 0;
1057 for (i, m) in mappings.iter().enumerate() {
1058 while current_line < m.generated_line as usize {
1059 current_line += 1;
1060 if current_line < line_offsets.len() {
1061 line_offsets[current_line] = i as u32;
1062 }
1063 }
1064 }
1065 if !line_offsets.is_empty() {
1067 let last = mappings.len() as u32;
1068 for offset in line_offsets.iter_mut().skip(current_line + 1) {
1069 *offset = last;
1070 }
1071 }
1072
1073 let source_map = build_source_map(&sources);
1074 let has_range_mappings = mappings.iter().any(|m| m.is_range_mapping);
1075
1076 Self {
1077 file,
1078 source_root,
1079 sources,
1080 sources_content,
1081 names,
1082 ignore_list,
1083 extensions: HashMap::new(),
1084 debug_id,
1085 scopes,
1086 mappings,
1087 line_offsets,
1088 reverse_index: OnceCell::new(),
1089 source_map,
1090 has_range_mappings,
1091 }
1092 }
1093
1094 #[allow(clippy::too_many_arguments)]
1100 pub fn from_vlq(
1101 mappings_str: &str,
1102 sources: Vec<String>,
1103 names: Vec<String>,
1104 file: Option<String>,
1105 source_root: Option<String>,
1106 sources_content: Vec<Option<String>>,
1107 ignore_list: Vec<u32>,
1108 debug_id: Option<String>,
1109 ) -> Result<Self, ParseError> {
1110 Self::from_vlq_with_range_mappings(
1111 mappings_str,
1112 sources,
1113 names,
1114 file,
1115 source_root,
1116 sources_content,
1117 ignore_list,
1118 debug_id,
1119 None,
1120 )
1121 }
1122
1123 #[allow(clippy::too_many_arguments)]
1126 pub fn from_vlq_with_range_mappings(
1127 mappings_str: &str,
1128 sources: Vec<String>,
1129 names: Vec<String>,
1130 file: Option<String>,
1131 source_root: Option<String>,
1132 sources_content: Vec<Option<String>>,
1133 ignore_list: Vec<u32>,
1134 debug_id: Option<String>,
1135 range_mappings_str: Option<&str>,
1136 ) -> Result<Self, ParseError> {
1137 let (mut mappings, line_offsets) = decode_mappings(mappings_str)?;
1138 if let Some(rm_str) = range_mappings_str
1139 && !rm_str.is_empty()
1140 {
1141 decode_range_mappings(rm_str, &mut mappings, &line_offsets)?;
1142 }
1143 let source_map = build_source_map(&sources);
1144 let has_range_mappings = mappings.iter().any(|m| m.is_range_mapping);
1145 Ok(Self {
1146 file,
1147 source_root,
1148 sources,
1149 sources_content,
1150 names,
1151 ignore_list,
1152 extensions: HashMap::new(),
1153 debug_id,
1154 scopes: None,
1155 mappings,
1156 line_offsets,
1157 reverse_index: OnceCell::new(),
1158 source_map,
1159 has_range_mappings,
1160 })
1161 }
1162
1163 pub fn builder() -> SourceMapBuilder {
1190 SourceMapBuilder::new()
1191 }
1192
1193 pub fn from_json_lines(json: &str, start_line: u32, end_line: u32) -> Result<Self, ParseError> {
1199 let raw: RawSourceMap<'_> = serde_json::from_str(json)?;
1200
1201 if raw.version != 3 {
1202 return Err(ParseError::InvalidVersion(raw.version));
1203 }
1204
1205 let source_root = raw.source_root.as_deref().unwrap_or("");
1206 let sources = resolve_sources(&raw.sources, source_root);
1207 let sources_content = raw.sources_content.unwrap_or_default();
1208 let source_map = build_source_map(&sources);
1209
1210 let (mappings, line_offsets) = decode_mappings_range(raw.mappings, start_line, end_line)?;
1212
1213 let num_sources = sources.len();
1215 let scopes = match raw.scopes {
1216 Some(scopes_str) if !scopes_str.is_empty() => Some(srcmap_scopes::decode_scopes(
1217 scopes_str,
1218 &raw.names,
1219 num_sources,
1220 )?),
1221 _ => None,
1222 };
1223
1224 let ignore_list = match raw.ignore_list {
1225 Some(list) => list,
1226 None => raw.x_google_ignore_list.unwrap_or_default(),
1227 };
1228
1229 let extensions: HashMap<String, serde_json::Value> = raw
1231 .extensions
1232 .into_iter()
1233 .filter(|(k, _)| k.starts_with("x_") || k.starts_with("x-"))
1234 .collect();
1235
1236 let has_range_mappings = mappings.iter().any(|m| m.is_range_mapping);
1237
1238 Ok(Self {
1239 file: raw.file,
1240 source_root: raw.source_root,
1241 sources,
1242 sources_content,
1243 names: raw.names,
1244 ignore_list,
1245 extensions,
1246 debug_id: raw.debug_id,
1247 scopes,
1248 mappings,
1249 line_offsets,
1250 reverse_index: OnceCell::new(),
1251 source_map,
1252 has_range_mappings,
1253 })
1254 }
1255
1256 pub fn encode_mappings(&self) -> String {
1258 if self.mappings.is_empty() {
1259 return String::new();
1260 }
1261
1262 let mut out: Vec<u8> = Vec::with_capacity(self.mappings.len() * 6);
1263
1264 let mut prev_gen_col: i64 = 0;
1265 let mut prev_source: i64 = 0;
1266 let mut prev_orig_line: i64 = 0;
1267 let mut prev_orig_col: i64 = 0;
1268 let mut prev_name: i64 = 0;
1269 let mut prev_gen_line: u32 = 0;
1270 let mut first_in_line = true;
1271
1272 for m in &self.mappings {
1273 while prev_gen_line < m.generated_line {
1274 out.push(b';');
1275 prev_gen_line += 1;
1276 prev_gen_col = 0;
1277 first_in_line = true;
1278 }
1279
1280 if !first_in_line {
1281 out.push(b',');
1282 }
1283 first_in_line = false;
1284
1285 srcmap_codec::vlq_encode(&mut out, m.generated_column as i64 - prev_gen_col);
1286 prev_gen_col = m.generated_column as i64;
1287
1288 if m.source != NO_SOURCE {
1289 srcmap_codec::vlq_encode(&mut out, m.source as i64 - prev_source);
1290 prev_source = m.source as i64;
1291
1292 srcmap_codec::vlq_encode(&mut out, m.original_line as i64 - prev_orig_line);
1293 prev_orig_line = m.original_line as i64;
1294
1295 srcmap_codec::vlq_encode(&mut out, m.original_column as i64 - prev_orig_col);
1296 prev_orig_col = m.original_column as i64;
1297
1298 if m.name != NO_NAME {
1299 srcmap_codec::vlq_encode(&mut out, m.name as i64 - prev_name);
1300 prev_name = m.name as i64;
1301 }
1302 }
1303 }
1304
1305 debug_assert!(out.is_ascii());
1308 unsafe { String::from_utf8_unchecked(out) }
1309 }
1310
1311 pub fn encode_range_mappings(&self) -> Option<String> {
1312 if !self.has_range_mappings {
1313 return None;
1314 }
1315 let line_count = self.line_offsets.len().saturating_sub(1);
1316 let mut out: Vec<u8> = Vec::new();
1317 for line_idx in 0..line_count {
1318 if line_idx > 0 {
1319 out.push(b';');
1320 }
1321 let start = self.line_offsets[line_idx] as usize;
1322 let end = self.line_offsets[line_idx + 1] as usize;
1323 let mut prev_offset: u64 = 0;
1324 let mut first_on_line = true;
1325 for (i, mapping) in self.mappings[start..end].iter().enumerate() {
1326 if mapping.is_range_mapping {
1327 if !first_on_line {
1328 out.push(b',');
1329 }
1330 first_on_line = false;
1331 vlq_encode_unsigned(&mut out, i as u64 - prev_offset);
1332 prev_offset = i as u64;
1333 }
1334 }
1335 }
1336 while out.last() == Some(&b';') {
1337 out.pop();
1338 }
1339 if out.is_empty() {
1340 return None;
1341 }
1342 debug_assert!(out.is_ascii());
1345 Some(unsafe { String::from_utf8_unchecked(out) })
1346 }
1347
1348 #[inline]
1349 pub fn has_range_mappings(&self) -> bool {
1350 self.has_range_mappings
1351 }
1352
1353 #[inline]
1354 pub fn range_mapping_count(&self) -> usize {
1355 self.mappings.iter().filter(|m| m.is_range_mapping).count()
1356 }
1357}
1358
1359#[derive(Debug, Clone, Copy)]
1363struct VlqState {
1364 source_index: i64,
1365 original_line: i64,
1366 original_column: i64,
1367 name_index: i64,
1368}
1369
1370#[derive(Debug, Clone)]
1372struct LineInfo {
1373 byte_offset: usize,
1375 byte_end: usize,
1377 state: VlqState,
1379}
1380
1381#[derive(Debug)]
1400pub struct LazySourceMap {
1401 pub file: Option<String>,
1402 pub source_root: Option<String>,
1403 pub sources: Vec<String>,
1404 pub sources_content: Vec<Option<String>>,
1405 pub names: Vec<String>,
1406 pub ignore_list: Vec<u32>,
1407 pub extensions: HashMap<String, serde_json::Value>,
1408 pub debug_id: Option<String>,
1409 pub scopes: Option<ScopeInfo>,
1410
1411 raw_mappings: String,
1413
1414 line_info: Vec<LineInfo>,
1416
1417 decoded_lines: RefCell<HashMap<u32, Vec<Mapping>>>,
1419
1420 source_map: HashMap<String, u32>,
1422}
1423
1424impl LazySourceMap {
1425 pub fn from_json(json: &str) -> Result<Self, ParseError> {
1430 let raw: RawSourceMap<'_> = serde_json::from_str(json)?;
1431
1432 if raw.version != 3 {
1433 return Err(ParseError::InvalidVersion(raw.version));
1434 }
1435
1436 let source_root = raw.source_root.as_deref().unwrap_or("");
1437 let sources = resolve_sources(&raw.sources, source_root);
1438 let sources_content = raw.sources_content.unwrap_or_default();
1439 let source_map = build_source_map(&sources);
1440
1441 let raw_mappings = raw.mappings.to_string();
1444 let line_info = prescan_mappings(&raw_mappings)?;
1445
1446 let num_sources = sources.len();
1448 let scopes = match raw.scopes {
1449 Some(scopes_str) if !scopes_str.is_empty() => Some(srcmap_scopes::decode_scopes(
1450 scopes_str,
1451 &raw.names,
1452 num_sources,
1453 )?),
1454 _ => None,
1455 };
1456
1457 let ignore_list = match raw.ignore_list {
1458 Some(list) => list,
1459 None => raw.x_google_ignore_list.unwrap_or_default(),
1460 };
1461
1462 let extensions: HashMap<String, serde_json::Value> = raw
1464 .extensions
1465 .into_iter()
1466 .filter(|(k, _)| k.starts_with("x_") || k.starts_with("x-"))
1467 .collect();
1468
1469 Ok(Self {
1470 file: raw.file,
1471 source_root: raw.source_root,
1472 sources,
1473 sources_content,
1474 names: raw.names,
1475 ignore_list,
1476 extensions,
1477 debug_id: raw.debug_id,
1478 scopes,
1479 raw_mappings,
1480 line_info,
1481 decoded_lines: RefCell::new(HashMap::new()),
1482 source_map,
1483 })
1484 }
1485
1486 pub fn decode_line(&self, line: u32) -> Result<Vec<Mapping>, DecodeError> {
1491 if let Some(cached) = self.decoded_lines.borrow().get(&line) {
1493 return Ok(cached.clone());
1494 }
1495
1496 let line_idx = line as usize;
1497 if line_idx >= self.line_info.len() {
1498 return Ok(Vec::new());
1499 }
1500
1501 let info = &self.line_info[line_idx];
1502 let bytes = self.raw_mappings.as_bytes();
1503 let slice = &bytes[info.byte_offset..info.byte_end];
1504
1505 let mut mappings = Vec::new();
1506 let mut source_index = info.state.source_index;
1507 let mut original_line = info.state.original_line;
1508 let mut original_column = info.state.original_column;
1509 let mut name_index = info.state.name_index;
1510 let mut generated_column: i64 = 0;
1511 let mut pos: usize = 0;
1512 let len = slice.len();
1513
1514 let base_offset = info.byte_offset;
1517
1518 while pos < len {
1519 let byte = slice[pos];
1520
1521 if byte == b',' {
1522 pos += 1;
1523 continue;
1524 }
1525
1526 let mut abs_pos = base_offset + pos;
1528
1529 generated_column += vlq_fast(bytes, &mut abs_pos)?;
1531
1532 if abs_pos < base_offset + len && bytes[abs_pos] != b',' && bytes[abs_pos] != b';' {
1533 source_index += vlq_fast(bytes, &mut abs_pos)?;
1535
1536 if abs_pos >= base_offset + len || bytes[abs_pos] == b',' || bytes[abs_pos] == b';'
1538 {
1539 return Err(DecodeError::InvalidSegmentLength {
1540 fields: 2,
1541 offset: abs_pos,
1542 });
1543 }
1544
1545 original_line += vlq_fast(bytes, &mut abs_pos)?;
1547
1548 if abs_pos >= base_offset + len || bytes[abs_pos] == b',' || bytes[abs_pos] == b';'
1550 {
1551 return Err(DecodeError::InvalidSegmentLength {
1552 fields: 3,
1553 offset: abs_pos,
1554 });
1555 }
1556
1557 original_column += vlq_fast(bytes, &mut abs_pos)?;
1559
1560 let name = if abs_pos < base_offset + len
1562 && bytes[abs_pos] != b','
1563 && bytes[abs_pos] != b';'
1564 {
1565 name_index += vlq_fast(bytes, &mut abs_pos)?;
1566 name_index as u32
1567 } else {
1568 NO_NAME
1569 };
1570
1571 mappings.push(Mapping {
1572 generated_line: line,
1573 generated_column: generated_column as u32,
1574 source: source_index as u32,
1575 original_line: original_line as u32,
1576 original_column: original_column as u32,
1577 name,
1578 is_range_mapping: false,
1579 });
1580 } else {
1581 mappings.push(Mapping {
1583 generated_line: line,
1584 generated_column: generated_column as u32,
1585 source: NO_SOURCE,
1586 original_line: 0,
1587 original_column: 0,
1588 name: NO_NAME,
1589 is_range_mapping: false,
1590 });
1591 }
1592
1593 pos = abs_pos - base_offset;
1594 }
1595
1596 self.decoded_lines
1598 .borrow_mut()
1599 .insert(line, mappings.clone());
1600
1601 Ok(mappings)
1602 }
1603
1604 pub fn original_position_for(&self, line: u32, column: u32) -> Option<OriginalLocation> {
1609 let line_mappings = self.decode_line(line).ok()?;
1610
1611 if line_mappings.is_empty() {
1612 return None;
1613 }
1614
1615 let idx = match line_mappings.binary_search_by_key(&column, |m| m.generated_column) {
1617 Ok(i) => i,
1618 Err(0) => return None,
1619 Err(i) => i - 1,
1620 };
1621
1622 let mapping = &line_mappings[idx];
1623
1624 if mapping.source == NO_SOURCE {
1625 return None;
1626 }
1627
1628 Some(OriginalLocation {
1629 source: mapping.source,
1630 line: mapping.original_line,
1631 column: mapping.original_column,
1632 name: if mapping.name == NO_NAME {
1633 None
1634 } else {
1635 Some(mapping.name)
1636 },
1637 })
1638 }
1639
1640 #[inline]
1642 pub fn line_count(&self) -> usize {
1643 self.line_info.len()
1644 }
1645
1646 #[inline]
1653 pub fn source(&self, index: u32) -> &str {
1654 &self.sources[index as usize]
1655 }
1656
1657 #[inline]
1659 pub fn get_source(&self, index: u32) -> Option<&str> {
1660 self.sources.get(index as usize).map(|s| s.as_str())
1661 }
1662
1663 #[inline]
1670 pub fn name(&self, index: u32) -> &str {
1671 &self.names[index as usize]
1672 }
1673
1674 #[inline]
1676 pub fn get_name(&self, index: u32) -> Option<&str> {
1677 self.names.get(index as usize).map(|s| s.as_str())
1678 }
1679
1680 #[inline]
1682 pub fn source_index(&self, name: &str) -> Option<u32> {
1683 self.source_map.get(name).copied()
1684 }
1685
1686 pub fn mappings_for_line(&self, line: u32) -> Vec<Mapping> {
1688 self.decode_line(line).unwrap_or_default()
1689 }
1690
1691 pub fn into_sourcemap(self) -> Result<SourceMap, ParseError> {
1695 let (mappings, line_offsets) = decode_mappings(&self.raw_mappings)?;
1696 let has_range_mappings = mappings.iter().any(|m| m.is_range_mapping);
1697
1698 Ok(SourceMap {
1699 file: self.file,
1700 source_root: self.source_root,
1701 sources: self.sources.clone(),
1702 sources_content: self.sources_content,
1703 names: self.names,
1704 ignore_list: self.ignore_list,
1705 extensions: self.extensions,
1706 debug_id: self.debug_id,
1707 scopes: self.scopes,
1708 mappings,
1709 line_offsets,
1710 reverse_index: OnceCell::new(),
1711 source_map: self.source_map,
1712 has_range_mappings,
1713 })
1714 }
1715}
1716
1717fn prescan_mappings(input: &str) -> Result<Vec<LineInfo>, DecodeError> {
1720 if input.is_empty() {
1721 return Ok(Vec::new());
1722 }
1723
1724 let bytes = input.as_bytes();
1725 let len = bytes.len();
1726
1727 let line_count = bytes.iter().filter(|&&b| b == b';').count() + 1;
1729 let mut line_info: Vec<LineInfo> = Vec::with_capacity(line_count);
1730
1731 let mut source_index: i64 = 0;
1732 let mut original_line: i64 = 0;
1733 let mut original_column: i64 = 0;
1734 let mut name_index: i64 = 0;
1735 let mut pos: usize = 0;
1736
1737 loop {
1738 let line_start = pos;
1739 let state = VlqState {
1740 source_index,
1741 original_line,
1742 original_column,
1743 name_index,
1744 };
1745
1746 let mut saw_semicolon = false;
1747
1748 while pos < len {
1750 let byte = bytes[pos];
1751
1752 if byte == b';' {
1753 pos += 1;
1754 saw_semicolon = true;
1755 break;
1756 }
1757
1758 if byte == b',' {
1759 pos += 1;
1760 continue;
1761 }
1762
1763 vlq_fast(bytes, &mut pos)?;
1765
1766 if pos < len && bytes[pos] != b',' && bytes[pos] != b';' {
1767 source_index += vlq_fast(bytes, &mut pos)?;
1769
1770 if pos >= len || bytes[pos] == b',' || bytes[pos] == b';' {
1772 return Err(DecodeError::InvalidSegmentLength {
1773 fields: 2,
1774 offset: pos,
1775 });
1776 }
1777
1778 original_line += vlq_fast(bytes, &mut pos)?;
1780
1781 if pos >= len || bytes[pos] == b',' || bytes[pos] == b';' {
1783 return Err(DecodeError::InvalidSegmentLength {
1784 fields: 3,
1785 offset: pos,
1786 });
1787 }
1788
1789 original_column += vlq_fast(bytes, &mut pos)?;
1791
1792 if pos < len && bytes[pos] != b',' && bytes[pos] != b';' {
1794 name_index += vlq_fast(bytes, &mut pos)?;
1795 }
1796 }
1797 }
1798
1799 let byte_end = if saw_semicolon { pos - 1 } else { pos };
1801
1802 line_info.push(LineInfo {
1803 byte_offset: line_start,
1804 byte_end,
1805 state,
1806 });
1807
1808 if !saw_semicolon {
1809 break;
1810 }
1811 }
1812
1813 Ok(line_info)
1814}
1815
1816#[derive(Debug, Clone, PartialEq, Eq)]
1818pub enum SourceMappingUrl {
1819 Inline(String),
1821 External(String),
1823}
1824
1825pub fn parse_source_mapping_url(source: &str) -> Option<SourceMappingUrl> {
1831 for line in source.lines().rev() {
1833 let trimmed = line.trim();
1834 let url = if let Some(rest) = trimmed.strip_prefix("//# sourceMappingURL=") {
1835 rest.trim()
1836 } else if let Some(rest) = trimmed.strip_prefix("//@ sourceMappingURL=") {
1837 rest.trim()
1838 } else if let Some(rest) = trimmed.strip_prefix("/*# sourceMappingURL=") {
1839 rest.trim_end_matches("*/").trim()
1840 } else if let Some(rest) = trimmed.strip_prefix("/*@ sourceMappingURL=") {
1841 rest.trim_end_matches("*/").trim()
1842 } else {
1843 continue;
1844 };
1845
1846 if url.is_empty() {
1847 continue;
1848 }
1849
1850 if let Some(base64_data) = url
1852 .strip_prefix("data:application/json;base64,")
1853 .or_else(|| url.strip_prefix("data:application/json;charset=utf-8;base64,"))
1854 .or_else(|| url.strip_prefix("data:application/json;charset=UTF-8;base64,"))
1855 {
1856 let decoded = base64_decode(base64_data);
1858 if let Some(json) = decoded {
1859 return Some(SourceMappingUrl::Inline(json));
1860 }
1861 }
1862
1863 return Some(SourceMappingUrl::External(url.to_string()));
1864 }
1865
1866 None
1867}
1868
1869fn base64_decode(input: &str) -> Option<String> {
1871 let input = input.trim();
1872 let bytes: Vec<u8> = input.bytes().filter(|b| !b.is_ascii_whitespace()).collect();
1873
1874 let mut output = Vec::with_capacity(bytes.len() * 3 / 4);
1875
1876 for chunk in bytes.chunks(4) {
1877 let mut buf = [0u8; 4];
1878 let mut len = 0;
1879
1880 for &b in chunk {
1881 if b == b'=' {
1882 break;
1883 }
1884 let val = match b {
1885 b'A'..=b'Z' => b - b'A',
1886 b'a'..=b'z' => b - b'a' + 26,
1887 b'0'..=b'9' => b - b'0' + 52,
1888 b'+' => 62,
1889 b'/' => 63,
1890 _ => return None,
1891 };
1892 buf[len] = val;
1893 len += 1;
1894 }
1895
1896 if len >= 2 {
1897 output.push((buf[0] << 2) | (buf[1] >> 4));
1898 }
1899 if len >= 3 {
1900 output.push((buf[1] << 4) | (buf[2] >> 2));
1901 }
1902 if len >= 4 {
1903 output.push((buf[2] << 6) | buf[3]);
1904 }
1905 }
1906
1907 String::from_utf8(output).ok()
1908}
1909
1910pub fn validate_deep(sm: &SourceMap) -> Vec<String> {
1915 let mut warnings = Vec::new();
1916
1917 let mut prev_line: u32 = 0;
1919 let mut prev_col: u32 = 0;
1920 let mappings = sm.all_mappings();
1921 for m in mappings {
1922 if m.generated_line < prev_line
1923 || (m.generated_line == prev_line && m.generated_column < prev_col)
1924 {
1925 warnings.push(format!(
1926 "mappings out of order at {}:{}",
1927 m.generated_line, m.generated_column
1928 ));
1929 }
1930 prev_line = m.generated_line;
1931 prev_col = m.generated_column;
1932 }
1933
1934 for m in mappings {
1936 if m.source != NO_SOURCE && m.source as usize >= sm.sources.len() {
1937 warnings.push(format!(
1938 "source index {} out of bounds (max {})",
1939 m.source,
1940 sm.sources.len()
1941 ));
1942 }
1943 if m.name != NO_NAME && m.name as usize >= sm.names.len() {
1944 warnings.push(format!(
1945 "name index {} out of bounds (max {})",
1946 m.name,
1947 sm.names.len()
1948 ));
1949 }
1950 }
1951
1952 for &idx in &sm.ignore_list {
1954 if idx as usize >= sm.sources.len() {
1955 warnings.push(format!(
1956 "ignoreList index {} out of bounds (max {})",
1957 idx,
1958 sm.sources.len()
1959 ));
1960 }
1961 }
1962
1963 let mut referenced_sources = std::collections::HashSet::new();
1965 for m in mappings {
1966 if m.source != NO_SOURCE {
1967 referenced_sources.insert(m.source);
1968 }
1969 }
1970 for (i, source) in sm.sources.iter().enumerate() {
1971 if !referenced_sources.contains(&(i as u32)) {
1972 warnings.push(format!("source \"{source}\" (index {i}) is unreferenced"));
1973 }
1974 }
1975
1976 warnings
1977}
1978
1979fn json_quote_into(out: &mut String, s: &str) {
1981 let bytes = s.as_bytes();
1982 out.push('"');
1983
1984 let mut start = 0;
1985 for (i, &b) in bytes.iter().enumerate() {
1986 let escape = match b {
1987 b'"' => "\\\"",
1988 b'\\' => "\\\\",
1989 b'\n' => "\\n",
1990 b'\r' => "\\r",
1991 b'\t' => "\\t",
1992 0x00..=0x1f => {
1993 if start < i {
1994 out.push_str(&s[start..i]);
1995 }
1996 use std::fmt::Write;
1997 let _ = write!(out, "\\u{:04x}", b);
1998 start = i + 1;
1999 continue;
2000 }
2001 _ => continue,
2002 };
2003 if start < i {
2004 out.push_str(&s[start..i]);
2005 }
2006 out.push_str(escape);
2007 start = i + 1;
2008 }
2009
2010 if start < bytes.len() {
2011 out.push_str(&s[start..]);
2012 }
2013
2014 out.push('"');
2015}
2016
2017const B64: [u8; 128] = {
2021 let mut table = [0xFFu8; 128];
2022 let chars = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2023 let mut i = 0u8;
2024 while i < 64 {
2025 table[chars[i as usize] as usize] = i;
2026 i += 1;
2027 }
2028 table
2029};
2030
2031#[inline(always)]
2034fn vlq_fast(bytes: &[u8], pos: &mut usize) -> Result<i64, DecodeError> {
2035 let p = *pos;
2036 if p >= bytes.len() {
2037 return Err(DecodeError::UnexpectedEof { offset: p });
2038 }
2039
2040 let b0 = bytes[p];
2041 if b0 >= 128 {
2042 return Err(DecodeError::InvalidBase64 {
2043 byte: b0,
2044 offset: p,
2045 });
2046 }
2047 let d0 = B64[b0 as usize];
2048 if d0 == 0xFF {
2049 return Err(DecodeError::InvalidBase64 {
2050 byte: b0,
2051 offset: p,
2052 });
2053 }
2054
2055 if (d0 & 0x20) == 0 {
2057 *pos = p + 1;
2058 let val = (d0 >> 1) as i64;
2059 return Ok(if (d0 & 1) != 0 { -val } else { val });
2060 }
2061
2062 let mut result: u64 = (d0 & 0x1F) as u64;
2064 let mut shift: u32 = 5;
2065 let mut i = p + 1;
2066
2067 loop {
2068 if i >= bytes.len() {
2069 return Err(DecodeError::UnexpectedEof { offset: i });
2070 }
2071 let b = bytes[i];
2072 if b >= 128 {
2073 return Err(DecodeError::InvalidBase64 { byte: b, offset: i });
2074 }
2075 let d = B64[b as usize];
2076 if d == 0xFF {
2077 return Err(DecodeError::InvalidBase64 { byte: b, offset: i });
2078 }
2079 i += 1;
2080
2081 if shift >= 64 {
2082 return Err(DecodeError::VlqOverflow { offset: p });
2083 }
2084
2085 result += ((d & 0x1F) as u64) << shift;
2086 shift += 5;
2087
2088 if (d & 0x20) == 0 {
2089 break;
2090 }
2091 }
2092
2093 *pos = i;
2094 let value = if (result & 1) == 1 {
2095 -((result >> 1) as i64)
2096 } else {
2097 (result >> 1) as i64
2098 };
2099 Ok(value)
2100}
2101
2102#[inline(always)]
2103fn vlq_unsigned_fast(bytes: &[u8], pos: &mut usize) -> Result<u64, DecodeError> {
2104 let p = *pos;
2105 if p >= bytes.len() {
2106 return Err(DecodeError::UnexpectedEof { offset: p });
2107 }
2108 let b0 = bytes[p];
2109 if b0 >= 128 {
2110 return Err(DecodeError::InvalidBase64 {
2111 byte: b0,
2112 offset: p,
2113 });
2114 }
2115 let d0 = B64[b0 as usize];
2116 if d0 == 0xFF {
2117 return Err(DecodeError::InvalidBase64 {
2118 byte: b0,
2119 offset: p,
2120 });
2121 }
2122 if (d0 & 0x20) == 0 {
2123 *pos = p + 1;
2124 return Ok(d0 as u64);
2125 }
2126 let mut result: u64 = (d0 & 0x1F) as u64;
2127 let mut shift: u32 = 5;
2128 let mut i = p + 1;
2129 loop {
2130 if i >= bytes.len() {
2131 return Err(DecodeError::UnexpectedEof { offset: i });
2132 }
2133 let b = bytes[i];
2134 if b >= 128 {
2135 return Err(DecodeError::InvalidBase64 { byte: b, offset: i });
2136 }
2137 let d = B64[b as usize];
2138 if d == 0xFF {
2139 return Err(DecodeError::InvalidBase64 { byte: b, offset: i });
2140 }
2141 i += 1;
2142 if shift >= 64 {
2143 return Err(DecodeError::VlqOverflow { offset: p });
2144 }
2145 result |= ((d & 0x1F) as u64) << shift;
2146 shift += 5;
2147 if (d & 0x20) == 0 {
2148 break;
2149 }
2150 }
2151 *pos = i;
2152 Ok(result)
2153}
2154
2155fn decode_range_mappings(
2156 input: &str,
2157 mappings: &mut [Mapping],
2158 line_offsets: &[u32],
2159) -> Result<(), DecodeError> {
2160 let bytes = input.as_bytes();
2161 let len = bytes.len();
2162 let mut pos: usize = 0;
2163 let mut generated_line: usize = 0;
2164 while pos < len {
2165 let line_start = if generated_line + 1 < line_offsets.len() {
2166 line_offsets[generated_line] as usize
2167 } else {
2168 break;
2169 };
2170 let mut mapping_index: u64 = 0;
2171 while pos < len {
2172 let byte = bytes[pos];
2173 if byte == b';' {
2174 pos += 1;
2175 break;
2176 }
2177 if byte == b',' {
2178 pos += 1;
2179 continue;
2180 }
2181 let offset = vlq_unsigned_fast(bytes, &mut pos)?;
2182 mapping_index += offset;
2183 let abs_idx = line_start + mapping_index as usize;
2184 if abs_idx < mappings.len() {
2185 mappings[abs_idx].is_range_mapping = true;
2186 }
2187 }
2188 generated_line += 1;
2189 }
2190 Ok(())
2191}
2192
2193fn decode_mappings(input: &str) -> Result<(Vec<Mapping>, Vec<u32>), DecodeError> {
2194 if input.is_empty() {
2195 return Ok((Vec::new(), vec![0]));
2196 }
2197
2198 let bytes = input.as_bytes();
2199 let len = bytes.len();
2200
2201 let mut semicolons = 0usize;
2203 let mut commas = 0usize;
2204 for &b in bytes {
2205 semicolons += (b == b';') as usize;
2206 commas += (b == b',') as usize;
2207 }
2208 let line_count = semicolons + 1;
2209 let approx_segments = commas + line_count;
2210
2211 let mut mappings: Vec<Mapping> = Vec::with_capacity(approx_segments);
2212 let mut line_offsets: Vec<u32> = Vec::with_capacity(line_count + 1);
2213
2214 let mut source_index: i64 = 0;
2215 let mut original_line: i64 = 0;
2216 let mut original_column: i64 = 0;
2217 let mut name_index: i64 = 0;
2218 let mut generated_line: u32 = 0;
2219 let mut pos: usize = 0;
2220
2221 loop {
2222 line_offsets.push(mappings.len() as u32);
2223 let mut generated_column: i64 = 0;
2224 let mut saw_semicolon = false;
2225
2226 while pos < len {
2227 let byte = bytes[pos];
2228
2229 if byte == b';' {
2230 pos += 1;
2231 saw_semicolon = true;
2232 break;
2233 }
2234
2235 if byte == b',' {
2236 pos += 1;
2237 continue;
2238 }
2239
2240 generated_column += vlq_fast(bytes, &mut pos)?;
2242
2243 if pos < len && bytes[pos] != b',' && bytes[pos] != b';' {
2244 source_index += vlq_fast(bytes, &mut pos)?;
2246
2247 if pos >= len || bytes[pos] == b',' || bytes[pos] == b';' {
2249 return Err(DecodeError::InvalidSegmentLength {
2250 fields: 2,
2251 offset: pos,
2252 });
2253 }
2254
2255 original_line += vlq_fast(bytes, &mut pos)?;
2257
2258 if pos >= len || bytes[pos] == b',' || bytes[pos] == b';' {
2260 return Err(DecodeError::InvalidSegmentLength {
2261 fields: 3,
2262 offset: pos,
2263 });
2264 }
2265
2266 original_column += vlq_fast(bytes, &mut pos)?;
2268
2269 let name = if pos < len && bytes[pos] != b',' && bytes[pos] != b';' {
2271 name_index += vlq_fast(bytes, &mut pos)?;
2272 name_index as u32
2273 } else {
2274 NO_NAME
2275 };
2276
2277 mappings.push(Mapping {
2278 generated_line,
2279 generated_column: generated_column as u32,
2280 source: source_index as u32,
2281 original_line: original_line as u32,
2282 original_column: original_column as u32,
2283 name,
2284 is_range_mapping: false,
2285 });
2286 } else {
2287 mappings.push(Mapping {
2289 generated_line,
2290 generated_column: generated_column as u32,
2291 source: NO_SOURCE,
2292 original_line: 0,
2293 original_column: 0,
2294 name: NO_NAME,
2295 is_range_mapping: false,
2296 });
2297 }
2298 }
2299
2300 if !saw_semicolon {
2301 break;
2302 }
2303 generated_line += 1;
2304 }
2305
2306 line_offsets.push(mappings.len() as u32);
2308
2309 Ok((mappings, line_offsets))
2310}
2311
2312fn decode_mappings_range(
2319 input: &str,
2320 start_line: u32,
2321 end_line: u32,
2322) -> Result<(Vec<Mapping>, Vec<u32>), DecodeError> {
2323 if input.is_empty() || start_line >= end_line {
2324 return Ok((Vec::new(), vec![0; end_line as usize + 1]));
2325 }
2326
2327 let bytes = input.as_bytes();
2328 let len = bytes.len();
2329
2330 let mut mappings: Vec<Mapping> = Vec::new();
2331
2332 let mut source_index: i64 = 0;
2333 let mut original_line: i64 = 0;
2334 let mut original_column: i64 = 0;
2335 let mut name_index: i64 = 0;
2336 let mut generated_line: u32 = 0;
2337 let mut pos: usize = 0;
2338
2339 let mut line_starts: Vec<(u32, u32)> = Vec::new();
2342
2343 loop {
2344 let in_range = generated_line >= start_line && generated_line < end_line;
2345 if in_range {
2346 line_starts.push((generated_line, mappings.len() as u32));
2347 }
2348
2349 let mut generated_column: i64 = 0;
2350 let mut saw_semicolon = false;
2351
2352 while pos < len {
2353 let byte = bytes[pos];
2354
2355 if byte == b';' {
2356 pos += 1;
2357 saw_semicolon = true;
2358 break;
2359 }
2360
2361 if byte == b',' {
2362 pos += 1;
2363 continue;
2364 }
2365
2366 generated_column += vlq_fast(bytes, &mut pos)?;
2368
2369 if pos < len && bytes[pos] != b',' && bytes[pos] != b';' {
2370 source_index += vlq_fast(bytes, &mut pos)?;
2372
2373 if pos >= len || bytes[pos] == b',' || bytes[pos] == b';' {
2375 return Err(DecodeError::InvalidSegmentLength {
2376 fields: 2,
2377 offset: pos,
2378 });
2379 }
2380
2381 original_line += vlq_fast(bytes, &mut pos)?;
2383
2384 if pos >= len || bytes[pos] == b',' || bytes[pos] == b';' {
2386 return Err(DecodeError::InvalidSegmentLength {
2387 fields: 3,
2388 offset: pos,
2389 });
2390 }
2391
2392 original_column += vlq_fast(bytes, &mut pos)?;
2394
2395 let name = if pos < len && bytes[pos] != b',' && bytes[pos] != b';' {
2397 name_index += vlq_fast(bytes, &mut pos)?;
2398 name_index as u32
2399 } else {
2400 NO_NAME
2401 };
2402
2403 if in_range {
2404 mappings.push(Mapping {
2405 generated_line,
2406 generated_column: generated_column as u32,
2407 source: source_index as u32,
2408 original_line: original_line as u32,
2409 original_column: original_column as u32,
2410 name,
2411 is_range_mapping: false,
2412 });
2413 }
2414 } else {
2415 if in_range {
2417 mappings.push(Mapping {
2418 generated_line,
2419 generated_column: generated_column as u32,
2420 source: NO_SOURCE,
2421 original_line: 0,
2422 original_column: 0,
2423 name: NO_NAME,
2424 is_range_mapping: false,
2425 });
2426 }
2427 }
2428 }
2429
2430 if !saw_semicolon {
2431 break;
2432 }
2433 generated_line += 1;
2434
2435 if generated_line >= end_line {
2437 break;
2438 }
2439 }
2440
2441 let total = mappings.len() as u32;
2445 let mut line_offsets: Vec<u32> = vec![total; end_line as usize + 1];
2446
2447 for i in 0..=start_line as usize {
2450 if i < line_offsets.len() {
2451 line_offsets[i] = 0;
2452 }
2453 }
2454
2455 for &(line, offset) in &line_starts {
2457 line_offsets[line as usize] = offset;
2458 }
2459
2460 for i in start_line as usize..=end_line as usize {
2483 if i < line_offsets.len() {
2484 line_offsets[i] = total;
2485 }
2486 }
2487
2488 for &(line, offset) in &line_starts {
2490 line_offsets[line as usize] = offset;
2491 }
2492
2493 let mut next_offset = total;
2497 for i in (start_line as usize..end_line as usize).rev() {
2498 if line_offsets[i] == total {
2499 line_offsets[i] = next_offset;
2501 } else {
2502 next_offset = line_offsets[i];
2503 }
2504 }
2505
2506 for offset in line_offsets.iter_mut().take(start_line as usize) {
2509 *offset = 0;
2510 }
2511
2512 Ok((mappings, line_offsets))
2513}
2514
2515fn build_reverse_index(mappings: &[Mapping]) -> Vec<u32> {
2517 let mut indices: Vec<u32> = (0..mappings.len() as u32)
2518 .filter(|&i| mappings[i as usize].source != NO_SOURCE)
2519 .collect();
2520
2521 indices.sort_unstable_by(|&a, &b| {
2522 let ma = &mappings[a as usize];
2523 let mb = &mappings[b as usize];
2524 ma.source
2525 .cmp(&mb.source)
2526 .then(ma.original_line.cmp(&mb.original_line))
2527 .then(ma.original_column.cmp(&mb.original_column))
2528 });
2529
2530 indices
2531}
2532
2533pub struct MappingsIter<'a> {
2553 bytes: &'a [u8],
2554 len: usize,
2555 pos: usize,
2556 source_index: i64,
2557 original_line: i64,
2558 original_column: i64,
2559 name_index: i64,
2560 generated_line: u32,
2561 generated_column: i64,
2562 done: bool,
2563}
2564
2565impl<'a> MappingsIter<'a> {
2566 pub fn new(vlq: &'a str) -> Self {
2568 let bytes = vlq.as_bytes();
2569 Self {
2570 bytes,
2571 len: bytes.len(),
2572 pos: 0,
2573 source_index: 0,
2574 original_line: 0,
2575 original_column: 0,
2576 name_index: 0,
2577 generated_line: 0,
2578 generated_column: 0,
2579 done: false,
2580 }
2581 }
2582}
2583
2584impl Iterator for MappingsIter<'_> {
2585 type Item = Result<Mapping, DecodeError>;
2586
2587 fn next(&mut self) -> Option<Self::Item> {
2588 if self.done {
2589 return None;
2590 }
2591
2592 loop {
2593 if self.pos >= self.len {
2594 self.done = true;
2595 return None;
2596 }
2597
2598 let byte = self.bytes[self.pos];
2599
2600 if byte == b';' {
2601 self.pos += 1;
2602 self.generated_line += 1;
2603 self.generated_column = 0;
2604 continue;
2605 }
2606
2607 if byte == b',' {
2608 self.pos += 1;
2609 continue;
2610 }
2611
2612 match vlq_fast(self.bytes, &mut self.pos) {
2614 Ok(delta) => self.generated_column += delta,
2615 Err(e) => {
2616 self.done = true;
2617 return Some(Err(e));
2618 }
2619 }
2620
2621 if self.pos < self.len && self.bytes[self.pos] != b',' && self.bytes[self.pos] != b';' {
2622 match vlq_fast(self.bytes, &mut self.pos) {
2624 Ok(delta) => self.source_index += delta,
2625 Err(e) => {
2626 self.done = true;
2627 return Some(Err(e));
2628 }
2629 }
2630 match vlq_fast(self.bytes, &mut self.pos) {
2631 Ok(delta) => self.original_line += delta,
2632 Err(e) => {
2633 self.done = true;
2634 return Some(Err(e));
2635 }
2636 }
2637 match vlq_fast(self.bytes, &mut self.pos) {
2638 Ok(delta) => self.original_column += delta,
2639 Err(e) => {
2640 self.done = true;
2641 return Some(Err(e));
2642 }
2643 }
2644
2645 let name = if self.pos < self.len
2647 && self.bytes[self.pos] != b','
2648 && self.bytes[self.pos] != b';'
2649 {
2650 match vlq_fast(self.bytes, &mut self.pos) {
2651 Ok(delta) => {
2652 self.name_index += delta;
2653 self.name_index as u32
2654 }
2655 Err(e) => {
2656 self.done = true;
2657 return Some(Err(e));
2658 }
2659 }
2660 } else {
2661 NO_NAME
2662 };
2663
2664 return Some(Ok(Mapping {
2665 generated_line: self.generated_line,
2666 generated_column: self.generated_column as u32,
2667 source: self.source_index as u32,
2668 original_line: self.original_line as u32,
2669 original_column: self.original_column as u32,
2670 name,
2671 is_range_mapping: false,
2672 }));
2673 } else {
2674 return Some(Ok(Mapping {
2676 generated_line: self.generated_line,
2677 generated_column: self.generated_column as u32,
2678 source: NO_SOURCE,
2679 original_line: 0,
2680 original_column: 0,
2681 name: NO_NAME,
2682 is_range_mapping: false,
2683 }));
2684 }
2685 }
2686 }
2687}
2688
2689pub struct SourceMapBuilder {
2696 file: Option<String>,
2697 source_root: Option<String>,
2698 sources: Vec<String>,
2699 sources_content: Vec<Option<String>>,
2700 names: Vec<String>,
2701 mappings: Vec<Mapping>,
2702 ignore_list: Vec<u32>,
2703 debug_id: Option<String>,
2704 scopes: Option<ScopeInfo>,
2705}
2706
2707impl SourceMapBuilder {
2708 pub fn new() -> Self {
2709 Self {
2710 file: None,
2711 source_root: None,
2712 sources: Vec::new(),
2713 sources_content: Vec::new(),
2714 names: Vec::new(),
2715 mappings: Vec::new(),
2716 ignore_list: Vec::new(),
2717 debug_id: None,
2718 scopes: None,
2719 }
2720 }
2721
2722 pub fn file(mut self, file: impl Into<String>) -> Self {
2723 self.file = Some(file.into());
2724 self
2725 }
2726
2727 pub fn source_root(mut self, root: impl Into<String>) -> Self {
2728 self.source_root = Some(root.into());
2729 self
2730 }
2731
2732 pub fn sources(mut self, sources: impl IntoIterator<Item = impl Into<String>>) -> Self {
2733 self.sources = sources.into_iter().map(Into::into).collect();
2734 self
2735 }
2736
2737 pub fn sources_content(
2738 mut self,
2739 content: impl IntoIterator<Item = Option<impl Into<String>>>,
2740 ) -> Self {
2741 self.sources_content = content.into_iter().map(|c| c.map(Into::into)).collect();
2742 self
2743 }
2744
2745 pub fn names(mut self, names: impl IntoIterator<Item = impl Into<String>>) -> Self {
2746 self.names = names.into_iter().map(Into::into).collect();
2747 self
2748 }
2749
2750 pub fn mappings(mut self, mappings: impl IntoIterator<Item = Mapping>) -> Self {
2751 self.mappings = mappings.into_iter().collect();
2752 self
2753 }
2754
2755 pub fn ignore_list(mut self, list: impl IntoIterator<Item = u32>) -> Self {
2756 self.ignore_list = list.into_iter().collect();
2757 self
2758 }
2759
2760 pub fn debug_id(mut self, id: impl Into<String>) -> Self {
2761 self.debug_id = Some(id.into());
2762 self
2763 }
2764
2765 pub fn scopes(mut self, scopes: ScopeInfo) -> Self {
2766 self.scopes = Some(scopes);
2767 self
2768 }
2769
2770 pub fn build(self) -> SourceMap {
2774 SourceMap::from_parts(
2775 self.file,
2776 self.source_root,
2777 self.sources,
2778 self.sources_content,
2779 self.names,
2780 self.mappings,
2781 self.ignore_list,
2782 self.debug_id,
2783 self.scopes,
2784 )
2785 }
2786}
2787
2788impl Default for SourceMapBuilder {
2789 fn default() -> Self {
2790 Self::new()
2791 }
2792}
2793
2794#[cfg(test)]
2797mod tests {
2798 use super::*;
2799
2800 fn simple_map() -> &'static str {
2801 r#"{"version":3,"sources":["input.js"],"names":["hello"],"mappings":"AAAA;AACA,EAAA;AACA"}"#
2802 }
2803
2804 #[test]
2805 fn parse_basic() {
2806 let sm = SourceMap::from_json(simple_map()).unwrap();
2807 assert_eq!(sm.sources, vec!["input.js"]);
2808 assert_eq!(sm.names, vec!["hello"]);
2809 assert_eq!(sm.line_count(), 3);
2810 assert!(sm.mapping_count() > 0);
2811 }
2812
2813 #[test]
2814 fn to_json_roundtrip() {
2815 let json = simple_map();
2816 let sm = SourceMap::from_json(json).unwrap();
2817 let output = sm.to_json();
2818
2819 let sm2 = SourceMap::from_json(&output).unwrap();
2821 assert_eq!(sm2.sources, sm.sources);
2822 assert_eq!(sm2.names, sm.names);
2823 assert_eq!(sm2.mapping_count(), sm.mapping_count());
2824 assert_eq!(sm2.line_count(), sm.line_count());
2825
2826 for m in sm.all_mappings() {
2828 let loc1 = sm.original_position_for(m.generated_line, m.generated_column);
2829 let loc2 = sm2.original_position_for(m.generated_line, m.generated_column);
2830 match (loc1, loc2) {
2831 (Some(a), Some(b)) => {
2832 assert_eq!(a.source, b.source);
2833 assert_eq!(a.line, b.line);
2834 assert_eq!(a.column, b.column);
2835 assert_eq!(a.name, b.name);
2836 }
2837 (None, None) => {}
2838 _ => panic!(
2839 "lookup mismatch at ({}, {})",
2840 m.generated_line, m.generated_column
2841 ),
2842 }
2843 }
2844 }
2845
2846 #[test]
2847 fn to_json_roundtrip_large() {
2848 let json = generate_test_sourcemap(50, 10, 3);
2849 let sm = SourceMap::from_json(&json).unwrap();
2850 let output = sm.to_json();
2851 let sm2 = SourceMap::from_json(&output).unwrap();
2852
2853 assert_eq!(sm2.mapping_count(), sm.mapping_count());
2854
2855 for line in (0..sm.line_count() as u32).step_by(5) {
2857 for col in [0u32, 10, 20, 50] {
2858 let a = sm.original_position_for(line, col);
2859 let b = sm2.original_position_for(line, col);
2860 match (a, b) {
2861 (Some(a), Some(b)) => {
2862 assert_eq!(a.source, b.source);
2863 assert_eq!(a.line, b.line);
2864 assert_eq!(a.column, b.column);
2865 }
2866 (None, None) => {}
2867 _ => panic!("mismatch at ({line}, {col})"),
2868 }
2869 }
2870 }
2871 }
2872
2873 #[test]
2874 fn to_json_preserves_fields() {
2875 let json = r#"{"version":3,"file":"out.js","sourceRoot":"src/","sources":["app.ts"],"sourcesContent":["const x = 1;"],"names":["x"],"mappings":"AAAAA","ignoreList":[0]}"#;
2876 let sm = SourceMap::from_json(json).unwrap();
2877 let output = sm.to_json();
2878
2879 assert!(output.contains(r#""file":"out.js""#));
2880 assert!(output.contains(r#""sourceRoot":"src/""#));
2881 assert!(output.contains(r#""sourcesContent":["const x = 1;"]"#));
2882 assert!(output.contains(r#""ignoreList":[0]"#));
2883
2884 let sm2 = SourceMap::from_json(&output).unwrap();
2886 assert_eq!(sm2.file.as_deref(), Some("out.js"));
2887 assert_eq!(sm2.ignore_list, vec![0]);
2888 }
2889
2890 #[test]
2891 fn original_position_for_exact_match() {
2892 let sm = SourceMap::from_json(simple_map()).unwrap();
2893 let loc = sm.original_position_for(0, 0).unwrap();
2894 assert_eq!(loc.source, 0);
2895 assert_eq!(loc.line, 0);
2896 assert_eq!(loc.column, 0);
2897 }
2898
2899 #[test]
2900 fn original_position_for_column_within_segment() {
2901 let sm = SourceMap::from_json(simple_map()).unwrap();
2902 let loc = sm.original_position_for(1, 5);
2904 assert!(loc.is_some());
2905 }
2906
2907 #[test]
2908 fn original_position_for_nonexistent_line() {
2909 let sm = SourceMap::from_json(simple_map()).unwrap();
2910 assert!(sm.original_position_for(999, 0).is_none());
2911 }
2912
2913 #[test]
2914 fn original_position_for_before_first_mapping() {
2915 let sm = SourceMap::from_json(simple_map()).unwrap();
2917 let loc = sm.original_position_for(1, 0);
2918 let _ = loc;
2921 }
2922
2923 #[test]
2924 fn generated_position_for_basic() {
2925 let sm = SourceMap::from_json(simple_map()).unwrap();
2926 let loc = sm.generated_position_for("input.js", 0, 0).unwrap();
2927 assert_eq!(loc.line, 0);
2928 assert_eq!(loc.column, 0);
2929 }
2930
2931 #[test]
2932 fn generated_position_for_unknown_source() {
2933 let sm = SourceMap::from_json(simple_map()).unwrap();
2934 assert!(sm.generated_position_for("nonexistent.js", 0, 0).is_none());
2935 }
2936
2937 #[test]
2938 fn parse_invalid_version() {
2939 let json = r#"{"version":2,"sources":[],"names":[],"mappings":""}"#;
2940 let err = SourceMap::from_json(json).unwrap_err();
2941 assert!(matches!(err, ParseError::InvalidVersion(2)));
2942 }
2943
2944 #[test]
2945 fn parse_empty_mappings() {
2946 let json = r#"{"version":3,"sources":[],"names":[],"mappings":""}"#;
2947 let sm = SourceMap::from_json(json).unwrap();
2948 assert_eq!(sm.mapping_count(), 0);
2949 assert!(sm.original_position_for(0, 0).is_none());
2950 }
2951
2952 #[test]
2953 fn parse_with_source_root() {
2954 let json = r#"{"version":3,"sourceRoot":"src/","sources":["foo.js"],"names":[],"mappings":"AAAA"}"#;
2955 let sm = SourceMap::from_json(json).unwrap();
2956 assert_eq!(sm.sources, vec!["src/foo.js"]);
2957 }
2958
2959 #[test]
2960 fn parse_with_sources_content() {
2961 let json = r#"{"version":3,"sources":["a.js"],"sourcesContent":["var x = 1;"],"names":[],"mappings":"AAAA"}"#;
2962 let sm = SourceMap::from_json(json).unwrap();
2963 assert_eq!(sm.sources_content, vec![Some("var x = 1;".to_string())]);
2964 }
2965
2966 #[test]
2967 fn mappings_for_line() {
2968 let sm = SourceMap::from_json(simple_map()).unwrap();
2969 let line0 = sm.mappings_for_line(0);
2970 assert!(!line0.is_empty());
2971 let empty = sm.mappings_for_line(999);
2972 assert!(empty.is_empty());
2973 }
2974
2975 #[test]
2976 fn large_sourcemap_lookup() {
2977 let json = generate_test_sourcemap(500, 20, 5);
2979 let sm = SourceMap::from_json(&json).unwrap();
2980
2981 for line in [0, 10, 100, 250, 499] {
2983 let mappings = sm.mappings_for_line(line);
2984 if let Some(m) = mappings.first() {
2985 let loc = sm.original_position_for(line, m.generated_column);
2986 assert!(loc.is_some(), "lookup failed for line {line}");
2987 }
2988 }
2989 }
2990
2991 #[test]
2992 fn reverse_lookup_roundtrip() {
2993 let json = generate_test_sourcemap(100, 10, 3);
2994 let sm = SourceMap::from_json(&json).unwrap();
2995
2996 let mapping = &sm.mappings[50];
2998 if mapping.source != NO_SOURCE {
2999 let source_name = sm.source(mapping.source);
3000 let result = sm.generated_position_for(
3001 source_name,
3002 mapping.original_line,
3003 mapping.original_column,
3004 );
3005 assert!(result.is_some(), "reverse lookup failed");
3006 }
3007 }
3008
3009 #[test]
3010 fn all_generated_positions_for_basic() {
3011 let sm = SourceMap::from_json(simple_map()).unwrap();
3012 let results = sm.all_generated_positions_for("input.js", 0, 0);
3013 assert!(!results.is_empty(), "should find at least one position");
3014 assert_eq!(results[0].line, 0);
3015 assert_eq!(results[0].column, 0);
3016 }
3017
3018 #[test]
3019 fn all_generated_positions_for_unknown_source() {
3020 let sm = SourceMap::from_json(simple_map()).unwrap();
3021 let results = sm.all_generated_positions_for("nonexistent.js", 0, 0);
3022 assert!(results.is_empty());
3023 }
3024
3025 #[test]
3026 fn all_generated_positions_for_no_match() {
3027 let sm = SourceMap::from_json(simple_map()).unwrap();
3028 let results = sm.all_generated_positions_for("input.js", 999, 999);
3029 assert!(results.is_empty());
3030 }
3031
3032 #[test]
3033 fn encode_mappings_roundtrip() {
3034 let json = generate_test_sourcemap(50, 10, 3);
3035 let sm = SourceMap::from_json(&json).unwrap();
3036 let encoded = sm.encode_mappings();
3037 let json2 = format!(
3039 r#"{{"version":3,"sources":{sources},"names":{names},"mappings":"{mappings}"}}"#,
3040 sources = serde_json::to_string(&sm.sources).unwrap(),
3041 names = serde_json::to_string(&sm.names).unwrap(),
3042 mappings = encoded,
3043 );
3044 let sm2 = SourceMap::from_json(&json2).unwrap();
3045 assert_eq!(sm2.mapping_count(), sm.mapping_count());
3046 }
3047
3048 #[test]
3049 fn indexed_source_map() {
3050 let json = r#"{
3051 "version": 3,
3052 "file": "bundle.js",
3053 "sections": [
3054 {
3055 "offset": {"line": 0, "column": 0},
3056 "map": {
3057 "version": 3,
3058 "sources": ["a.js"],
3059 "names": ["foo"],
3060 "mappings": "AAAAA"
3061 }
3062 },
3063 {
3064 "offset": {"line": 10, "column": 0},
3065 "map": {
3066 "version": 3,
3067 "sources": ["b.js"],
3068 "names": ["bar"],
3069 "mappings": "AAAAA"
3070 }
3071 }
3072 ]
3073 }"#;
3074
3075 let sm = SourceMap::from_json(json).unwrap();
3076
3077 assert_eq!(sm.sources.len(), 2);
3079 assert!(sm.sources.contains(&"a.js".to_string()));
3080 assert!(sm.sources.contains(&"b.js".to_string()));
3081
3082 assert_eq!(sm.names.len(), 2);
3084 assert!(sm.names.contains(&"foo".to_string()));
3085 assert!(sm.names.contains(&"bar".to_string()));
3086
3087 let loc = sm.original_position_for(0, 0).unwrap();
3089 assert_eq!(sm.source(loc.source), "a.js");
3090 assert_eq!(loc.line, 0);
3091 assert_eq!(loc.column, 0);
3092
3093 let loc = sm.original_position_for(10, 0).unwrap();
3095 assert_eq!(sm.source(loc.source), "b.js");
3096 assert_eq!(loc.line, 0);
3097 assert_eq!(loc.column, 0);
3098 }
3099
3100 #[test]
3101 fn indexed_source_map_shared_sources() {
3102 let json = r#"{
3104 "version": 3,
3105 "sections": [
3106 {
3107 "offset": {"line": 0, "column": 0},
3108 "map": {
3109 "version": 3,
3110 "sources": ["shared.js"],
3111 "names": [],
3112 "mappings": "AAAA"
3113 }
3114 },
3115 {
3116 "offset": {"line": 5, "column": 0},
3117 "map": {
3118 "version": 3,
3119 "sources": ["shared.js"],
3120 "names": [],
3121 "mappings": "AACA"
3122 }
3123 }
3124 ]
3125 }"#;
3126
3127 let sm = SourceMap::from_json(json).unwrap();
3128
3129 assert_eq!(sm.sources.len(), 1);
3131 assert_eq!(sm.sources[0], "shared.js");
3132
3133 let loc0 = sm.original_position_for(0, 0).unwrap();
3135 let loc5 = sm.original_position_for(5, 0).unwrap();
3136 assert_eq!(loc0.source, loc5.source);
3137 }
3138
3139 #[test]
3140 fn parse_ignore_list() {
3141 let json = r#"{"version":3,"sources":["app.js","node_modules/lib.js"],"names":[],"mappings":"AAAA;ACAA","ignoreList":[1]}"#;
3142 let sm = SourceMap::from_json(json).unwrap();
3143 assert_eq!(sm.ignore_list, vec![1]);
3144 }
3145
3146 fn build_sourcemap_json(
3148 sources: &[&str],
3149 names: &[&str],
3150 mappings_data: &[Vec<Vec<i64>>],
3151 ) -> String {
3152 let converted: Vec<Vec<srcmap_codec::Segment>> = mappings_data
3153 .iter()
3154 .map(|line| {
3155 line.iter()
3156 .map(|seg| srcmap_codec::Segment::from(seg.as_slice()))
3157 .collect()
3158 })
3159 .collect();
3160 let encoded = srcmap_codec::encode(&converted);
3161 format!(
3162 r#"{{"version":3,"sources":[{}],"names":[{}],"mappings":"{}"}}"#,
3163 sources
3164 .iter()
3165 .map(|s| format!("\"{s}\""))
3166 .collect::<Vec<_>>()
3167 .join(","),
3168 names
3169 .iter()
3170 .map(|n| format!("\"{n}\""))
3171 .collect::<Vec<_>>()
3172 .join(","),
3173 encoded,
3174 )
3175 }
3176
3177 #[test]
3180 fn decode_multiple_consecutive_semicolons() {
3181 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;;;AACA"}"#;
3182 let sm = SourceMap::from_json(json).unwrap();
3183 assert_eq!(sm.line_count(), 4);
3184 assert!(sm.mappings_for_line(1).is_empty());
3185 assert!(sm.mappings_for_line(2).is_empty());
3186 assert!(!sm.mappings_for_line(0).is_empty());
3187 assert!(!sm.mappings_for_line(3).is_empty());
3188 }
3189
3190 #[test]
3191 fn decode_trailing_semicolons() {
3192 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;;"}"#;
3193 let sm = SourceMap::from_json(json).unwrap();
3194 assert_eq!(sm.line_count(), 3);
3195 assert!(!sm.mappings_for_line(0).is_empty());
3196 assert!(sm.mappings_for_line(1).is_empty());
3197 assert!(sm.mappings_for_line(2).is_empty());
3198 }
3199
3200 #[test]
3201 fn decode_leading_comma() {
3202 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":",AAAA"}"#;
3203 let sm = SourceMap::from_json(json).unwrap();
3204 assert_eq!(sm.mapping_count(), 1);
3205 let m = &sm.all_mappings()[0];
3206 assert_eq!(m.generated_line, 0);
3207 assert_eq!(m.generated_column, 0);
3208 }
3209
3210 #[test]
3211 fn decode_single_field_segments() {
3212 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A,C"}"#;
3213 let sm = SourceMap::from_json(json).unwrap();
3214 assert_eq!(sm.mapping_count(), 2);
3215 for m in sm.all_mappings() {
3216 assert_eq!(m.source, NO_SOURCE);
3217 }
3218 assert_eq!(sm.all_mappings()[0].generated_column, 0);
3219 assert_eq!(sm.all_mappings()[1].generated_column, 1);
3220 assert!(sm.original_position_for(0, 0).is_none());
3221 assert!(sm.original_position_for(0, 1).is_none());
3222 }
3223
3224 #[test]
3225 fn decode_five_field_segments_with_names() {
3226 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0, 0], vec![10, 0, 0, 5, 1]]];
3227 let json = build_sourcemap_json(&["app.js"], &["foo", "bar"], &mappings_data);
3228 let sm = SourceMap::from_json(&json).unwrap();
3229 assert_eq!(sm.mapping_count(), 2);
3230 assert_eq!(sm.all_mappings()[0].name, 0);
3231 assert_eq!(sm.all_mappings()[1].name, 1);
3232
3233 let loc = sm.original_position_for(0, 0).unwrap();
3234 assert_eq!(loc.name, Some(0));
3235 assert_eq!(sm.name(0), "foo");
3236
3237 let loc = sm.original_position_for(0, 10).unwrap();
3238 assert_eq!(loc.name, Some(1));
3239 assert_eq!(sm.name(1), "bar");
3240 }
3241
3242 #[test]
3243 fn decode_large_vlq_values() {
3244 let mappings_data = vec![vec![vec![500_i64, 0, 1000, 2000]]];
3245 let json = build_sourcemap_json(&["big.js"], &[], &mappings_data);
3246 let sm = SourceMap::from_json(&json).unwrap();
3247 assert_eq!(sm.mapping_count(), 1);
3248 let m = &sm.all_mappings()[0];
3249 assert_eq!(m.generated_column, 500);
3250 assert_eq!(m.original_line, 1000);
3251 assert_eq!(m.original_column, 2000);
3252
3253 let loc = sm.original_position_for(0, 500).unwrap();
3254 assert_eq!(loc.line, 1000);
3255 assert_eq!(loc.column, 2000);
3256 }
3257
3258 #[test]
3259 fn decode_only_semicolons() {
3260 let json = r#"{"version":3,"sources":[],"names":[],"mappings":";;;"}"#;
3261 let sm = SourceMap::from_json(json).unwrap();
3262 assert_eq!(sm.line_count(), 4);
3263 assert_eq!(sm.mapping_count(), 0);
3264 for line in 0..4 {
3265 assert!(sm.mappings_for_line(line).is_empty());
3266 }
3267 }
3268
3269 #[test]
3270 fn decode_mixed_single_and_four_field_segments() {
3271 let mappings_data = vec![vec![srcmap_codec::Segment::four(5, 0, 0, 0)]];
3272 let four_field_encoded = srcmap_codec::encode(&mappings_data);
3273 let combined_mappings = format!("A,{four_field_encoded}");
3274 let json = format!(
3275 r#"{{"version":3,"sources":["x.js"],"names":[],"mappings":"{combined_mappings}"}}"#,
3276 );
3277 let sm = SourceMap::from_json(&json).unwrap();
3278 assert_eq!(sm.mapping_count(), 2);
3279 assert_eq!(sm.all_mappings()[0].source, NO_SOURCE);
3280 assert_eq!(sm.all_mappings()[1].source, 0);
3281 }
3282
3283 #[test]
3286 fn parse_missing_optional_fields() {
3287 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
3288 let sm = SourceMap::from_json(json).unwrap();
3289 assert!(sm.file.is_none());
3290 assert!(sm.source_root.is_none());
3291 assert!(sm.sources_content.is_empty());
3292 assert!(sm.ignore_list.is_empty());
3293 }
3294
3295 #[test]
3296 fn parse_with_file_field() {
3297 let json =
3298 r#"{"version":3,"file":"output.js","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
3299 let sm = SourceMap::from_json(json).unwrap();
3300 assert_eq!(sm.file.as_deref(), Some("output.js"));
3301 }
3302
3303 #[test]
3304 fn parse_null_entries_in_sources() {
3305 let json = r#"{"version":3,"sources":["a.js",null,"c.js"],"names":[],"mappings":"AAAA"}"#;
3306 let sm = SourceMap::from_json(json).unwrap();
3307 assert_eq!(sm.sources.len(), 3);
3308 assert_eq!(sm.sources[0], "a.js");
3309 assert_eq!(sm.sources[1], "");
3310 assert_eq!(sm.sources[2], "c.js");
3311 }
3312
3313 #[test]
3314 fn parse_null_entries_in_sources_with_source_root() {
3315 let json = r#"{"version":3,"sourceRoot":"lib/","sources":["a.js",null],"names":[],"mappings":"AAAA"}"#;
3316 let sm = SourceMap::from_json(json).unwrap();
3317 assert_eq!(sm.sources[0], "lib/a.js");
3318 assert_eq!(sm.sources[1], "");
3319 }
3320
3321 #[test]
3322 fn parse_empty_names_array() {
3323 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
3324 let sm = SourceMap::from_json(json).unwrap();
3325 assert!(sm.names.is_empty());
3326 }
3327
3328 #[test]
3329 fn parse_invalid_json() {
3330 let result = SourceMap::from_json("not valid json");
3331 assert!(result.is_err());
3332 assert!(matches!(result.unwrap_err(), ParseError::Json(_)));
3333 }
3334
3335 #[test]
3336 fn parse_json_missing_version() {
3337 let result = SourceMap::from_json(r#"{"sources":[],"names":[],"mappings":""}"#);
3338 assert!(result.is_err());
3339 }
3340
3341 #[test]
3342 fn parse_multiple_sources_overlapping_original_positions() {
3343 let mappings_data = vec![vec![vec![0_i64, 0, 5, 10], vec![10, 1, 5, 10]]];
3344 let json = build_sourcemap_json(&["a.js", "b.js"], &[], &mappings_data);
3345 let sm = SourceMap::from_json(&json).unwrap();
3346
3347 let loc0 = sm.original_position_for(0, 0).unwrap();
3348 assert_eq!(loc0.source, 0);
3349 assert_eq!(sm.source(loc0.source), "a.js");
3350
3351 let loc1 = sm.original_position_for(0, 10).unwrap();
3352 assert_eq!(loc1.source, 1);
3353 assert_eq!(sm.source(loc1.source), "b.js");
3354
3355 assert_eq!(loc0.line, loc1.line);
3356 assert_eq!(loc0.column, loc1.column);
3357 }
3358
3359 #[test]
3360 fn parse_sources_content_with_null_entries() {
3361 let json = r#"{"version":3,"sources":["a.js","b.js"],"sourcesContent":["content a",null],"names":[],"mappings":"AAAA"}"#;
3362 let sm = SourceMap::from_json(json).unwrap();
3363 assert_eq!(sm.sources_content.len(), 2);
3364 assert_eq!(sm.sources_content[0], Some("content a".to_string()));
3365 assert_eq!(sm.sources_content[1], None);
3366 }
3367
3368 #[test]
3369 fn parse_empty_sources_and_names() {
3370 let json = r#"{"version":3,"sources":[],"names":[],"mappings":""}"#;
3371 let sm = SourceMap::from_json(json).unwrap();
3372 assert!(sm.sources.is_empty());
3373 assert!(sm.names.is_empty());
3374 assert_eq!(sm.mapping_count(), 0);
3375 }
3376
3377 #[test]
3380 fn lookup_exact_match() {
3381 let mappings_data = vec![vec![
3382 vec![0_i64, 0, 10, 20],
3383 vec![5, 0, 10, 25],
3384 vec![15, 0, 11, 0],
3385 ]];
3386 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3387 let sm = SourceMap::from_json(&json).unwrap();
3388
3389 let loc = sm.original_position_for(0, 5).unwrap();
3390 assert_eq!(loc.line, 10);
3391 assert_eq!(loc.column, 25);
3392 }
3393
3394 #[test]
3395 fn lookup_before_first_segment() {
3396 let mappings_data = vec![vec![vec![5_i64, 0, 0, 0]]];
3397 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3398 let sm = SourceMap::from_json(&json).unwrap();
3399
3400 assert!(sm.original_position_for(0, 0).is_none());
3401 assert!(sm.original_position_for(0, 4).is_none());
3402 }
3403
3404 #[test]
3405 fn lookup_between_segments() {
3406 let mappings_data = vec![vec![
3407 vec![0_i64, 0, 1, 0],
3408 vec![10, 0, 2, 0],
3409 vec![20, 0, 3, 0],
3410 ]];
3411 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3412 let sm = SourceMap::from_json(&json).unwrap();
3413
3414 let loc = sm.original_position_for(0, 7).unwrap();
3415 assert_eq!(loc.line, 1);
3416 assert_eq!(loc.column, 0);
3417
3418 let loc = sm.original_position_for(0, 15).unwrap();
3419 assert_eq!(loc.line, 2);
3420 assert_eq!(loc.column, 0);
3421 }
3422
3423 #[test]
3424 fn lookup_after_last_segment() {
3425 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0], vec![10, 0, 1, 5]]];
3426 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3427 let sm = SourceMap::from_json(&json).unwrap();
3428
3429 let loc = sm.original_position_for(0, 100).unwrap();
3430 assert_eq!(loc.line, 1);
3431 assert_eq!(loc.column, 5);
3432 }
3433
3434 #[test]
3435 fn lookup_empty_lines_no_mappings() {
3436 let mappings_data = vec![
3437 vec![vec![0_i64, 0, 0, 0]],
3438 vec![],
3439 vec![vec![0_i64, 0, 2, 0]],
3440 ];
3441 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3442 let sm = SourceMap::from_json(&json).unwrap();
3443
3444 assert!(sm.original_position_for(1, 0).is_none());
3445 assert!(sm.original_position_for(1, 10).is_none());
3446 assert!(sm.original_position_for(0, 0).is_some());
3447 assert!(sm.original_position_for(2, 0).is_some());
3448 }
3449
3450 #[test]
3451 fn lookup_line_with_single_mapping() {
3452 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0]]];
3453 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3454 let sm = SourceMap::from_json(&json).unwrap();
3455
3456 let loc = sm.original_position_for(0, 0).unwrap();
3457 assert_eq!(loc.line, 0);
3458 assert_eq!(loc.column, 0);
3459
3460 let loc = sm.original_position_for(0, 50).unwrap();
3461 assert_eq!(loc.line, 0);
3462 assert_eq!(loc.column, 0);
3463 }
3464
3465 #[test]
3466 fn lookup_column_0_vs_column_nonzero() {
3467 let mappings_data = vec![vec![vec![0_i64, 0, 10, 0], vec![8, 0, 20, 5]]];
3468 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3469 let sm = SourceMap::from_json(&json).unwrap();
3470
3471 let loc0 = sm.original_position_for(0, 0).unwrap();
3472 assert_eq!(loc0.line, 10);
3473 assert_eq!(loc0.column, 0);
3474
3475 let loc8 = sm.original_position_for(0, 8).unwrap();
3476 assert_eq!(loc8.line, 20);
3477 assert_eq!(loc8.column, 5);
3478
3479 let loc4 = sm.original_position_for(0, 4).unwrap();
3480 assert_eq!(loc4.line, 10);
3481 }
3482
3483 #[test]
3484 fn lookup_beyond_last_line() {
3485 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0]]];
3486 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3487 let sm = SourceMap::from_json(&json).unwrap();
3488
3489 assert!(sm.original_position_for(1, 0).is_none());
3490 assert!(sm.original_position_for(100, 0).is_none());
3491 }
3492
3493 #[test]
3494 fn lookup_single_field_returns_none() {
3495 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A"}"#;
3496 let sm = SourceMap::from_json(json).unwrap();
3497 assert_eq!(sm.mapping_count(), 1);
3498 assert!(sm.original_position_for(0, 0).is_none());
3499 }
3500
3501 #[test]
3504 fn reverse_lookup_exact_match() {
3505 let mappings_data = vec![
3506 vec![vec![0_i64, 0, 0, 0]],
3507 vec![vec![4, 0, 1, 0], vec![10, 0, 1, 8]],
3508 vec![vec![0, 0, 2, 0]],
3509 ];
3510 let json = build_sourcemap_json(&["main.js"], &[], &mappings_data);
3511 let sm = SourceMap::from_json(&json).unwrap();
3512
3513 let loc = sm.generated_position_for("main.js", 1, 8).unwrap();
3514 assert_eq!(loc.line, 1);
3515 assert_eq!(loc.column, 10);
3516 }
3517
3518 #[test]
3519 fn reverse_lookup_no_match() {
3520 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0], vec![10, 0, 0, 10]]];
3521 let json = build_sourcemap_json(&["main.js"], &[], &mappings_data);
3522 let sm = SourceMap::from_json(&json).unwrap();
3523
3524 assert!(sm.generated_position_for("main.js", 99, 0).is_none());
3525 }
3526
3527 #[test]
3528 fn reverse_lookup_unknown_source() {
3529 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0]]];
3530 let json = build_sourcemap_json(&["main.js"], &[], &mappings_data);
3531 let sm = SourceMap::from_json(&json).unwrap();
3532
3533 assert!(sm.generated_position_for("unknown.js", 0, 0).is_none());
3534 }
3535
3536 #[test]
3537 fn reverse_lookup_multiple_mappings_same_original() {
3538 let mappings_data = vec![vec![vec![0_i64, 0, 5, 10]], vec![vec![20, 0, 5, 10]]];
3539 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3540 let sm = SourceMap::from_json(&json).unwrap();
3541
3542 let loc = sm.generated_position_for("src.js", 5, 10);
3543 assert!(loc.is_some());
3544 let loc = loc.unwrap();
3545 assert!(
3546 (loc.line == 0 && loc.column == 0) || (loc.line == 1 && loc.column == 20),
3547 "Expected (0,0) or (1,20), got ({},{})",
3548 loc.line,
3549 loc.column
3550 );
3551 }
3552
3553 #[test]
3554 fn reverse_lookup_with_multiple_sources() {
3555 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0], vec![10, 1, 0, 0]]];
3556 let json = build_sourcemap_json(&["a.js", "b.js"], &[], &mappings_data);
3557 let sm = SourceMap::from_json(&json).unwrap();
3558
3559 let loc_a = sm.generated_position_for("a.js", 0, 0).unwrap();
3560 assert_eq!(loc_a.line, 0);
3561 assert_eq!(loc_a.column, 0);
3562
3563 let loc_b = sm.generated_position_for("b.js", 0, 0).unwrap();
3564 assert_eq!(loc_b.line, 0);
3565 assert_eq!(loc_b.column, 10);
3566 }
3567
3568 #[test]
3569 fn reverse_lookup_skips_single_field_segments() {
3570 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A,KAAAA"}"#;
3571 let sm = SourceMap::from_json(json).unwrap();
3572
3573 let loc = sm.generated_position_for("a.js", 0, 0).unwrap();
3574 assert_eq!(loc.line, 0);
3575 assert_eq!(loc.column, 5);
3576 }
3577
3578 #[test]
3579 fn reverse_lookup_finds_each_original_line() {
3580 let mappings_data = vec![
3581 vec![vec![0_i64, 0, 0, 0]],
3582 vec![vec![0, 0, 1, 0]],
3583 vec![vec![0, 0, 2, 0]],
3584 vec![vec![0, 0, 3, 0]],
3585 ];
3586 let json = build_sourcemap_json(&["x.js"], &[], &mappings_data);
3587 let sm = SourceMap::from_json(&json).unwrap();
3588
3589 for orig_line in 0..4 {
3590 let loc = sm.generated_position_for("x.js", orig_line, 0).unwrap();
3591 assert_eq!(
3592 loc.line, orig_line,
3593 "reverse lookup for orig line {orig_line}"
3594 );
3595 assert_eq!(loc.column, 0);
3596 }
3597 }
3598
3599 #[test]
3602 fn parse_with_ignore_list_multiple() {
3603 let json = r#"{"version":3,"sources":["app.js","node_modules/lib.js","vendor.js"],"names":[],"mappings":"AAAA","ignoreList":[1,2]}"#;
3604 let sm = SourceMap::from_json(json).unwrap();
3605 assert_eq!(sm.ignore_list, vec![1, 2]);
3606 }
3607
3608 #[test]
3609 fn parse_with_empty_ignore_list() {
3610 let json =
3611 r#"{"version":3,"sources":["app.js"],"names":[],"mappings":"AAAA","ignoreList":[]}"#;
3612 let sm = SourceMap::from_json(json).unwrap();
3613 assert!(sm.ignore_list.is_empty());
3614 }
3615
3616 #[test]
3617 fn parse_without_ignore_list_field() {
3618 let json = r#"{"version":3,"sources":["app.js"],"names":[],"mappings":"AAAA"}"#;
3619 let sm = SourceMap::from_json(json).unwrap();
3620 assert!(sm.ignore_list.is_empty());
3621 }
3622
3623 #[test]
3626 fn source_index_lookup() {
3627 let json = r#"{"version":3,"sources":["a.js","b.js","c.js"],"names":[],"mappings":"AAAA"}"#;
3628 let sm = SourceMap::from_json(json).unwrap();
3629 assert_eq!(sm.source_index("a.js"), Some(0));
3630 assert_eq!(sm.source_index("b.js"), Some(1));
3631 assert_eq!(sm.source_index("c.js"), Some(2));
3632 assert_eq!(sm.source_index("d.js"), None);
3633 }
3634
3635 #[test]
3636 fn all_mappings_returns_complete_list() {
3637 let mappings_data = vec![
3638 vec![vec![0_i64, 0, 0, 0], vec![5, 0, 0, 5]],
3639 vec![vec![0, 0, 1, 0]],
3640 ];
3641 let json = build_sourcemap_json(&["x.js"], &[], &mappings_data);
3642 let sm = SourceMap::from_json(&json).unwrap();
3643 assert_eq!(sm.all_mappings().len(), 3);
3644 assert_eq!(sm.mapping_count(), 3);
3645 }
3646
3647 #[test]
3648 fn line_count_matches_decoded_lines() {
3649 let mappings_data = vec![
3650 vec![vec![0_i64, 0, 0, 0]],
3651 vec![],
3652 vec![vec![0_i64, 0, 2, 0]],
3653 vec![],
3654 vec![],
3655 ];
3656 let json = build_sourcemap_json(&["x.js"], &[], &mappings_data);
3657 let sm = SourceMap::from_json(&json).unwrap();
3658 assert_eq!(sm.line_count(), 5);
3659 }
3660
3661 #[test]
3662 fn parse_error_display() {
3663 let err = ParseError::InvalidVersion(5);
3664 assert_eq!(format!("{err}"), "unsupported source map version: 5");
3665
3666 let json_err = SourceMap::from_json("{}").unwrap_err();
3667 let display = format!("{json_err}");
3668 assert!(display.contains("JSON parse error") || display.contains("missing field"));
3669 }
3670
3671 #[test]
3672 fn original_position_name_none_for_four_field() {
3673 let mappings_data = vec![vec![vec![0_i64, 0, 5, 10]]];
3674 let json = build_sourcemap_json(&["a.js"], &["unused_name"], &mappings_data);
3675 let sm = SourceMap::from_json(&json).unwrap();
3676
3677 let loc = sm.original_position_for(0, 0).unwrap();
3678 assert!(loc.name.is_none());
3679 }
3680
3681 #[test]
3682 fn forward_and_reverse_roundtrip_comprehensive() {
3683 let mappings_data = vec![
3684 vec![vec![0_i64, 0, 0, 0], vec![10, 0, 0, 10], vec![20, 1, 5, 0]],
3685 vec![vec![0, 0, 1, 0], vec![5, 1, 6, 3]],
3686 vec![vec![0, 0, 2, 0]],
3687 ];
3688 let json = build_sourcemap_json(&["a.js", "b.js"], &[], &mappings_data);
3689 let sm = SourceMap::from_json(&json).unwrap();
3690
3691 for m in sm.all_mappings() {
3692 if m.source == NO_SOURCE {
3693 continue;
3694 }
3695 let source_name = sm.source(m.source);
3696
3697 let orig = sm
3698 .original_position_for(m.generated_line, m.generated_column)
3699 .unwrap();
3700 assert_eq!(orig.source, m.source);
3701 assert_eq!(orig.line, m.original_line);
3702 assert_eq!(orig.column, m.original_column);
3703
3704 let gen_loc = sm
3705 .generated_position_for(source_name, m.original_line, m.original_column)
3706 .unwrap();
3707 assert_eq!(gen_loc.line, m.generated_line);
3708 assert_eq!(gen_loc.column, m.generated_column);
3709 }
3710 }
3711
3712 #[test]
3717 fn source_root_with_multiple_sources() {
3718 let json = r#"{"version":3,"sourceRoot":"lib/","sources":["a.js","b.js","c.js"],"names":[],"mappings":"AAAA,KACA,KACA"}"#;
3719 let sm = SourceMap::from_json(json).unwrap();
3720 assert_eq!(sm.sources, vec!["lib/a.js", "lib/b.js", "lib/c.js"]);
3721 }
3722
3723 #[test]
3724 fn source_root_empty_string() {
3725 let json =
3726 r#"{"version":3,"sourceRoot":"","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
3727 let sm = SourceMap::from_json(json).unwrap();
3728 assert_eq!(sm.sources, vec!["a.js"]);
3729 }
3730
3731 #[test]
3732 fn source_root_preserved_in_to_json() {
3733 let json =
3734 r#"{"version":3,"sourceRoot":"src/","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
3735 let sm = SourceMap::from_json(json).unwrap();
3736 let output = sm.to_json();
3737 assert!(output.contains(r#""sourceRoot":"src/""#));
3738 }
3739
3740 #[test]
3741 fn source_root_reverse_lookup_uses_prefixed_name() {
3742 let json =
3743 r#"{"version":3,"sourceRoot":"src/","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
3744 let sm = SourceMap::from_json(json).unwrap();
3745 assert!(sm.generated_position_for("src/a.js", 0, 0).is_some());
3747 assert!(sm.generated_position_for("a.js", 0, 0).is_none());
3748 }
3749
3750 #[test]
3751 fn source_root_with_trailing_slash() {
3752 let json =
3753 r#"{"version":3,"sourceRoot":"src/","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
3754 let sm = SourceMap::from_json(json).unwrap();
3755 assert_eq!(sm.sources[0], "src/a.js");
3756 }
3757
3758 #[test]
3759 fn source_root_without_trailing_slash() {
3760 let json =
3761 r#"{"version":3,"sourceRoot":"src","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
3762 let sm = SourceMap::from_json(json).unwrap();
3763 assert_eq!(sm.sources[0], "srca.js");
3765 let output = sm.to_json();
3767 let sm2 = SourceMap::from_json(&output).unwrap();
3768 assert_eq!(sm2.sources[0], "srca.js");
3769 }
3770
3771 #[test]
3774 fn parse_empty_json_object() {
3775 let result = SourceMap::from_json("{}");
3777 assert!(result.is_err());
3778 }
3779
3780 #[test]
3781 fn parse_version_0() {
3782 let json = r#"{"version":0,"sources":[],"names":[],"mappings":""}"#;
3783 assert!(matches!(
3784 SourceMap::from_json(json).unwrap_err(),
3785 ParseError::InvalidVersion(0)
3786 ));
3787 }
3788
3789 #[test]
3790 fn parse_version_4() {
3791 let json = r#"{"version":4,"sources":[],"names":[],"mappings":""}"#;
3792 assert!(matches!(
3793 SourceMap::from_json(json).unwrap_err(),
3794 ParseError::InvalidVersion(4)
3795 ));
3796 }
3797
3798 #[test]
3799 fn parse_extra_unknown_fields_ignored() {
3800 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","x_custom_field":true,"x_debug":{"foo":"bar"}}"#;
3801 let sm = SourceMap::from_json(json).unwrap();
3802 assert_eq!(sm.mapping_count(), 1);
3803 }
3804
3805 #[test]
3806 fn parse_vlq_error_propagated() {
3807 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AA!A"}"#;
3809 let result = SourceMap::from_json(json);
3810 assert!(result.is_err());
3811 assert!(matches!(result.unwrap_err(), ParseError::Vlq(_)));
3812 }
3813
3814 #[test]
3815 fn parse_truncated_vlq_error() {
3816 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"g"}"#;
3818 let result = SourceMap::from_json(json);
3819 assert!(result.is_err());
3820 }
3821
3822 #[test]
3825 fn to_json_produces_valid_json() {
3826 let json = r#"{"version":3,"file":"out.js","sourceRoot":"src/","sources":["a.ts","b.ts"],"sourcesContent":["const x = 1;\nconst y = \"hello\";",null],"names":["x","y"],"mappings":"AAAAA,KACAC;AACA","ignoreList":[1]}"#;
3827 let sm = SourceMap::from_json(json).unwrap();
3828 let output = sm.to_json();
3829 let _: serde_json::Value = serde_json::from_str(&output).unwrap();
3831 }
3832
3833 #[test]
3834 fn to_json_escapes_special_chars() {
3835 let json = r#"{"version":3,"sources":["path/with\"quotes.js"],"sourcesContent":["line1\nline2\ttab\\backslash"],"names":[],"mappings":"AAAA"}"#;
3836 let sm = SourceMap::from_json(json).unwrap();
3837 let output = sm.to_json();
3838 let _: serde_json::Value = serde_json::from_str(&output).unwrap();
3839 let sm2 = SourceMap::from_json(&output).unwrap();
3840 assert_eq!(
3841 sm2.sources_content[0].as_deref(),
3842 Some("line1\nline2\ttab\\backslash")
3843 );
3844 }
3845
3846 #[test]
3847 fn to_json_empty_map() {
3848 let json = r#"{"version":3,"sources":[],"names":[],"mappings":""}"#;
3849 let sm = SourceMap::from_json(json).unwrap();
3850 let output = sm.to_json();
3851 let sm2 = SourceMap::from_json(&output).unwrap();
3852 assert_eq!(sm2.mapping_count(), 0);
3853 assert!(sm2.sources.is_empty());
3854 }
3855
3856 #[test]
3857 fn to_json_roundtrip_with_names() {
3858 let mappings_data = vec![vec![
3859 vec![0_i64, 0, 0, 0, 0],
3860 vec![10, 0, 0, 10, 1],
3861 vec![20, 0, 1, 0, 2],
3862 ]];
3863 let json = build_sourcemap_json(&["src.js"], &["foo", "bar", "baz"], &mappings_data);
3864 let sm = SourceMap::from_json(&json).unwrap();
3865 let output = sm.to_json();
3866 let sm2 = SourceMap::from_json(&output).unwrap();
3867
3868 for m in sm2.all_mappings() {
3869 if m.source != NO_SOURCE && m.name != NO_NAME {
3870 let loc = sm2
3871 .original_position_for(m.generated_line, m.generated_column)
3872 .unwrap();
3873 assert!(loc.name.is_some());
3874 }
3875 }
3876 }
3877
3878 #[test]
3881 fn indexed_source_map_column_offset() {
3882 let json = r#"{
3883 "version": 3,
3884 "sections": [
3885 {
3886 "offset": {"line": 0, "column": 10},
3887 "map": {
3888 "version": 3,
3889 "sources": ["a.js"],
3890 "names": [],
3891 "mappings": "AAAA"
3892 }
3893 }
3894 ]
3895 }"#;
3896 let sm = SourceMap::from_json(json).unwrap();
3897 let loc = sm.original_position_for(0, 10).unwrap();
3899 assert_eq!(loc.line, 0);
3900 assert_eq!(loc.column, 0);
3901 assert!(sm.original_position_for(0, 0).is_none());
3903 }
3904
3905 #[test]
3906 fn indexed_source_map_column_offset_only_first_line() {
3907 let json = r#"{
3909 "version": 3,
3910 "sections": [
3911 {
3912 "offset": {"line": 0, "column": 20},
3913 "map": {
3914 "version": 3,
3915 "sources": ["a.js"],
3916 "names": [],
3917 "mappings": "AAAA;AAAA"
3918 }
3919 }
3920 ]
3921 }"#;
3922 let sm = SourceMap::from_json(json).unwrap();
3923 let loc = sm.original_position_for(0, 20).unwrap();
3925 assert_eq!(loc.column, 0);
3926 let loc = sm.original_position_for(1, 0).unwrap();
3928 assert_eq!(loc.column, 0);
3929 }
3930
3931 #[test]
3932 fn indexed_source_map_empty_section() {
3933 let json = r#"{
3934 "version": 3,
3935 "sections": [
3936 {
3937 "offset": {"line": 0, "column": 0},
3938 "map": {
3939 "version": 3,
3940 "sources": [],
3941 "names": [],
3942 "mappings": ""
3943 }
3944 },
3945 {
3946 "offset": {"line": 5, "column": 0},
3947 "map": {
3948 "version": 3,
3949 "sources": ["b.js"],
3950 "names": [],
3951 "mappings": "AAAA"
3952 }
3953 }
3954 ]
3955 }"#;
3956 let sm = SourceMap::from_json(json).unwrap();
3957 assert_eq!(sm.sources.len(), 1);
3958 let loc = sm.original_position_for(5, 0).unwrap();
3959 assert_eq!(sm.source(loc.source), "b.js");
3960 }
3961
3962 #[test]
3963 fn indexed_source_map_with_sources_content() {
3964 let json = r#"{
3965 "version": 3,
3966 "sections": [
3967 {
3968 "offset": {"line": 0, "column": 0},
3969 "map": {
3970 "version": 3,
3971 "sources": ["a.js"],
3972 "sourcesContent": ["var a = 1;"],
3973 "names": [],
3974 "mappings": "AAAA"
3975 }
3976 },
3977 {
3978 "offset": {"line": 5, "column": 0},
3979 "map": {
3980 "version": 3,
3981 "sources": ["b.js"],
3982 "sourcesContent": ["var b = 2;"],
3983 "names": [],
3984 "mappings": "AAAA"
3985 }
3986 }
3987 ]
3988 }"#;
3989 let sm = SourceMap::from_json(json).unwrap();
3990 assert_eq!(sm.sources_content.len(), 2);
3991 assert_eq!(sm.sources_content[0], Some("var a = 1;".to_string()));
3992 assert_eq!(sm.sources_content[1], Some("var b = 2;".to_string()));
3993 }
3994
3995 #[test]
3996 fn indexed_source_map_with_ignore_list() {
3997 let json = r#"{
3998 "version": 3,
3999 "sections": [
4000 {
4001 "offset": {"line": 0, "column": 0},
4002 "map": {
4003 "version": 3,
4004 "sources": ["app.js", "vendor.js"],
4005 "names": [],
4006 "mappings": "AAAA",
4007 "ignoreList": [1]
4008 }
4009 }
4010 ]
4011 }"#;
4012 let sm = SourceMap::from_json(json).unwrap();
4013 assert!(!sm.ignore_list.is_empty());
4014 }
4015
4016 #[test]
4019 fn lookup_max_column_on_line() {
4020 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0]]];
4021 let json = build_sourcemap_json(&["a.js"], &[], &mappings_data);
4022 let sm = SourceMap::from_json(&json).unwrap();
4023 let loc = sm.original_position_for(0, u32::MAX - 1).unwrap();
4025 assert_eq!(loc.line, 0);
4026 assert_eq!(loc.column, 0);
4027 }
4028
4029 #[test]
4030 fn mappings_for_line_beyond_end() {
4031 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
4032 let sm = SourceMap::from_json(json).unwrap();
4033 assert!(sm.mappings_for_line(u32::MAX).is_empty());
4034 }
4035
4036 #[test]
4037 fn source_with_unicode_path() {
4038 let json =
4039 r#"{"version":3,"sources":["src/日本語.ts"],"names":["変数"],"mappings":"AAAAA"}"#;
4040 let sm = SourceMap::from_json(json).unwrap();
4041 assert_eq!(sm.sources[0], "src/日本語.ts");
4042 assert_eq!(sm.names[0], "変数");
4043 let loc = sm.original_position_for(0, 0).unwrap();
4044 assert_eq!(sm.source(loc.source), "src/日本語.ts");
4045 assert_eq!(sm.name(loc.name.unwrap()), "変数");
4046 }
4047
4048 #[test]
4049 fn to_json_roundtrip_unicode_sources() {
4050 let json = r#"{"version":3,"sources":["src/日本語.ts"],"sourcesContent":["const 変数 = 1;"],"names":["変数"],"mappings":"AAAAA"}"#;
4051 let sm = SourceMap::from_json(json).unwrap();
4052 let output = sm.to_json();
4053 let _: serde_json::Value = serde_json::from_str(&output).unwrap();
4054 let sm2 = SourceMap::from_json(&output).unwrap();
4055 assert_eq!(sm2.sources[0], "src/日本語.ts");
4056 assert_eq!(sm2.sources_content[0], Some("const 変数 = 1;".to_string()));
4057 }
4058
4059 #[test]
4060 fn many_sources_lookup() {
4061 let sources: Vec<String> = (0..100).map(|i| format!("src/file{i}.js")).collect();
4063 let source_strs: Vec<&str> = sources.iter().map(|s| s.as_str()).collect();
4064 let mappings_data = vec![
4065 sources
4066 .iter()
4067 .enumerate()
4068 .map(|(i, _)| vec![(i * 10) as i64, i as i64, 0, 0])
4069 .collect::<Vec<_>>(),
4070 ];
4071 let json = build_sourcemap_json(&source_strs, &[], &mappings_data);
4072 let sm = SourceMap::from_json(&json).unwrap();
4073
4074 for (i, src) in sources.iter().enumerate() {
4075 assert_eq!(sm.source_index(src), Some(i as u32));
4076 }
4077 }
4078
4079 #[test]
4080 fn clone_sourcemap() {
4081 let json = r#"{"version":3,"sources":["a.js"],"names":["x"],"mappings":"AAAAA"}"#;
4082 let sm = SourceMap::from_json(json).unwrap();
4083 let sm2 = sm.clone();
4084 assert_eq!(sm2.sources, sm.sources);
4085 assert_eq!(sm2.mapping_count(), sm.mapping_count());
4086 let loc = sm2.original_position_for(0, 0).unwrap();
4087 assert_eq!(sm2.source(loc.source), "a.js");
4088 }
4089
4090 #[test]
4091 fn parse_debug_id() {
4092 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","debugId":"85314830-023f-4cf1-a267-535f4e37bb17"}"#;
4093 let sm = SourceMap::from_json(json).unwrap();
4094 assert_eq!(
4095 sm.debug_id.as_deref(),
4096 Some("85314830-023f-4cf1-a267-535f4e37bb17")
4097 );
4098 }
4099
4100 #[test]
4101 fn parse_debug_id_snake_case() {
4102 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","debug_id":"85314830-023f-4cf1-a267-535f4e37bb17"}"#;
4103 let sm = SourceMap::from_json(json).unwrap();
4104 assert_eq!(
4105 sm.debug_id.as_deref(),
4106 Some("85314830-023f-4cf1-a267-535f4e37bb17")
4107 );
4108 }
4109
4110 #[test]
4111 fn parse_no_debug_id() {
4112 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
4113 let sm = SourceMap::from_json(json).unwrap();
4114 assert_eq!(sm.debug_id, None);
4115 }
4116
4117 #[test]
4118 fn debug_id_roundtrip() {
4119 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","debugId":"85314830-023f-4cf1-a267-535f4e37bb17"}"#;
4120 let sm = SourceMap::from_json(json).unwrap();
4121 let output = sm.to_json();
4122 assert!(output.contains(r#""debugId":"85314830-023f-4cf1-a267-535f4e37bb17""#));
4123 let sm2 = SourceMap::from_json(&output).unwrap();
4124 assert_eq!(sm.debug_id, sm2.debug_id);
4125 }
4126
4127 #[test]
4128 fn debug_id_not_in_json_when_absent() {
4129 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
4130 let sm = SourceMap::from_json(json).unwrap();
4131 let output = sm.to_json();
4132 assert!(!output.contains("debugId"));
4133 }
4134
4135 fn generate_test_sourcemap(lines: usize, segs_per_line: usize, num_sources: usize) -> String {
4137 let sources: Vec<String> = (0..num_sources)
4138 .map(|i| format!("src/file{i}.js"))
4139 .collect();
4140 let names: Vec<String> = (0..20).map(|i| format!("var{i}")).collect();
4141
4142 let mut mappings_parts = Vec::with_capacity(lines);
4143 let mut gen_col;
4144 let mut src: i64 = 0;
4145 let mut src_line: i64 = 0;
4146 let mut src_col: i64;
4147 let mut name: i64 = 0;
4148
4149 for _ in 0..lines {
4150 gen_col = 0i64;
4151 let mut line_parts = Vec::with_capacity(segs_per_line);
4152
4153 for s in 0..segs_per_line {
4154 let gc_delta = 2 + (s as i64 * 3) % 20;
4155 gen_col += gc_delta;
4156
4157 let src_delta = if s % 7 == 0 { 1 } else { 0 };
4158 src = (src + src_delta) % num_sources as i64;
4159
4160 src_line += 1;
4161 src_col = (s as i64 * 5 + 1) % 30;
4162
4163 let has_name = s % 4 == 0;
4164 if has_name {
4165 name = (name + 1) % names.len() as i64;
4166 }
4167
4168 let segment = if has_name {
4170 srcmap_codec::Segment::five(gen_col, src, src_line, src_col, name)
4171 } else {
4172 srcmap_codec::Segment::four(gen_col, src, src_line, src_col)
4173 };
4174
4175 line_parts.push(segment);
4176 }
4177
4178 mappings_parts.push(line_parts);
4179 }
4180
4181 let encoded = srcmap_codec::encode(&mappings_parts);
4182
4183 format!(
4184 r#"{{"version":3,"sources":[{}],"names":[{}],"mappings":"{}"}}"#,
4185 sources
4186 .iter()
4187 .map(|s| format!("\"{s}\""))
4188 .collect::<Vec<_>>()
4189 .join(","),
4190 names
4191 .iter()
4192 .map(|n| format!("\"{n}\""))
4193 .collect::<Vec<_>>()
4194 .join(","),
4195 encoded,
4196 )
4197 }
4198
4199 fn bias_map() -> &'static str {
4204 r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA,KAAK,KAAK"}"#
4206 }
4207
4208 #[test]
4209 fn original_position_glb_exact_match() {
4210 let sm = SourceMap::from_json(bias_map()).unwrap();
4211 let loc = sm
4212 .original_position_for_with_bias(0, 5, Bias::GreatestLowerBound)
4213 .unwrap();
4214 assert_eq!(loc.column, 5);
4215 }
4216
4217 #[test]
4218 fn original_position_glb_snaps_left() {
4219 let sm = SourceMap::from_json(bias_map()).unwrap();
4220 let loc = sm
4222 .original_position_for_with_bias(0, 7, Bias::GreatestLowerBound)
4223 .unwrap();
4224 assert_eq!(loc.column, 5);
4225 }
4226
4227 #[test]
4228 fn original_position_lub_exact_match() {
4229 let sm = SourceMap::from_json(bias_map()).unwrap();
4230 let loc = sm
4231 .original_position_for_with_bias(0, 5, Bias::LeastUpperBound)
4232 .unwrap();
4233 assert_eq!(loc.column, 5);
4234 }
4235
4236 #[test]
4237 fn original_position_lub_snaps_right() {
4238 let sm = SourceMap::from_json(bias_map()).unwrap();
4239 let loc = sm
4241 .original_position_for_with_bias(0, 3, Bias::LeastUpperBound)
4242 .unwrap();
4243 assert_eq!(loc.column, 5);
4244 }
4245
4246 #[test]
4247 fn original_position_lub_before_first() {
4248 let sm = SourceMap::from_json(bias_map()).unwrap();
4249 let loc = sm
4251 .original_position_for_with_bias(0, 0, Bias::LeastUpperBound)
4252 .unwrap();
4253 assert_eq!(loc.column, 0);
4254 }
4255
4256 #[test]
4257 fn original_position_lub_after_last() {
4258 let sm = SourceMap::from_json(bias_map()).unwrap();
4259 let loc = sm.original_position_for_with_bias(0, 15, Bias::LeastUpperBound);
4261 assert!(loc.is_none());
4262 }
4263
4264 #[test]
4265 fn original_position_glb_before_first() {
4266 let sm = SourceMap::from_json(bias_map()).unwrap();
4267 let loc = sm
4269 .original_position_for_with_bias(0, 0, Bias::GreatestLowerBound)
4270 .unwrap();
4271 assert_eq!(loc.column, 0);
4272 }
4273
4274 #[test]
4275 fn generated_position_lub() {
4276 let sm = SourceMap::from_json(bias_map()).unwrap();
4277 let loc = sm
4279 .generated_position_for_with_bias("input.js", 0, 3, Bias::LeastUpperBound)
4280 .unwrap();
4281 assert_eq!(loc.column, 5);
4282 }
4283
4284 #[test]
4285 fn generated_position_glb() {
4286 let sm = SourceMap::from_json(bias_map()).unwrap();
4287 let loc = sm
4289 .generated_position_for_with_bias("input.js", 0, 7, Bias::GreatestLowerBound)
4290 .unwrap();
4291 assert_eq!(loc.column, 5);
4292 }
4293
4294 #[test]
4297 fn map_range_basic() {
4298 let sm = SourceMap::from_json(bias_map()).unwrap();
4299 let range = sm.map_range(0, 0, 0, 10).unwrap();
4300 assert_eq!(range.source, 0);
4301 assert_eq!(range.original_start_line, 0);
4302 assert_eq!(range.original_start_column, 0);
4303 assert_eq!(range.original_end_line, 0);
4304 assert_eq!(range.original_end_column, 10);
4305 }
4306
4307 #[test]
4308 fn map_range_no_mapping() {
4309 let sm = SourceMap::from_json(bias_map()).unwrap();
4310 let range = sm.map_range(0, 0, 5, 0);
4312 assert!(range.is_none());
4313 }
4314
4315 #[test]
4316 fn map_range_different_sources() {
4317 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":[],"mappings":"AAAA;ACAA"}"#;
4319 let sm = SourceMap::from_json(json).unwrap();
4320 let range = sm.map_range(0, 0, 1, 0);
4322 assert!(range.is_none());
4323 }
4324
4325 #[test]
4328 fn extension_fields_preserved() {
4329 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","x_facebook_sources":[[{"names":["<global>"]}]],"x_google_linecount":42}"#;
4330 let sm = SourceMap::from_json(json).unwrap();
4331
4332 assert!(sm.extensions.contains_key("x_facebook_sources"));
4333 assert!(sm.extensions.contains_key("x_google_linecount"));
4334 assert_eq!(
4335 sm.extensions.get("x_google_linecount"),
4336 Some(&serde_json::json!(42))
4337 );
4338
4339 let output = sm.to_json();
4341 assert!(output.contains("x_facebook_sources"));
4342 assert!(output.contains("x_google_linecount"));
4343 }
4344
4345 #[test]
4346 fn x_google_ignorelist_fallback() {
4347 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":[],"mappings":"AAAA","x_google_ignoreList":[1]}"#;
4348 let sm = SourceMap::from_json(json).unwrap();
4349 assert_eq!(sm.ignore_list, vec![1]);
4350 }
4351
4352 #[test]
4353 fn ignorelist_takes_precedence_over_x_google() {
4354 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":[],"mappings":"AAAA","ignoreList":[0],"x_google_ignoreList":[1]}"#;
4355 let sm = SourceMap::from_json(json).unwrap();
4356 assert_eq!(sm.ignore_list, vec![0]);
4357 }
4358
4359 #[test]
4360 fn source_mapping_url_external() {
4361 let source = "var a = 1;\n//# sourceMappingURL=app.js.map\n";
4362 let result = parse_source_mapping_url(source).unwrap();
4363 assert_eq!(result, SourceMappingUrl::External("app.js.map".to_string()));
4364 }
4365
4366 #[test]
4367 fn source_mapping_url_inline() {
4368 let json = r#"{"version":3,"sources":[],"names":[],"mappings":""}"#;
4369 let b64 = base64_encode_simple(json);
4370 let source =
4371 format!("var a = 1;\n//# sourceMappingURL=data:application/json;base64,{b64}\n");
4372 match parse_source_mapping_url(&source).unwrap() {
4373 SourceMappingUrl::Inline(decoded) => {
4374 assert_eq!(decoded, json);
4375 }
4376 _ => panic!("expected inline"),
4377 }
4378 }
4379
4380 #[test]
4381 fn source_mapping_url_at_sign() {
4382 let source = "var a = 1;\n//@ sourceMappingURL=old-style.map";
4383 let result = parse_source_mapping_url(source).unwrap();
4384 assert_eq!(
4385 result,
4386 SourceMappingUrl::External("old-style.map".to_string())
4387 );
4388 }
4389
4390 #[test]
4391 fn source_mapping_url_css_comment() {
4392 let source = "body { }\n/*# sourceMappingURL=styles.css.map */";
4393 let result = parse_source_mapping_url(source).unwrap();
4394 assert_eq!(
4395 result,
4396 SourceMappingUrl::External("styles.css.map".to_string())
4397 );
4398 }
4399
4400 #[test]
4401 fn source_mapping_url_none() {
4402 let source = "var a = 1;";
4403 assert!(parse_source_mapping_url(source).is_none());
4404 }
4405
4406 #[test]
4407 fn exclude_content_option() {
4408 let json = r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":[],"mappings":"AAAA"}"#;
4409 let sm = SourceMap::from_json(json).unwrap();
4410
4411 let with_content = sm.to_json();
4412 assert!(with_content.contains("sourcesContent"));
4413
4414 let without_content = sm.to_json_with_options(true);
4415 assert!(!without_content.contains("sourcesContent"));
4416 }
4417
4418 #[test]
4419 fn validate_deep_clean_map() {
4420 let sm = SourceMap::from_json(simple_map()).unwrap();
4421 let warnings = validate_deep(&sm);
4422 assert!(warnings.is_empty(), "unexpected warnings: {warnings:?}");
4423 }
4424
4425 #[test]
4426 fn validate_deep_unreferenced_source() {
4427 let json =
4429 r#"{"version":3,"sources":["used.js","unused.js"],"names":[],"mappings":"AAAA"}"#;
4430 let sm = SourceMap::from_json(json).unwrap();
4431 let warnings = validate_deep(&sm);
4432 assert!(warnings.iter().any(|w| w.contains("unused.js")));
4433 }
4434
4435 #[test]
4438 fn from_parts_basic() {
4439 let mappings = vec![
4440 Mapping {
4441 generated_line: 0,
4442 generated_column: 0,
4443 source: 0,
4444 original_line: 0,
4445 original_column: 0,
4446 name: NO_NAME,
4447 is_range_mapping: false,
4448 },
4449 Mapping {
4450 generated_line: 1,
4451 generated_column: 4,
4452 source: 0,
4453 original_line: 1,
4454 original_column: 2,
4455 name: NO_NAME,
4456 is_range_mapping: false,
4457 },
4458 ];
4459
4460 let sm = SourceMap::from_parts(
4461 Some("out.js".to_string()),
4462 None,
4463 vec!["input.js".to_string()],
4464 vec![Some("var x = 1;".to_string())],
4465 vec![],
4466 mappings,
4467 vec![],
4468 None,
4469 None,
4470 );
4471
4472 assert_eq!(sm.line_count(), 2);
4473 assert_eq!(sm.mapping_count(), 2);
4474
4475 let loc = sm.original_position_for(0, 0).unwrap();
4476 assert_eq!(loc.source, 0);
4477 assert_eq!(loc.line, 0);
4478 assert_eq!(loc.column, 0);
4479
4480 let loc = sm.original_position_for(1, 4).unwrap();
4481 assert_eq!(loc.line, 1);
4482 assert_eq!(loc.column, 2);
4483 }
4484
4485 #[test]
4486 fn from_parts_empty() {
4487 let sm = SourceMap::from_parts(
4488 None,
4489 None,
4490 vec![],
4491 vec![],
4492 vec![],
4493 vec![],
4494 vec![],
4495 None,
4496 None,
4497 );
4498 assert_eq!(sm.line_count(), 0);
4499 assert_eq!(sm.mapping_count(), 0);
4500 assert!(sm.original_position_for(0, 0).is_none());
4501 }
4502
4503 #[test]
4504 fn from_parts_with_names() {
4505 let mappings = vec![Mapping {
4506 generated_line: 0,
4507 generated_column: 0,
4508 source: 0,
4509 original_line: 0,
4510 original_column: 0,
4511 name: 0,
4512 is_range_mapping: false,
4513 }];
4514
4515 let sm = SourceMap::from_parts(
4516 None,
4517 None,
4518 vec!["input.js".to_string()],
4519 vec![],
4520 vec!["myVar".to_string()],
4521 mappings,
4522 vec![],
4523 None,
4524 None,
4525 );
4526
4527 let loc = sm.original_position_for(0, 0).unwrap();
4528 assert_eq!(loc.name, Some(0));
4529 assert_eq!(sm.name(0), "myVar");
4530 }
4531
4532 #[test]
4533 fn from_parts_roundtrip_via_json() {
4534 let json = generate_test_sourcemap(50, 10, 3);
4535 let sm = SourceMap::from_json(&json).unwrap();
4536
4537 let sm2 = SourceMap::from_parts(
4538 sm.file.clone(),
4539 sm.source_root.clone(),
4540 sm.sources.clone(),
4541 sm.sources_content.clone(),
4542 sm.names.clone(),
4543 sm.all_mappings().to_vec(),
4544 sm.ignore_list.clone(),
4545 sm.debug_id.clone(),
4546 None,
4547 );
4548
4549 assert_eq!(sm2.mapping_count(), sm.mapping_count());
4550 assert_eq!(sm2.line_count(), sm.line_count());
4551
4552 for m in sm.all_mappings() {
4554 if m.source != NO_SOURCE {
4555 let a = sm.original_position_for(m.generated_line, m.generated_column);
4556 let b = sm2.original_position_for(m.generated_line, m.generated_column);
4557 match (a, b) {
4558 (Some(a), Some(b)) => {
4559 assert_eq!(a.source, b.source);
4560 assert_eq!(a.line, b.line);
4561 assert_eq!(a.column, b.column);
4562 }
4563 (None, None) => {}
4564 _ => panic!("mismatch at ({}, {})", m.generated_line, m.generated_column),
4565 }
4566 }
4567 }
4568 }
4569
4570 #[test]
4571 fn from_parts_reverse_lookup() {
4572 let mappings = vec![
4573 Mapping {
4574 generated_line: 0,
4575 generated_column: 0,
4576 source: 0,
4577 original_line: 10,
4578 original_column: 5,
4579 name: NO_NAME,
4580 is_range_mapping: false,
4581 },
4582 Mapping {
4583 generated_line: 1,
4584 generated_column: 8,
4585 source: 0,
4586 original_line: 20,
4587 original_column: 0,
4588 name: NO_NAME,
4589 is_range_mapping: false,
4590 },
4591 ];
4592
4593 let sm = SourceMap::from_parts(
4594 None,
4595 None,
4596 vec!["src.js".to_string()],
4597 vec![],
4598 vec![],
4599 mappings,
4600 vec![],
4601 None,
4602 None,
4603 );
4604
4605 let loc = sm.generated_position_for("src.js", 10, 5).unwrap();
4606 assert_eq!(loc.line, 0);
4607 assert_eq!(loc.column, 0);
4608
4609 let loc = sm.generated_position_for("src.js", 20, 0).unwrap();
4610 assert_eq!(loc.line, 1);
4611 assert_eq!(loc.column, 8);
4612 }
4613
4614 #[test]
4615 fn from_parts_sparse_lines() {
4616 let mappings = vec![
4617 Mapping {
4618 generated_line: 0,
4619 generated_column: 0,
4620 source: 0,
4621 original_line: 0,
4622 original_column: 0,
4623 name: NO_NAME,
4624 is_range_mapping: false,
4625 },
4626 Mapping {
4627 generated_line: 5,
4628 generated_column: 0,
4629 source: 0,
4630 original_line: 5,
4631 original_column: 0,
4632 name: NO_NAME,
4633 is_range_mapping: false,
4634 },
4635 ];
4636
4637 let sm = SourceMap::from_parts(
4638 None,
4639 None,
4640 vec!["src.js".to_string()],
4641 vec![],
4642 vec![],
4643 mappings,
4644 vec![],
4645 None,
4646 None,
4647 );
4648
4649 assert_eq!(sm.line_count(), 6);
4650 assert!(sm.original_position_for(0, 0).is_some());
4651 assert!(sm.original_position_for(2, 0).is_none());
4652 assert!(sm.original_position_for(5, 0).is_some());
4653 }
4654
4655 #[test]
4658 fn from_json_lines_basic() {
4659 let json = generate_test_sourcemap(10, 5, 2);
4660 let sm_full = SourceMap::from_json(&json).unwrap();
4661
4662 let sm_partial = SourceMap::from_json_lines(&json, 3, 7).unwrap();
4664
4665 for line in 3..7u32 {
4667 let full_mappings = sm_full.mappings_for_line(line);
4668 let partial_mappings = sm_partial.mappings_for_line(line);
4669 assert_eq!(
4670 full_mappings.len(),
4671 partial_mappings.len(),
4672 "line {line} mapping count mismatch"
4673 );
4674 for (a, b) in full_mappings.iter().zip(partial_mappings.iter()) {
4675 assert_eq!(a.generated_column, b.generated_column);
4676 assert_eq!(a.source, b.source);
4677 assert_eq!(a.original_line, b.original_line);
4678 assert_eq!(a.original_column, b.original_column);
4679 assert_eq!(a.name, b.name);
4680 }
4681 }
4682 }
4683
4684 #[test]
4685 fn from_json_lines_first_lines() {
4686 let json = generate_test_sourcemap(10, 5, 2);
4687 let sm_full = SourceMap::from_json(&json).unwrap();
4688 let sm_partial = SourceMap::from_json_lines(&json, 0, 3).unwrap();
4689
4690 for line in 0..3u32 {
4691 let full_mappings = sm_full.mappings_for_line(line);
4692 let partial_mappings = sm_partial.mappings_for_line(line);
4693 assert_eq!(full_mappings.len(), partial_mappings.len());
4694 }
4695 }
4696
4697 #[test]
4698 fn from_json_lines_last_lines() {
4699 let json = generate_test_sourcemap(10, 5, 2);
4700 let sm_full = SourceMap::from_json(&json).unwrap();
4701 let sm_partial = SourceMap::from_json_lines(&json, 7, 10).unwrap();
4702
4703 for line in 7..10u32 {
4704 let full_mappings = sm_full.mappings_for_line(line);
4705 let partial_mappings = sm_partial.mappings_for_line(line);
4706 assert_eq!(full_mappings.len(), partial_mappings.len(), "line {line}");
4707 }
4708 }
4709
4710 #[test]
4711 fn from_json_lines_empty_range() {
4712 let json = generate_test_sourcemap(10, 5, 2);
4713 let sm = SourceMap::from_json_lines(&json, 5, 5).unwrap();
4714 assert_eq!(sm.mapping_count(), 0);
4715 }
4716
4717 #[test]
4718 fn from_json_lines_beyond_end() {
4719 let json = generate_test_sourcemap(5, 3, 1);
4720 let sm = SourceMap::from_json_lines(&json, 3, 100).unwrap();
4722 assert!(sm.mapping_count() > 0);
4724 }
4725
4726 #[test]
4727 fn from_json_lines_single_line() {
4728 let json = generate_test_sourcemap(10, 5, 2);
4729 let sm_full = SourceMap::from_json(&json).unwrap();
4730 let sm_partial = SourceMap::from_json_lines(&json, 5, 6).unwrap();
4731
4732 let full_mappings = sm_full.mappings_for_line(5);
4733 let partial_mappings = sm_partial.mappings_for_line(5);
4734 assert_eq!(full_mappings.len(), partial_mappings.len());
4735 }
4736
4737 #[test]
4740 fn lazy_basic_lookup() {
4741 let json = r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA;AACA"}"#;
4742 let sm = LazySourceMap::from_json(json).unwrap();
4743
4744 assert_eq!(sm.line_count(), 2);
4745 assert_eq!(sm.sources, vec!["input.js"]);
4746
4747 let loc = sm.original_position_for(0, 0).unwrap();
4748 assert_eq!(sm.source(loc.source), "input.js");
4749 assert_eq!(loc.line, 0);
4750 assert_eq!(loc.column, 0);
4751 }
4752
4753 #[test]
4754 fn lazy_multiple_lines() {
4755 let json = generate_test_sourcemap(20, 5, 3);
4756 let sm_eager = SourceMap::from_json(&json).unwrap();
4757 let sm_lazy = LazySourceMap::from_json(&json).unwrap();
4758
4759 assert_eq!(sm_lazy.line_count(), sm_eager.line_count());
4760
4761 for m in sm_eager.all_mappings() {
4763 if m.source == NO_SOURCE {
4764 continue;
4765 }
4766 let eager_loc = sm_eager
4767 .original_position_for(m.generated_line, m.generated_column)
4768 .unwrap();
4769 let lazy_loc = sm_lazy
4770 .original_position_for(m.generated_line, m.generated_column)
4771 .unwrap();
4772 assert_eq!(eager_loc.source, lazy_loc.source);
4773 assert_eq!(eager_loc.line, lazy_loc.line);
4774 assert_eq!(eager_loc.column, lazy_loc.column);
4775 assert_eq!(eager_loc.name, lazy_loc.name);
4776 }
4777 }
4778
4779 #[test]
4780 fn lazy_empty_mappings() {
4781 let json = r#"{"version":3,"sources":[],"names":[],"mappings":""}"#;
4782 let sm = LazySourceMap::from_json(json).unwrap();
4783 assert_eq!(sm.line_count(), 0);
4784 assert!(sm.original_position_for(0, 0).is_none());
4785 }
4786
4787 #[test]
4788 fn lazy_empty_lines() {
4789 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;;;AACA"}"#;
4790 let sm = LazySourceMap::from_json(json).unwrap();
4791 assert_eq!(sm.line_count(), 4);
4792
4793 assert!(sm.original_position_for(0, 0).is_some());
4794 assert!(sm.original_position_for(1, 0).is_none());
4795 assert!(sm.original_position_for(2, 0).is_none());
4796 assert!(sm.original_position_for(3, 0).is_some());
4797 }
4798
4799 #[test]
4800 fn lazy_decode_line_caching() {
4801 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,KACA;AACA"}"#;
4802 let sm = LazySourceMap::from_json(json).unwrap();
4803
4804 let line0_a = sm.decode_line(0).unwrap();
4806 let line0_b = sm.decode_line(0).unwrap();
4808 assert_eq!(line0_a.len(), line0_b.len());
4809 assert_eq!(line0_a[0].generated_column, line0_b[0].generated_column);
4810 }
4811
4812 #[test]
4813 fn lazy_with_names() {
4814 let json = r#"{"version":3,"sources":["input.js"],"names":["foo","bar"],"mappings":"AAAAA,KACAC"}"#;
4815 let sm = LazySourceMap::from_json(json).unwrap();
4816
4817 let loc = sm.original_position_for(0, 0).unwrap();
4818 assert_eq!(loc.name, Some(0));
4819 assert_eq!(sm.name(0), "foo");
4820
4821 let loc = sm.original_position_for(0, 5).unwrap();
4822 assert_eq!(loc.name, Some(1));
4823 assert_eq!(sm.name(1), "bar");
4824 }
4825
4826 #[test]
4827 fn lazy_nonexistent_line() {
4828 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
4829 let sm = LazySourceMap::from_json(json).unwrap();
4830 assert!(sm.original_position_for(99, 0).is_none());
4831 let line = sm.decode_line(99).unwrap();
4832 assert!(line.is_empty());
4833 }
4834
4835 #[test]
4836 fn lazy_into_sourcemap() {
4837 let json = generate_test_sourcemap(20, 5, 3);
4838 let sm_eager = SourceMap::from_json(&json).unwrap();
4839 let sm_lazy = LazySourceMap::from_json(&json).unwrap();
4840 let sm_converted = sm_lazy.into_sourcemap().unwrap();
4841
4842 assert_eq!(sm_converted.mapping_count(), sm_eager.mapping_count());
4843 assert_eq!(sm_converted.line_count(), sm_eager.line_count());
4844
4845 for m in sm_eager.all_mappings() {
4847 let a = sm_eager.original_position_for(m.generated_line, m.generated_column);
4848 let b = sm_converted.original_position_for(m.generated_line, m.generated_column);
4849 match (a, b) {
4850 (Some(a), Some(b)) => {
4851 assert_eq!(a.source, b.source);
4852 assert_eq!(a.line, b.line);
4853 assert_eq!(a.column, b.column);
4854 }
4855 (None, None) => {}
4856 _ => panic!("mismatch at ({}, {})", m.generated_line, m.generated_column),
4857 }
4858 }
4859 }
4860
4861 #[test]
4862 fn lazy_source_index_lookup() {
4863 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":[],"mappings":"AAAA;ACAA"}"#;
4864 let sm = LazySourceMap::from_json(json).unwrap();
4865 assert_eq!(sm.source_index("a.js"), Some(0));
4866 assert_eq!(sm.source_index("b.js"), Some(1));
4867 assert_eq!(sm.source_index("c.js"), None);
4868 }
4869
4870 #[test]
4871 fn lazy_mappings_for_line() {
4872 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,KACA;AACA"}"#;
4873 let sm = LazySourceMap::from_json(json).unwrap();
4874
4875 let line0 = sm.mappings_for_line(0);
4876 assert_eq!(line0.len(), 2);
4877
4878 let line1 = sm.mappings_for_line(1);
4879 assert_eq!(line1.len(), 1);
4880
4881 let line99 = sm.mappings_for_line(99);
4882 assert!(line99.is_empty());
4883 }
4884
4885 #[test]
4886 fn lazy_large_map_selective_decode() {
4887 let json = generate_test_sourcemap(100, 10, 5);
4889 let sm_eager = SourceMap::from_json(&json).unwrap();
4890 let sm_lazy = LazySourceMap::from_json(&json).unwrap();
4891
4892 for line in [50, 75] {
4894 let eager_mappings = sm_eager.mappings_for_line(line);
4895 let lazy_mappings = sm_lazy.mappings_for_line(line);
4896 assert_eq!(
4897 eager_mappings.len(),
4898 lazy_mappings.len(),
4899 "line {line} count mismatch"
4900 );
4901 for (a, b) in eager_mappings.iter().zip(lazy_mappings.iter()) {
4902 assert_eq!(a.generated_column, b.generated_column);
4903 assert_eq!(a.source, b.source);
4904 assert_eq!(a.original_line, b.original_line);
4905 assert_eq!(a.original_column, b.original_column);
4906 assert_eq!(a.name, b.name);
4907 }
4908 }
4909 }
4910
4911 #[test]
4912 fn lazy_single_field_segments() {
4913 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A,KAAAA"}"#;
4914 let sm = LazySourceMap::from_json(json).unwrap();
4915
4916 assert!(sm.original_position_for(0, 0).is_none());
4918 let loc = sm.original_position_for(0, 5).unwrap();
4920 assert_eq!(loc.source, 0);
4921 }
4922
4923 #[test]
4926 fn parse_error_display_vlq() {
4927 let err = ParseError::Vlq(srcmap_codec::DecodeError::UnexpectedEof { offset: 3 });
4928 assert!(err.to_string().contains("VLQ decode error"));
4929 }
4930
4931 #[test]
4932 fn parse_error_display_scopes() {
4933 let err = ParseError::Scopes(srcmap_scopes::ScopesError::UnclosedScope);
4934 assert!(err.to_string().contains("scopes decode error"));
4935 }
4936
4937 #[test]
4938 fn indexed_map_with_names_in_sections() {
4939 let json = r#"{
4940 "version": 3,
4941 "sections": [
4942 {
4943 "offset": {"line": 0, "column": 0},
4944 "map": {
4945 "version": 3,
4946 "sources": ["a.js"],
4947 "names": ["foo"],
4948 "mappings": "AAAAA"
4949 }
4950 },
4951 {
4952 "offset": {"line": 1, "column": 0},
4953 "map": {
4954 "version": 3,
4955 "sources": ["a.js"],
4956 "names": ["foo"],
4957 "mappings": "AAAAA"
4958 }
4959 }
4960 ]
4961 }"#;
4962 let sm = SourceMap::from_json(json).unwrap();
4963 assert_eq!(sm.sources.len(), 1);
4965 assert_eq!(sm.names.len(), 1);
4966 }
4967
4968 #[test]
4969 fn indexed_map_with_ignore_list() {
4970 let json = r#"{
4971 "version": 3,
4972 "sections": [
4973 {
4974 "offset": {"line": 0, "column": 0},
4975 "map": {
4976 "version": 3,
4977 "sources": ["vendor.js"],
4978 "names": [],
4979 "mappings": "AAAA",
4980 "ignoreList": [0]
4981 }
4982 }
4983 ]
4984 }"#;
4985 let sm = SourceMap::from_json(json).unwrap();
4986 assert_eq!(sm.ignore_list, vec![0]);
4987 }
4988
4989 #[test]
4990 fn indexed_map_with_generated_only_segment() {
4991 let json = r#"{
4993 "version": 3,
4994 "sections": [
4995 {
4996 "offset": {"line": 0, "column": 0},
4997 "map": {
4998 "version": 3,
4999 "sources": ["a.js"],
5000 "names": [],
5001 "mappings": "A,AAAA"
5002 }
5003 }
5004 ]
5005 }"#;
5006 let sm = SourceMap::from_json(json).unwrap();
5007 assert!(sm.mapping_count() >= 1);
5008 }
5009
5010 #[test]
5011 fn indexed_map_empty_mappings() {
5012 let json = r#"{
5013 "version": 3,
5014 "sections": [
5015 {
5016 "offset": {"line": 0, "column": 0},
5017 "map": {
5018 "version": 3,
5019 "sources": [],
5020 "names": [],
5021 "mappings": ""
5022 }
5023 }
5024 ]
5025 }"#;
5026 let sm = SourceMap::from_json(json).unwrap();
5027 assert_eq!(sm.mapping_count(), 0);
5028 }
5029
5030 #[test]
5031 fn generated_position_glb_exact_match() {
5032 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAE,OAAO"}"#;
5033 let sm = SourceMap::from_json(json).unwrap();
5034
5035 let loc = sm.generated_position_for_with_bias("a.js", 0, 0, Bias::GreatestLowerBound);
5036 assert!(loc.is_some());
5037 assert_eq!(loc.unwrap().column, 0);
5038 }
5039
5040 #[test]
5041 fn generated_position_glb_no_exact_match() {
5042 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAE"}"#;
5043 let sm = SourceMap::from_json(json).unwrap();
5044
5045 let loc = sm.generated_position_for_with_bias("a.js", 0, 0, Bias::GreatestLowerBound);
5047 assert!(loc.is_some());
5048 }
5049
5050 #[test]
5051 fn generated_position_glb_wrong_source() {
5052 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":[],"mappings":"AAAA,KCCA"}"#;
5053 let sm = SourceMap::from_json(json).unwrap();
5054
5055 let loc = sm.generated_position_for_with_bias("b.js", 5, 0, Bias::GreatestLowerBound);
5057 if let Some(l) = loc {
5060 assert_eq!(l.line, 0);
5062 }
5063 }
5064
5065 #[test]
5066 fn generated_position_lub_wrong_source() {
5067 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
5068 let sm = SourceMap::from_json(json).unwrap();
5069
5070 let loc =
5072 sm.generated_position_for_with_bias("nonexistent.js", 0, 0, Bias::LeastUpperBound);
5073 assert!(loc.is_none());
5074 }
5075
5076 #[test]
5077 fn to_json_with_ignore_list() {
5078 let json = r#"{"version":3,"sources":["vendor.js"],"names":[],"mappings":"AAAA","ignoreList":[0]}"#;
5079 let sm = SourceMap::from_json(json).unwrap();
5080 let output = sm.to_json();
5081 assert!(output.contains("\"ignoreList\":[0]"));
5082 }
5083
5084 #[test]
5085 fn to_json_with_extensions() {
5086 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","x_custom":"test_value"}"#;
5087 let sm = SourceMap::from_json(json).unwrap();
5088 let output = sm.to_json();
5089 assert!(output.contains("x_custom"));
5090 assert!(output.contains("test_value"));
5091 }
5092
5093 #[test]
5094 fn from_parts_empty_mappings() {
5095 let sm = SourceMap::from_parts(
5096 None,
5097 None,
5098 vec!["a.js".to_string()],
5099 vec![Some("content".to_string())],
5100 vec![],
5101 vec![],
5102 vec![],
5103 None,
5104 None,
5105 );
5106 assert_eq!(sm.mapping_count(), 0);
5107 assert_eq!(sm.sources, vec!["a.js"]);
5108 }
5109
5110 #[test]
5111 fn from_vlq_basic() {
5112 let sm = SourceMap::from_vlq(
5113 "AAAA;AACA",
5114 vec!["a.js".to_string()],
5115 vec![],
5116 Some("out.js".to_string()),
5117 None,
5118 vec![Some("content".to_string())],
5119 vec![],
5120 None,
5121 )
5122 .unwrap();
5123
5124 assert_eq!(sm.file.as_deref(), Some("out.js"));
5125 assert_eq!(sm.sources, vec!["a.js"]);
5126 let loc = sm.original_position_for(0, 0).unwrap();
5127 assert_eq!(sm.source(loc.source), "a.js");
5128 assert_eq!(loc.line, 0);
5129 }
5130
5131 #[test]
5132 fn from_json_lines_basic_coverage() {
5133 let json =
5134 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA"}"#;
5135 let sm = SourceMap::from_json_lines(json, 1, 3).unwrap();
5136 assert!(sm.original_position_for(1, 0).is_some());
5138 assert!(sm.original_position_for(2, 0).is_some());
5139 }
5140
5141 #[test]
5142 fn from_json_lines_with_source_root() {
5143 let json = r#"{"version":3,"sourceRoot":"src/","sources":["a.js"],"names":[],"mappings":"AAAA;AACA"}"#;
5144 let sm = SourceMap::from_json_lines(json, 0, 2).unwrap();
5145 assert_eq!(sm.sources[0], "src/a.js");
5146 }
5147
5148 #[test]
5149 fn from_json_lines_with_null_source() {
5150 let json = r#"{"version":3,"sources":[null,"a.js"],"names":[],"mappings":"AAAA,KCCA"}"#;
5151 let sm = SourceMap::from_json_lines(json, 0, 1).unwrap();
5152 assert_eq!(sm.sources.len(), 2);
5153 }
5154
5155 #[test]
5156 fn json_escaping_special_chars_sourcemap() {
5157 let json = r#"{"version":3,"sources":["path/with\nnewline.js"],"sourcesContent":["line1\r\nline2\t\"quoted\"\\\u0001"],"names":[],"mappings":"AAAA"}"#;
5160 let sm = SourceMap::from_json(json).unwrap();
5161 let output = sm.to_json();
5163 let sm2 = SourceMap::from_json(&output).unwrap();
5164 assert_eq!(sm.sources[0], sm2.sources[0]);
5165 assert_eq!(sm.sources_content[0], sm2.sources_content[0]);
5166 }
5167
5168 #[test]
5169 fn to_json_exclude_content() {
5170 let json = r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":[],"mappings":"AAAA"}"#;
5171 let sm = SourceMap::from_json(json).unwrap();
5172 let output = sm.to_json_with_options(true);
5173 assert!(!output.contains("sourcesContent"));
5174 let output_with = sm.to_json_with_options(false);
5175 assert!(output_with.contains("sourcesContent"));
5176 }
5177
5178 #[test]
5179 fn encode_mappings_with_name() {
5180 let json = r#"{"version":3,"sources":["a.js"],"names":["foo"],"mappings":"AAAAA"}"#;
5182 let sm = SourceMap::from_json(json).unwrap();
5183 let encoded = sm.encode_mappings();
5184 assert_eq!(encoded, "AAAAA");
5185 }
5186
5187 #[test]
5188 fn encode_mappings_generated_only() {
5189 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A,AAAA"}"#;
5191 let sm = SourceMap::from_json(json).unwrap();
5192 let encoded = sm.encode_mappings();
5193 let roundtrip = SourceMap::from_json(&format!(
5194 r#"{{"version":3,"sources":["a.js"],"names":[],"mappings":"{}"}}"#,
5195 encoded
5196 ))
5197 .unwrap();
5198 assert_eq!(roundtrip.mapping_count(), sm.mapping_count());
5199 }
5200
5201 #[test]
5202 fn map_range_single_result() {
5203 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAC,OAAO"}"#;
5204 let sm = SourceMap::from_json(json).unwrap();
5205 let result = sm.map_range(0, 0, 0, 1);
5207 assert!(result.is_some());
5208 let range = result.unwrap();
5209 assert_eq!(range.source, 0);
5210 }
5211
5212 #[test]
5213 fn scopes_in_from_json() {
5214 let info = srcmap_scopes::ScopeInfo {
5216 scopes: vec![Some(srcmap_scopes::OriginalScope {
5217 start: srcmap_scopes::Position { line: 0, column: 0 },
5218 end: srcmap_scopes::Position { line: 5, column: 0 },
5219 name: None,
5220 kind: None,
5221 is_stack_frame: false,
5222 variables: vec![],
5223 children: vec![],
5224 })],
5225 ranges: vec![],
5226 };
5227 let mut names = vec![];
5228 let scopes_str = srcmap_scopes::encode_scopes(&info, &mut names);
5229
5230 let json = format!(
5231 r#"{{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","scopes":"{scopes_str}"}}"#
5232 );
5233
5234 let sm = SourceMap::from_json(&json).unwrap();
5235 assert!(sm.scopes.is_some());
5236 }
5237
5238 #[test]
5239 fn from_json_lines_with_scopes() {
5240 let info = srcmap_scopes::ScopeInfo {
5241 scopes: vec![Some(srcmap_scopes::OriginalScope {
5242 start: srcmap_scopes::Position { line: 0, column: 0 },
5243 end: srcmap_scopes::Position { line: 5, column: 0 },
5244 name: None,
5245 kind: None,
5246 is_stack_frame: false,
5247 variables: vec![],
5248 children: vec![],
5249 })],
5250 ranges: vec![],
5251 };
5252 let mut names = vec![];
5253 let scopes_str = srcmap_scopes::encode_scopes(&info, &mut names);
5254 let json = format!(
5255 r#"{{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;AACA","scopes":"{scopes_str}"}}"#
5256 );
5257 let sm = SourceMap::from_json_lines(&json, 0, 2).unwrap();
5258 assert!(sm.scopes.is_some());
5259 }
5260
5261 #[test]
5262 fn from_json_lines_with_extensions() {
5263 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","x_custom":"val","not_x":"skip"}"#;
5264 let sm = SourceMap::from_json_lines(json, 0, 1).unwrap();
5265 assert!(sm.extensions.contains_key("x_custom"));
5266 assert!(!sm.extensions.contains_key("not_x"));
5267 }
5268
5269 #[test]
5270 fn lazy_sourcemap_version_error() {
5271 let json = r#"{"version":2,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
5272 let err = LazySourceMap::from_json(json).unwrap_err();
5273 assert!(matches!(err, ParseError::InvalidVersion(2)));
5274 }
5275
5276 #[test]
5277 fn lazy_sourcemap_with_source_root() {
5278 let json =
5279 r#"{"version":3,"sourceRoot":"src/","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
5280 let sm = LazySourceMap::from_json(json).unwrap();
5281 assert_eq!(sm.sources[0], "src/a.js");
5282 }
5283
5284 #[test]
5285 fn lazy_sourcemap_with_ignore_list_and_extensions() {
5286 let json = r#"{"version":3,"sources":["v.js"],"names":[],"mappings":"AAAA","ignoreList":[0],"x_custom":"val","not_x":"skip"}"#;
5287 let sm = LazySourceMap::from_json(json).unwrap();
5288 assert_eq!(sm.ignore_list, vec![0]);
5289 assert!(sm.extensions.contains_key("x_custom"));
5290 assert!(!sm.extensions.contains_key("not_x"));
5291 }
5292
5293 #[test]
5294 fn lazy_sourcemap_with_scopes() {
5295 let info = srcmap_scopes::ScopeInfo {
5296 scopes: vec![Some(srcmap_scopes::OriginalScope {
5297 start: srcmap_scopes::Position { line: 0, column: 0 },
5298 end: srcmap_scopes::Position { line: 5, column: 0 },
5299 name: None,
5300 kind: None,
5301 is_stack_frame: false,
5302 variables: vec![],
5303 children: vec![],
5304 })],
5305 ranges: vec![],
5306 };
5307 let mut names = vec![];
5308 let scopes_str = srcmap_scopes::encode_scopes(&info, &mut names);
5309 let json = format!(
5310 r#"{{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","scopes":"{scopes_str}"}}"#
5311 );
5312 let sm = LazySourceMap::from_json(&json).unwrap();
5313 assert!(sm.scopes.is_some());
5314 }
5315
5316 #[test]
5317 fn lazy_sourcemap_null_source() {
5318 let json = r#"{"version":3,"sources":[null,"a.js"],"names":[],"mappings":"AAAA,KCCA"}"#;
5319 let sm = LazySourceMap::from_json(json).unwrap();
5320 assert_eq!(sm.sources.len(), 2);
5321 }
5322
5323 #[test]
5324 fn indexed_map_multi_line_section() {
5325 let json = r#"{
5327 "version": 3,
5328 "sections": [
5329 {
5330 "offset": {"line": 0, "column": 0},
5331 "map": {
5332 "version": 3,
5333 "sources": ["a.js"],
5334 "names": [],
5335 "mappings": "AAAA;AACA;AACA"
5336 }
5337 },
5338 {
5339 "offset": {"line": 5, "column": 0},
5340 "map": {
5341 "version": 3,
5342 "sources": ["b.js"],
5343 "names": [],
5344 "mappings": "AAAA;AACA"
5345 }
5346 }
5347 ]
5348 }"#;
5349 let sm = SourceMap::from_json(json).unwrap();
5350 assert!(sm.original_position_for(0, 0).is_some());
5351 assert!(sm.original_position_for(5, 0).is_some());
5352 }
5353
5354 #[test]
5355 fn source_mapping_url_extraction() {
5356 let input = "var x = 1;\n//# sourceMappingURL=bundle.js.map";
5358 let url = parse_source_mapping_url(input);
5359 assert!(matches!(url, Some(SourceMappingUrl::External(ref s)) if s == "bundle.js.map"));
5360
5361 let input = "body { }\n/*# sourceMappingURL=style.css.map */";
5363 let url = parse_source_mapping_url(input);
5364 assert!(matches!(url, Some(SourceMappingUrl::External(ref s)) if s == "style.css.map"));
5365
5366 let input = "var x;\n//@ sourceMappingURL=old-style.map";
5368 let url = parse_source_mapping_url(input);
5369 assert!(matches!(url, Some(SourceMappingUrl::External(ref s)) if s == "old-style.map"));
5370
5371 let input = "body{}\n/*@ sourceMappingURL=old-css.map */";
5373 let url = parse_source_mapping_url(input);
5374 assert!(matches!(url, Some(SourceMappingUrl::External(ref s)) if s == "old-css.map"));
5375
5376 let input = "var x = 1;";
5378 let url = parse_source_mapping_url(input);
5379 assert!(url.is_none());
5380
5381 let input = "//# sourceMappingURL=";
5383 let url = parse_source_mapping_url(input);
5384 assert!(url.is_none());
5385
5386 let map_json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
5388 let encoded = base64_encode_simple(map_json);
5389 let input = format!("var x;\n//# sourceMappingURL=data:application/json;base64,{encoded}");
5390 let url = parse_source_mapping_url(&input);
5391 assert!(matches!(url, Some(SourceMappingUrl::Inline(_))));
5392 }
5393
5394 #[test]
5395 fn validate_deep_unreferenced_coverage() {
5396 let sm = SourceMap::from_parts(
5398 None,
5399 None,
5400 vec!["used.js".to_string(), "unused.js".to_string()],
5401 vec![None, None],
5402 vec![],
5403 vec![Mapping {
5404 generated_line: 0,
5405 generated_column: 0,
5406 source: 0,
5407 original_line: 0,
5408 original_column: 0,
5409 name: NO_NAME,
5410 is_range_mapping: false,
5411 }],
5412 vec![],
5413 None,
5414 None,
5415 );
5416 let warnings = validate_deep(&sm);
5417 assert!(warnings.iter().any(|w| w.contains("unreferenced")));
5418 }
5419
5420 #[test]
5421 fn from_json_lines_generated_only_segment() {
5422 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A,AAAA;AACA"}"#;
5424 let sm = SourceMap::from_json_lines(json, 0, 2).unwrap();
5425 assert!(sm.mapping_count() >= 2);
5426 }
5427
5428 #[test]
5429 fn from_json_lines_with_names() {
5430 let json = r#"{"version":3,"sources":["a.js"],"names":["foo"],"mappings":"AAAAA;AACAA"}"#;
5431 let sm = SourceMap::from_json_lines(json, 0, 2).unwrap();
5432 let loc = sm.original_position_for(0, 0).unwrap();
5433 assert_eq!(loc.name, Some(0));
5434 }
5435
5436 #[test]
5437 fn from_parts_with_line_gap() {
5438 let sm = SourceMap::from_parts(
5440 None,
5441 None,
5442 vec!["a.js".to_string()],
5443 vec![None],
5444 vec![],
5445 vec![
5446 Mapping {
5447 generated_line: 0,
5448 generated_column: 0,
5449 source: 0,
5450 original_line: 0,
5451 original_column: 0,
5452 name: NO_NAME,
5453 is_range_mapping: false,
5454 },
5455 Mapping {
5456 generated_line: 5,
5457 generated_column: 0,
5458 source: 0,
5459 original_line: 5,
5460 original_column: 0,
5461 name: NO_NAME,
5462 is_range_mapping: false,
5463 },
5464 ],
5465 vec![],
5466 None,
5467 None,
5468 );
5469 assert!(sm.original_position_for(0, 0).is_some());
5470 assert!(sm.original_position_for(5, 0).is_some());
5471 assert!(sm.original_position_for(1, 0).is_none());
5473 }
5474
5475 #[test]
5476 fn lazy_decode_line_with_names_and_generated_only() {
5477 let json = r#"{"version":3,"sources":["a.js"],"names":["fn"],"mappings":"A,AAAAC"}"#;
5479 let sm = LazySourceMap::from_json(json).unwrap();
5480 let line = sm.decode_line(0).unwrap();
5481 assert!(line.len() >= 2);
5482 assert_eq!(line[0].source, NO_SOURCE);
5484 assert_ne!(line[1].name, NO_NAME);
5486 }
5487
5488 #[test]
5489 fn generated_position_glb_source_mismatch() {
5490 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":[],"mappings":"AAAA,KCCA"}"#;
5492 let sm = SourceMap::from_json(json).unwrap();
5493
5494 let loc = sm.generated_position_for_with_bias("a.js", 100, 0, Bias::LeastUpperBound);
5496 assert!(loc.is_none());
5497
5498 let loc = sm.generated_position_for_with_bias("b.js", 0, 0, Bias::GreatestLowerBound);
5502 assert!(loc.is_none());
5503
5504 let loc = sm.generated_position_for_with_bias("b.js", 1, 0, Bias::GreatestLowerBound);
5506 assert!(loc.is_some());
5507
5508 let loc = sm.generated_position_for_with_bias("b.js", 99, 0, Bias::LeastUpperBound);
5510 assert!(loc.is_none());
5511 }
5512
5513 #[test]
5516 fn from_json_invalid_scopes_error() {
5517 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","scopes":"!!invalid!!"}"#;
5519 let err = SourceMap::from_json(json).unwrap_err();
5520 assert!(matches!(err, ParseError::Scopes(_)));
5521 }
5522
5523 #[test]
5524 fn lazy_from_json_invalid_scopes_error() {
5525 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","scopes":"!!invalid!!"}"#;
5526 let err = LazySourceMap::from_json(json).unwrap_err();
5527 assert!(matches!(err, ParseError::Scopes(_)));
5528 }
5529
5530 #[test]
5531 fn from_json_lines_invalid_scopes_error() {
5532 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","scopes":"!!invalid!!"}"#;
5533 let err = SourceMap::from_json_lines(json, 0, 1).unwrap_err();
5534 assert!(matches!(err, ParseError::Scopes(_)));
5535 }
5536
5537 #[test]
5538 fn from_json_lines_invalid_version() {
5539 let json = r#"{"version":2,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
5540 let err = SourceMap::from_json_lines(json, 0, 1).unwrap_err();
5541 assert!(matches!(err, ParseError::InvalidVersion(2)));
5542 }
5543
5544 #[test]
5545 fn indexed_map_with_ignore_list_remapped() {
5546 let json = r#"{
5548 "version": 3,
5549 "sections": [{
5550 "offset": {"line": 0, "column": 0},
5551 "map": {
5552 "version": 3,
5553 "sources": ["a.js", "b.js"],
5554 "names": [],
5555 "mappings": "AAAA;ACAA",
5556 "ignoreList": [1]
5557 }
5558 }, {
5559 "offset": {"line": 5, "column": 0},
5560 "map": {
5561 "version": 3,
5562 "sources": ["b.js", "c.js"],
5563 "names": [],
5564 "mappings": "AAAA;ACAA",
5565 "ignoreList": [0]
5566 }
5567 }]
5568 }"#;
5569 let sm = SourceMap::from_json(json).unwrap();
5570 assert!(!sm.ignore_list.is_empty());
5572 }
5573
5574 #[test]
5575 fn to_json_with_debug_id() {
5576 let sm = SourceMap::from_parts(
5577 Some("out.js".to_string()),
5578 None,
5579 vec!["a.js".to_string()],
5580 vec![None],
5581 vec![],
5582 vec![Mapping {
5583 generated_line: 0,
5584 generated_column: 0,
5585 source: 0,
5586 original_line: 0,
5587 original_column: 0,
5588 name: NO_NAME,
5589 is_range_mapping: false,
5590 }],
5591 vec![],
5592 Some("abc-123".to_string()),
5593 None,
5594 );
5595 let json = sm.to_json();
5596 assert!(json.contains(r#""debugId":"abc-123""#));
5597 }
5598
5599 #[test]
5600 fn to_json_with_ignore_list_and_extensions() {
5601 let mut sm = SourceMap::from_parts(
5602 None,
5603 None,
5604 vec!["a.js".to_string(), "b.js".to_string()],
5605 vec![None, None],
5606 vec![],
5607 vec![Mapping {
5608 generated_line: 0,
5609 generated_column: 0,
5610 source: 0,
5611 original_line: 0,
5612 original_column: 0,
5613 name: NO_NAME,
5614 is_range_mapping: false,
5615 }],
5616 vec![1],
5617 None,
5618 None,
5619 );
5620 sm.extensions
5621 .insert("x_test".to_string(), serde_json::json!(42));
5622 let json = sm.to_json();
5623 assert!(json.contains("\"ignoreList\":[1]"));
5624 assert!(json.contains("\"x_test\":42"));
5625 }
5626
5627 #[test]
5628 fn from_vlq_with_all_options() {
5629 let sm = SourceMap::from_vlq(
5630 "AAAA;AACA",
5631 vec!["a.js".to_string()],
5632 vec![],
5633 Some("out.js".to_string()),
5634 Some("src/".to_string()),
5635 vec![Some("content".to_string())],
5636 vec![0],
5637 Some("debug-123".to_string()),
5638 )
5639 .unwrap();
5640 assert_eq!(sm.source(0), "a.js");
5641 assert!(sm.original_position_for(0, 0).is_some());
5642 assert!(sm.original_position_for(1, 0).is_some());
5643 }
5644
5645 #[test]
5646 fn lazy_into_sourcemap_roundtrip() {
5647 let json = r#"{"version":3,"sources":["a.js"],"names":["x"],"mappings":"AAAAA;AACAA"}"#;
5648 let lazy = LazySourceMap::from_json(json).unwrap();
5649 let sm = lazy.into_sourcemap().unwrap();
5650 assert!(sm.original_position_for(0, 0).is_some());
5651 assert!(sm.original_position_for(1, 0).is_some());
5652 assert_eq!(sm.name(0), "x");
5653 }
5654
5655 #[test]
5656 fn lazy_original_position_for_no_match() {
5657 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"KAAA"}"#;
5659 let sm = LazySourceMap::from_json(json).unwrap();
5660 assert!(sm.original_position_for(0, 0).is_none());
5662 }
5663
5664 #[test]
5665 fn lazy_original_position_for_empty_line() {
5666 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":";AAAA"}"#;
5667 let sm = LazySourceMap::from_json(json).unwrap();
5668 assert!(sm.original_position_for(0, 0).is_none());
5670 assert!(sm.original_position_for(1, 0).is_some());
5672 }
5673
5674 #[test]
5675 fn lazy_original_position_generated_only() {
5676 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A;AAAA"}"#;
5678 let sm = LazySourceMap::from_json(json).unwrap();
5679 assert!(sm.original_position_for(0, 0).is_none());
5681 assert!(sm.original_position_for(1, 0).is_some());
5683 }
5684
5685 #[test]
5686 fn from_json_lines_null_source() {
5687 let json = r#"{"version":3,"sources":[null,"a.js"],"names":[],"mappings":"ACAA"}"#;
5688 let sm = SourceMap::from_json_lines(json, 0, 1).unwrap();
5689 assert!(sm.mapping_count() >= 1);
5690 }
5691
5692 #[test]
5693 fn from_json_lines_with_source_root_prefix() {
5694 let json =
5695 r#"{"version":3,"sourceRoot":"lib/","sources":["b.js"],"names":[],"mappings":"AAAA"}"#;
5696 let sm = SourceMap::from_json_lines(json, 0, 1).unwrap();
5697 assert_eq!(sm.source(0), "lib/b.js");
5698 }
5699
5700 #[test]
5701 fn generated_position_for_glb_idx_zero() {
5702 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAKA"}"#;
5706 let sm = SourceMap::from_json(json).unwrap();
5707 let loc = sm.generated_position_for_with_bias("a.js", 0, 0, Bias::GreatestLowerBound);
5708 assert!(loc.is_none());
5709 }
5710
5711 #[test]
5712 fn from_json_lines_with_ignore_list() {
5713 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":[],"mappings":"AAAA;ACAA","ignoreList":[1]}"#;
5714 let sm = SourceMap::from_json_lines(json, 0, 2).unwrap();
5715 assert_eq!(sm.ignore_list, vec![1]);
5716 }
5717
5718 #[test]
5719 fn validate_deep_out_of_order_mappings() {
5720 let sm = SourceMap::from_parts(
5722 None,
5723 None,
5724 vec!["a.js".to_string()],
5725 vec![None],
5726 vec![],
5727 vec![
5728 Mapping {
5729 generated_line: 1,
5730 generated_column: 0,
5731 source: 0,
5732 original_line: 0,
5733 original_column: 0,
5734 name: NO_NAME,
5735 is_range_mapping: false,
5736 },
5737 Mapping {
5738 generated_line: 0,
5739 generated_column: 0,
5740 source: 0,
5741 original_line: 0,
5742 original_column: 0,
5743 name: NO_NAME,
5744 is_range_mapping: false,
5745 },
5746 ],
5747 vec![],
5748 None,
5749 None,
5750 );
5751 let warnings = validate_deep(&sm);
5752 assert!(warnings.iter().any(|w| w.contains("out of order")));
5753 }
5754
5755 #[test]
5756 fn validate_deep_out_of_bounds_source() {
5757 let sm = SourceMap::from_parts(
5758 None,
5759 None,
5760 vec!["a.js".to_string()],
5761 vec![None],
5762 vec![],
5763 vec![Mapping {
5764 generated_line: 0,
5765 generated_column: 0,
5766 source: 5,
5767 original_line: 0,
5768 original_column: 0,
5769 name: NO_NAME,
5770 is_range_mapping: false,
5771 }],
5772 vec![],
5773 None,
5774 None,
5775 );
5776 let warnings = validate_deep(&sm);
5777 assert!(
5778 warnings
5779 .iter()
5780 .any(|w| w.contains("source index") && w.contains("out of bounds"))
5781 );
5782 }
5783
5784 #[test]
5785 fn validate_deep_out_of_bounds_name() {
5786 let sm = SourceMap::from_parts(
5787 None,
5788 None,
5789 vec!["a.js".to_string()],
5790 vec![None],
5791 vec!["foo".to_string()],
5792 vec![Mapping {
5793 generated_line: 0,
5794 generated_column: 0,
5795 source: 0,
5796 original_line: 0,
5797 original_column: 0,
5798 name: 5,
5799 is_range_mapping: false,
5800 }],
5801 vec![],
5802 None,
5803 None,
5804 );
5805 let warnings = validate_deep(&sm);
5806 assert!(
5807 warnings
5808 .iter()
5809 .any(|w| w.contains("name index") && w.contains("out of bounds"))
5810 );
5811 }
5812
5813 #[test]
5814 fn validate_deep_out_of_bounds_ignore_list() {
5815 let sm = SourceMap::from_parts(
5816 None,
5817 None,
5818 vec!["a.js".to_string()],
5819 vec![None],
5820 vec![],
5821 vec![Mapping {
5822 generated_line: 0,
5823 generated_column: 0,
5824 source: 0,
5825 original_line: 0,
5826 original_column: 0,
5827 name: NO_NAME,
5828 is_range_mapping: false,
5829 }],
5830 vec![10],
5831 None,
5832 None,
5833 );
5834 let warnings = validate_deep(&sm);
5835 assert!(
5836 warnings
5837 .iter()
5838 .any(|w| w.contains("ignoreList") && w.contains("out of bounds"))
5839 );
5840 }
5841
5842 #[test]
5843 fn source_mapping_url_inline_decoded() {
5844 let map_json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
5846 let encoded = base64_encode_simple(map_json);
5847 let input = format!("var x;\n//# sourceMappingURL=data:application/json;base64,{encoded}");
5848 let url = parse_source_mapping_url(&input);
5849 match url {
5850 Some(SourceMappingUrl::Inline(json)) => {
5851 assert!(json.contains("version"));
5852 assert!(json.contains("AAAA"));
5853 }
5854 _ => panic!("expected inline source map"),
5855 }
5856 }
5857
5858 #[test]
5859 fn source_mapping_url_charset_variant() {
5860 let map_json = r#"{"version":3}"#;
5861 let encoded = base64_encode_simple(map_json);
5862 let input =
5863 format!("x\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,{encoded}");
5864 let url = parse_source_mapping_url(&input);
5865 assert!(matches!(url, Some(SourceMappingUrl::Inline(_))));
5866 }
5867
5868 #[test]
5869 fn source_mapping_url_invalid_base64_falls_through_to_external() {
5870 let input = "x\n//# sourceMappingURL=data:application/json;base64,!!!invalid!!!";
5872 let url = parse_source_mapping_url(input);
5873 assert!(matches!(url, Some(SourceMappingUrl::External(_))));
5875 }
5876
5877 #[test]
5878 fn from_json_lines_with_extensions_preserved() {
5879 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","x_custom":99}"#;
5880 let sm = SourceMap::from_json_lines(json, 0, 1).unwrap();
5881 assert!(sm.extensions.contains_key("x_custom"));
5882 }
5883
5884 fn base64_encode_simple(input: &str) -> String {
5886 const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
5887 let bytes = input.as_bytes();
5888 let mut result = String::new();
5889 for chunk in bytes.chunks(3) {
5890 let b0 = chunk[0] as u32;
5891 let b1 = if chunk.len() > 1 { chunk[1] as u32 } else { 0 };
5892 let b2 = if chunk.len() > 2 { chunk[2] as u32 } else { 0 };
5893 let n = (b0 << 16) | (b1 << 8) | b2;
5894 result.push(CHARS[((n >> 18) & 0x3F) as usize] as char);
5895 result.push(CHARS[((n >> 12) & 0x3F) as usize] as char);
5896 if chunk.len() > 1 {
5897 result.push(CHARS[((n >> 6) & 0x3F) as usize] as char);
5898 } else {
5899 result.push('=');
5900 }
5901 if chunk.len() > 2 {
5902 result.push(CHARS[(n & 0x3F) as usize] as char);
5903 } else {
5904 result.push('=');
5905 }
5906 }
5907 result
5908 }
5909
5910 #[test]
5913 fn mappings_iter_matches_decode() {
5914 let vlq = "AAAA;AACA,EAAA;AACA";
5915 let iter_mappings: Vec<Mapping> = MappingsIter::new(vlq).collect::<Result<_, _>>().unwrap();
5916 let (decoded, _) = decode_mappings(vlq).unwrap();
5917 assert_eq!(iter_mappings.len(), decoded.len());
5918 for (a, b) in iter_mappings.iter().zip(decoded.iter()) {
5919 assert_eq!(a.generated_line, b.generated_line);
5920 assert_eq!(a.generated_column, b.generated_column);
5921 assert_eq!(a.source, b.source);
5922 assert_eq!(a.original_line, b.original_line);
5923 assert_eq!(a.original_column, b.original_column);
5924 assert_eq!(a.name, b.name);
5925 }
5926 }
5927
5928 #[test]
5929 fn mappings_iter_empty() {
5930 let mappings: Vec<Mapping> = MappingsIter::new("").collect::<Result<_, _>>().unwrap();
5931 assert!(mappings.is_empty());
5932 }
5933
5934 #[test]
5935 fn mappings_iter_generated_only() {
5936 let vlq = "A,AAAA";
5937 let mappings: Vec<Mapping> = MappingsIter::new(vlq).collect::<Result<_, _>>().unwrap();
5938 assert_eq!(mappings.len(), 2);
5939 assert_eq!(mappings[0].source, u32::MAX);
5940 assert_eq!(mappings[1].source, 0);
5941 }
5942
5943 #[test]
5944 fn mappings_iter_with_names() {
5945 let vlq = "AAAAA";
5946 let mappings: Vec<Mapping> = MappingsIter::new(vlq).collect::<Result<_, _>>().unwrap();
5947 assert_eq!(mappings.len(), 1);
5948 assert_eq!(mappings[0].name, 0);
5949 }
5950
5951 #[test]
5952 fn mappings_iter_multiple_lines() {
5953 let vlq = "AAAA;AACA;AACA";
5954 let mappings: Vec<Mapping> = MappingsIter::new(vlq).collect::<Result<_, _>>().unwrap();
5955 assert_eq!(mappings.len(), 3);
5956 assert_eq!(mappings[0].generated_line, 0);
5957 assert_eq!(mappings[1].generated_line, 1);
5958 assert_eq!(mappings[2].generated_line, 2);
5959 }
5960 #[test]
5963 fn range_mappings_basic_decode() {
5964 let json = r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA,CAAC,GAAG","rangeMappings":"A,C"}"#;
5965 let sm = SourceMap::from_json(json).unwrap();
5966 assert!(sm.all_mappings()[0].is_range_mapping);
5967 assert!(!sm.all_mappings()[1].is_range_mapping);
5968 assert!(sm.all_mappings()[2].is_range_mapping);
5969 }
5970
5971 #[test]
5972 fn range_mapping_lookup_with_delta() {
5973 let json = r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA,GAAG","rangeMappings":"A"}"#;
5974 let sm = SourceMap::from_json(json).unwrap();
5975 assert_eq!(sm.original_position_for(0, 0).unwrap().column, 0);
5976 assert_eq!(sm.original_position_for(0, 1).unwrap().column, 1);
5977 assert_eq!(sm.original_position_for(0, 2).unwrap().column, 2);
5978 assert_eq!(sm.original_position_for(0, 3).unwrap().column, 3);
5979 }
5980
5981 #[test]
5982 fn range_mapping_cross_line() {
5983 let json = r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA","rangeMappings":"A"}"#;
5984 let sm = SourceMap::from_json(json).unwrap();
5985 assert_eq!(sm.original_position_for(1, 5).unwrap().line, 1);
5986 assert_eq!(sm.original_position_for(1, 5).unwrap().column, 0);
5987 assert_eq!(sm.original_position_for(2, 10).unwrap().line, 2);
5988 }
5989
5990 #[test]
5991 fn range_mapping_encode_roundtrip() {
5992 let json = r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA,CAAC,GAAG","rangeMappings":"A,C"}"#;
5993 assert_eq!(
5994 SourceMap::from_json(json)
5995 .unwrap()
5996 .encode_range_mappings()
5997 .unwrap(),
5998 "A,C"
5999 );
6000 }
6001
6002 #[test]
6003 fn no_range_mappings_test() {
6004 let sm = SourceMap::from_json(
6005 r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA"}"#,
6006 )
6007 .unwrap();
6008 assert!(!sm.has_range_mappings());
6009 assert!(sm.encode_range_mappings().is_none());
6010 }
6011
6012 #[test]
6013 fn range_mappings_multi_line_test() {
6014 let sm = SourceMap::from_json(r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA,CAAC;AAAA","rangeMappings":"A;A"}"#).unwrap();
6015 assert!(sm.all_mappings()[0].is_range_mapping);
6016 assert!(!sm.all_mappings()[1].is_range_mapping);
6017 assert!(sm.all_mappings()[2].is_range_mapping);
6018 }
6019
6020 #[test]
6021 fn range_mappings_json_roundtrip() {
6022 let sm = SourceMap::from_json(r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA,CAAC,GAAG","rangeMappings":"A,C"}"#).unwrap();
6023 let output = sm.to_json();
6024 assert!(output.contains("rangeMappings"));
6025 assert_eq!(
6026 SourceMap::from_json(&output).unwrap().range_mapping_count(),
6027 2
6028 );
6029 }
6030
6031 #[test]
6032 fn range_mappings_absent_from_json_test() {
6033 assert!(
6034 !SourceMap::from_json(
6035 r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA"}"#
6036 )
6037 .unwrap()
6038 .to_json()
6039 .contains("rangeMappings")
6040 );
6041 }
6042
6043 #[test]
6044 fn range_mapping_fallback_test() {
6045 let sm = SourceMap::from_json(r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA;KACK","rangeMappings":"A"}"#).unwrap();
6046 let loc = sm.original_position_for(1, 2).unwrap();
6047 assert_eq!(loc.line, 1);
6048 assert_eq!(loc.column, 0);
6049 }
6050
6051 #[test]
6052 fn range_mapping_no_fallback_non_range() {
6053 assert!(
6054 SourceMap::from_json(
6055 r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA"}"#
6056 )
6057 .unwrap()
6058 .original_position_for(1, 5)
6059 .is_none()
6060 );
6061 }
6062
6063 #[test]
6064 fn range_mapping_from_vlq_test() {
6065 let sm = SourceMap::from_vlq_with_range_mappings(
6066 "AAAA,CAAC",
6067 vec!["input.js".into()],
6068 vec![],
6069 None,
6070 None,
6071 vec![],
6072 vec![],
6073 None,
6074 Some("A"),
6075 )
6076 .unwrap();
6077 assert!(sm.all_mappings()[0].is_range_mapping);
6078 assert!(!sm.all_mappings()[1].is_range_mapping);
6079 }
6080
6081 #[test]
6082 fn range_mapping_encode_multi_line_test() {
6083 let sm = SourceMap::from_json(r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA,CAAC;AAAA,CAAC","rangeMappings":"A;B"}"#).unwrap();
6084 assert!(sm.all_mappings()[0].is_range_mapping);
6085 assert!(!sm.all_mappings()[1].is_range_mapping);
6086 assert!(!sm.all_mappings()[2].is_range_mapping);
6087 assert!(sm.all_mappings()[3].is_range_mapping);
6088 assert_eq!(sm.encode_range_mappings().unwrap(), "A;B");
6089 }
6090
6091 #[test]
6092 fn range_mapping_from_parts_test() {
6093 let sm = SourceMap::from_parts(
6094 None,
6095 None,
6096 vec!["input.js".into()],
6097 vec![],
6098 vec![],
6099 vec![
6100 Mapping {
6101 generated_line: 0,
6102 generated_column: 0,
6103 source: 0,
6104 original_line: 0,
6105 original_column: 0,
6106 name: NO_NAME,
6107 is_range_mapping: true,
6108 },
6109 Mapping {
6110 generated_line: 0,
6111 generated_column: 5,
6112 source: 0,
6113 original_line: 0,
6114 original_column: 5,
6115 name: NO_NAME,
6116 is_range_mapping: false,
6117 },
6118 ],
6119 vec![],
6120 None,
6121 None,
6122 );
6123 assert_eq!(sm.original_position_for(0, 2).unwrap().column, 2);
6124 assert_eq!(sm.original_position_for(0, 6).unwrap().column, 5);
6125 }
6126
6127 #[test]
6128 fn range_mapping_indexed_test() {
6129 let sm = SourceMap::from_json(r#"{"version":3,"sections":[{"offset":{"line":0,"column":0},"map":{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","rangeMappings":"A"}}]}"#).unwrap();
6130 assert!(sm.has_range_mappings());
6131 assert_eq!(sm.original_position_for(1, 3).unwrap().line, 1);
6132 }
6133
6134 #[test]
6135 fn range_mapping_empty_string_test() {
6136 assert!(!SourceMap::from_json(r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA","rangeMappings":""}"#).unwrap().has_range_mappings());
6137 }
6138
6139 #[test]
6140 fn range_mapping_lub_no_underflow() {
6141 let json = r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"KAAK","rangeMappings":"A"}"#;
6144 let sm = SourceMap::from_json(json).unwrap();
6145
6146 let loc = sm.original_position_for_with_bias(0, 2, Bias::LeastUpperBound);
6147 assert!(loc.is_some());
6148 let loc = loc.unwrap();
6149 assert_eq!(loc.line, 0);
6151 assert_eq!(loc.column, 5);
6152 }
6153
6154 #[test]
6157 fn builder_basic() {
6158 let sm = SourceMap::builder()
6159 .file("output.js")
6160 .sources(["input.ts"])
6161 .sources_content([Some("let x = 1;")])
6162 .names(["x"])
6163 .mappings([Mapping {
6164 generated_line: 0,
6165 generated_column: 0,
6166 source: 0,
6167 original_line: 0,
6168 original_column: 4,
6169 name: 0,
6170 is_range_mapping: false,
6171 }])
6172 .build();
6173
6174 assert_eq!(sm.file.as_deref(), Some("output.js"));
6175 assert_eq!(sm.sources, vec!["input.ts"]);
6176 assert_eq!(sm.sources_content, vec![Some("let x = 1;".to_string())]);
6177 assert_eq!(sm.names, vec!["x"]);
6178 assert_eq!(sm.mapping_count(), 1);
6179
6180 let loc = sm.original_position_for(0, 0).unwrap();
6181 assert_eq!(sm.source(loc.source), "input.ts");
6182 assert_eq!(loc.column, 4);
6183 assert_eq!(sm.name(loc.name.unwrap()), "x");
6184 }
6185
6186 #[test]
6187 fn builder_empty() {
6188 let sm = SourceMap::builder().build();
6189 assert_eq!(sm.mapping_count(), 0);
6190 assert_eq!(sm.sources.len(), 0);
6191 assert_eq!(sm.names.len(), 0);
6192 assert!(sm.file.is_none());
6193 }
6194
6195 #[test]
6196 fn builder_multiple_sources() {
6197 let sm = SourceMap::builder()
6198 .sources(["a.ts", "b.ts", "c.ts"])
6199 .sources_content([Some("// a"), Some("// b"), None])
6200 .mappings([
6201 Mapping {
6202 generated_line: 0,
6203 generated_column: 0,
6204 source: 0,
6205 original_line: 0,
6206 original_column: 0,
6207 name: u32::MAX,
6208 is_range_mapping: false,
6209 },
6210 Mapping {
6211 generated_line: 1,
6212 generated_column: 0,
6213 source: 1,
6214 original_line: 0,
6215 original_column: 0,
6216 name: u32::MAX,
6217 is_range_mapping: false,
6218 },
6219 Mapping {
6220 generated_line: 2,
6221 generated_column: 0,
6222 source: 2,
6223 original_line: 0,
6224 original_column: 0,
6225 name: u32::MAX,
6226 is_range_mapping: false,
6227 },
6228 ])
6229 .build();
6230
6231 assert_eq!(sm.sources.len(), 3);
6232 assert_eq!(sm.mapping_count(), 3);
6233 assert_eq!(sm.line_count(), 3);
6234
6235 let loc0 = sm.original_position_for(0, 0).unwrap();
6236 assert_eq!(sm.source(loc0.source), "a.ts");
6237
6238 let loc1 = sm.original_position_for(1, 0).unwrap();
6239 assert_eq!(sm.source(loc1.source), "b.ts");
6240
6241 let loc2 = sm.original_position_for(2, 0).unwrap();
6242 assert_eq!(sm.source(loc2.source), "c.ts");
6243 }
6244
6245 #[test]
6246 fn builder_with_iterators() {
6247 let source_names: Vec<String> = (0..5).map(|i| format!("mod_{i}.ts")).collect();
6248 let mappings = (0..5u32).map(|i| Mapping {
6249 generated_line: i,
6250 generated_column: 0,
6251 source: i,
6252 original_line: i,
6253 original_column: 0,
6254 name: u32::MAX,
6255 is_range_mapping: false,
6256 });
6257
6258 let sm = SourceMap::builder()
6259 .sources(source_names.iter().map(|s| s.as_str()))
6260 .mappings(mappings)
6261 .build();
6262
6263 assert_eq!(sm.sources.len(), 5);
6264 assert_eq!(sm.mapping_count(), 5);
6265 for i in 0..5u32 {
6266 let loc = sm.original_position_for(i, 0).unwrap();
6267 assert_eq!(sm.source(loc.source), format!("mod_{i}.ts"));
6268 }
6269 }
6270
6271 #[test]
6272 fn builder_ignore_list_and_debug_id() {
6273 let sm = SourceMap::builder()
6274 .sources(["app.ts", "node_modules/lib.js"])
6275 .ignore_list([1])
6276 .debug_id("85314830-023f-4cf1-a267-535f4e37bb17")
6277 .build();
6278
6279 assert_eq!(sm.ignore_list, vec![1]);
6280 assert_eq!(
6281 sm.debug_id.as_deref(),
6282 Some("85314830-023f-4cf1-a267-535f4e37bb17")
6283 );
6284 }
6285
6286 #[test]
6287 fn builder_range_mappings() {
6288 let sm = SourceMap::builder()
6289 .sources(["input.ts"])
6290 .mappings([
6291 Mapping {
6292 generated_line: 0,
6293 generated_column: 0,
6294 source: 0,
6295 original_line: 0,
6296 original_column: 0,
6297 name: u32::MAX,
6298 is_range_mapping: true,
6299 },
6300 Mapping {
6301 generated_line: 0,
6302 generated_column: 10,
6303 source: 0,
6304 original_line: 5,
6305 original_column: 0,
6306 name: u32::MAX,
6307 is_range_mapping: false,
6308 },
6309 ])
6310 .build();
6311
6312 assert!(sm.has_range_mappings());
6313 assert_eq!(sm.mapping_count(), 2);
6314 }
6315
6316 #[test]
6317 fn builder_json_roundtrip() {
6318 let sm = SourceMap::builder()
6319 .file("out.js")
6320 .source_root("/src/")
6321 .sources(["a.ts", "b.ts"])
6322 .sources_content([Some("// a"), Some("// b")])
6323 .names(["foo", "bar"])
6324 .mappings([
6325 Mapping {
6326 generated_line: 0,
6327 generated_column: 0,
6328 source: 0,
6329 original_line: 0,
6330 original_column: 0,
6331 name: 0,
6332 is_range_mapping: false,
6333 },
6334 Mapping {
6335 generated_line: 1,
6336 generated_column: 5,
6337 source: 1,
6338 original_line: 3,
6339 original_column: 2,
6340 name: 1,
6341 is_range_mapping: false,
6342 },
6343 ])
6344 .build();
6345
6346 let json = sm.to_json();
6347 let sm2 = SourceMap::from_json(&json).unwrap();
6348
6349 assert_eq!(sm2.file, sm.file);
6350 assert_eq!(sm2.sources, vec!["/src/a.ts", "/src/b.ts"]);
6352 assert_eq!(sm2.names, sm.names);
6353 assert_eq!(sm2.mapping_count(), sm.mapping_count());
6354
6355 for m in sm.all_mappings() {
6356 let a = sm.original_position_for(m.generated_line, m.generated_column);
6357 let b = sm2.original_position_for(m.generated_line, m.generated_column);
6358 match (a, b) {
6359 (Some(a), Some(b)) => {
6360 assert_eq!(a.source, b.source);
6361 assert_eq!(a.line, b.line);
6362 assert_eq!(a.column, b.column);
6363 assert_eq!(a.name, b.name);
6364 }
6365 (None, None) => {}
6366 _ => panic!("lookup mismatch"),
6367 }
6368 }
6369 }
6370}