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}
135
136impl fmt::Display for ParseError {
137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138 match self {
139 Self::Json(e) => write!(f, "JSON parse error: {e}"),
140 Self::Vlq(e) => write!(f, "VLQ decode error: {e}"),
141 Self::InvalidVersion(v) => write!(f, "unsupported source map version: {v}"),
142 Self::Scopes(e) => write!(f, "scopes decode error: {e}"),
143 }
144 }
145}
146
147impl std::error::Error for ParseError {
148 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
149 match self {
150 Self::Json(e) => Some(e),
151 Self::Vlq(e) => Some(e),
152 Self::Scopes(e) => Some(e),
153 Self::InvalidVersion(_) => None,
154 }
155 }
156}
157
158impl From<serde_json::Error> for ParseError {
159 fn from(e: serde_json::Error) -> Self {
160 Self::Json(e)
161 }
162}
163
164impl From<DecodeError> for ParseError {
165 fn from(e: DecodeError) -> Self {
166 Self::Vlq(e)
167 }
168}
169
170impl From<srcmap_scopes::ScopesError> for ParseError {
171 fn from(e: srcmap_scopes::ScopesError) -> Self {
172 Self::Scopes(e)
173 }
174}
175
176fn resolve_sources(raw_sources: &[Option<String>], source_root: &str) -> Vec<String> {
180 raw_sources
181 .iter()
182 .map(|s| match s {
183 Some(s) if !source_root.is_empty() => format!("{source_root}{s}"),
184 Some(s) => s.clone(),
185 None => String::new(),
186 })
187 .collect()
188}
189
190fn build_source_map(sources: &[String]) -> HashMap<String, u32> {
192 sources
193 .iter()
194 .enumerate()
195 .map(|(i, s)| (s.clone(), i as u32))
196 .collect()
197}
198
199#[derive(Deserialize)]
202struct RawSourceMap<'a> {
203 version: u32,
204 #[serde(default)]
205 file: Option<String>,
206 #[serde(default, rename = "sourceRoot")]
207 source_root: Option<String>,
208 #[serde(default)]
209 sources: Vec<Option<String>>,
210 #[serde(default, rename = "sourcesContent")]
211 sources_content: Option<Vec<Option<String>>>,
212 #[serde(default)]
213 names: Vec<String>,
214 #[serde(default, borrow)]
215 mappings: &'a str,
216 #[serde(default, rename = "ignoreList")]
217 ignore_list: Vec<u32>,
218 #[serde(default, rename = "x_google_ignoreList")]
220 x_google_ignore_list: Option<Vec<u32>>,
221 #[serde(default, rename = "debugId", alias = "debug_id")]
224 debug_id: Option<String>,
225 #[serde(default, borrow)]
227 scopes: Option<&'a str>,
228 #[serde(default, borrow, rename = "rangeMappings")]
230 range_mappings: Option<&'a str>,
231 #[serde(default)]
233 sections: Option<Vec<RawSection>>,
234 #[serde(flatten)]
236 extensions: HashMap<String, serde_json::Value>,
237}
238
239#[derive(Deserialize)]
241struct RawSection {
242 offset: RawOffset,
243 map: Box<serde_json::value::RawValue>,
244}
245
246#[derive(Deserialize)]
247struct RawOffset {
248 line: u32,
249 column: u32,
250}
251
252#[derive(Debug, Clone)]
277pub struct SourceMap {
278 pub file: Option<String>,
279 pub source_root: Option<String>,
280 pub sources: Vec<String>,
281 pub sources_content: Vec<Option<String>>,
282 pub names: Vec<String>,
283 pub ignore_list: Vec<u32>,
284 pub extensions: HashMap<String, serde_json::Value>,
286 pub debug_id: Option<String>,
288 pub scopes: Option<ScopeInfo>,
290
291 mappings: Vec<Mapping>,
293
294 line_offsets: Vec<u32>,
297
298 reverse_index: OnceCell<Vec<u32>>,
301
302 source_map: HashMap<String, u32>,
304
305 has_range_mappings: bool,
307}
308
309impl SourceMap {
310 pub fn from_json(json: &str) -> Result<Self, ParseError> {
313 let raw: RawSourceMap<'_> = serde_json::from_str(json)?;
314
315 if raw.version != 3 {
316 return Err(ParseError::InvalidVersion(raw.version));
317 }
318
319 if let Some(sections) = raw.sections {
321 return Self::from_sections(raw.file, sections);
322 }
323
324 Self::from_regular(raw)
325 }
326
327 fn from_regular(raw: RawSourceMap<'_>) -> Result<Self, ParseError> {
329 let source_root = raw.source_root.as_deref().unwrap_or("");
330 let sources = resolve_sources(&raw.sources, source_root);
331 let sources_content = raw.sources_content.unwrap_or_default();
332 let source_map = build_source_map(&sources);
333
334 let (mut mappings, line_offsets) = decode_mappings(raw.mappings)?;
336
337 if let Some(range_mappings_str) = raw.range_mappings
339 && !range_mappings_str.is_empty()
340 {
341 decode_range_mappings(range_mappings_str, &mut mappings, &line_offsets)?;
342 }
343
344 let num_sources = sources.len();
346 let scopes = match raw.scopes {
347 Some(scopes_str) if !scopes_str.is_empty() => Some(srcmap_scopes::decode_scopes(
348 scopes_str,
349 &raw.names,
350 num_sources,
351 )?),
352 _ => None,
353 };
354
355 let ignore_list = if raw.ignore_list.is_empty() {
357 raw.x_google_ignore_list.unwrap_or_default()
358 } else {
359 raw.ignore_list
360 };
361
362 let extensions: HashMap<String, serde_json::Value> = raw
364 .extensions
365 .into_iter()
366 .filter(|(k, _)| k.starts_with("x_"))
367 .collect();
368
369 let has_range_mappings = mappings.iter().any(|m| m.is_range_mapping);
370
371 Ok(Self {
372 file: raw.file,
373 source_root: raw.source_root,
374 sources,
375 sources_content,
376 names: raw.names,
377 ignore_list,
378 extensions,
379 debug_id: raw.debug_id,
380 scopes,
381 mappings,
382 line_offsets,
383 reverse_index: OnceCell::new(),
384 source_map,
385 has_range_mappings,
386 })
387 }
388
389 fn from_sections(file: Option<String>, sections: Vec<RawSection>) -> Result<Self, ParseError> {
391 let mut all_sources: Vec<String> = Vec::new();
392 let mut all_sources_content: Vec<Option<String>> = Vec::new();
393 let mut all_names: Vec<String> = Vec::new();
394 let mut all_mappings: Vec<Mapping> = Vec::new();
395 let mut all_ignore_list: Vec<u32> = Vec::new();
396 let mut max_line: u32 = 0;
397
398 let mut source_index_map: HashMap<String, u32> = HashMap::new();
400 let mut name_index_map: HashMap<String, u32> = HashMap::new();
401
402 for section in §ions {
403 let sub = Self::from_json(section.map.get())?;
404
405 let line_offset = section.offset.line;
406 let col_offset = section.offset.column;
407
408 let source_remap: Vec<u32> = sub
410 .sources
411 .iter()
412 .enumerate()
413 .map(|(i, s)| {
414 if let Some(&existing) = source_index_map.get(s) {
415 existing
416 } else {
417 let idx = all_sources.len() as u32;
418 all_sources.push(s.clone());
419 let content = sub.sources_content.get(i).cloned().unwrap_or(None);
421 all_sources_content.push(content);
422 source_index_map.insert(s.clone(), idx);
423 idx
424 }
425 })
426 .collect();
427
428 let name_remap: Vec<u32> = sub
430 .names
431 .iter()
432 .map(|n| {
433 if let Some(&existing) = name_index_map.get(n) {
434 existing
435 } else {
436 let idx = all_names.len() as u32;
437 all_names.push(n.clone());
438 name_index_map.insert(n.clone(), idx);
439 idx
440 }
441 })
442 .collect();
443
444 for &idx in &sub.ignore_list {
446 let global_idx = source_remap[idx as usize];
447 if !all_ignore_list.contains(&global_idx) {
448 all_ignore_list.push(global_idx);
449 }
450 }
451
452 for m in &sub.mappings {
454 let gen_line = m.generated_line + line_offset;
455 let gen_col = if m.generated_line == 0 {
456 m.generated_column + col_offset
457 } else {
458 m.generated_column
459 };
460
461 all_mappings.push(Mapping {
462 generated_line: gen_line,
463 generated_column: gen_col,
464 source: if m.source == NO_SOURCE {
465 NO_SOURCE
466 } else {
467 source_remap[m.source as usize]
468 },
469 original_line: m.original_line,
470 original_column: m.original_column,
471 name: if m.name == NO_NAME {
472 NO_NAME
473 } else {
474 name_remap[m.name as usize]
475 },
476 is_range_mapping: m.is_range_mapping,
477 });
478
479 if gen_line > max_line {
480 max_line = gen_line;
481 }
482 }
483 }
484
485 all_mappings.sort_unstable_by(|a, b| {
487 a.generated_line
488 .cmp(&b.generated_line)
489 .then(a.generated_column.cmp(&b.generated_column))
490 });
491
492 let line_count = if all_mappings.is_empty() {
494 0
495 } else {
496 max_line as usize + 1
497 };
498 let mut line_offsets: Vec<u32> = vec![0; line_count + 1];
499 let mut current_line: usize = 0;
500 for (i, m) in all_mappings.iter().enumerate() {
501 while current_line < m.generated_line as usize {
502 current_line += 1;
503 if current_line < line_offsets.len() {
504 line_offsets[current_line] = i as u32;
505 }
506 }
507 }
508 if !line_offsets.is_empty() {
510 let last = all_mappings.len() as u32;
511 for offset in line_offsets.iter_mut().skip(current_line + 1) {
512 *offset = last;
513 }
514 }
515
516 let source_map = build_source_map(&all_sources);
517 let has_range_mappings = all_mappings.iter().any(|m| m.is_range_mapping);
518
519 Ok(Self {
520 file,
521 source_root: None,
522 sources: all_sources,
523 sources_content: all_sources_content,
524 names: all_names,
525 ignore_list: all_ignore_list,
526 extensions: HashMap::new(),
527 debug_id: None,
528 scopes: None, mappings: all_mappings,
530 line_offsets,
531 reverse_index: OnceCell::new(),
532 source_map,
533 has_range_mappings,
534 })
535 }
536
537 pub fn original_position_for(&self, line: u32, column: u32) -> Option<OriginalLocation> {
542 self.original_position_for_with_bias(line, column, Bias::GreatestLowerBound)
543 }
544
545 pub fn original_position_for_with_bias(
551 &self,
552 line: u32,
553 column: u32,
554 bias: Bias,
555 ) -> Option<OriginalLocation> {
556 let line_idx = line as usize;
557 if line_idx + 1 >= self.line_offsets.len() {
558 return self.range_mapping_fallback(line, column);
559 }
560
561 let start = self.line_offsets[line_idx] as usize;
562 let end = self.line_offsets[line_idx + 1] as usize;
563
564 if start == end {
565 return self.range_mapping_fallback(line, column);
566 }
567
568 let line_mappings = &self.mappings[start..end];
569
570 let idx = match bias {
571 Bias::GreatestLowerBound => {
572 match line_mappings.binary_search_by_key(&column, |m| m.generated_column) {
573 Ok(i) => i,
574 Err(0) => return self.range_mapping_fallback(line, column),
575 Err(i) => i - 1,
576 }
577 }
578 Bias::LeastUpperBound => {
579 match line_mappings.binary_search_by_key(&column, |m| m.generated_column) {
580 Ok(i) => i,
581 Err(i) => {
582 if i >= line_mappings.len() {
583 return None;
584 }
585 i
586 }
587 }
588 }
589 };
590
591 let mapping = &line_mappings[idx];
592
593 if mapping.source == NO_SOURCE {
594 return None;
595 }
596
597 if mapping.is_range_mapping && column >= mapping.generated_column {
598 let column_delta = column - mapping.generated_column;
599 return Some(OriginalLocation {
600 source: mapping.source,
601 line: mapping.original_line,
602 column: mapping.original_column + column_delta,
603 name: if mapping.name == NO_NAME {
604 None
605 } else {
606 Some(mapping.name)
607 },
608 });
609 }
610
611 Some(OriginalLocation {
612 source: mapping.source,
613 line: mapping.original_line,
614 column: mapping.original_column,
615 name: if mapping.name == NO_NAME {
616 None
617 } else {
618 Some(mapping.name)
619 },
620 })
621 }
622
623 fn range_mapping_fallback(&self, line: u32, column: u32) -> Option<OriginalLocation> {
624 let line_idx = line as usize;
625 let search_end = if line_idx + 1 < self.line_offsets.len() {
626 self.line_offsets[line_idx] as usize
627 } else {
628 self.mappings.len()
629 };
630 if search_end == 0 {
631 return None;
632 }
633 let last_mapping = &self.mappings[search_end - 1];
634 if !last_mapping.is_range_mapping || last_mapping.source == NO_SOURCE {
635 return None;
636 }
637 let line_delta = line - last_mapping.generated_line;
638 let column_delta = if line_delta == 0 {
639 column - last_mapping.generated_column
640 } else {
641 0
642 };
643 Some(OriginalLocation {
644 source: last_mapping.source,
645 line: last_mapping.original_line + line_delta,
646 column: last_mapping.original_column + column_delta,
647 name: if last_mapping.name == NO_NAME {
648 None
649 } else {
650 Some(last_mapping.name)
651 },
652 })
653 }
654
655 pub fn generated_position_for(
660 &self,
661 source: &str,
662 line: u32,
663 column: u32,
664 ) -> Option<GeneratedLocation> {
665 self.generated_position_for_with_bias(source, line, column, Bias::LeastUpperBound)
666 }
667
668 pub fn generated_position_for_with_bias(
674 &self,
675 source: &str,
676 line: u32,
677 column: u32,
678 bias: Bias,
679 ) -> Option<GeneratedLocation> {
680 let &source_idx = self.source_map.get(source)?;
681
682 let reverse_index = self
683 .reverse_index
684 .get_or_init(|| build_reverse_index(&self.mappings));
685
686 let idx = reverse_index.partition_point(|&i| {
688 let m = &self.mappings[i as usize];
689 (m.source, m.original_line, m.original_column) < (source_idx, line, column)
690 });
691
692 match bias {
693 Bias::GreatestLowerBound => {
694 if idx < reverse_index.len() {
698 let mapping = &self.mappings[reverse_index[idx] as usize];
699 if mapping.source == source_idx
700 && mapping.original_line == line
701 && mapping.original_column == column
702 {
703 return Some(GeneratedLocation {
704 line: mapping.generated_line,
705 column: mapping.generated_column,
706 });
707 }
708 }
709 if idx == 0 {
711 return None;
712 }
713 let mapping = &self.mappings[reverse_index[idx - 1] as usize];
714 if mapping.source != source_idx {
715 return None;
716 }
717 Some(GeneratedLocation {
718 line: mapping.generated_line,
719 column: mapping.generated_column,
720 })
721 }
722 Bias::LeastUpperBound => {
723 if idx >= reverse_index.len() {
724 return None;
725 }
726 let mapping = &self.mappings[reverse_index[idx] as usize];
727 if mapping.source != source_idx {
728 return None;
729 }
730 Some(GeneratedLocation {
731 line: mapping.generated_line,
732 column: mapping.generated_column,
733 })
734 }
735 }
736 }
737
738 pub fn all_generated_positions_for(
743 &self,
744 source: &str,
745 line: u32,
746 column: u32,
747 ) -> Vec<GeneratedLocation> {
748 let Some(&source_idx) = self.source_map.get(source) else {
749 return Vec::new();
750 };
751
752 let reverse_index = self
753 .reverse_index
754 .get_or_init(|| build_reverse_index(&self.mappings));
755
756 let start = reverse_index.partition_point(|&i| {
758 let m = &self.mappings[i as usize];
759 (m.source, m.original_line, m.original_column) < (source_idx, line, column)
760 });
761
762 let mut results = Vec::new();
763
764 for &ri in &reverse_index[start..] {
765 let m = &self.mappings[ri as usize];
766 if m.source != source_idx || m.original_line != line || m.original_column != column {
767 break;
768 }
769 results.push(GeneratedLocation {
770 line: m.generated_line,
771 column: m.generated_column,
772 });
773 }
774
775 results
776 }
777
778 pub fn map_range(
784 &self,
785 start_line: u32,
786 start_column: u32,
787 end_line: u32,
788 end_column: u32,
789 ) -> Option<MappedRange> {
790 let start = self.original_position_for(start_line, start_column)?;
791 let end = self.original_position_for(end_line, end_column)?;
792
793 if start.source != end.source {
795 return None;
796 }
797
798 Some(MappedRange {
799 source: start.source,
800 original_start_line: start.line,
801 original_start_column: start.column,
802 original_end_line: end.line,
803 original_end_column: end.column,
804 })
805 }
806
807 #[inline]
814 pub fn source(&self, index: u32) -> &str {
815 &self.sources[index as usize]
816 }
817
818 #[inline]
820 pub fn get_source(&self, index: u32) -> Option<&str> {
821 self.sources.get(index as usize).map(|s| s.as_str())
822 }
823
824 #[inline]
831 pub fn name(&self, index: u32) -> &str {
832 &self.names[index as usize]
833 }
834
835 #[inline]
837 pub fn get_name(&self, index: u32) -> Option<&str> {
838 self.names.get(index as usize).map(|s| s.as_str())
839 }
840
841 #[inline]
843 pub fn source_index(&self, name: &str) -> Option<u32> {
844 self.source_map.get(name).copied()
845 }
846
847 #[inline]
849 pub fn mapping_count(&self) -> usize {
850 self.mappings.len()
851 }
852
853 #[inline]
855 pub fn line_count(&self) -> usize {
856 self.line_offsets.len().saturating_sub(1)
857 }
858
859 #[inline]
861 pub fn mappings_for_line(&self, line: u32) -> &[Mapping] {
862 let line_idx = line as usize;
863 if line_idx + 1 >= self.line_offsets.len() {
864 return &[];
865 }
866 let start = self.line_offsets[line_idx] as usize;
867 let end = self.line_offsets[line_idx + 1] as usize;
868 &self.mappings[start..end]
869 }
870
871 #[inline]
873 pub fn all_mappings(&self) -> &[Mapping] {
874 &self.mappings
875 }
876
877 pub fn to_json(&self) -> String {
882 self.to_json_with_options(false)
883 }
884
885 pub fn to_json_with_options(&self, exclude_content: bool) -> String {
889 let mappings = self.encode_mappings();
890
891 let mut json = String::with_capacity(256 + mappings.len());
892 json.push_str(r#"{"version":3"#);
893
894 if let Some(ref file) = self.file {
895 json.push_str(r#","file":"#);
896 json_quote_into(&mut json, file);
897 }
898
899 if let Some(ref root) = self.source_root {
900 json.push_str(r#","sourceRoot":"#);
901 json_quote_into(&mut json, root);
902 }
903
904 json.push_str(r#","sources":["#);
905 for (i, s) in self.sources.iter().enumerate() {
906 if i > 0 {
907 json.push(',');
908 }
909 json_quote_into(&mut json, s);
910 }
911 json.push(']');
912
913 if !exclude_content
914 && !self.sources_content.is_empty()
915 && self.sources_content.iter().any(|c| c.is_some())
916 {
917 json.push_str(r#","sourcesContent":["#);
918 for (i, c) in self.sources_content.iter().enumerate() {
919 if i > 0 {
920 json.push(',');
921 }
922 match c {
923 Some(content) => json_quote_into(&mut json, content),
924 None => json.push_str("null"),
925 }
926 }
927 json.push(']');
928 }
929
930 json.push_str(r#","names":["#);
931 for (i, n) in self.names.iter().enumerate() {
932 if i > 0 {
933 json.push(',');
934 }
935 json_quote_into(&mut json, n);
936 }
937 json.push(']');
938
939 json.push_str(r#","mappings":""#);
941 json.push_str(&mappings);
942 json.push('"');
943
944 if let Some(range_mappings) = self.encode_range_mappings() {
945 json.push_str(r#","rangeMappings":""#);
947 json.push_str(&range_mappings);
948 json.push('"');
949 }
950
951 if !self.ignore_list.is_empty() {
952 use std::fmt::Write;
953 json.push_str(r#","ignoreList":["#);
954 for (i, &idx) in self.ignore_list.iter().enumerate() {
955 if i > 0 {
956 json.push(',');
957 }
958 let _ = write!(json, "{idx}");
959 }
960 json.push(']');
961 }
962
963 if let Some(ref id) = self.debug_id {
964 json.push_str(r#","debugId":"#);
965 json_quote_into(&mut json, id);
966 }
967
968 let mut ext_keys: Vec<&String> = self.extensions.keys().collect();
970 ext_keys.sort();
971 for key in ext_keys {
972 if let Some(val) = self.extensions.get(key) {
973 json.push(',');
974 json_quote_into(&mut json, key);
975 json.push(':');
976 json.push_str(&serde_json::to_string(val).unwrap_or_default());
977 }
978 }
979
980 json.push('}');
981 json
982 }
983
984 #[allow(clippy::too_many_arguments)]
990 pub fn from_parts(
991 file: Option<String>,
992 source_root: Option<String>,
993 sources: Vec<String>,
994 sources_content: Vec<Option<String>>,
995 names: Vec<String>,
996 mappings: Vec<Mapping>,
997 ignore_list: Vec<u32>,
998 debug_id: Option<String>,
999 scopes: Option<ScopeInfo>,
1000 ) -> Self {
1001 let line_count = mappings.last().map_or(0, |m| m.generated_line as usize + 1);
1003 let mut line_offsets: Vec<u32> = vec![0; line_count + 1];
1004 let mut current_line: usize = 0;
1005 for (i, m) in mappings.iter().enumerate() {
1006 while current_line < m.generated_line as usize {
1007 current_line += 1;
1008 if current_line < line_offsets.len() {
1009 line_offsets[current_line] = i as u32;
1010 }
1011 }
1012 }
1013 if !line_offsets.is_empty() {
1015 let last = mappings.len() as u32;
1016 for offset in line_offsets.iter_mut().skip(current_line + 1) {
1017 *offset = last;
1018 }
1019 }
1020
1021 let source_map = build_source_map(&sources);
1022 let has_range_mappings = mappings.iter().any(|m| m.is_range_mapping);
1023
1024 Self {
1025 file,
1026 source_root,
1027 sources,
1028 sources_content,
1029 names,
1030 ignore_list,
1031 extensions: HashMap::new(),
1032 debug_id,
1033 scopes,
1034 mappings,
1035 line_offsets,
1036 reverse_index: OnceCell::new(),
1037 source_map,
1038 has_range_mappings,
1039 }
1040 }
1041
1042 #[allow(clippy::too_many_arguments)]
1048 pub fn from_vlq(
1049 mappings_str: &str,
1050 sources: Vec<String>,
1051 names: Vec<String>,
1052 file: Option<String>,
1053 source_root: Option<String>,
1054 sources_content: Vec<Option<String>>,
1055 ignore_list: Vec<u32>,
1056 debug_id: Option<String>,
1057 ) -> Result<Self, ParseError> {
1058 Self::from_vlq_with_range_mappings(
1059 mappings_str,
1060 sources,
1061 names,
1062 file,
1063 source_root,
1064 sources_content,
1065 ignore_list,
1066 debug_id,
1067 None,
1068 )
1069 }
1070
1071 #[allow(clippy::too_many_arguments)]
1074 pub fn from_vlq_with_range_mappings(
1075 mappings_str: &str,
1076 sources: Vec<String>,
1077 names: Vec<String>,
1078 file: Option<String>,
1079 source_root: Option<String>,
1080 sources_content: Vec<Option<String>>,
1081 ignore_list: Vec<u32>,
1082 debug_id: Option<String>,
1083 range_mappings_str: Option<&str>,
1084 ) -> Result<Self, ParseError> {
1085 let (mut mappings, line_offsets) = decode_mappings(mappings_str)?;
1086 if let Some(rm_str) = range_mappings_str
1087 && !rm_str.is_empty()
1088 {
1089 decode_range_mappings(rm_str, &mut mappings, &line_offsets)?;
1090 }
1091 let source_map = build_source_map(&sources);
1092 let has_range_mappings = mappings.iter().any(|m| m.is_range_mapping);
1093 Ok(Self {
1094 file,
1095 source_root,
1096 sources,
1097 sources_content,
1098 names,
1099 ignore_list,
1100 extensions: HashMap::new(),
1101 debug_id,
1102 scopes: None,
1103 mappings,
1104 line_offsets,
1105 reverse_index: OnceCell::new(),
1106 source_map,
1107 has_range_mappings,
1108 })
1109 }
1110
1111 pub fn from_json_lines(json: &str, start_line: u32, end_line: u32) -> Result<Self, ParseError> {
1117 let raw: RawSourceMap<'_> = serde_json::from_str(json)?;
1118
1119 if raw.version != 3 {
1120 return Err(ParseError::InvalidVersion(raw.version));
1121 }
1122
1123 let source_root = raw.source_root.as_deref().unwrap_or("");
1124 let sources = resolve_sources(&raw.sources, source_root);
1125 let sources_content = raw.sources_content.unwrap_or_default();
1126 let source_map = build_source_map(&sources);
1127
1128 let (mappings, line_offsets) = decode_mappings_range(raw.mappings, start_line, end_line)?;
1130
1131 let num_sources = sources.len();
1133 let scopes = match raw.scopes {
1134 Some(scopes_str) if !scopes_str.is_empty() => Some(srcmap_scopes::decode_scopes(
1135 scopes_str,
1136 &raw.names,
1137 num_sources,
1138 )?),
1139 _ => None,
1140 };
1141
1142 let ignore_list = if raw.ignore_list.is_empty() {
1143 raw.x_google_ignore_list.unwrap_or_default()
1144 } else {
1145 raw.ignore_list
1146 };
1147
1148 let extensions: HashMap<String, serde_json::Value> = raw
1150 .extensions
1151 .into_iter()
1152 .filter(|(k, _)| k.starts_with("x_"))
1153 .collect();
1154
1155 let has_range_mappings = mappings.iter().any(|m| m.is_range_mapping);
1156
1157 Ok(Self {
1158 file: raw.file,
1159 source_root: raw.source_root,
1160 sources,
1161 sources_content,
1162 names: raw.names,
1163 ignore_list,
1164 extensions,
1165 debug_id: raw.debug_id,
1166 scopes,
1167 mappings,
1168 line_offsets,
1169 reverse_index: OnceCell::new(),
1170 source_map,
1171 has_range_mappings,
1172 })
1173 }
1174
1175 pub fn encode_mappings(&self) -> String {
1177 if self.mappings.is_empty() {
1178 return String::new();
1179 }
1180
1181 let mut out: Vec<u8> = Vec::with_capacity(self.mappings.len() * 6);
1182
1183 let mut prev_gen_col: i64 = 0;
1184 let mut prev_source: i64 = 0;
1185 let mut prev_orig_line: i64 = 0;
1186 let mut prev_orig_col: i64 = 0;
1187 let mut prev_name: i64 = 0;
1188 let mut prev_gen_line: u32 = 0;
1189 let mut first_in_line = true;
1190
1191 for m in &self.mappings {
1192 while prev_gen_line < m.generated_line {
1193 out.push(b';');
1194 prev_gen_line += 1;
1195 prev_gen_col = 0;
1196 first_in_line = true;
1197 }
1198
1199 if !first_in_line {
1200 out.push(b',');
1201 }
1202 first_in_line = false;
1203
1204 srcmap_codec::vlq_encode(&mut out, m.generated_column as i64 - prev_gen_col);
1205 prev_gen_col = m.generated_column as i64;
1206
1207 if m.source != NO_SOURCE {
1208 srcmap_codec::vlq_encode(&mut out, m.source as i64 - prev_source);
1209 prev_source = m.source as i64;
1210
1211 srcmap_codec::vlq_encode(&mut out, m.original_line as i64 - prev_orig_line);
1212 prev_orig_line = m.original_line as i64;
1213
1214 srcmap_codec::vlq_encode(&mut out, m.original_column as i64 - prev_orig_col);
1215 prev_orig_col = m.original_column as i64;
1216
1217 if m.name != NO_NAME {
1218 srcmap_codec::vlq_encode(&mut out, m.name as i64 - prev_name);
1219 prev_name = m.name as i64;
1220 }
1221 }
1222 }
1223
1224 debug_assert!(out.is_ascii());
1227 unsafe { String::from_utf8_unchecked(out) }
1228 }
1229
1230 pub fn encode_range_mappings(&self) -> Option<String> {
1231 if !self.has_range_mappings {
1232 return None;
1233 }
1234 let line_count = self.line_offsets.len().saturating_sub(1);
1235 let mut out: Vec<u8> = Vec::new();
1236 for line_idx in 0..line_count {
1237 if line_idx > 0 {
1238 out.push(b';');
1239 }
1240 let start = self.line_offsets[line_idx] as usize;
1241 let end = self.line_offsets[line_idx + 1] as usize;
1242 let mut prev_offset: u64 = 0;
1243 let mut first_on_line = true;
1244 for (i, mapping) in self.mappings[start..end].iter().enumerate() {
1245 if mapping.is_range_mapping {
1246 if !first_on_line {
1247 out.push(b',');
1248 }
1249 first_on_line = false;
1250 vlq_encode_unsigned(&mut out, i as u64 - prev_offset);
1251 prev_offset = i as u64;
1252 }
1253 }
1254 }
1255 while out.last() == Some(&b';') {
1256 out.pop();
1257 }
1258 if out.is_empty() {
1259 return None;
1260 }
1261 debug_assert!(out.is_ascii());
1264 Some(unsafe { String::from_utf8_unchecked(out) })
1265 }
1266
1267 #[inline]
1268 pub fn has_range_mappings(&self) -> bool {
1269 self.has_range_mappings
1270 }
1271
1272 #[inline]
1273 pub fn range_mapping_count(&self) -> usize {
1274 self.mappings.iter().filter(|m| m.is_range_mapping).count()
1275 }
1276}
1277
1278#[derive(Debug, Clone, Copy)]
1282struct VlqState {
1283 source_index: i64,
1284 original_line: i64,
1285 original_column: i64,
1286 name_index: i64,
1287}
1288
1289#[derive(Debug, Clone)]
1291struct LineInfo {
1292 byte_offset: usize,
1294 byte_end: usize,
1296 state: VlqState,
1298}
1299
1300#[derive(Debug)]
1319pub struct LazySourceMap {
1320 pub file: Option<String>,
1321 pub source_root: Option<String>,
1322 pub sources: Vec<String>,
1323 pub sources_content: Vec<Option<String>>,
1324 pub names: Vec<String>,
1325 pub ignore_list: Vec<u32>,
1326 pub extensions: HashMap<String, serde_json::Value>,
1327 pub debug_id: Option<String>,
1328 pub scopes: Option<ScopeInfo>,
1329
1330 raw_mappings: String,
1332
1333 line_info: Vec<LineInfo>,
1335
1336 decoded_lines: RefCell<HashMap<u32, Vec<Mapping>>>,
1338
1339 source_map: HashMap<String, u32>,
1341}
1342
1343impl LazySourceMap {
1344 pub fn from_json(json: &str) -> Result<Self, ParseError> {
1349 let raw: RawSourceMap<'_> = serde_json::from_str(json)?;
1350
1351 if raw.version != 3 {
1352 return Err(ParseError::InvalidVersion(raw.version));
1353 }
1354
1355 let source_root = raw.source_root.as_deref().unwrap_or("");
1356 let sources = resolve_sources(&raw.sources, source_root);
1357 let sources_content = raw.sources_content.unwrap_or_default();
1358 let source_map = build_source_map(&sources);
1359
1360 let raw_mappings = raw.mappings.to_string();
1363 let line_info = prescan_mappings(&raw_mappings)?;
1364
1365 let num_sources = sources.len();
1367 let scopes = match raw.scopes {
1368 Some(scopes_str) if !scopes_str.is_empty() => Some(srcmap_scopes::decode_scopes(
1369 scopes_str,
1370 &raw.names,
1371 num_sources,
1372 )?),
1373 _ => None,
1374 };
1375
1376 let ignore_list = if raw.ignore_list.is_empty() {
1377 raw.x_google_ignore_list.unwrap_or_default()
1378 } else {
1379 raw.ignore_list
1380 };
1381
1382 let extensions: HashMap<String, serde_json::Value> = raw
1384 .extensions
1385 .into_iter()
1386 .filter(|(k, _)| k.starts_with("x_"))
1387 .collect();
1388
1389 Ok(Self {
1390 file: raw.file,
1391 source_root: raw.source_root,
1392 sources,
1393 sources_content,
1394 names: raw.names,
1395 ignore_list,
1396 extensions,
1397 debug_id: raw.debug_id,
1398 scopes,
1399 raw_mappings,
1400 line_info,
1401 decoded_lines: RefCell::new(HashMap::new()),
1402 source_map,
1403 })
1404 }
1405
1406 pub fn decode_line(&self, line: u32) -> Result<Vec<Mapping>, DecodeError> {
1411 if let Some(cached) = self.decoded_lines.borrow().get(&line) {
1413 return Ok(cached.clone());
1414 }
1415
1416 let line_idx = line as usize;
1417 if line_idx >= self.line_info.len() {
1418 return Ok(Vec::new());
1419 }
1420
1421 let info = &self.line_info[line_idx];
1422 let bytes = self.raw_mappings.as_bytes();
1423 let slice = &bytes[info.byte_offset..info.byte_end];
1424
1425 let mut mappings = Vec::new();
1426 let mut source_index = info.state.source_index;
1427 let mut original_line = info.state.original_line;
1428 let mut original_column = info.state.original_column;
1429 let mut name_index = info.state.name_index;
1430 let mut generated_column: i64 = 0;
1431 let mut pos: usize = 0;
1432 let len = slice.len();
1433
1434 let base_offset = info.byte_offset;
1437
1438 while pos < len {
1439 let byte = slice[pos];
1440
1441 if byte == b',' {
1442 pos += 1;
1443 continue;
1444 }
1445
1446 let mut abs_pos = base_offset + pos;
1448
1449 generated_column += vlq_fast(bytes, &mut abs_pos)?;
1451
1452 if abs_pos < base_offset + len && bytes[abs_pos] != b',' && bytes[abs_pos] != b';' {
1453 source_index += vlq_fast(bytes, &mut abs_pos)?;
1455 original_line += vlq_fast(bytes, &mut abs_pos)?;
1456 original_column += vlq_fast(bytes, &mut abs_pos)?;
1457
1458 let name = if abs_pos < base_offset + len
1460 && bytes[abs_pos] != b','
1461 && bytes[abs_pos] != b';'
1462 {
1463 name_index += vlq_fast(bytes, &mut abs_pos)?;
1464 name_index as u32
1465 } else {
1466 NO_NAME
1467 };
1468
1469 mappings.push(Mapping {
1470 generated_line: line,
1471 generated_column: generated_column as u32,
1472 source: source_index as u32,
1473 original_line: original_line as u32,
1474 original_column: original_column as u32,
1475 name,
1476 is_range_mapping: false,
1477 });
1478 } else {
1479 mappings.push(Mapping {
1481 generated_line: line,
1482 generated_column: generated_column as u32,
1483 source: NO_SOURCE,
1484 original_line: 0,
1485 original_column: 0,
1486 name: NO_NAME,
1487 is_range_mapping: false,
1488 });
1489 }
1490
1491 pos = abs_pos - base_offset;
1492 }
1493
1494 self.decoded_lines
1496 .borrow_mut()
1497 .insert(line, mappings.clone());
1498
1499 Ok(mappings)
1500 }
1501
1502 pub fn original_position_for(&self, line: u32, column: u32) -> Option<OriginalLocation> {
1507 let line_mappings = self.decode_line(line).ok()?;
1508
1509 if line_mappings.is_empty() {
1510 return None;
1511 }
1512
1513 let idx = match line_mappings.binary_search_by_key(&column, |m| m.generated_column) {
1515 Ok(i) => i,
1516 Err(0) => return None,
1517 Err(i) => i - 1,
1518 };
1519
1520 let mapping = &line_mappings[idx];
1521
1522 if mapping.source == NO_SOURCE {
1523 return None;
1524 }
1525
1526 Some(OriginalLocation {
1527 source: mapping.source,
1528 line: mapping.original_line,
1529 column: mapping.original_column,
1530 name: if mapping.name == NO_NAME {
1531 None
1532 } else {
1533 Some(mapping.name)
1534 },
1535 })
1536 }
1537
1538 #[inline]
1540 pub fn line_count(&self) -> usize {
1541 self.line_info.len()
1542 }
1543
1544 #[inline]
1551 pub fn source(&self, index: u32) -> &str {
1552 &self.sources[index as usize]
1553 }
1554
1555 #[inline]
1557 pub fn get_source(&self, index: u32) -> Option<&str> {
1558 self.sources.get(index as usize).map(|s| s.as_str())
1559 }
1560
1561 #[inline]
1568 pub fn name(&self, index: u32) -> &str {
1569 &self.names[index as usize]
1570 }
1571
1572 #[inline]
1574 pub fn get_name(&self, index: u32) -> Option<&str> {
1575 self.names.get(index as usize).map(|s| s.as_str())
1576 }
1577
1578 #[inline]
1580 pub fn source_index(&self, name: &str) -> Option<u32> {
1581 self.source_map.get(name).copied()
1582 }
1583
1584 pub fn mappings_for_line(&self, line: u32) -> Vec<Mapping> {
1586 self.decode_line(line).unwrap_or_default()
1587 }
1588
1589 pub fn into_sourcemap(self) -> Result<SourceMap, ParseError> {
1593 let (mappings, line_offsets) = decode_mappings(&self.raw_mappings)?;
1594 let has_range_mappings = mappings.iter().any(|m| m.is_range_mapping);
1595
1596 Ok(SourceMap {
1597 file: self.file,
1598 source_root: self.source_root,
1599 sources: self.sources.clone(),
1600 sources_content: self.sources_content,
1601 names: self.names,
1602 ignore_list: self.ignore_list,
1603 extensions: self.extensions,
1604 debug_id: self.debug_id,
1605 scopes: self.scopes,
1606 mappings,
1607 line_offsets,
1608 reverse_index: OnceCell::new(),
1609 source_map: self.source_map,
1610 has_range_mappings,
1611 })
1612 }
1613}
1614
1615fn prescan_mappings(input: &str) -> Result<Vec<LineInfo>, DecodeError> {
1618 if input.is_empty() {
1619 return Ok(Vec::new());
1620 }
1621
1622 let bytes = input.as_bytes();
1623 let len = bytes.len();
1624
1625 let line_count = bytes.iter().filter(|&&b| b == b';').count() + 1;
1627 let mut line_info: Vec<LineInfo> = Vec::with_capacity(line_count);
1628
1629 let mut source_index: i64 = 0;
1630 let mut original_line: i64 = 0;
1631 let mut original_column: i64 = 0;
1632 let mut name_index: i64 = 0;
1633 let mut pos: usize = 0;
1634
1635 loop {
1636 let line_start = pos;
1637 let state = VlqState {
1638 source_index,
1639 original_line,
1640 original_column,
1641 name_index,
1642 };
1643
1644 let mut saw_semicolon = false;
1645
1646 while pos < len {
1648 let byte = bytes[pos];
1649
1650 if byte == b';' {
1651 pos += 1;
1652 saw_semicolon = true;
1653 break;
1654 }
1655
1656 if byte == b',' {
1657 pos += 1;
1658 continue;
1659 }
1660
1661 vlq_fast(bytes, &mut pos)?;
1663
1664 if pos < len && bytes[pos] != b',' && bytes[pos] != b';' {
1665 source_index += vlq_fast(bytes, &mut pos)?;
1667 original_line += vlq_fast(bytes, &mut pos)?;
1668 original_column += vlq_fast(bytes, &mut pos)?;
1669
1670 if pos < len && bytes[pos] != b',' && bytes[pos] != b';' {
1672 name_index += vlq_fast(bytes, &mut pos)?;
1673 }
1674 }
1675 }
1676
1677 let byte_end = if saw_semicolon { pos - 1 } else { pos };
1679
1680 line_info.push(LineInfo {
1681 byte_offset: line_start,
1682 byte_end,
1683 state,
1684 });
1685
1686 if !saw_semicolon {
1687 break;
1688 }
1689 }
1690
1691 Ok(line_info)
1692}
1693
1694#[derive(Debug, Clone, PartialEq, Eq)]
1696pub enum SourceMappingUrl {
1697 Inline(String),
1699 External(String),
1701}
1702
1703pub fn parse_source_mapping_url(source: &str) -> Option<SourceMappingUrl> {
1709 for line in source.lines().rev() {
1711 let trimmed = line.trim();
1712 let url = if let Some(rest) = trimmed.strip_prefix("//# sourceMappingURL=") {
1713 rest.trim()
1714 } else if let Some(rest) = trimmed.strip_prefix("//@ sourceMappingURL=") {
1715 rest.trim()
1716 } else if let Some(rest) = trimmed.strip_prefix("/*# sourceMappingURL=") {
1717 rest.trim_end_matches("*/").trim()
1718 } else if let Some(rest) = trimmed.strip_prefix("/*@ sourceMappingURL=") {
1719 rest.trim_end_matches("*/").trim()
1720 } else {
1721 continue;
1722 };
1723
1724 if url.is_empty() {
1725 continue;
1726 }
1727
1728 if let Some(base64_data) = url
1730 .strip_prefix("data:application/json;base64,")
1731 .or_else(|| url.strip_prefix("data:application/json;charset=utf-8;base64,"))
1732 .or_else(|| url.strip_prefix("data:application/json;charset=UTF-8;base64,"))
1733 {
1734 let decoded = base64_decode(base64_data);
1736 if let Some(json) = decoded {
1737 return Some(SourceMappingUrl::Inline(json));
1738 }
1739 }
1740
1741 return Some(SourceMappingUrl::External(url.to_string()));
1742 }
1743
1744 None
1745}
1746
1747fn base64_decode(input: &str) -> Option<String> {
1749 let input = input.trim();
1750 let bytes: Vec<u8> = input.bytes().filter(|b| !b.is_ascii_whitespace()).collect();
1751
1752 let mut output = Vec::with_capacity(bytes.len() * 3 / 4);
1753
1754 for chunk in bytes.chunks(4) {
1755 let mut buf = [0u8; 4];
1756 let mut len = 0;
1757
1758 for &b in chunk {
1759 if b == b'=' {
1760 break;
1761 }
1762 let val = match b {
1763 b'A'..=b'Z' => b - b'A',
1764 b'a'..=b'z' => b - b'a' + 26,
1765 b'0'..=b'9' => b - b'0' + 52,
1766 b'+' => 62,
1767 b'/' => 63,
1768 _ => return None,
1769 };
1770 buf[len] = val;
1771 len += 1;
1772 }
1773
1774 if len >= 2 {
1775 output.push((buf[0] << 2) | (buf[1] >> 4));
1776 }
1777 if len >= 3 {
1778 output.push((buf[1] << 4) | (buf[2] >> 2));
1779 }
1780 if len >= 4 {
1781 output.push((buf[2] << 6) | buf[3]);
1782 }
1783 }
1784
1785 String::from_utf8(output).ok()
1786}
1787
1788pub fn validate_deep(sm: &SourceMap) -> Vec<String> {
1793 let mut warnings = Vec::new();
1794
1795 let mut prev_line: u32 = 0;
1797 let mut prev_col: u32 = 0;
1798 let mappings = sm.all_mappings();
1799 for m in mappings {
1800 if m.generated_line < prev_line
1801 || (m.generated_line == prev_line && m.generated_column < prev_col)
1802 {
1803 warnings.push(format!(
1804 "mappings out of order at {}:{}",
1805 m.generated_line, m.generated_column
1806 ));
1807 }
1808 prev_line = m.generated_line;
1809 prev_col = m.generated_column;
1810 }
1811
1812 for m in mappings {
1814 if m.source != NO_SOURCE && m.source as usize >= sm.sources.len() {
1815 warnings.push(format!(
1816 "source index {} out of bounds (max {})",
1817 m.source,
1818 sm.sources.len()
1819 ));
1820 }
1821 if m.name != NO_NAME && m.name as usize >= sm.names.len() {
1822 warnings.push(format!(
1823 "name index {} out of bounds (max {})",
1824 m.name,
1825 sm.names.len()
1826 ));
1827 }
1828 }
1829
1830 for &idx in &sm.ignore_list {
1832 if idx as usize >= sm.sources.len() {
1833 warnings.push(format!(
1834 "ignoreList index {} out of bounds (max {})",
1835 idx,
1836 sm.sources.len()
1837 ));
1838 }
1839 }
1840
1841 let mut referenced_sources = std::collections::HashSet::new();
1843 for m in mappings {
1844 if m.source != NO_SOURCE {
1845 referenced_sources.insert(m.source);
1846 }
1847 }
1848 for (i, source) in sm.sources.iter().enumerate() {
1849 if !referenced_sources.contains(&(i as u32)) {
1850 warnings.push(format!("source \"{source}\" (index {i}) is unreferenced"));
1851 }
1852 }
1853
1854 warnings
1855}
1856
1857fn json_quote_into(out: &mut String, s: &str) {
1859 let bytes = s.as_bytes();
1860 out.push('"');
1861
1862 let mut start = 0;
1863 for (i, &b) in bytes.iter().enumerate() {
1864 let escape = match b {
1865 b'"' => "\\\"",
1866 b'\\' => "\\\\",
1867 b'\n' => "\\n",
1868 b'\r' => "\\r",
1869 b'\t' => "\\t",
1870 0x00..=0x1f => {
1871 if start < i {
1872 out.push_str(&s[start..i]);
1873 }
1874 use std::fmt::Write;
1875 let _ = write!(out, "\\u{:04x}", b);
1876 start = i + 1;
1877 continue;
1878 }
1879 _ => continue,
1880 };
1881 if start < i {
1882 out.push_str(&s[start..i]);
1883 }
1884 out.push_str(escape);
1885 start = i + 1;
1886 }
1887
1888 if start < bytes.len() {
1889 out.push_str(&s[start..]);
1890 }
1891
1892 out.push('"');
1893}
1894
1895const B64: [u8; 128] = {
1899 let mut table = [0xFFu8; 128];
1900 let chars = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1901 let mut i = 0u8;
1902 while i < 64 {
1903 table[chars[i as usize] as usize] = i;
1904 i += 1;
1905 }
1906 table
1907};
1908
1909#[inline(always)]
1912fn vlq_fast(bytes: &[u8], pos: &mut usize) -> Result<i64, DecodeError> {
1913 let p = *pos;
1914 if p >= bytes.len() {
1915 return Err(DecodeError::UnexpectedEof { offset: p });
1916 }
1917
1918 let b0 = bytes[p];
1919 if b0 >= 128 {
1920 return Err(DecodeError::InvalidBase64 {
1921 byte: b0,
1922 offset: p,
1923 });
1924 }
1925 let d0 = B64[b0 as usize];
1926 if d0 == 0xFF {
1927 return Err(DecodeError::InvalidBase64 {
1928 byte: b0,
1929 offset: p,
1930 });
1931 }
1932
1933 if (d0 & 0x20) == 0 {
1935 *pos = p + 1;
1936 let val = (d0 >> 1) as i64;
1937 return Ok(if (d0 & 1) != 0 { -val } else { val });
1938 }
1939
1940 let mut result: u64 = (d0 & 0x1F) as u64;
1942 let mut shift: u32 = 5;
1943 let mut i = p + 1;
1944
1945 loop {
1946 if i >= bytes.len() {
1947 return Err(DecodeError::UnexpectedEof { offset: i });
1948 }
1949 let b = bytes[i];
1950 if b >= 128 {
1951 return Err(DecodeError::InvalidBase64 { byte: b, offset: i });
1952 }
1953 let d = B64[b as usize];
1954 if d == 0xFF {
1955 return Err(DecodeError::InvalidBase64 { byte: b, offset: i });
1956 }
1957 i += 1;
1958
1959 if shift >= 64 {
1960 return Err(DecodeError::VlqOverflow { offset: p });
1961 }
1962
1963 result += ((d & 0x1F) as u64) << shift;
1964 shift += 5;
1965
1966 if (d & 0x20) == 0 {
1967 break;
1968 }
1969 }
1970
1971 *pos = i;
1972 let value = if (result & 1) == 1 {
1973 -((result >> 1) as i64)
1974 } else {
1975 (result >> 1) as i64
1976 };
1977 Ok(value)
1978}
1979
1980#[inline(always)]
1981fn vlq_unsigned_fast(bytes: &[u8], pos: &mut usize) -> Result<u64, DecodeError> {
1982 let p = *pos;
1983 if p >= bytes.len() {
1984 return Err(DecodeError::UnexpectedEof { offset: p });
1985 }
1986 let b0 = bytes[p];
1987 if b0 >= 128 {
1988 return Err(DecodeError::InvalidBase64 {
1989 byte: b0,
1990 offset: p,
1991 });
1992 }
1993 let d0 = B64[b0 as usize];
1994 if d0 == 0xFF {
1995 return Err(DecodeError::InvalidBase64 {
1996 byte: b0,
1997 offset: p,
1998 });
1999 }
2000 if (d0 & 0x20) == 0 {
2001 *pos = p + 1;
2002 return Ok(d0 as u64);
2003 }
2004 let mut result: u64 = (d0 & 0x1F) as u64;
2005 let mut shift: u32 = 5;
2006 let mut i = p + 1;
2007 loop {
2008 if i >= bytes.len() {
2009 return Err(DecodeError::UnexpectedEof { offset: i });
2010 }
2011 let b = bytes[i];
2012 if b >= 128 {
2013 return Err(DecodeError::InvalidBase64 { byte: b, offset: i });
2014 }
2015 let d = B64[b as usize];
2016 if d == 0xFF {
2017 return Err(DecodeError::InvalidBase64 { byte: b, offset: i });
2018 }
2019 i += 1;
2020 if shift >= 64 {
2021 return Err(DecodeError::VlqOverflow { offset: p });
2022 }
2023 result |= ((d & 0x1F) as u64) << shift;
2024 shift += 5;
2025 if (d & 0x20) == 0 {
2026 break;
2027 }
2028 }
2029 *pos = i;
2030 Ok(result)
2031}
2032
2033fn decode_range_mappings(
2034 input: &str,
2035 mappings: &mut [Mapping],
2036 line_offsets: &[u32],
2037) -> Result<(), DecodeError> {
2038 let bytes = input.as_bytes();
2039 let len = bytes.len();
2040 let mut pos: usize = 0;
2041 let mut generated_line: usize = 0;
2042 while pos < len {
2043 let line_start = if generated_line + 1 < line_offsets.len() {
2044 line_offsets[generated_line] as usize
2045 } else {
2046 break;
2047 };
2048 let mut mapping_index: u64 = 0;
2049 while pos < len {
2050 let byte = bytes[pos];
2051 if byte == b';' {
2052 pos += 1;
2053 break;
2054 }
2055 if byte == b',' {
2056 pos += 1;
2057 continue;
2058 }
2059 let offset = vlq_unsigned_fast(bytes, &mut pos)?;
2060 mapping_index += offset;
2061 let abs_idx = line_start + mapping_index as usize;
2062 if abs_idx < mappings.len() {
2063 mappings[abs_idx].is_range_mapping = true;
2064 }
2065 }
2066 generated_line += 1;
2067 }
2068 Ok(())
2069}
2070
2071fn decode_mappings(input: &str) -> Result<(Vec<Mapping>, Vec<u32>), DecodeError> {
2072 if input.is_empty() {
2073 return Ok((Vec::new(), vec![0]));
2074 }
2075
2076 let bytes = input.as_bytes();
2077 let len = bytes.len();
2078
2079 let mut semicolons = 0usize;
2081 let mut commas = 0usize;
2082 for &b in bytes {
2083 semicolons += (b == b';') as usize;
2084 commas += (b == b',') as usize;
2085 }
2086 let line_count = semicolons + 1;
2087 let approx_segments = commas + line_count;
2088
2089 let mut mappings: Vec<Mapping> = Vec::with_capacity(approx_segments);
2090 let mut line_offsets: Vec<u32> = Vec::with_capacity(line_count + 1);
2091
2092 let mut source_index: i64 = 0;
2093 let mut original_line: i64 = 0;
2094 let mut original_column: i64 = 0;
2095 let mut name_index: i64 = 0;
2096 let mut generated_line: u32 = 0;
2097 let mut pos: usize = 0;
2098
2099 loop {
2100 line_offsets.push(mappings.len() as u32);
2101 let mut generated_column: i64 = 0;
2102 let mut saw_semicolon = false;
2103
2104 while pos < len {
2105 let byte = bytes[pos];
2106
2107 if byte == b';' {
2108 pos += 1;
2109 saw_semicolon = true;
2110 break;
2111 }
2112
2113 if byte == b',' {
2114 pos += 1;
2115 continue;
2116 }
2117
2118 generated_column += vlq_fast(bytes, &mut pos)?;
2120
2121 if pos < len && bytes[pos] != b',' && bytes[pos] != b';' {
2122 source_index += vlq_fast(bytes, &mut pos)?;
2124 original_line += vlq_fast(bytes, &mut pos)?;
2125 original_column += vlq_fast(bytes, &mut pos)?;
2126
2127 let name = if pos < len && bytes[pos] != b',' && bytes[pos] != b';' {
2129 name_index += vlq_fast(bytes, &mut pos)?;
2130 name_index as u32
2131 } else {
2132 NO_NAME
2133 };
2134
2135 mappings.push(Mapping {
2136 generated_line,
2137 generated_column: generated_column as u32,
2138 source: source_index as u32,
2139 original_line: original_line as u32,
2140 original_column: original_column as u32,
2141 name,
2142 is_range_mapping: false,
2143 });
2144 } else {
2145 mappings.push(Mapping {
2147 generated_line,
2148 generated_column: generated_column as u32,
2149 source: NO_SOURCE,
2150 original_line: 0,
2151 original_column: 0,
2152 name: NO_NAME,
2153 is_range_mapping: false,
2154 });
2155 }
2156 }
2157
2158 if !saw_semicolon {
2159 break;
2160 }
2161 generated_line += 1;
2162 }
2163
2164 line_offsets.push(mappings.len() as u32);
2166
2167 Ok((mappings, line_offsets))
2168}
2169
2170fn decode_mappings_range(
2177 input: &str,
2178 start_line: u32,
2179 end_line: u32,
2180) -> Result<(Vec<Mapping>, Vec<u32>), DecodeError> {
2181 if input.is_empty() || start_line >= end_line {
2182 return Ok((Vec::new(), vec![0; end_line as usize + 1]));
2183 }
2184
2185 let bytes = input.as_bytes();
2186 let len = bytes.len();
2187
2188 let mut mappings: Vec<Mapping> = Vec::new();
2189
2190 let mut source_index: i64 = 0;
2191 let mut original_line: i64 = 0;
2192 let mut original_column: i64 = 0;
2193 let mut name_index: i64 = 0;
2194 let mut generated_line: u32 = 0;
2195 let mut pos: usize = 0;
2196
2197 let mut line_starts: Vec<(u32, u32)> = Vec::new();
2200
2201 loop {
2202 let in_range = generated_line >= start_line && generated_line < end_line;
2203 if in_range {
2204 line_starts.push((generated_line, mappings.len() as u32));
2205 }
2206
2207 let mut generated_column: i64 = 0;
2208 let mut saw_semicolon = false;
2209
2210 while pos < len {
2211 let byte = bytes[pos];
2212
2213 if byte == b';' {
2214 pos += 1;
2215 saw_semicolon = true;
2216 break;
2217 }
2218
2219 if byte == b',' {
2220 pos += 1;
2221 continue;
2222 }
2223
2224 generated_column += vlq_fast(bytes, &mut pos)?;
2226
2227 if pos < len && bytes[pos] != b',' && bytes[pos] != b';' {
2228 source_index += vlq_fast(bytes, &mut pos)?;
2230 original_line += vlq_fast(bytes, &mut pos)?;
2231 original_column += vlq_fast(bytes, &mut pos)?;
2232
2233 let name = if pos < len && bytes[pos] != b',' && bytes[pos] != b';' {
2235 name_index += vlq_fast(bytes, &mut pos)?;
2236 name_index as u32
2237 } else {
2238 NO_NAME
2239 };
2240
2241 if in_range {
2242 mappings.push(Mapping {
2243 generated_line,
2244 generated_column: generated_column as u32,
2245 source: source_index as u32,
2246 original_line: original_line as u32,
2247 original_column: original_column as u32,
2248 name,
2249 is_range_mapping: false,
2250 });
2251 }
2252 } else {
2253 if in_range {
2255 mappings.push(Mapping {
2256 generated_line,
2257 generated_column: generated_column as u32,
2258 source: NO_SOURCE,
2259 original_line: 0,
2260 original_column: 0,
2261 name: NO_NAME,
2262 is_range_mapping: false,
2263 });
2264 }
2265 }
2266 }
2267
2268 if !saw_semicolon {
2269 break;
2270 }
2271 generated_line += 1;
2272
2273 if generated_line >= end_line {
2275 break;
2276 }
2277 }
2278
2279 let total = mappings.len() as u32;
2283 let mut line_offsets: Vec<u32> = vec![total; end_line as usize + 1];
2284
2285 for i in 0..=start_line as usize {
2288 if i < line_offsets.len() {
2289 line_offsets[i] = 0;
2290 }
2291 }
2292
2293 for &(line, offset) in &line_starts {
2295 line_offsets[line as usize] = offset;
2296 }
2297
2298 for i in start_line as usize..=end_line as usize {
2321 if i < line_offsets.len() {
2322 line_offsets[i] = total;
2323 }
2324 }
2325
2326 for &(line, offset) in &line_starts {
2328 line_offsets[line as usize] = offset;
2329 }
2330
2331 let mut next_offset = total;
2335 for i in (start_line as usize..end_line as usize).rev() {
2336 if line_offsets[i] == total {
2337 line_offsets[i] = next_offset;
2339 } else {
2340 next_offset = line_offsets[i];
2341 }
2342 }
2343
2344 for offset in line_offsets.iter_mut().take(start_line as usize) {
2347 *offset = 0;
2348 }
2349
2350 Ok((mappings, line_offsets))
2351}
2352
2353fn build_reverse_index(mappings: &[Mapping]) -> Vec<u32> {
2355 let mut indices: Vec<u32> = (0..mappings.len() as u32)
2356 .filter(|&i| mappings[i as usize].source != NO_SOURCE)
2357 .collect();
2358
2359 indices.sort_unstable_by(|&a, &b| {
2360 let ma = &mappings[a as usize];
2361 let mb = &mappings[b as usize];
2362 ma.source
2363 .cmp(&mb.source)
2364 .then(ma.original_line.cmp(&mb.original_line))
2365 .then(ma.original_column.cmp(&mb.original_column))
2366 });
2367
2368 indices
2369}
2370
2371pub struct MappingsIter<'a> {
2391 bytes: &'a [u8],
2392 len: usize,
2393 pos: usize,
2394 source_index: i64,
2395 original_line: i64,
2396 original_column: i64,
2397 name_index: i64,
2398 generated_line: u32,
2399 generated_column: i64,
2400 done: bool,
2401}
2402
2403impl<'a> MappingsIter<'a> {
2404 pub fn new(vlq: &'a str) -> Self {
2406 let bytes = vlq.as_bytes();
2407 Self {
2408 bytes,
2409 len: bytes.len(),
2410 pos: 0,
2411 source_index: 0,
2412 original_line: 0,
2413 original_column: 0,
2414 name_index: 0,
2415 generated_line: 0,
2416 generated_column: 0,
2417 done: false,
2418 }
2419 }
2420}
2421
2422impl Iterator for MappingsIter<'_> {
2423 type Item = Result<Mapping, DecodeError>;
2424
2425 fn next(&mut self) -> Option<Self::Item> {
2426 if self.done {
2427 return None;
2428 }
2429
2430 loop {
2431 if self.pos >= self.len {
2432 self.done = true;
2433 return None;
2434 }
2435
2436 let byte = self.bytes[self.pos];
2437
2438 if byte == b';' {
2439 self.pos += 1;
2440 self.generated_line += 1;
2441 self.generated_column = 0;
2442 continue;
2443 }
2444
2445 if byte == b',' {
2446 self.pos += 1;
2447 continue;
2448 }
2449
2450 match vlq_fast(self.bytes, &mut self.pos) {
2452 Ok(delta) => self.generated_column += delta,
2453 Err(e) => {
2454 self.done = true;
2455 return Some(Err(e));
2456 }
2457 }
2458
2459 if self.pos < self.len && self.bytes[self.pos] != b',' && self.bytes[self.pos] != b';' {
2460 match vlq_fast(self.bytes, &mut self.pos) {
2462 Ok(delta) => self.source_index += delta,
2463 Err(e) => {
2464 self.done = true;
2465 return Some(Err(e));
2466 }
2467 }
2468 match vlq_fast(self.bytes, &mut self.pos) {
2469 Ok(delta) => self.original_line += delta,
2470 Err(e) => {
2471 self.done = true;
2472 return Some(Err(e));
2473 }
2474 }
2475 match vlq_fast(self.bytes, &mut self.pos) {
2476 Ok(delta) => self.original_column += delta,
2477 Err(e) => {
2478 self.done = true;
2479 return Some(Err(e));
2480 }
2481 }
2482
2483 let name = if self.pos < self.len
2485 && self.bytes[self.pos] != b','
2486 && self.bytes[self.pos] != b';'
2487 {
2488 match vlq_fast(self.bytes, &mut self.pos) {
2489 Ok(delta) => {
2490 self.name_index += delta;
2491 self.name_index as u32
2492 }
2493 Err(e) => {
2494 self.done = true;
2495 return Some(Err(e));
2496 }
2497 }
2498 } else {
2499 NO_NAME
2500 };
2501
2502 return Some(Ok(Mapping {
2503 generated_line: self.generated_line,
2504 generated_column: self.generated_column as u32,
2505 source: self.source_index as u32,
2506 original_line: self.original_line as u32,
2507 original_column: self.original_column as u32,
2508 name,
2509 is_range_mapping: false,
2510 }));
2511 } else {
2512 return Some(Ok(Mapping {
2514 generated_line: self.generated_line,
2515 generated_column: self.generated_column as u32,
2516 source: NO_SOURCE,
2517 original_line: 0,
2518 original_column: 0,
2519 name: NO_NAME,
2520 is_range_mapping: false,
2521 }));
2522 }
2523 }
2524 }
2525}
2526
2527#[cfg(test)]
2530mod tests {
2531 use super::*;
2532
2533 fn simple_map() -> &'static str {
2534 r#"{"version":3,"sources":["input.js"],"names":["hello"],"mappings":"AAAA;AACA,EAAA;AACA"}"#
2535 }
2536
2537 #[test]
2538 fn parse_basic() {
2539 let sm = SourceMap::from_json(simple_map()).unwrap();
2540 assert_eq!(sm.sources, vec!["input.js"]);
2541 assert_eq!(sm.names, vec!["hello"]);
2542 assert_eq!(sm.line_count(), 3);
2543 assert!(sm.mapping_count() > 0);
2544 }
2545
2546 #[test]
2547 fn to_json_roundtrip() {
2548 let json = simple_map();
2549 let sm = SourceMap::from_json(json).unwrap();
2550 let output = sm.to_json();
2551
2552 let sm2 = SourceMap::from_json(&output).unwrap();
2554 assert_eq!(sm2.sources, sm.sources);
2555 assert_eq!(sm2.names, sm.names);
2556 assert_eq!(sm2.mapping_count(), sm.mapping_count());
2557 assert_eq!(sm2.line_count(), sm.line_count());
2558
2559 for m in sm.all_mappings() {
2561 let loc1 = sm.original_position_for(m.generated_line, m.generated_column);
2562 let loc2 = sm2.original_position_for(m.generated_line, m.generated_column);
2563 match (loc1, loc2) {
2564 (Some(a), Some(b)) => {
2565 assert_eq!(a.source, b.source);
2566 assert_eq!(a.line, b.line);
2567 assert_eq!(a.column, b.column);
2568 assert_eq!(a.name, b.name);
2569 }
2570 (None, None) => {}
2571 _ => panic!(
2572 "lookup mismatch at ({}, {})",
2573 m.generated_line, m.generated_column
2574 ),
2575 }
2576 }
2577 }
2578
2579 #[test]
2580 fn to_json_roundtrip_large() {
2581 let json = generate_test_sourcemap(50, 10, 3);
2582 let sm = SourceMap::from_json(&json).unwrap();
2583 let output = sm.to_json();
2584 let sm2 = SourceMap::from_json(&output).unwrap();
2585
2586 assert_eq!(sm2.mapping_count(), sm.mapping_count());
2587
2588 for line in (0..sm.line_count() as u32).step_by(5) {
2590 for col in [0u32, 10, 20, 50] {
2591 let a = sm.original_position_for(line, col);
2592 let b = sm2.original_position_for(line, col);
2593 match (a, b) {
2594 (Some(a), Some(b)) => {
2595 assert_eq!(a.source, b.source);
2596 assert_eq!(a.line, b.line);
2597 assert_eq!(a.column, b.column);
2598 }
2599 (None, None) => {}
2600 _ => panic!("mismatch at ({line}, {col})"),
2601 }
2602 }
2603 }
2604 }
2605
2606 #[test]
2607 fn to_json_preserves_fields() {
2608 let json = r#"{"version":3,"file":"out.js","sourceRoot":"src/","sources":["app.ts"],"sourcesContent":["const x = 1;"],"names":["x"],"mappings":"AAAAA","ignoreList":[0]}"#;
2609 let sm = SourceMap::from_json(json).unwrap();
2610 let output = sm.to_json();
2611
2612 assert!(output.contains(r#""file":"out.js""#));
2613 assert!(output.contains(r#""sourceRoot":"src/""#));
2614 assert!(output.contains(r#""sourcesContent":["const x = 1;"]"#));
2615 assert!(output.contains(r#""ignoreList":[0]"#));
2616
2617 let sm2 = SourceMap::from_json(&output).unwrap();
2619 assert_eq!(sm2.file.as_deref(), Some("out.js"));
2620 assert_eq!(sm2.ignore_list, vec![0]);
2621 }
2622
2623 #[test]
2624 fn original_position_for_exact_match() {
2625 let sm = SourceMap::from_json(simple_map()).unwrap();
2626 let loc = sm.original_position_for(0, 0).unwrap();
2627 assert_eq!(loc.source, 0);
2628 assert_eq!(loc.line, 0);
2629 assert_eq!(loc.column, 0);
2630 }
2631
2632 #[test]
2633 fn original_position_for_column_within_segment() {
2634 let sm = SourceMap::from_json(simple_map()).unwrap();
2635 let loc = sm.original_position_for(1, 5);
2637 assert!(loc.is_some());
2638 }
2639
2640 #[test]
2641 fn original_position_for_nonexistent_line() {
2642 let sm = SourceMap::from_json(simple_map()).unwrap();
2643 assert!(sm.original_position_for(999, 0).is_none());
2644 }
2645
2646 #[test]
2647 fn original_position_for_before_first_mapping() {
2648 let sm = SourceMap::from_json(simple_map()).unwrap();
2650 let loc = sm.original_position_for(1, 0);
2651 let _ = loc;
2654 }
2655
2656 #[test]
2657 fn generated_position_for_basic() {
2658 let sm = SourceMap::from_json(simple_map()).unwrap();
2659 let loc = sm.generated_position_for("input.js", 0, 0).unwrap();
2660 assert_eq!(loc.line, 0);
2661 assert_eq!(loc.column, 0);
2662 }
2663
2664 #[test]
2665 fn generated_position_for_unknown_source() {
2666 let sm = SourceMap::from_json(simple_map()).unwrap();
2667 assert!(sm.generated_position_for("nonexistent.js", 0, 0).is_none());
2668 }
2669
2670 #[test]
2671 fn parse_invalid_version() {
2672 let json = r#"{"version":2,"sources":[],"names":[],"mappings":""}"#;
2673 let err = SourceMap::from_json(json).unwrap_err();
2674 assert!(matches!(err, ParseError::InvalidVersion(2)));
2675 }
2676
2677 #[test]
2678 fn parse_empty_mappings() {
2679 let json = r#"{"version":3,"sources":[],"names":[],"mappings":""}"#;
2680 let sm = SourceMap::from_json(json).unwrap();
2681 assert_eq!(sm.mapping_count(), 0);
2682 assert!(sm.original_position_for(0, 0).is_none());
2683 }
2684
2685 #[test]
2686 fn parse_with_source_root() {
2687 let json = r#"{"version":3,"sourceRoot":"src/","sources":["foo.js"],"names":[],"mappings":"AAAA"}"#;
2688 let sm = SourceMap::from_json(json).unwrap();
2689 assert_eq!(sm.sources, vec!["src/foo.js"]);
2690 }
2691
2692 #[test]
2693 fn parse_with_sources_content() {
2694 let json = r#"{"version":3,"sources":["a.js"],"sourcesContent":["var x = 1;"],"names":[],"mappings":"AAAA"}"#;
2695 let sm = SourceMap::from_json(json).unwrap();
2696 assert_eq!(sm.sources_content, vec![Some("var x = 1;".to_string())]);
2697 }
2698
2699 #[test]
2700 fn mappings_for_line() {
2701 let sm = SourceMap::from_json(simple_map()).unwrap();
2702 let line0 = sm.mappings_for_line(0);
2703 assert!(!line0.is_empty());
2704 let empty = sm.mappings_for_line(999);
2705 assert!(empty.is_empty());
2706 }
2707
2708 #[test]
2709 fn large_sourcemap_lookup() {
2710 let json = generate_test_sourcemap(500, 20, 5);
2712 let sm = SourceMap::from_json(&json).unwrap();
2713
2714 for line in [0, 10, 100, 250, 499] {
2716 let mappings = sm.mappings_for_line(line);
2717 if let Some(m) = mappings.first() {
2718 let loc = sm.original_position_for(line, m.generated_column);
2719 assert!(loc.is_some(), "lookup failed for line {line}");
2720 }
2721 }
2722 }
2723
2724 #[test]
2725 fn reverse_lookup_roundtrip() {
2726 let json = generate_test_sourcemap(100, 10, 3);
2727 let sm = SourceMap::from_json(&json).unwrap();
2728
2729 let mapping = &sm.mappings[50];
2731 if mapping.source != NO_SOURCE {
2732 let source_name = sm.source(mapping.source);
2733 let result = sm.generated_position_for(
2734 source_name,
2735 mapping.original_line,
2736 mapping.original_column,
2737 );
2738 assert!(result.is_some(), "reverse lookup failed");
2739 }
2740 }
2741
2742 #[test]
2743 fn all_generated_positions_for_basic() {
2744 let sm = SourceMap::from_json(simple_map()).unwrap();
2745 let results = sm.all_generated_positions_for("input.js", 0, 0);
2746 assert!(!results.is_empty(), "should find at least one position");
2747 assert_eq!(results[0].line, 0);
2748 assert_eq!(results[0].column, 0);
2749 }
2750
2751 #[test]
2752 fn all_generated_positions_for_unknown_source() {
2753 let sm = SourceMap::from_json(simple_map()).unwrap();
2754 let results = sm.all_generated_positions_for("nonexistent.js", 0, 0);
2755 assert!(results.is_empty());
2756 }
2757
2758 #[test]
2759 fn all_generated_positions_for_no_match() {
2760 let sm = SourceMap::from_json(simple_map()).unwrap();
2761 let results = sm.all_generated_positions_for("input.js", 999, 999);
2762 assert!(results.is_empty());
2763 }
2764
2765 #[test]
2766 fn encode_mappings_roundtrip() {
2767 let json = generate_test_sourcemap(50, 10, 3);
2768 let sm = SourceMap::from_json(&json).unwrap();
2769 let encoded = sm.encode_mappings();
2770 let json2 = format!(
2772 r#"{{"version":3,"sources":{sources},"names":{names},"mappings":"{mappings}"}}"#,
2773 sources = serde_json::to_string(&sm.sources).unwrap(),
2774 names = serde_json::to_string(&sm.names).unwrap(),
2775 mappings = encoded,
2776 );
2777 let sm2 = SourceMap::from_json(&json2).unwrap();
2778 assert_eq!(sm2.mapping_count(), sm.mapping_count());
2779 }
2780
2781 #[test]
2782 fn indexed_source_map() {
2783 let json = r#"{
2784 "version": 3,
2785 "file": "bundle.js",
2786 "sections": [
2787 {
2788 "offset": {"line": 0, "column": 0},
2789 "map": {
2790 "version": 3,
2791 "sources": ["a.js"],
2792 "names": ["foo"],
2793 "mappings": "AAAAA"
2794 }
2795 },
2796 {
2797 "offset": {"line": 10, "column": 0},
2798 "map": {
2799 "version": 3,
2800 "sources": ["b.js"],
2801 "names": ["bar"],
2802 "mappings": "AAAAA"
2803 }
2804 }
2805 ]
2806 }"#;
2807
2808 let sm = SourceMap::from_json(json).unwrap();
2809
2810 assert_eq!(sm.sources.len(), 2);
2812 assert!(sm.sources.contains(&"a.js".to_string()));
2813 assert!(sm.sources.contains(&"b.js".to_string()));
2814
2815 assert_eq!(sm.names.len(), 2);
2817 assert!(sm.names.contains(&"foo".to_string()));
2818 assert!(sm.names.contains(&"bar".to_string()));
2819
2820 let loc = sm.original_position_for(0, 0).unwrap();
2822 assert_eq!(sm.source(loc.source), "a.js");
2823 assert_eq!(loc.line, 0);
2824 assert_eq!(loc.column, 0);
2825
2826 let loc = sm.original_position_for(10, 0).unwrap();
2828 assert_eq!(sm.source(loc.source), "b.js");
2829 assert_eq!(loc.line, 0);
2830 assert_eq!(loc.column, 0);
2831 }
2832
2833 #[test]
2834 fn indexed_source_map_shared_sources() {
2835 let json = r#"{
2837 "version": 3,
2838 "sections": [
2839 {
2840 "offset": {"line": 0, "column": 0},
2841 "map": {
2842 "version": 3,
2843 "sources": ["shared.js"],
2844 "names": [],
2845 "mappings": "AAAA"
2846 }
2847 },
2848 {
2849 "offset": {"line": 5, "column": 0},
2850 "map": {
2851 "version": 3,
2852 "sources": ["shared.js"],
2853 "names": [],
2854 "mappings": "AACA"
2855 }
2856 }
2857 ]
2858 }"#;
2859
2860 let sm = SourceMap::from_json(json).unwrap();
2861
2862 assert_eq!(sm.sources.len(), 1);
2864 assert_eq!(sm.sources[0], "shared.js");
2865
2866 let loc0 = sm.original_position_for(0, 0).unwrap();
2868 let loc5 = sm.original_position_for(5, 0).unwrap();
2869 assert_eq!(loc0.source, loc5.source);
2870 }
2871
2872 #[test]
2873 fn parse_ignore_list() {
2874 let json = r#"{"version":3,"sources":["app.js","node_modules/lib.js"],"names":[],"mappings":"AAAA;ACAA","ignoreList":[1]}"#;
2875 let sm = SourceMap::from_json(json).unwrap();
2876 assert_eq!(sm.ignore_list, vec![1]);
2877 }
2878
2879 fn build_sourcemap_json(
2881 sources: &[&str],
2882 names: &[&str],
2883 mappings_data: &[Vec<Vec<i64>>],
2884 ) -> String {
2885 let converted: Vec<Vec<srcmap_codec::Segment>> = mappings_data
2886 .iter()
2887 .map(|line| {
2888 line.iter()
2889 .map(|seg| srcmap_codec::Segment::from(seg.as_slice()))
2890 .collect()
2891 })
2892 .collect();
2893 let encoded = srcmap_codec::encode(&converted);
2894 format!(
2895 r#"{{"version":3,"sources":[{}],"names":[{}],"mappings":"{}"}}"#,
2896 sources
2897 .iter()
2898 .map(|s| format!("\"{s}\""))
2899 .collect::<Vec<_>>()
2900 .join(","),
2901 names
2902 .iter()
2903 .map(|n| format!("\"{n}\""))
2904 .collect::<Vec<_>>()
2905 .join(","),
2906 encoded,
2907 )
2908 }
2909
2910 #[test]
2913 fn decode_multiple_consecutive_semicolons() {
2914 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;;;AACA"}"#;
2915 let sm = SourceMap::from_json(json).unwrap();
2916 assert_eq!(sm.line_count(), 4);
2917 assert!(sm.mappings_for_line(1).is_empty());
2918 assert!(sm.mappings_for_line(2).is_empty());
2919 assert!(!sm.mappings_for_line(0).is_empty());
2920 assert!(!sm.mappings_for_line(3).is_empty());
2921 }
2922
2923 #[test]
2924 fn decode_trailing_semicolons() {
2925 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;;"}"#;
2926 let sm = SourceMap::from_json(json).unwrap();
2927 assert_eq!(sm.line_count(), 3);
2928 assert!(!sm.mappings_for_line(0).is_empty());
2929 assert!(sm.mappings_for_line(1).is_empty());
2930 assert!(sm.mappings_for_line(2).is_empty());
2931 }
2932
2933 #[test]
2934 fn decode_leading_comma() {
2935 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":",AAAA"}"#;
2936 let sm = SourceMap::from_json(json).unwrap();
2937 assert_eq!(sm.mapping_count(), 1);
2938 let m = &sm.all_mappings()[0];
2939 assert_eq!(m.generated_line, 0);
2940 assert_eq!(m.generated_column, 0);
2941 }
2942
2943 #[test]
2944 fn decode_single_field_segments() {
2945 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A,C"}"#;
2946 let sm = SourceMap::from_json(json).unwrap();
2947 assert_eq!(sm.mapping_count(), 2);
2948 for m in sm.all_mappings() {
2949 assert_eq!(m.source, NO_SOURCE);
2950 }
2951 assert_eq!(sm.all_mappings()[0].generated_column, 0);
2952 assert_eq!(sm.all_mappings()[1].generated_column, 1);
2953 assert!(sm.original_position_for(0, 0).is_none());
2954 assert!(sm.original_position_for(0, 1).is_none());
2955 }
2956
2957 #[test]
2958 fn decode_five_field_segments_with_names() {
2959 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0, 0], vec![10, 0, 0, 5, 1]]];
2960 let json = build_sourcemap_json(&["app.js"], &["foo", "bar"], &mappings_data);
2961 let sm = SourceMap::from_json(&json).unwrap();
2962 assert_eq!(sm.mapping_count(), 2);
2963 assert_eq!(sm.all_mappings()[0].name, 0);
2964 assert_eq!(sm.all_mappings()[1].name, 1);
2965
2966 let loc = sm.original_position_for(0, 0).unwrap();
2967 assert_eq!(loc.name, Some(0));
2968 assert_eq!(sm.name(0), "foo");
2969
2970 let loc = sm.original_position_for(0, 10).unwrap();
2971 assert_eq!(loc.name, Some(1));
2972 assert_eq!(sm.name(1), "bar");
2973 }
2974
2975 #[test]
2976 fn decode_large_vlq_values() {
2977 let mappings_data = vec![vec![vec![500_i64, 0, 1000, 2000]]];
2978 let json = build_sourcemap_json(&["big.js"], &[], &mappings_data);
2979 let sm = SourceMap::from_json(&json).unwrap();
2980 assert_eq!(sm.mapping_count(), 1);
2981 let m = &sm.all_mappings()[0];
2982 assert_eq!(m.generated_column, 500);
2983 assert_eq!(m.original_line, 1000);
2984 assert_eq!(m.original_column, 2000);
2985
2986 let loc = sm.original_position_for(0, 500).unwrap();
2987 assert_eq!(loc.line, 1000);
2988 assert_eq!(loc.column, 2000);
2989 }
2990
2991 #[test]
2992 fn decode_only_semicolons() {
2993 let json = r#"{"version":3,"sources":[],"names":[],"mappings":";;;"}"#;
2994 let sm = SourceMap::from_json(json).unwrap();
2995 assert_eq!(sm.line_count(), 4);
2996 assert_eq!(sm.mapping_count(), 0);
2997 for line in 0..4 {
2998 assert!(sm.mappings_for_line(line).is_empty());
2999 }
3000 }
3001
3002 #[test]
3003 fn decode_mixed_single_and_four_field_segments() {
3004 let mappings_data = vec![vec![srcmap_codec::Segment::four(5, 0, 0, 0)]];
3005 let four_field_encoded = srcmap_codec::encode(&mappings_data);
3006 let combined_mappings = format!("A,{four_field_encoded}");
3007 let json = format!(
3008 r#"{{"version":3,"sources":["x.js"],"names":[],"mappings":"{combined_mappings}"}}"#,
3009 );
3010 let sm = SourceMap::from_json(&json).unwrap();
3011 assert_eq!(sm.mapping_count(), 2);
3012 assert_eq!(sm.all_mappings()[0].source, NO_SOURCE);
3013 assert_eq!(sm.all_mappings()[1].source, 0);
3014 }
3015
3016 #[test]
3019 fn parse_missing_optional_fields() {
3020 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
3021 let sm = SourceMap::from_json(json).unwrap();
3022 assert!(sm.file.is_none());
3023 assert!(sm.source_root.is_none());
3024 assert!(sm.sources_content.is_empty());
3025 assert!(sm.ignore_list.is_empty());
3026 }
3027
3028 #[test]
3029 fn parse_with_file_field() {
3030 let json =
3031 r#"{"version":3,"file":"output.js","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
3032 let sm = SourceMap::from_json(json).unwrap();
3033 assert_eq!(sm.file.as_deref(), Some("output.js"));
3034 }
3035
3036 #[test]
3037 fn parse_null_entries_in_sources() {
3038 let json = r#"{"version":3,"sources":["a.js",null,"c.js"],"names":[],"mappings":"AAAA"}"#;
3039 let sm = SourceMap::from_json(json).unwrap();
3040 assert_eq!(sm.sources.len(), 3);
3041 assert_eq!(sm.sources[0], "a.js");
3042 assert_eq!(sm.sources[1], "");
3043 assert_eq!(sm.sources[2], "c.js");
3044 }
3045
3046 #[test]
3047 fn parse_null_entries_in_sources_with_source_root() {
3048 let json = r#"{"version":3,"sourceRoot":"lib/","sources":["a.js",null],"names":[],"mappings":"AAAA"}"#;
3049 let sm = SourceMap::from_json(json).unwrap();
3050 assert_eq!(sm.sources[0], "lib/a.js");
3051 assert_eq!(sm.sources[1], "");
3052 }
3053
3054 #[test]
3055 fn parse_empty_names_array() {
3056 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
3057 let sm = SourceMap::from_json(json).unwrap();
3058 assert!(sm.names.is_empty());
3059 }
3060
3061 #[test]
3062 fn parse_invalid_json() {
3063 let result = SourceMap::from_json("not valid json");
3064 assert!(result.is_err());
3065 assert!(matches!(result.unwrap_err(), ParseError::Json(_)));
3066 }
3067
3068 #[test]
3069 fn parse_json_missing_version() {
3070 let result = SourceMap::from_json(r#"{"sources":[],"names":[],"mappings":""}"#);
3071 assert!(result.is_err());
3072 }
3073
3074 #[test]
3075 fn parse_multiple_sources_overlapping_original_positions() {
3076 let mappings_data = vec![vec![vec![0_i64, 0, 5, 10], vec![10, 1, 5, 10]]];
3077 let json = build_sourcemap_json(&["a.js", "b.js"], &[], &mappings_data);
3078 let sm = SourceMap::from_json(&json).unwrap();
3079
3080 let loc0 = sm.original_position_for(0, 0).unwrap();
3081 assert_eq!(loc0.source, 0);
3082 assert_eq!(sm.source(loc0.source), "a.js");
3083
3084 let loc1 = sm.original_position_for(0, 10).unwrap();
3085 assert_eq!(loc1.source, 1);
3086 assert_eq!(sm.source(loc1.source), "b.js");
3087
3088 assert_eq!(loc0.line, loc1.line);
3089 assert_eq!(loc0.column, loc1.column);
3090 }
3091
3092 #[test]
3093 fn parse_sources_content_with_null_entries() {
3094 let json = r#"{"version":3,"sources":["a.js","b.js"],"sourcesContent":["content a",null],"names":[],"mappings":"AAAA"}"#;
3095 let sm = SourceMap::from_json(json).unwrap();
3096 assert_eq!(sm.sources_content.len(), 2);
3097 assert_eq!(sm.sources_content[0], Some("content a".to_string()));
3098 assert_eq!(sm.sources_content[1], None);
3099 }
3100
3101 #[test]
3102 fn parse_empty_sources_and_names() {
3103 let json = r#"{"version":3,"sources":[],"names":[],"mappings":""}"#;
3104 let sm = SourceMap::from_json(json).unwrap();
3105 assert!(sm.sources.is_empty());
3106 assert!(sm.names.is_empty());
3107 assert_eq!(sm.mapping_count(), 0);
3108 }
3109
3110 #[test]
3113 fn lookup_exact_match() {
3114 let mappings_data = vec![vec![
3115 vec![0_i64, 0, 10, 20],
3116 vec![5, 0, 10, 25],
3117 vec![15, 0, 11, 0],
3118 ]];
3119 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3120 let sm = SourceMap::from_json(&json).unwrap();
3121
3122 let loc = sm.original_position_for(0, 5).unwrap();
3123 assert_eq!(loc.line, 10);
3124 assert_eq!(loc.column, 25);
3125 }
3126
3127 #[test]
3128 fn lookup_before_first_segment() {
3129 let mappings_data = vec![vec![vec![5_i64, 0, 0, 0]]];
3130 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3131 let sm = SourceMap::from_json(&json).unwrap();
3132
3133 assert!(sm.original_position_for(0, 0).is_none());
3134 assert!(sm.original_position_for(0, 4).is_none());
3135 }
3136
3137 #[test]
3138 fn lookup_between_segments() {
3139 let mappings_data = vec![vec![
3140 vec![0_i64, 0, 1, 0],
3141 vec![10, 0, 2, 0],
3142 vec![20, 0, 3, 0],
3143 ]];
3144 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3145 let sm = SourceMap::from_json(&json).unwrap();
3146
3147 let loc = sm.original_position_for(0, 7).unwrap();
3148 assert_eq!(loc.line, 1);
3149 assert_eq!(loc.column, 0);
3150
3151 let loc = sm.original_position_for(0, 15).unwrap();
3152 assert_eq!(loc.line, 2);
3153 assert_eq!(loc.column, 0);
3154 }
3155
3156 #[test]
3157 fn lookup_after_last_segment() {
3158 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0], vec![10, 0, 1, 5]]];
3159 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3160 let sm = SourceMap::from_json(&json).unwrap();
3161
3162 let loc = sm.original_position_for(0, 100).unwrap();
3163 assert_eq!(loc.line, 1);
3164 assert_eq!(loc.column, 5);
3165 }
3166
3167 #[test]
3168 fn lookup_empty_lines_no_mappings() {
3169 let mappings_data = vec![
3170 vec![vec![0_i64, 0, 0, 0]],
3171 vec![],
3172 vec![vec![0_i64, 0, 2, 0]],
3173 ];
3174 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3175 let sm = SourceMap::from_json(&json).unwrap();
3176
3177 assert!(sm.original_position_for(1, 0).is_none());
3178 assert!(sm.original_position_for(1, 10).is_none());
3179 assert!(sm.original_position_for(0, 0).is_some());
3180 assert!(sm.original_position_for(2, 0).is_some());
3181 }
3182
3183 #[test]
3184 fn lookup_line_with_single_mapping() {
3185 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0]]];
3186 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3187 let sm = SourceMap::from_json(&json).unwrap();
3188
3189 let loc = sm.original_position_for(0, 0).unwrap();
3190 assert_eq!(loc.line, 0);
3191 assert_eq!(loc.column, 0);
3192
3193 let loc = sm.original_position_for(0, 50).unwrap();
3194 assert_eq!(loc.line, 0);
3195 assert_eq!(loc.column, 0);
3196 }
3197
3198 #[test]
3199 fn lookup_column_0_vs_column_nonzero() {
3200 let mappings_data = vec![vec![vec![0_i64, 0, 10, 0], vec![8, 0, 20, 5]]];
3201 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3202 let sm = SourceMap::from_json(&json).unwrap();
3203
3204 let loc0 = sm.original_position_for(0, 0).unwrap();
3205 assert_eq!(loc0.line, 10);
3206 assert_eq!(loc0.column, 0);
3207
3208 let loc8 = sm.original_position_for(0, 8).unwrap();
3209 assert_eq!(loc8.line, 20);
3210 assert_eq!(loc8.column, 5);
3211
3212 let loc4 = sm.original_position_for(0, 4).unwrap();
3213 assert_eq!(loc4.line, 10);
3214 }
3215
3216 #[test]
3217 fn lookup_beyond_last_line() {
3218 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0]]];
3219 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3220 let sm = SourceMap::from_json(&json).unwrap();
3221
3222 assert!(sm.original_position_for(1, 0).is_none());
3223 assert!(sm.original_position_for(100, 0).is_none());
3224 }
3225
3226 #[test]
3227 fn lookup_single_field_returns_none() {
3228 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A"}"#;
3229 let sm = SourceMap::from_json(json).unwrap();
3230 assert_eq!(sm.mapping_count(), 1);
3231 assert!(sm.original_position_for(0, 0).is_none());
3232 }
3233
3234 #[test]
3237 fn reverse_lookup_exact_match() {
3238 let mappings_data = vec![
3239 vec![vec![0_i64, 0, 0, 0]],
3240 vec![vec![4, 0, 1, 0], vec![10, 0, 1, 8]],
3241 vec![vec![0, 0, 2, 0]],
3242 ];
3243 let json = build_sourcemap_json(&["main.js"], &[], &mappings_data);
3244 let sm = SourceMap::from_json(&json).unwrap();
3245
3246 let loc = sm.generated_position_for("main.js", 1, 8).unwrap();
3247 assert_eq!(loc.line, 1);
3248 assert_eq!(loc.column, 10);
3249 }
3250
3251 #[test]
3252 fn reverse_lookup_no_match() {
3253 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0], vec![10, 0, 0, 10]]];
3254 let json = build_sourcemap_json(&["main.js"], &[], &mappings_data);
3255 let sm = SourceMap::from_json(&json).unwrap();
3256
3257 assert!(sm.generated_position_for("main.js", 99, 0).is_none());
3258 }
3259
3260 #[test]
3261 fn reverse_lookup_unknown_source() {
3262 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0]]];
3263 let json = build_sourcemap_json(&["main.js"], &[], &mappings_data);
3264 let sm = SourceMap::from_json(&json).unwrap();
3265
3266 assert!(sm.generated_position_for("unknown.js", 0, 0).is_none());
3267 }
3268
3269 #[test]
3270 fn reverse_lookup_multiple_mappings_same_original() {
3271 let mappings_data = vec![vec![vec![0_i64, 0, 5, 10]], vec![vec![20, 0, 5, 10]]];
3272 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3273 let sm = SourceMap::from_json(&json).unwrap();
3274
3275 let loc = sm.generated_position_for("src.js", 5, 10);
3276 assert!(loc.is_some());
3277 let loc = loc.unwrap();
3278 assert!(
3279 (loc.line == 0 && loc.column == 0) || (loc.line == 1 && loc.column == 20),
3280 "Expected (0,0) or (1,20), got ({},{})",
3281 loc.line,
3282 loc.column
3283 );
3284 }
3285
3286 #[test]
3287 fn reverse_lookup_with_multiple_sources() {
3288 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0], vec![10, 1, 0, 0]]];
3289 let json = build_sourcemap_json(&["a.js", "b.js"], &[], &mappings_data);
3290 let sm = SourceMap::from_json(&json).unwrap();
3291
3292 let loc_a = sm.generated_position_for("a.js", 0, 0).unwrap();
3293 assert_eq!(loc_a.line, 0);
3294 assert_eq!(loc_a.column, 0);
3295
3296 let loc_b = sm.generated_position_for("b.js", 0, 0).unwrap();
3297 assert_eq!(loc_b.line, 0);
3298 assert_eq!(loc_b.column, 10);
3299 }
3300
3301 #[test]
3302 fn reverse_lookup_skips_single_field_segments() {
3303 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A,KAAAA"}"#;
3304 let sm = SourceMap::from_json(json).unwrap();
3305
3306 let loc = sm.generated_position_for("a.js", 0, 0).unwrap();
3307 assert_eq!(loc.line, 0);
3308 assert_eq!(loc.column, 5);
3309 }
3310
3311 #[test]
3312 fn reverse_lookup_finds_each_original_line() {
3313 let mappings_data = vec![
3314 vec![vec![0_i64, 0, 0, 0]],
3315 vec![vec![0, 0, 1, 0]],
3316 vec![vec![0, 0, 2, 0]],
3317 vec![vec![0, 0, 3, 0]],
3318 ];
3319 let json = build_sourcemap_json(&["x.js"], &[], &mappings_data);
3320 let sm = SourceMap::from_json(&json).unwrap();
3321
3322 for orig_line in 0..4 {
3323 let loc = sm.generated_position_for("x.js", orig_line, 0).unwrap();
3324 assert_eq!(
3325 loc.line, orig_line,
3326 "reverse lookup for orig line {orig_line}"
3327 );
3328 assert_eq!(loc.column, 0);
3329 }
3330 }
3331
3332 #[test]
3335 fn parse_with_ignore_list_multiple() {
3336 let json = r#"{"version":3,"sources":["app.js","node_modules/lib.js","vendor.js"],"names":[],"mappings":"AAAA","ignoreList":[1,2]}"#;
3337 let sm = SourceMap::from_json(json).unwrap();
3338 assert_eq!(sm.ignore_list, vec![1, 2]);
3339 }
3340
3341 #[test]
3342 fn parse_with_empty_ignore_list() {
3343 let json =
3344 r#"{"version":3,"sources":["app.js"],"names":[],"mappings":"AAAA","ignoreList":[]}"#;
3345 let sm = SourceMap::from_json(json).unwrap();
3346 assert!(sm.ignore_list.is_empty());
3347 }
3348
3349 #[test]
3350 fn parse_without_ignore_list_field() {
3351 let json = r#"{"version":3,"sources":["app.js"],"names":[],"mappings":"AAAA"}"#;
3352 let sm = SourceMap::from_json(json).unwrap();
3353 assert!(sm.ignore_list.is_empty());
3354 }
3355
3356 #[test]
3359 fn source_index_lookup() {
3360 let json = r#"{"version":3,"sources":["a.js","b.js","c.js"],"names":[],"mappings":"AAAA"}"#;
3361 let sm = SourceMap::from_json(json).unwrap();
3362 assert_eq!(sm.source_index("a.js"), Some(0));
3363 assert_eq!(sm.source_index("b.js"), Some(1));
3364 assert_eq!(sm.source_index("c.js"), Some(2));
3365 assert_eq!(sm.source_index("d.js"), None);
3366 }
3367
3368 #[test]
3369 fn all_mappings_returns_complete_list() {
3370 let mappings_data = vec![
3371 vec![vec![0_i64, 0, 0, 0], vec![5, 0, 0, 5]],
3372 vec![vec![0, 0, 1, 0]],
3373 ];
3374 let json = build_sourcemap_json(&["x.js"], &[], &mappings_data);
3375 let sm = SourceMap::from_json(&json).unwrap();
3376 assert_eq!(sm.all_mappings().len(), 3);
3377 assert_eq!(sm.mapping_count(), 3);
3378 }
3379
3380 #[test]
3381 fn line_count_matches_decoded_lines() {
3382 let mappings_data = vec![
3383 vec![vec![0_i64, 0, 0, 0]],
3384 vec![],
3385 vec![vec![0_i64, 0, 2, 0]],
3386 vec![],
3387 vec![],
3388 ];
3389 let json = build_sourcemap_json(&["x.js"], &[], &mappings_data);
3390 let sm = SourceMap::from_json(&json).unwrap();
3391 assert_eq!(sm.line_count(), 5);
3392 }
3393
3394 #[test]
3395 fn parse_error_display() {
3396 let err = ParseError::InvalidVersion(5);
3397 assert_eq!(format!("{err}"), "unsupported source map version: 5");
3398
3399 let json_err = SourceMap::from_json("{}").unwrap_err();
3400 let display = format!("{json_err}");
3401 assert!(display.contains("JSON parse error") || display.contains("missing field"));
3402 }
3403
3404 #[test]
3405 fn original_position_name_none_for_four_field() {
3406 let mappings_data = vec![vec![vec![0_i64, 0, 5, 10]]];
3407 let json = build_sourcemap_json(&["a.js"], &["unused_name"], &mappings_data);
3408 let sm = SourceMap::from_json(&json).unwrap();
3409
3410 let loc = sm.original_position_for(0, 0).unwrap();
3411 assert!(loc.name.is_none());
3412 }
3413
3414 #[test]
3415 fn forward_and_reverse_roundtrip_comprehensive() {
3416 let mappings_data = vec![
3417 vec![vec![0_i64, 0, 0, 0], vec![10, 0, 0, 10], vec![20, 1, 5, 0]],
3418 vec![vec![0, 0, 1, 0], vec![5, 1, 6, 3]],
3419 vec![vec![0, 0, 2, 0]],
3420 ];
3421 let json = build_sourcemap_json(&["a.js", "b.js"], &[], &mappings_data);
3422 let sm = SourceMap::from_json(&json).unwrap();
3423
3424 for m in sm.all_mappings() {
3425 if m.source == NO_SOURCE {
3426 continue;
3427 }
3428 let source_name = sm.source(m.source);
3429
3430 let orig = sm
3431 .original_position_for(m.generated_line, m.generated_column)
3432 .unwrap();
3433 assert_eq!(orig.source, m.source);
3434 assert_eq!(orig.line, m.original_line);
3435 assert_eq!(orig.column, m.original_column);
3436
3437 let gen_loc = sm
3438 .generated_position_for(source_name, m.original_line, m.original_column)
3439 .unwrap();
3440 assert_eq!(gen_loc.line, m.generated_line);
3441 assert_eq!(gen_loc.column, m.generated_column);
3442 }
3443 }
3444
3445 #[test]
3450 fn source_root_with_multiple_sources() {
3451 let json = r#"{"version":3,"sourceRoot":"lib/","sources":["a.js","b.js","c.js"],"names":[],"mappings":"AAAA,KACA,KACA"}"#;
3452 let sm = SourceMap::from_json(json).unwrap();
3453 assert_eq!(sm.sources, vec!["lib/a.js", "lib/b.js", "lib/c.js"]);
3454 }
3455
3456 #[test]
3457 fn source_root_empty_string() {
3458 let json =
3459 r#"{"version":3,"sourceRoot":"","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
3460 let sm = SourceMap::from_json(json).unwrap();
3461 assert_eq!(sm.sources, vec!["a.js"]);
3462 }
3463
3464 #[test]
3465 fn source_root_preserved_in_to_json() {
3466 let json =
3467 r#"{"version":3,"sourceRoot":"src/","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
3468 let sm = SourceMap::from_json(json).unwrap();
3469 let output = sm.to_json();
3470 assert!(output.contains(r#""sourceRoot":"src/""#));
3471 }
3472
3473 #[test]
3474 fn source_root_reverse_lookup_uses_prefixed_name() {
3475 let json =
3476 r#"{"version":3,"sourceRoot":"src/","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
3477 let sm = SourceMap::from_json(json).unwrap();
3478 assert!(sm.generated_position_for("src/a.js", 0, 0).is_some());
3480 assert!(sm.generated_position_for("a.js", 0, 0).is_none());
3481 }
3482
3483 #[test]
3484 fn source_root_with_trailing_slash() {
3485 let json =
3486 r#"{"version":3,"sourceRoot":"src/","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
3487 let sm = SourceMap::from_json(json).unwrap();
3488 assert_eq!(sm.sources[0], "src/a.js");
3489 }
3490
3491 #[test]
3492 fn source_root_without_trailing_slash() {
3493 let json =
3494 r#"{"version":3,"sourceRoot":"src","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
3495 let sm = SourceMap::from_json(json).unwrap();
3496 assert_eq!(sm.sources[0], "srca.js"); }
3498
3499 #[test]
3502 fn parse_empty_json_object() {
3503 let result = SourceMap::from_json("{}");
3505 assert!(result.is_err());
3506 }
3507
3508 #[test]
3509 fn parse_version_0() {
3510 let json = r#"{"version":0,"sources":[],"names":[],"mappings":""}"#;
3511 assert!(matches!(
3512 SourceMap::from_json(json).unwrap_err(),
3513 ParseError::InvalidVersion(0)
3514 ));
3515 }
3516
3517 #[test]
3518 fn parse_version_4() {
3519 let json = r#"{"version":4,"sources":[],"names":[],"mappings":""}"#;
3520 assert!(matches!(
3521 SourceMap::from_json(json).unwrap_err(),
3522 ParseError::InvalidVersion(4)
3523 ));
3524 }
3525
3526 #[test]
3527 fn parse_extra_unknown_fields_ignored() {
3528 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","x_custom_field":true,"x_debug":{"foo":"bar"}}"#;
3529 let sm = SourceMap::from_json(json).unwrap();
3530 assert_eq!(sm.mapping_count(), 1);
3531 }
3532
3533 #[test]
3534 fn parse_vlq_error_propagated() {
3535 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AA!A"}"#;
3537 let result = SourceMap::from_json(json);
3538 assert!(result.is_err());
3539 assert!(matches!(result.unwrap_err(), ParseError::Vlq(_)));
3540 }
3541
3542 #[test]
3543 fn parse_truncated_vlq_error() {
3544 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"g"}"#;
3546 let result = SourceMap::from_json(json);
3547 assert!(result.is_err());
3548 }
3549
3550 #[test]
3553 fn to_json_produces_valid_json() {
3554 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]}"#;
3555 let sm = SourceMap::from_json(json).unwrap();
3556 let output = sm.to_json();
3557 let _: serde_json::Value = serde_json::from_str(&output).unwrap();
3559 }
3560
3561 #[test]
3562 fn to_json_escapes_special_chars() {
3563 let json = r#"{"version":3,"sources":["path/with\"quotes.js"],"sourcesContent":["line1\nline2\ttab\\backslash"],"names":[],"mappings":"AAAA"}"#;
3564 let sm = SourceMap::from_json(json).unwrap();
3565 let output = sm.to_json();
3566 let _: serde_json::Value = serde_json::from_str(&output).unwrap();
3567 let sm2 = SourceMap::from_json(&output).unwrap();
3568 assert_eq!(
3569 sm2.sources_content[0].as_deref(),
3570 Some("line1\nline2\ttab\\backslash")
3571 );
3572 }
3573
3574 #[test]
3575 fn to_json_empty_map() {
3576 let json = r#"{"version":3,"sources":[],"names":[],"mappings":""}"#;
3577 let sm = SourceMap::from_json(json).unwrap();
3578 let output = sm.to_json();
3579 let sm2 = SourceMap::from_json(&output).unwrap();
3580 assert_eq!(sm2.mapping_count(), 0);
3581 assert!(sm2.sources.is_empty());
3582 }
3583
3584 #[test]
3585 fn to_json_roundtrip_with_names() {
3586 let mappings_data = vec![vec![
3587 vec![0_i64, 0, 0, 0, 0],
3588 vec![10, 0, 0, 10, 1],
3589 vec![20, 0, 1, 0, 2],
3590 ]];
3591 let json = build_sourcemap_json(&["src.js"], &["foo", "bar", "baz"], &mappings_data);
3592 let sm = SourceMap::from_json(&json).unwrap();
3593 let output = sm.to_json();
3594 let sm2 = SourceMap::from_json(&output).unwrap();
3595
3596 for m in sm2.all_mappings() {
3597 if m.source != NO_SOURCE && m.name != NO_NAME {
3598 let loc = sm2
3599 .original_position_for(m.generated_line, m.generated_column)
3600 .unwrap();
3601 assert!(loc.name.is_some());
3602 }
3603 }
3604 }
3605
3606 #[test]
3609 fn indexed_source_map_column_offset() {
3610 let json = r#"{
3611 "version": 3,
3612 "sections": [
3613 {
3614 "offset": {"line": 0, "column": 10},
3615 "map": {
3616 "version": 3,
3617 "sources": ["a.js"],
3618 "names": [],
3619 "mappings": "AAAA"
3620 }
3621 }
3622 ]
3623 }"#;
3624 let sm = SourceMap::from_json(json).unwrap();
3625 let loc = sm.original_position_for(0, 10).unwrap();
3627 assert_eq!(loc.line, 0);
3628 assert_eq!(loc.column, 0);
3629 assert!(sm.original_position_for(0, 0).is_none());
3631 }
3632
3633 #[test]
3634 fn indexed_source_map_column_offset_only_first_line() {
3635 let json = r#"{
3637 "version": 3,
3638 "sections": [
3639 {
3640 "offset": {"line": 0, "column": 20},
3641 "map": {
3642 "version": 3,
3643 "sources": ["a.js"],
3644 "names": [],
3645 "mappings": "AAAA;AAAA"
3646 }
3647 }
3648 ]
3649 }"#;
3650 let sm = SourceMap::from_json(json).unwrap();
3651 let loc = sm.original_position_for(0, 20).unwrap();
3653 assert_eq!(loc.column, 0);
3654 let loc = sm.original_position_for(1, 0).unwrap();
3656 assert_eq!(loc.column, 0);
3657 }
3658
3659 #[test]
3660 fn indexed_source_map_empty_section() {
3661 let json = r#"{
3662 "version": 3,
3663 "sections": [
3664 {
3665 "offset": {"line": 0, "column": 0},
3666 "map": {
3667 "version": 3,
3668 "sources": [],
3669 "names": [],
3670 "mappings": ""
3671 }
3672 },
3673 {
3674 "offset": {"line": 5, "column": 0},
3675 "map": {
3676 "version": 3,
3677 "sources": ["b.js"],
3678 "names": [],
3679 "mappings": "AAAA"
3680 }
3681 }
3682 ]
3683 }"#;
3684 let sm = SourceMap::from_json(json).unwrap();
3685 assert_eq!(sm.sources.len(), 1);
3686 let loc = sm.original_position_for(5, 0).unwrap();
3687 assert_eq!(sm.source(loc.source), "b.js");
3688 }
3689
3690 #[test]
3691 fn indexed_source_map_with_sources_content() {
3692 let json = r#"{
3693 "version": 3,
3694 "sections": [
3695 {
3696 "offset": {"line": 0, "column": 0},
3697 "map": {
3698 "version": 3,
3699 "sources": ["a.js"],
3700 "sourcesContent": ["var a = 1;"],
3701 "names": [],
3702 "mappings": "AAAA"
3703 }
3704 },
3705 {
3706 "offset": {"line": 5, "column": 0},
3707 "map": {
3708 "version": 3,
3709 "sources": ["b.js"],
3710 "sourcesContent": ["var b = 2;"],
3711 "names": [],
3712 "mappings": "AAAA"
3713 }
3714 }
3715 ]
3716 }"#;
3717 let sm = SourceMap::from_json(json).unwrap();
3718 assert_eq!(sm.sources_content.len(), 2);
3719 assert_eq!(sm.sources_content[0], Some("var a = 1;".to_string()));
3720 assert_eq!(sm.sources_content[1], Some("var b = 2;".to_string()));
3721 }
3722
3723 #[test]
3724 fn indexed_source_map_with_ignore_list() {
3725 let json = r#"{
3726 "version": 3,
3727 "sections": [
3728 {
3729 "offset": {"line": 0, "column": 0},
3730 "map": {
3731 "version": 3,
3732 "sources": ["app.js", "vendor.js"],
3733 "names": [],
3734 "mappings": "AAAA",
3735 "ignoreList": [1]
3736 }
3737 }
3738 ]
3739 }"#;
3740 let sm = SourceMap::from_json(json).unwrap();
3741 assert!(!sm.ignore_list.is_empty());
3742 }
3743
3744 #[test]
3747 fn lookup_max_column_on_line() {
3748 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0]]];
3749 let json = build_sourcemap_json(&["a.js"], &[], &mappings_data);
3750 let sm = SourceMap::from_json(&json).unwrap();
3751 let loc = sm.original_position_for(0, u32::MAX - 1).unwrap();
3753 assert_eq!(loc.line, 0);
3754 assert_eq!(loc.column, 0);
3755 }
3756
3757 #[test]
3758 fn mappings_for_line_beyond_end() {
3759 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
3760 let sm = SourceMap::from_json(json).unwrap();
3761 assert!(sm.mappings_for_line(u32::MAX).is_empty());
3762 }
3763
3764 #[test]
3765 fn source_with_unicode_path() {
3766 let json =
3767 r#"{"version":3,"sources":["src/日本語.ts"],"names":["変数"],"mappings":"AAAAA"}"#;
3768 let sm = SourceMap::from_json(json).unwrap();
3769 assert_eq!(sm.sources[0], "src/日本語.ts");
3770 assert_eq!(sm.names[0], "変数");
3771 let loc = sm.original_position_for(0, 0).unwrap();
3772 assert_eq!(sm.source(loc.source), "src/日本語.ts");
3773 assert_eq!(sm.name(loc.name.unwrap()), "変数");
3774 }
3775
3776 #[test]
3777 fn to_json_roundtrip_unicode_sources() {
3778 let json = r#"{"version":3,"sources":["src/日本語.ts"],"sourcesContent":["const 変数 = 1;"],"names":["変数"],"mappings":"AAAAA"}"#;
3779 let sm = SourceMap::from_json(json).unwrap();
3780 let output = sm.to_json();
3781 let _: serde_json::Value = serde_json::from_str(&output).unwrap();
3782 let sm2 = SourceMap::from_json(&output).unwrap();
3783 assert_eq!(sm2.sources[0], "src/日本語.ts");
3784 assert_eq!(sm2.sources_content[0], Some("const 変数 = 1;".to_string()));
3785 }
3786
3787 #[test]
3788 fn many_sources_lookup() {
3789 let sources: Vec<String> = (0..100).map(|i| format!("src/file{i}.js")).collect();
3791 let source_strs: Vec<&str> = sources.iter().map(|s| s.as_str()).collect();
3792 let mappings_data = vec![
3793 sources
3794 .iter()
3795 .enumerate()
3796 .map(|(i, _)| vec![(i * 10) as i64, i as i64, 0, 0])
3797 .collect::<Vec<_>>(),
3798 ];
3799 let json = build_sourcemap_json(&source_strs, &[], &mappings_data);
3800 let sm = SourceMap::from_json(&json).unwrap();
3801
3802 for (i, src) in sources.iter().enumerate() {
3803 assert_eq!(sm.source_index(src), Some(i as u32));
3804 }
3805 }
3806
3807 #[test]
3808 fn clone_sourcemap() {
3809 let json = r#"{"version":3,"sources":["a.js"],"names":["x"],"mappings":"AAAAA"}"#;
3810 let sm = SourceMap::from_json(json).unwrap();
3811 let sm2 = sm.clone();
3812 assert_eq!(sm2.sources, sm.sources);
3813 assert_eq!(sm2.mapping_count(), sm.mapping_count());
3814 let loc = sm2.original_position_for(0, 0).unwrap();
3815 assert_eq!(sm2.source(loc.source), "a.js");
3816 }
3817
3818 #[test]
3819 fn parse_debug_id() {
3820 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","debugId":"85314830-023f-4cf1-a267-535f4e37bb17"}"#;
3821 let sm = SourceMap::from_json(json).unwrap();
3822 assert_eq!(
3823 sm.debug_id.as_deref(),
3824 Some("85314830-023f-4cf1-a267-535f4e37bb17")
3825 );
3826 }
3827
3828 #[test]
3829 fn parse_debug_id_snake_case() {
3830 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","debug_id":"85314830-023f-4cf1-a267-535f4e37bb17"}"#;
3831 let sm = SourceMap::from_json(json).unwrap();
3832 assert_eq!(
3833 sm.debug_id.as_deref(),
3834 Some("85314830-023f-4cf1-a267-535f4e37bb17")
3835 );
3836 }
3837
3838 #[test]
3839 fn parse_no_debug_id() {
3840 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
3841 let sm = SourceMap::from_json(json).unwrap();
3842 assert_eq!(sm.debug_id, None);
3843 }
3844
3845 #[test]
3846 fn debug_id_roundtrip() {
3847 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","debugId":"85314830-023f-4cf1-a267-535f4e37bb17"}"#;
3848 let sm = SourceMap::from_json(json).unwrap();
3849 let output = sm.to_json();
3850 assert!(output.contains(r#""debugId":"85314830-023f-4cf1-a267-535f4e37bb17""#));
3851 let sm2 = SourceMap::from_json(&output).unwrap();
3852 assert_eq!(sm.debug_id, sm2.debug_id);
3853 }
3854
3855 #[test]
3856 fn debug_id_not_in_json_when_absent() {
3857 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
3858 let sm = SourceMap::from_json(json).unwrap();
3859 let output = sm.to_json();
3860 assert!(!output.contains("debugId"));
3861 }
3862
3863 fn generate_test_sourcemap(lines: usize, segs_per_line: usize, num_sources: usize) -> String {
3865 let sources: Vec<String> = (0..num_sources)
3866 .map(|i| format!("src/file{i}.js"))
3867 .collect();
3868 let names: Vec<String> = (0..20).map(|i| format!("var{i}")).collect();
3869
3870 let mut mappings_parts = Vec::with_capacity(lines);
3871 let mut gen_col;
3872 let mut src: i64 = 0;
3873 let mut src_line: i64 = 0;
3874 let mut src_col: i64;
3875 let mut name: i64 = 0;
3876
3877 for _ in 0..lines {
3878 gen_col = 0i64;
3879 let mut line_parts = Vec::with_capacity(segs_per_line);
3880
3881 for s in 0..segs_per_line {
3882 let gc_delta = 2 + (s as i64 * 3) % 20;
3883 gen_col += gc_delta;
3884
3885 let src_delta = if s % 7 == 0 { 1 } else { 0 };
3886 src = (src + src_delta) % num_sources as i64;
3887
3888 src_line += 1;
3889 src_col = (s as i64 * 5 + 1) % 30;
3890
3891 let has_name = s % 4 == 0;
3892 if has_name {
3893 name = (name + 1) % names.len() as i64;
3894 }
3895
3896 let segment = if has_name {
3898 srcmap_codec::Segment::five(gen_col, src, src_line, src_col, name)
3899 } else {
3900 srcmap_codec::Segment::four(gen_col, src, src_line, src_col)
3901 };
3902
3903 line_parts.push(segment);
3904 }
3905
3906 mappings_parts.push(line_parts);
3907 }
3908
3909 let encoded = srcmap_codec::encode(&mappings_parts);
3910
3911 format!(
3912 r#"{{"version":3,"sources":[{}],"names":[{}],"mappings":"{}"}}"#,
3913 sources
3914 .iter()
3915 .map(|s| format!("\"{s}\""))
3916 .collect::<Vec<_>>()
3917 .join(","),
3918 names
3919 .iter()
3920 .map(|n| format!("\"{n}\""))
3921 .collect::<Vec<_>>()
3922 .join(","),
3923 encoded,
3924 )
3925 }
3926
3927 fn bias_map() -> &'static str {
3932 r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA,KAAK,KAAK"}"#
3934 }
3935
3936 #[test]
3937 fn original_position_glb_exact_match() {
3938 let sm = SourceMap::from_json(bias_map()).unwrap();
3939 let loc = sm
3940 .original_position_for_with_bias(0, 5, Bias::GreatestLowerBound)
3941 .unwrap();
3942 assert_eq!(loc.column, 5);
3943 }
3944
3945 #[test]
3946 fn original_position_glb_snaps_left() {
3947 let sm = SourceMap::from_json(bias_map()).unwrap();
3948 let loc = sm
3950 .original_position_for_with_bias(0, 7, Bias::GreatestLowerBound)
3951 .unwrap();
3952 assert_eq!(loc.column, 5);
3953 }
3954
3955 #[test]
3956 fn original_position_lub_exact_match() {
3957 let sm = SourceMap::from_json(bias_map()).unwrap();
3958 let loc = sm
3959 .original_position_for_with_bias(0, 5, Bias::LeastUpperBound)
3960 .unwrap();
3961 assert_eq!(loc.column, 5);
3962 }
3963
3964 #[test]
3965 fn original_position_lub_snaps_right() {
3966 let sm = SourceMap::from_json(bias_map()).unwrap();
3967 let loc = sm
3969 .original_position_for_with_bias(0, 3, Bias::LeastUpperBound)
3970 .unwrap();
3971 assert_eq!(loc.column, 5);
3972 }
3973
3974 #[test]
3975 fn original_position_lub_before_first() {
3976 let sm = SourceMap::from_json(bias_map()).unwrap();
3977 let loc = sm
3979 .original_position_for_with_bias(0, 0, Bias::LeastUpperBound)
3980 .unwrap();
3981 assert_eq!(loc.column, 0);
3982 }
3983
3984 #[test]
3985 fn original_position_lub_after_last() {
3986 let sm = SourceMap::from_json(bias_map()).unwrap();
3987 let loc = sm.original_position_for_with_bias(0, 15, Bias::LeastUpperBound);
3989 assert!(loc.is_none());
3990 }
3991
3992 #[test]
3993 fn original_position_glb_before_first() {
3994 let sm = SourceMap::from_json(bias_map()).unwrap();
3995 let loc = sm
3997 .original_position_for_with_bias(0, 0, Bias::GreatestLowerBound)
3998 .unwrap();
3999 assert_eq!(loc.column, 0);
4000 }
4001
4002 #[test]
4003 fn generated_position_lub() {
4004 let sm = SourceMap::from_json(bias_map()).unwrap();
4005 let loc = sm
4007 .generated_position_for_with_bias("input.js", 0, 3, Bias::LeastUpperBound)
4008 .unwrap();
4009 assert_eq!(loc.column, 5);
4010 }
4011
4012 #[test]
4013 fn generated_position_glb() {
4014 let sm = SourceMap::from_json(bias_map()).unwrap();
4015 let loc = sm
4017 .generated_position_for_with_bias("input.js", 0, 7, Bias::GreatestLowerBound)
4018 .unwrap();
4019 assert_eq!(loc.column, 5);
4020 }
4021
4022 #[test]
4025 fn map_range_basic() {
4026 let sm = SourceMap::from_json(bias_map()).unwrap();
4027 let range = sm.map_range(0, 0, 0, 10).unwrap();
4028 assert_eq!(range.source, 0);
4029 assert_eq!(range.original_start_line, 0);
4030 assert_eq!(range.original_start_column, 0);
4031 assert_eq!(range.original_end_line, 0);
4032 assert_eq!(range.original_end_column, 10);
4033 }
4034
4035 #[test]
4036 fn map_range_no_mapping() {
4037 let sm = SourceMap::from_json(bias_map()).unwrap();
4038 let range = sm.map_range(0, 0, 5, 0);
4040 assert!(range.is_none());
4041 }
4042
4043 #[test]
4044 fn map_range_different_sources() {
4045 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":[],"mappings":"AAAA;ACAA"}"#;
4047 let sm = SourceMap::from_json(json).unwrap();
4048 let range = sm.map_range(0, 0, 1, 0);
4050 assert!(range.is_none());
4051 }
4052
4053 #[test]
4056 fn extension_fields_preserved() {
4057 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","x_facebook_sources":[[{"names":["<global>"]}]],"x_google_linecount":42}"#;
4058 let sm = SourceMap::from_json(json).unwrap();
4059
4060 assert!(sm.extensions.contains_key("x_facebook_sources"));
4061 assert!(sm.extensions.contains_key("x_google_linecount"));
4062 assert_eq!(
4063 sm.extensions.get("x_google_linecount"),
4064 Some(&serde_json::json!(42))
4065 );
4066
4067 let output = sm.to_json();
4069 assert!(output.contains("x_facebook_sources"));
4070 assert!(output.contains("x_google_linecount"));
4071 }
4072
4073 #[test]
4074 fn x_google_ignorelist_fallback() {
4075 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":[],"mappings":"AAAA","x_google_ignoreList":[1]}"#;
4076 let sm = SourceMap::from_json(json).unwrap();
4077 assert_eq!(sm.ignore_list, vec![1]);
4078 }
4079
4080 #[test]
4081 fn ignorelist_takes_precedence_over_x_google() {
4082 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":[],"mappings":"AAAA","ignoreList":[0],"x_google_ignoreList":[1]}"#;
4083 let sm = SourceMap::from_json(json).unwrap();
4084 assert_eq!(sm.ignore_list, vec![0]);
4085 }
4086
4087 #[test]
4088 fn source_mapping_url_external() {
4089 let source = "var a = 1;\n//# sourceMappingURL=app.js.map\n";
4090 let result = parse_source_mapping_url(source).unwrap();
4091 assert_eq!(result, SourceMappingUrl::External("app.js.map".to_string()));
4092 }
4093
4094 #[test]
4095 fn source_mapping_url_inline() {
4096 let json = r#"{"version":3,"sources":[],"names":[],"mappings":""}"#;
4097 let b64 = base64_encode_simple(json);
4098 let source =
4099 format!("var a = 1;\n//# sourceMappingURL=data:application/json;base64,{b64}\n");
4100 match parse_source_mapping_url(&source).unwrap() {
4101 SourceMappingUrl::Inline(decoded) => {
4102 assert_eq!(decoded, json);
4103 }
4104 _ => panic!("expected inline"),
4105 }
4106 }
4107
4108 #[test]
4109 fn source_mapping_url_at_sign() {
4110 let source = "var a = 1;\n//@ sourceMappingURL=old-style.map";
4111 let result = parse_source_mapping_url(source).unwrap();
4112 assert_eq!(
4113 result,
4114 SourceMappingUrl::External("old-style.map".to_string())
4115 );
4116 }
4117
4118 #[test]
4119 fn source_mapping_url_css_comment() {
4120 let source = "body { }\n/*# sourceMappingURL=styles.css.map */";
4121 let result = parse_source_mapping_url(source).unwrap();
4122 assert_eq!(
4123 result,
4124 SourceMappingUrl::External("styles.css.map".to_string())
4125 );
4126 }
4127
4128 #[test]
4129 fn source_mapping_url_none() {
4130 let source = "var a = 1;";
4131 assert!(parse_source_mapping_url(source).is_none());
4132 }
4133
4134 #[test]
4135 fn exclude_content_option() {
4136 let json = r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":[],"mappings":"AAAA"}"#;
4137 let sm = SourceMap::from_json(json).unwrap();
4138
4139 let with_content = sm.to_json();
4140 assert!(with_content.contains("sourcesContent"));
4141
4142 let without_content = sm.to_json_with_options(true);
4143 assert!(!without_content.contains("sourcesContent"));
4144 }
4145
4146 #[test]
4147 fn validate_deep_clean_map() {
4148 let sm = SourceMap::from_json(simple_map()).unwrap();
4149 let warnings = validate_deep(&sm);
4150 assert!(warnings.is_empty(), "unexpected warnings: {warnings:?}");
4151 }
4152
4153 #[test]
4154 fn validate_deep_unreferenced_source() {
4155 let json =
4157 r#"{"version":3,"sources":["used.js","unused.js"],"names":[],"mappings":"AAAA"}"#;
4158 let sm = SourceMap::from_json(json).unwrap();
4159 let warnings = validate_deep(&sm);
4160 assert!(warnings.iter().any(|w| w.contains("unused.js")));
4161 }
4162
4163 #[test]
4166 fn from_parts_basic() {
4167 let mappings = vec![
4168 Mapping {
4169 generated_line: 0,
4170 generated_column: 0,
4171 source: 0,
4172 original_line: 0,
4173 original_column: 0,
4174 name: NO_NAME,
4175 is_range_mapping: false,
4176 },
4177 Mapping {
4178 generated_line: 1,
4179 generated_column: 4,
4180 source: 0,
4181 original_line: 1,
4182 original_column: 2,
4183 name: NO_NAME,
4184 is_range_mapping: false,
4185 },
4186 ];
4187
4188 let sm = SourceMap::from_parts(
4189 Some("out.js".to_string()),
4190 None,
4191 vec!["input.js".to_string()],
4192 vec![Some("var x = 1;".to_string())],
4193 vec![],
4194 mappings,
4195 vec![],
4196 None,
4197 None,
4198 );
4199
4200 assert_eq!(sm.line_count(), 2);
4201 assert_eq!(sm.mapping_count(), 2);
4202
4203 let loc = sm.original_position_for(0, 0).unwrap();
4204 assert_eq!(loc.source, 0);
4205 assert_eq!(loc.line, 0);
4206 assert_eq!(loc.column, 0);
4207
4208 let loc = sm.original_position_for(1, 4).unwrap();
4209 assert_eq!(loc.line, 1);
4210 assert_eq!(loc.column, 2);
4211 }
4212
4213 #[test]
4214 fn from_parts_empty() {
4215 let sm = SourceMap::from_parts(
4216 None,
4217 None,
4218 vec![],
4219 vec![],
4220 vec![],
4221 vec![],
4222 vec![],
4223 None,
4224 None,
4225 );
4226 assert_eq!(sm.line_count(), 0);
4227 assert_eq!(sm.mapping_count(), 0);
4228 assert!(sm.original_position_for(0, 0).is_none());
4229 }
4230
4231 #[test]
4232 fn from_parts_with_names() {
4233 let mappings = vec![Mapping {
4234 generated_line: 0,
4235 generated_column: 0,
4236 source: 0,
4237 original_line: 0,
4238 original_column: 0,
4239 name: 0,
4240 is_range_mapping: false,
4241 }];
4242
4243 let sm = SourceMap::from_parts(
4244 None,
4245 None,
4246 vec!["input.js".to_string()],
4247 vec![],
4248 vec!["myVar".to_string()],
4249 mappings,
4250 vec![],
4251 None,
4252 None,
4253 );
4254
4255 let loc = sm.original_position_for(0, 0).unwrap();
4256 assert_eq!(loc.name, Some(0));
4257 assert_eq!(sm.name(0), "myVar");
4258 }
4259
4260 #[test]
4261 fn from_parts_roundtrip_via_json() {
4262 let json = generate_test_sourcemap(50, 10, 3);
4263 let sm = SourceMap::from_json(&json).unwrap();
4264
4265 let sm2 = SourceMap::from_parts(
4266 sm.file.clone(),
4267 sm.source_root.clone(),
4268 sm.sources.clone(),
4269 sm.sources_content.clone(),
4270 sm.names.clone(),
4271 sm.all_mappings().to_vec(),
4272 sm.ignore_list.clone(),
4273 sm.debug_id.clone(),
4274 None,
4275 );
4276
4277 assert_eq!(sm2.mapping_count(), sm.mapping_count());
4278 assert_eq!(sm2.line_count(), sm.line_count());
4279
4280 for m in sm.all_mappings() {
4282 if m.source != NO_SOURCE {
4283 let a = sm.original_position_for(m.generated_line, m.generated_column);
4284 let b = sm2.original_position_for(m.generated_line, m.generated_column);
4285 match (a, b) {
4286 (Some(a), Some(b)) => {
4287 assert_eq!(a.source, b.source);
4288 assert_eq!(a.line, b.line);
4289 assert_eq!(a.column, b.column);
4290 }
4291 (None, None) => {}
4292 _ => panic!("mismatch at ({}, {})", m.generated_line, m.generated_column),
4293 }
4294 }
4295 }
4296 }
4297
4298 #[test]
4299 fn from_parts_reverse_lookup() {
4300 let mappings = vec![
4301 Mapping {
4302 generated_line: 0,
4303 generated_column: 0,
4304 source: 0,
4305 original_line: 10,
4306 original_column: 5,
4307 name: NO_NAME,
4308 is_range_mapping: false,
4309 },
4310 Mapping {
4311 generated_line: 1,
4312 generated_column: 8,
4313 source: 0,
4314 original_line: 20,
4315 original_column: 0,
4316 name: NO_NAME,
4317 is_range_mapping: false,
4318 },
4319 ];
4320
4321 let sm = SourceMap::from_parts(
4322 None,
4323 None,
4324 vec!["src.js".to_string()],
4325 vec![],
4326 vec![],
4327 mappings,
4328 vec![],
4329 None,
4330 None,
4331 );
4332
4333 let loc = sm.generated_position_for("src.js", 10, 5).unwrap();
4334 assert_eq!(loc.line, 0);
4335 assert_eq!(loc.column, 0);
4336
4337 let loc = sm.generated_position_for("src.js", 20, 0).unwrap();
4338 assert_eq!(loc.line, 1);
4339 assert_eq!(loc.column, 8);
4340 }
4341
4342 #[test]
4343 fn from_parts_sparse_lines() {
4344 let mappings = vec![
4345 Mapping {
4346 generated_line: 0,
4347 generated_column: 0,
4348 source: 0,
4349 original_line: 0,
4350 original_column: 0,
4351 name: NO_NAME,
4352 is_range_mapping: false,
4353 },
4354 Mapping {
4355 generated_line: 5,
4356 generated_column: 0,
4357 source: 0,
4358 original_line: 5,
4359 original_column: 0,
4360 name: NO_NAME,
4361 is_range_mapping: false,
4362 },
4363 ];
4364
4365 let sm = SourceMap::from_parts(
4366 None,
4367 None,
4368 vec!["src.js".to_string()],
4369 vec![],
4370 vec![],
4371 mappings,
4372 vec![],
4373 None,
4374 None,
4375 );
4376
4377 assert_eq!(sm.line_count(), 6);
4378 assert!(sm.original_position_for(0, 0).is_some());
4379 assert!(sm.original_position_for(2, 0).is_none());
4380 assert!(sm.original_position_for(5, 0).is_some());
4381 }
4382
4383 #[test]
4386 fn from_json_lines_basic() {
4387 let json = generate_test_sourcemap(10, 5, 2);
4388 let sm_full = SourceMap::from_json(&json).unwrap();
4389
4390 let sm_partial = SourceMap::from_json_lines(&json, 3, 7).unwrap();
4392
4393 for line in 3..7u32 {
4395 let full_mappings = sm_full.mappings_for_line(line);
4396 let partial_mappings = sm_partial.mappings_for_line(line);
4397 assert_eq!(
4398 full_mappings.len(),
4399 partial_mappings.len(),
4400 "line {line} mapping count mismatch"
4401 );
4402 for (a, b) in full_mappings.iter().zip(partial_mappings.iter()) {
4403 assert_eq!(a.generated_column, b.generated_column);
4404 assert_eq!(a.source, b.source);
4405 assert_eq!(a.original_line, b.original_line);
4406 assert_eq!(a.original_column, b.original_column);
4407 assert_eq!(a.name, b.name);
4408 }
4409 }
4410 }
4411
4412 #[test]
4413 fn from_json_lines_first_lines() {
4414 let json = generate_test_sourcemap(10, 5, 2);
4415 let sm_full = SourceMap::from_json(&json).unwrap();
4416 let sm_partial = SourceMap::from_json_lines(&json, 0, 3).unwrap();
4417
4418 for line in 0..3u32 {
4419 let full_mappings = sm_full.mappings_for_line(line);
4420 let partial_mappings = sm_partial.mappings_for_line(line);
4421 assert_eq!(full_mappings.len(), partial_mappings.len());
4422 }
4423 }
4424
4425 #[test]
4426 fn from_json_lines_last_lines() {
4427 let json = generate_test_sourcemap(10, 5, 2);
4428 let sm_full = SourceMap::from_json(&json).unwrap();
4429 let sm_partial = SourceMap::from_json_lines(&json, 7, 10).unwrap();
4430
4431 for line in 7..10u32 {
4432 let full_mappings = sm_full.mappings_for_line(line);
4433 let partial_mappings = sm_partial.mappings_for_line(line);
4434 assert_eq!(full_mappings.len(), partial_mappings.len(), "line {line}");
4435 }
4436 }
4437
4438 #[test]
4439 fn from_json_lines_empty_range() {
4440 let json = generate_test_sourcemap(10, 5, 2);
4441 let sm = SourceMap::from_json_lines(&json, 5, 5).unwrap();
4442 assert_eq!(sm.mapping_count(), 0);
4443 }
4444
4445 #[test]
4446 fn from_json_lines_beyond_end() {
4447 let json = generate_test_sourcemap(5, 3, 1);
4448 let sm = SourceMap::from_json_lines(&json, 3, 100).unwrap();
4450 assert!(sm.mapping_count() > 0);
4452 }
4453
4454 #[test]
4455 fn from_json_lines_single_line() {
4456 let json = generate_test_sourcemap(10, 5, 2);
4457 let sm_full = SourceMap::from_json(&json).unwrap();
4458 let sm_partial = SourceMap::from_json_lines(&json, 5, 6).unwrap();
4459
4460 let full_mappings = sm_full.mappings_for_line(5);
4461 let partial_mappings = sm_partial.mappings_for_line(5);
4462 assert_eq!(full_mappings.len(), partial_mappings.len());
4463 }
4464
4465 #[test]
4468 fn lazy_basic_lookup() {
4469 let json = r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA;AACA"}"#;
4470 let sm = LazySourceMap::from_json(json).unwrap();
4471
4472 assert_eq!(sm.line_count(), 2);
4473 assert_eq!(sm.sources, vec!["input.js"]);
4474
4475 let loc = sm.original_position_for(0, 0).unwrap();
4476 assert_eq!(sm.source(loc.source), "input.js");
4477 assert_eq!(loc.line, 0);
4478 assert_eq!(loc.column, 0);
4479 }
4480
4481 #[test]
4482 fn lazy_multiple_lines() {
4483 let json = generate_test_sourcemap(20, 5, 3);
4484 let sm_eager = SourceMap::from_json(&json).unwrap();
4485 let sm_lazy = LazySourceMap::from_json(&json).unwrap();
4486
4487 assert_eq!(sm_lazy.line_count(), sm_eager.line_count());
4488
4489 for m in sm_eager.all_mappings() {
4491 if m.source == NO_SOURCE {
4492 continue;
4493 }
4494 let eager_loc = sm_eager
4495 .original_position_for(m.generated_line, m.generated_column)
4496 .unwrap();
4497 let lazy_loc = sm_lazy
4498 .original_position_for(m.generated_line, m.generated_column)
4499 .unwrap();
4500 assert_eq!(eager_loc.source, lazy_loc.source);
4501 assert_eq!(eager_loc.line, lazy_loc.line);
4502 assert_eq!(eager_loc.column, lazy_loc.column);
4503 assert_eq!(eager_loc.name, lazy_loc.name);
4504 }
4505 }
4506
4507 #[test]
4508 fn lazy_empty_mappings() {
4509 let json = r#"{"version":3,"sources":[],"names":[],"mappings":""}"#;
4510 let sm = LazySourceMap::from_json(json).unwrap();
4511 assert_eq!(sm.line_count(), 0);
4512 assert!(sm.original_position_for(0, 0).is_none());
4513 }
4514
4515 #[test]
4516 fn lazy_empty_lines() {
4517 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;;;AACA"}"#;
4518 let sm = LazySourceMap::from_json(json).unwrap();
4519 assert_eq!(sm.line_count(), 4);
4520
4521 assert!(sm.original_position_for(0, 0).is_some());
4522 assert!(sm.original_position_for(1, 0).is_none());
4523 assert!(sm.original_position_for(2, 0).is_none());
4524 assert!(sm.original_position_for(3, 0).is_some());
4525 }
4526
4527 #[test]
4528 fn lazy_decode_line_caching() {
4529 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,KACA;AACA"}"#;
4530 let sm = LazySourceMap::from_json(json).unwrap();
4531
4532 let line0_a = sm.decode_line(0).unwrap();
4534 let line0_b = sm.decode_line(0).unwrap();
4536 assert_eq!(line0_a.len(), line0_b.len());
4537 assert_eq!(line0_a[0].generated_column, line0_b[0].generated_column);
4538 }
4539
4540 #[test]
4541 fn lazy_with_names() {
4542 let json = r#"{"version":3,"sources":["input.js"],"names":["foo","bar"],"mappings":"AAAAA,KACAC"}"#;
4543 let sm = LazySourceMap::from_json(json).unwrap();
4544
4545 let loc = sm.original_position_for(0, 0).unwrap();
4546 assert_eq!(loc.name, Some(0));
4547 assert_eq!(sm.name(0), "foo");
4548
4549 let loc = sm.original_position_for(0, 5).unwrap();
4550 assert_eq!(loc.name, Some(1));
4551 assert_eq!(sm.name(1), "bar");
4552 }
4553
4554 #[test]
4555 fn lazy_nonexistent_line() {
4556 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
4557 let sm = LazySourceMap::from_json(json).unwrap();
4558 assert!(sm.original_position_for(99, 0).is_none());
4559 let line = sm.decode_line(99).unwrap();
4560 assert!(line.is_empty());
4561 }
4562
4563 #[test]
4564 fn lazy_into_sourcemap() {
4565 let json = generate_test_sourcemap(20, 5, 3);
4566 let sm_eager = SourceMap::from_json(&json).unwrap();
4567 let sm_lazy = LazySourceMap::from_json(&json).unwrap();
4568 let sm_converted = sm_lazy.into_sourcemap().unwrap();
4569
4570 assert_eq!(sm_converted.mapping_count(), sm_eager.mapping_count());
4571 assert_eq!(sm_converted.line_count(), sm_eager.line_count());
4572
4573 for m in sm_eager.all_mappings() {
4575 let a = sm_eager.original_position_for(m.generated_line, m.generated_column);
4576 let b = sm_converted.original_position_for(m.generated_line, m.generated_column);
4577 match (a, b) {
4578 (Some(a), Some(b)) => {
4579 assert_eq!(a.source, b.source);
4580 assert_eq!(a.line, b.line);
4581 assert_eq!(a.column, b.column);
4582 }
4583 (None, None) => {}
4584 _ => panic!("mismatch at ({}, {})", m.generated_line, m.generated_column),
4585 }
4586 }
4587 }
4588
4589 #[test]
4590 fn lazy_source_index_lookup() {
4591 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":[],"mappings":"AAAA;ACAA"}"#;
4592 let sm = LazySourceMap::from_json(json).unwrap();
4593 assert_eq!(sm.source_index("a.js"), Some(0));
4594 assert_eq!(sm.source_index("b.js"), Some(1));
4595 assert_eq!(sm.source_index("c.js"), None);
4596 }
4597
4598 #[test]
4599 fn lazy_mappings_for_line() {
4600 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,KACA;AACA"}"#;
4601 let sm = LazySourceMap::from_json(json).unwrap();
4602
4603 let line0 = sm.mappings_for_line(0);
4604 assert_eq!(line0.len(), 2);
4605
4606 let line1 = sm.mappings_for_line(1);
4607 assert_eq!(line1.len(), 1);
4608
4609 let line99 = sm.mappings_for_line(99);
4610 assert!(line99.is_empty());
4611 }
4612
4613 #[test]
4614 fn lazy_large_map_selective_decode() {
4615 let json = generate_test_sourcemap(100, 10, 5);
4617 let sm_eager = SourceMap::from_json(&json).unwrap();
4618 let sm_lazy = LazySourceMap::from_json(&json).unwrap();
4619
4620 for line in [50, 75] {
4622 let eager_mappings = sm_eager.mappings_for_line(line);
4623 let lazy_mappings = sm_lazy.mappings_for_line(line);
4624 assert_eq!(
4625 eager_mappings.len(),
4626 lazy_mappings.len(),
4627 "line {line} count mismatch"
4628 );
4629 for (a, b) in eager_mappings.iter().zip(lazy_mappings.iter()) {
4630 assert_eq!(a.generated_column, b.generated_column);
4631 assert_eq!(a.source, b.source);
4632 assert_eq!(a.original_line, b.original_line);
4633 assert_eq!(a.original_column, b.original_column);
4634 assert_eq!(a.name, b.name);
4635 }
4636 }
4637 }
4638
4639 #[test]
4640 fn lazy_single_field_segments() {
4641 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A,KAAAA"}"#;
4642 let sm = LazySourceMap::from_json(json).unwrap();
4643
4644 assert!(sm.original_position_for(0, 0).is_none());
4646 let loc = sm.original_position_for(0, 5).unwrap();
4648 assert_eq!(loc.source, 0);
4649 }
4650
4651 #[test]
4654 fn parse_error_display_vlq() {
4655 let err = ParseError::Vlq(srcmap_codec::DecodeError::UnexpectedEof { offset: 3 });
4656 assert!(err.to_string().contains("VLQ decode error"));
4657 }
4658
4659 #[test]
4660 fn parse_error_display_scopes() {
4661 let err = ParseError::Scopes(srcmap_scopes::ScopesError::UnclosedScope);
4662 assert!(err.to_string().contains("scopes decode error"));
4663 }
4664
4665 #[test]
4666 fn indexed_map_with_names_in_sections() {
4667 let json = r#"{
4668 "version": 3,
4669 "sections": [
4670 {
4671 "offset": {"line": 0, "column": 0},
4672 "map": {
4673 "version": 3,
4674 "sources": ["a.js"],
4675 "names": ["foo"],
4676 "mappings": "AAAAA"
4677 }
4678 },
4679 {
4680 "offset": {"line": 1, "column": 0},
4681 "map": {
4682 "version": 3,
4683 "sources": ["a.js"],
4684 "names": ["foo"],
4685 "mappings": "AAAAA"
4686 }
4687 }
4688 ]
4689 }"#;
4690 let sm = SourceMap::from_json(json).unwrap();
4691 assert_eq!(sm.sources.len(), 1);
4693 assert_eq!(sm.names.len(), 1);
4694 }
4695
4696 #[test]
4697 fn indexed_map_with_ignore_list() {
4698 let json = r#"{
4699 "version": 3,
4700 "sections": [
4701 {
4702 "offset": {"line": 0, "column": 0},
4703 "map": {
4704 "version": 3,
4705 "sources": ["vendor.js"],
4706 "names": [],
4707 "mappings": "AAAA",
4708 "ignoreList": [0]
4709 }
4710 }
4711 ]
4712 }"#;
4713 let sm = SourceMap::from_json(json).unwrap();
4714 assert_eq!(sm.ignore_list, vec![0]);
4715 }
4716
4717 #[test]
4718 fn indexed_map_with_generated_only_segment() {
4719 let json = r#"{
4721 "version": 3,
4722 "sections": [
4723 {
4724 "offset": {"line": 0, "column": 0},
4725 "map": {
4726 "version": 3,
4727 "sources": ["a.js"],
4728 "names": [],
4729 "mappings": "A,AAAA"
4730 }
4731 }
4732 ]
4733 }"#;
4734 let sm = SourceMap::from_json(json).unwrap();
4735 assert!(sm.mapping_count() >= 1);
4736 }
4737
4738 #[test]
4739 fn indexed_map_empty_mappings() {
4740 let json = r#"{
4741 "version": 3,
4742 "sections": [
4743 {
4744 "offset": {"line": 0, "column": 0},
4745 "map": {
4746 "version": 3,
4747 "sources": [],
4748 "names": [],
4749 "mappings": ""
4750 }
4751 }
4752 ]
4753 }"#;
4754 let sm = SourceMap::from_json(json).unwrap();
4755 assert_eq!(sm.mapping_count(), 0);
4756 }
4757
4758 #[test]
4759 fn generated_position_glb_exact_match() {
4760 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAE,OAAO"}"#;
4761 let sm = SourceMap::from_json(json).unwrap();
4762
4763 let loc = sm.generated_position_for_with_bias("a.js", 0, 0, Bias::GreatestLowerBound);
4764 assert!(loc.is_some());
4765 assert_eq!(loc.unwrap().column, 0);
4766 }
4767
4768 #[test]
4769 fn generated_position_glb_no_exact_match() {
4770 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAE"}"#;
4771 let sm = SourceMap::from_json(json).unwrap();
4772
4773 let loc = sm.generated_position_for_with_bias("a.js", 0, 0, Bias::GreatestLowerBound);
4775 assert!(loc.is_some());
4776 }
4777
4778 #[test]
4779 fn generated_position_glb_wrong_source() {
4780 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":[],"mappings":"AAAA,KCCA"}"#;
4781 let sm = SourceMap::from_json(json).unwrap();
4782
4783 let loc = sm.generated_position_for_with_bias("b.js", 5, 0, Bias::GreatestLowerBound);
4785 if let Some(l) = loc {
4788 assert_eq!(l.line, 0);
4790 }
4791 }
4792
4793 #[test]
4794 fn generated_position_lub_wrong_source() {
4795 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
4796 let sm = SourceMap::from_json(json).unwrap();
4797
4798 let loc =
4800 sm.generated_position_for_with_bias("nonexistent.js", 0, 0, Bias::LeastUpperBound);
4801 assert!(loc.is_none());
4802 }
4803
4804 #[test]
4805 fn to_json_with_ignore_list() {
4806 let json = r#"{"version":3,"sources":["vendor.js"],"names":[],"mappings":"AAAA","ignoreList":[0]}"#;
4807 let sm = SourceMap::from_json(json).unwrap();
4808 let output = sm.to_json();
4809 assert!(output.contains("\"ignoreList\":[0]"));
4810 }
4811
4812 #[test]
4813 fn to_json_with_extensions() {
4814 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","x_custom":"test_value"}"#;
4815 let sm = SourceMap::from_json(json).unwrap();
4816 let output = sm.to_json();
4817 assert!(output.contains("x_custom"));
4818 assert!(output.contains("test_value"));
4819 }
4820
4821 #[test]
4822 fn from_parts_empty_mappings() {
4823 let sm = SourceMap::from_parts(
4824 None,
4825 None,
4826 vec!["a.js".to_string()],
4827 vec![Some("content".to_string())],
4828 vec![],
4829 vec![],
4830 vec![],
4831 None,
4832 None,
4833 );
4834 assert_eq!(sm.mapping_count(), 0);
4835 assert_eq!(sm.sources, vec!["a.js"]);
4836 }
4837
4838 #[test]
4839 fn from_vlq_basic() {
4840 let sm = SourceMap::from_vlq(
4841 "AAAA;AACA",
4842 vec!["a.js".to_string()],
4843 vec![],
4844 Some("out.js".to_string()),
4845 None,
4846 vec![Some("content".to_string())],
4847 vec![],
4848 None,
4849 )
4850 .unwrap();
4851
4852 assert_eq!(sm.file.as_deref(), Some("out.js"));
4853 assert_eq!(sm.sources, vec!["a.js"]);
4854 let loc = sm.original_position_for(0, 0).unwrap();
4855 assert_eq!(sm.source(loc.source), "a.js");
4856 assert_eq!(loc.line, 0);
4857 }
4858
4859 #[test]
4860 fn from_json_lines_basic_coverage() {
4861 let json =
4862 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA"}"#;
4863 let sm = SourceMap::from_json_lines(json, 1, 3).unwrap();
4864 assert!(sm.original_position_for(1, 0).is_some());
4866 assert!(sm.original_position_for(2, 0).is_some());
4867 }
4868
4869 #[test]
4870 fn from_json_lines_with_source_root() {
4871 let json = r#"{"version":3,"sourceRoot":"src/","sources":["a.js"],"names":[],"mappings":"AAAA;AACA"}"#;
4872 let sm = SourceMap::from_json_lines(json, 0, 2).unwrap();
4873 assert_eq!(sm.sources[0], "src/a.js");
4874 }
4875
4876 #[test]
4877 fn from_json_lines_with_null_source() {
4878 let json = r#"{"version":3,"sources":[null,"a.js"],"names":[],"mappings":"AAAA,KCCA"}"#;
4879 let sm = SourceMap::from_json_lines(json, 0, 1).unwrap();
4880 assert_eq!(sm.sources.len(), 2);
4881 }
4882
4883 #[test]
4884 fn json_escaping_special_chars_sourcemap() {
4885 let json = r#"{"version":3,"sources":["path/with\nnewline.js"],"sourcesContent":["line1\r\nline2\t\"quoted\"\\\u0001"],"names":[],"mappings":"AAAA"}"#;
4888 let sm = SourceMap::from_json(json).unwrap();
4889 let output = sm.to_json();
4891 let sm2 = SourceMap::from_json(&output).unwrap();
4892 assert_eq!(sm.sources[0], sm2.sources[0]);
4893 assert_eq!(sm.sources_content[0], sm2.sources_content[0]);
4894 }
4895
4896 #[test]
4897 fn to_json_exclude_content() {
4898 let json = r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":[],"mappings":"AAAA"}"#;
4899 let sm = SourceMap::from_json(json).unwrap();
4900 let output = sm.to_json_with_options(true);
4901 assert!(!output.contains("sourcesContent"));
4902 let output_with = sm.to_json_with_options(false);
4903 assert!(output_with.contains("sourcesContent"));
4904 }
4905
4906 #[test]
4907 fn encode_mappings_with_name() {
4908 let json = r#"{"version":3,"sources":["a.js"],"names":["foo"],"mappings":"AAAAA"}"#;
4910 let sm = SourceMap::from_json(json).unwrap();
4911 let encoded = sm.encode_mappings();
4912 assert_eq!(encoded, "AAAAA");
4913 }
4914
4915 #[test]
4916 fn encode_mappings_generated_only() {
4917 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A,AAAA"}"#;
4919 let sm = SourceMap::from_json(json).unwrap();
4920 let encoded = sm.encode_mappings();
4921 let roundtrip = SourceMap::from_json(&format!(
4922 r#"{{"version":3,"sources":["a.js"],"names":[],"mappings":"{}"}}"#,
4923 encoded
4924 ))
4925 .unwrap();
4926 assert_eq!(roundtrip.mapping_count(), sm.mapping_count());
4927 }
4928
4929 #[test]
4930 fn map_range_single_result() {
4931 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAC,OAAO"}"#;
4932 let sm = SourceMap::from_json(json).unwrap();
4933 let result = sm.map_range(0, 0, 0, 1);
4935 assert!(result.is_some());
4936 let range = result.unwrap();
4937 assert_eq!(range.source, 0);
4938 }
4939
4940 #[test]
4941 fn scopes_in_from_json() {
4942 let info = srcmap_scopes::ScopeInfo {
4944 scopes: vec![Some(srcmap_scopes::OriginalScope {
4945 start: srcmap_scopes::Position { line: 0, column: 0 },
4946 end: srcmap_scopes::Position { line: 5, column: 0 },
4947 name: None,
4948 kind: None,
4949 is_stack_frame: false,
4950 variables: vec![],
4951 children: vec![],
4952 })],
4953 ranges: vec![],
4954 };
4955 let mut names = vec![];
4956 let scopes_str = srcmap_scopes::encode_scopes(&info, &mut names);
4957
4958 let json = format!(
4959 r#"{{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","scopes":"{scopes_str}"}}"#
4960 );
4961
4962 let sm = SourceMap::from_json(&json).unwrap();
4963 assert!(sm.scopes.is_some());
4964 }
4965
4966 #[test]
4967 fn from_json_lines_with_scopes() {
4968 let info = srcmap_scopes::ScopeInfo {
4969 scopes: vec![Some(srcmap_scopes::OriginalScope {
4970 start: srcmap_scopes::Position { line: 0, column: 0 },
4971 end: srcmap_scopes::Position { line: 5, column: 0 },
4972 name: None,
4973 kind: None,
4974 is_stack_frame: false,
4975 variables: vec![],
4976 children: vec![],
4977 })],
4978 ranges: vec![],
4979 };
4980 let mut names = vec![];
4981 let scopes_str = srcmap_scopes::encode_scopes(&info, &mut names);
4982 let json = format!(
4983 r#"{{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;AACA","scopes":"{scopes_str}"}}"#
4984 );
4985 let sm = SourceMap::from_json_lines(&json, 0, 2).unwrap();
4986 assert!(sm.scopes.is_some());
4987 }
4988
4989 #[test]
4990 fn from_json_lines_with_extensions() {
4991 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","x_custom":"val","not_x":"skip"}"#;
4992 let sm = SourceMap::from_json_lines(json, 0, 1).unwrap();
4993 assert!(sm.extensions.contains_key("x_custom"));
4994 assert!(!sm.extensions.contains_key("not_x"));
4995 }
4996
4997 #[test]
4998 fn lazy_sourcemap_version_error() {
4999 let json = r#"{"version":2,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
5000 let err = LazySourceMap::from_json(json).unwrap_err();
5001 assert!(matches!(err, ParseError::InvalidVersion(2)));
5002 }
5003
5004 #[test]
5005 fn lazy_sourcemap_with_source_root() {
5006 let json =
5007 r#"{"version":3,"sourceRoot":"src/","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
5008 let sm = LazySourceMap::from_json(json).unwrap();
5009 assert_eq!(sm.sources[0], "src/a.js");
5010 }
5011
5012 #[test]
5013 fn lazy_sourcemap_with_ignore_list_and_extensions() {
5014 let json = r#"{"version":3,"sources":["v.js"],"names":[],"mappings":"AAAA","ignoreList":[0],"x_custom":"val","not_x":"skip"}"#;
5015 let sm = LazySourceMap::from_json(json).unwrap();
5016 assert_eq!(sm.ignore_list, vec![0]);
5017 assert!(sm.extensions.contains_key("x_custom"));
5018 assert!(!sm.extensions.contains_key("not_x"));
5019 }
5020
5021 #[test]
5022 fn lazy_sourcemap_with_scopes() {
5023 let info = srcmap_scopes::ScopeInfo {
5024 scopes: vec![Some(srcmap_scopes::OriginalScope {
5025 start: srcmap_scopes::Position { line: 0, column: 0 },
5026 end: srcmap_scopes::Position { line: 5, column: 0 },
5027 name: None,
5028 kind: None,
5029 is_stack_frame: false,
5030 variables: vec![],
5031 children: vec![],
5032 })],
5033 ranges: vec![],
5034 };
5035 let mut names = vec![];
5036 let scopes_str = srcmap_scopes::encode_scopes(&info, &mut names);
5037 let json = format!(
5038 r#"{{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","scopes":"{scopes_str}"}}"#
5039 );
5040 let sm = LazySourceMap::from_json(&json).unwrap();
5041 assert!(sm.scopes.is_some());
5042 }
5043
5044 #[test]
5045 fn lazy_sourcemap_null_source() {
5046 let json = r#"{"version":3,"sources":[null,"a.js"],"names":[],"mappings":"AAAA,KCCA"}"#;
5047 let sm = LazySourceMap::from_json(json).unwrap();
5048 assert_eq!(sm.sources.len(), 2);
5049 }
5050
5051 #[test]
5052 fn indexed_map_multi_line_section() {
5053 let json = r#"{
5055 "version": 3,
5056 "sections": [
5057 {
5058 "offset": {"line": 0, "column": 0},
5059 "map": {
5060 "version": 3,
5061 "sources": ["a.js"],
5062 "names": [],
5063 "mappings": "AAAA;AACA;AACA"
5064 }
5065 },
5066 {
5067 "offset": {"line": 5, "column": 0},
5068 "map": {
5069 "version": 3,
5070 "sources": ["b.js"],
5071 "names": [],
5072 "mappings": "AAAA;AACA"
5073 }
5074 }
5075 ]
5076 }"#;
5077 let sm = SourceMap::from_json(json).unwrap();
5078 assert!(sm.original_position_for(0, 0).is_some());
5079 assert!(sm.original_position_for(5, 0).is_some());
5080 }
5081
5082 #[test]
5083 fn source_mapping_url_extraction() {
5084 let input = "var x = 1;\n//# sourceMappingURL=bundle.js.map";
5086 let url = parse_source_mapping_url(input);
5087 assert!(matches!(url, Some(SourceMappingUrl::External(ref s)) if s == "bundle.js.map"));
5088
5089 let input = "body { }\n/*# sourceMappingURL=style.css.map */";
5091 let url = parse_source_mapping_url(input);
5092 assert!(matches!(url, Some(SourceMappingUrl::External(ref s)) if s == "style.css.map"));
5093
5094 let input = "var x;\n//@ sourceMappingURL=old-style.map";
5096 let url = parse_source_mapping_url(input);
5097 assert!(matches!(url, Some(SourceMappingUrl::External(ref s)) if s == "old-style.map"));
5098
5099 let input = "body{}\n/*@ sourceMappingURL=old-css.map */";
5101 let url = parse_source_mapping_url(input);
5102 assert!(matches!(url, Some(SourceMappingUrl::External(ref s)) if s == "old-css.map"));
5103
5104 let input = "var x = 1;";
5106 let url = parse_source_mapping_url(input);
5107 assert!(url.is_none());
5108
5109 let input = "//# sourceMappingURL=";
5111 let url = parse_source_mapping_url(input);
5112 assert!(url.is_none());
5113
5114 let map_json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
5116 let encoded = base64_encode_simple(map_json);
5117 let input = format!("var x;\n//# sourceMappingURL=data:application/json;base64,{encoded}");
5118 let url = parse_source_mapping_url(&input);
5119 assert!(matches!(url, Some(SourceMappingUrl::Inline(_))));
5120 }
5121
5122 #[test]
5123 fn validate_deep_unreferenced_coverage() {
5124 let sm = SourceMap::from_parts(
5126 None,
5127 None,
5128 vec!["used.js".to_string(), "unused.js".to_string()],
5129 vec![None, None],
5130 vec![],
5131 vec![Mapping {
5132 generated_line: 0,
5133 generated_column: 0,
5134 source: 0,
5135 original_line: 0,
5136 original_column: 0,
5137 name: NO_NAME,
5138 is_range_mapping: false,
5139 }],
5140 vec![],
5141 None,
5142 None,
5143 );
5144 let warnings = validate_deep(&sm);
5145 assert!(warnings.iter().any(|w| w.contains("unreferenced")));
5146 }
5147
5148 #[test]
5149 fn from_json_lines_generated_only_segment() {
5150 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A,AAAA;AACA"}"#;
5152 let sm = SourceMap::from_json_lines(json, 0, 2).unwrap();
5153 assert!(sm.mapping_count() >= 2);
5154 }
5155
5156 #[test]
5157 fn from_json_lines_with_names() {
5158 let json = r#"{"version":3,"sources":["a.js"],"names":["foo"],"mappings":"AAAAA;AACAA"}"#;
5159 let sm = SourceMap::from_json_lines(json, 0, 2).unwrap();
5160 let loc = sm.original_position_for(0, 0).unwrap();
5161 assert_eq!(loc.name, Some(0));
5162 }
5163
5164 #[test]
5165 fn from_parts_with_line_gap() {
5166 let sm = SourceMap::from_parts(
5168 None,
5169 None,
5170 vec!["a.js".to_string()],
5171 vec![None],
5172 vec![],
5173 vec![
5174 Mapping {
5175 generated_line: 0,
5176 generated_column: 0,
5177 source: 0,
5178 original_line: 0,
5179 original_column: 0,
5180 name: NO_NAME,
5181 is_range_mapping: false,
5182 },
5183 Mapping {
5184 generated_line: 5,
5185 generated_column: 0,
5186 source: 0,
5187 original_line: 5,
5188 original_column: 0,
5189 name: NO_NAME,
5190 is_range_mapping: false,
5191 },
5192 ],
5193 vec![],
5194 None,
5195 None,
5196 );
5197 assert!(sm.original_position_for(0, 0).is_some());
5198 assert!(sm.original_position_for(5, 0).is_some());
5199 assert!(sm.original_position_for(1, 0).is_none());
5201 }
5202
5203 #[test]
5204 fn lazy_decode_line_with_names_and_generated_only() {
5205 let json = r#"{"version":3,"sources":["a.js"],"names":["fn"],"mappings":"A,AAAAC"}"#;
5207 let sm = LazySourceMap::from_json(json).unwrap();
5208 let line = sm.decode_line(0).unwrap();
5209 assert!(line.len() >= 2);
5210 assert_eq!(line[0].source, NO_SOURCE);
5212 assert_ne!(line[1].name, NO_NAME);
5214 }
5215
5216 #[test]
5217 fn generated_position_glb_source_mismatch() {
5218 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":[],"mappings":"AAAA,KCCA"}"#;
5220 let sm = SourceMap::from_json(json).unwrap();
5221
5222 let loc = sm.generated_position_for_with_bias("a.js", 100, 0, Bias::LeastUpperBound);
5224 assert!(loc.is_none());
5225
5226 let loc = sm.generated_position_for_with_bias("b.js", 0, 0, Bias::GreatestLowerBound);
5230 assert!(loc.is_none());
5231
5232 let loc = sm.generated_position_for_with_bias("b.js", 1, 0, Bias::GreatestLowerBound);
5234 assert!(loc.is_some());
5235
5236 let loc = sm.generated_position_for_with_bias("b.js", 99, 0, Bias::LeastUpperBound);
5238 assert!(loc.is_none());
5239 }
5240
5241 #[test]
5244 fn from_json_invalid_scopes_error() {
5245 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","scopes":"!!invalid!!"}"#;
5247 let err = SourceMap::from_json(json).unwrap_err();
5248 assert!(matches!(err, ParseError::Scopes(_)));
5249 }
5250
5251 #[test]
5252 fn lazy_from_json_invalid_scopes_error() {
5253 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","scopes":"!!invalid!!"}"#;
5254 let err = LazySourceMap::from_json(json).unwrap_err();
5255 assert!(matches!(err, ParseError::Scopes(_)));
5256 }
5257
5258 #[test]
5259 fn from_json_lines_invalid_scopes_error() {
5260 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","scopes":"!!invalid!!"}"#;
5261 let err = SourceMap::from_json_lines(json, 0, 1).unwrap_err();
5262 assert!(matches!(err, ParseError::Scopes(_)));
5263 }
5264
5265 #[test]
5266 fn from_json_lines_invalid_version() {
5267 let json = r#"{"version":2,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
5268 let err = SourceMap::from_json_lines(json, 0, 1).unwrap_err();
5269 assert!(matches!(err, ParseError::InvalidVersion(2)));
5270 }
5271
5272 #[test]
5273 fn indexed_map_with_ignore_list_remapped() {
5274 let json = r#"{
5276 "version": 3,
5277 "sections": [{
5278 "offset": {"line": 0, "column": 0},
5279 "map": {
5280 "version": 3,
5281 "sources": ["a.js", "b.js"],
5282 "names": [],
5283 "mappings": "AAAA;ACAA",
5284 "ignoreList": [1]
5285 }
5286 }, {
5287 "offset": {"line": 5, "column": 0},
5288 "map": {
5289 "version": 3,
5290 "sources": ["b.js", "c.js"],
5291 "names": [],
5292 "mappings": "AAAA;ACAA",
5293 "ignoreList": [0]
5294 }
5295 }]
5296 }"#;
5297 let sm = SourceMap::from_json(json).unwrap();
5298 assert!(!sm.ignore_list.is_empty());
5300 }
5301
5302 #[test]
5303 fn to_json_with_debug_id() {
5304 let sm = SourceMap::from_parts(
5305 Some("out.js".to_string()),
5306 None,
5307 vec!["a.js".to_string()],
5308 vec![None],
5309 vec![],
5310 vec![Mapping {
5311 generated_line: 0,
5312 generated_column: 0,
5313 source: 0,
5314 original_line: 0,
5315 original_column: 0,
5316 name: NO_NAME,
5317 is_range_mapping: false,
5318 }],
5319 vec![],
5320 Some("abc-123".to_string()),
5321 None,
5322 );
5323 let json = sm.to_json();
5324 assert!(json.contains(r#""debugId":"abc-123""#));
5325 }
5326
5327 #[test]
5328 fn to_json_with_ignore_list_and_extensions() {
5329 let mut sm = SourceMap::from_parts(
5330 None,
5331 None,
5332 vec!["a.js".to_string(), "b.js".to_string()],
5333 vec![None, None],
5334 vec![],
5335 vec![Mapping {
5336 generated_line: 0,
5337 generated_column: 0,
5338 source: 0,
5339 original_line: 0,
5340 original_column: 0,
5341 name: NO_NAME,
5342 is_range_mapping: false,
5343 }],
5344 vec![1],
5345 None,
5346 None,
5347 );
5348 sm.extensions
5349 .insert("x_test".to_string(), serde_json::json!(42));
5350 let json = sm.to_json();
5351 assert!(json.contains("\"ignoreList\":[1]"));
5352 assert!(json.contains("\"x_test\":42"));
5353 }
5354
5355 #[test]
5356 fn from_vlq_with_all_options() {
5357 let sm = SourceMap::from_vlq(
5358 "AAAA;AACA",
5359 vec!["a.js".to_string()],
5360 vec![],
5361 Some("out.js".to_string()),
5362 Some("src/".to_string()),
5363 vec![Some("content".to_string())],
5364 vec![0],
5365 Some("debug-123".to_string()),
5366 )
5367 .unwrap();
5368 assert_eq!(sm.source(0), "a.js");
5369 assert!(sm.original_position_for(0, 0).is_some());
5370 assert!(sm.original_position_for(1, 0).is_some());
5371 }
5372
5373 #[test]
5374 fn lazy_into_sourcemap_roundtrip() {
5375 let json = r#"{"version":3,"sources":["a.js"],"names":["x"],"mappings":"AAAAA;AACAA"}"#;
5376 let lazy = LazySourceMap::from_json(json).unwrap();
5377 let sm = lazy.into_sourcemap().unwrap();
5378 assert!(sm.original_position_for(0, 0).is_some());
5379 assert!(sm.original_position_for(1, 0).is_some());
5380 assert_eq!(sm.name(0), "x");
5381 }
5382
5383 #[test]
5384 fn lazy_original_position_for_no_match() {
5385 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"KAAA"}"#;
5387 let sm = LazySourceMap::from_json(json).unwrap();
5388 assert!(sm.original_position_for(0, 0).is_none());
5390 }
5391
5392 #[test]
5393 fn lazy_original_position_for_empty_line() {
5394 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":";AAAA"}"#;
5395 let sm = LazySourceMap::from_json(json).unwrap();
5396 assert!(sm.original_position_for(0, 0).is_none());
5398 assert!(sm.original_position_for(1, 0).is_some());
5400 }
5401
5402 #[test]
5403 fn lazy_original_position_generated_only() {
5404 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A;AAAA"}"#;
5406 let sm = LazySourceMap::from_json(json).unwrap();
5407 assert!(sm.original_position_for(0, 0).is_none());
5409 assert!(sm.original_position_for(1, 0).is_some());
5411 }
5412
5413 #[test]
5414 fn from_json_lines_null_source() {
5415 let json = r#"{"version":3,"sources":[null,"a.js"],"names":[],"mappings":"ACAA"}"#;
5416 let sm = SourceMap::from_json_lines(json, 0, 1).unwrap();
5417 assert!(sm.mapping_count() >= 1);
5418 }
5419
5420 #[test]
5421 fn from_json_lines_with_source_root_prefix() {
5422 let json =
5423 r#"{"version":3,"sourceRoot":"lib/","sources":["b.js"],"names":[],"mappings":"AAAA"}"#;
5424 let sm = SourceMap::from_json_lines(json, 0, 1).unwrap();
5425 assert_eq!(sm.source(0), "lib/b.js");
5426 }
5427
5428 #[test]
5429 fn generated_position_for_glb_idx_zero() {
5430 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAKA"}"#;
5434 let sm = SourceMap::from_json(json).unwrap();
5435 let loc = sm.generated_position_for_with_bias("a.js", 0, 0, Bias::GreatestLowerBound);
5436 assert!(loc.is_none());
5437 }
5438
5439 #[test]
5440 fn from_json_lines_with_ignore_list() {
5441 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":[],"mappings":"AAAA;ACAA","ignoreList":[1]}"#;
5442 let sm = SourceMap::from_json_lines(json, 0, 2).unwrap();
5443 assert_eq!(sm.ignore_list, vec![1]);
5444 }
5445
5446 #[test]
5447 fn validate_deep_out_of_order_mappings() {
5448 let sm = SourceMap::from_parts(
5450 None,
5451 None,
5452 vec!["a.js".to_string()],
5453 vec![None],
5454 vec![],
5455 vec![
5456 Mapping {
5457 generated_line: 1,
5458 generated_column: 0,
5459 source: 0,
5460 original_line: 0,
5461 original_column: 0,
5462 name: NO_NAME,
5463 is_range_mapping: false,
5464 },
5465 Mapping {
5466 generated_line: 0,
5467 generated_column: 0,
5468 source: 0,
5469 original_line: 0,
5470 original_column: 0,
5471 name: NO_NAME,
5472 is_range_mapping: false,
5473 },
5474 ],
5475 vec![],
5476 None,
5477 None,
5478 );
5479 let warnings = validate_deep(&sm);
5480 assert!(warnings.iter().any(|w| w.contains("out of order")));
5481 }
5482
5483 #[test]
5484 fn validate_deep_out_of_bounds_source() {
5485 let sm = SourceMap::from_parts(
5486 None,
5487 None,
5488 vec!["a.js".to_string()],
5489 vec![None],
5490 vec![],
5491 vec![Mapping {
5492 generated_line: 0,
5493 generated_column: 0,
5494 source: 5,
5495 original_line: 0,
5496 original_column: 0,
5497 name: NO_NAME,
5498 is_range_mapping: false,
5499 }],
5500 vec![],
5501 None,
5502 None,
5503 );
5504 let warnings = validate_deep(&sm);
5505 assert!(
5506 warnings
5507 .iter()
5508 .any(|w| w.contains("source index") && w.contains("out of bounds"))
5509 );
5510 }
5511
5512 #[test]
5513 fn validate_deep_out_of_bounds_name() {
5514 let sm = SourceMap::from_parts(
5515 None,
5516 None,
5517 vec!["a.js".to_string()],
5518 vec![None],
5519 vec!["foo".to_string()],
5520 vec![Mapping {
5521 generated_line: 0,
5522 generated_column: 0,
5523 source: 0,
5524 original_line: 0,
5525 original_column: 0,
5526 name: 5,
5527 is_range_mapping: false,
5528 }],
5529 vec![],
5530 None,
5531 None,
5532 );
5533 let warnings = validate_deep(&sm);
5534 assert!(
5535 warnings
5536 .iter()
5537 .any(|w| w.contains("name index") && w.contains("out of bounds"))
5538 );
5539 }
5540
5541 #[test]
5542 fn validate_deep_out_of_bounds_ignore_list() {
5543 let sm = SourceMap::from_parts(
5544 None,
5545 None,
5546 vec!["a.js".to_string()],
5547 vec![None],
5548 vec![],
5549 vec![Mapping {
5550 generated_line: 0,
5551 generated_column: 0,
5552 source: 0,
5553 original_line: 0,
5554 original_column: 0,
5555 name: NO_NAME,
5556 is_range_mapping: false,
5557 }],
5558 vec![10],
5559 None,
5560 None,
5561 );
5562 let warnings = validate_deep(&sm);
5563 assert!(
5564 warnings
5565 .iter()
5566 .any(|w| w.contains("ignoreList") && w.contains("out of bounds"))
5567 );
5568 }
5569
5570 #[test]
5571 fn source_mapping_url_inline_decoded() {
5572 let map_json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
5574 let encoded = base64_encode_simple(map_json);
5575 let input = format!("var x;\n//# sourceMappingURL=data:application/json;base64,{encoded}");
5576 let url = parse_source_mapping_url(&input);
5577 match url {
5578 Some(SourceMappingUrl::Inline(json)) => {
5579 assert!(json.contains("version"));
5580 assert!(json.contains("AAAA"));
5581 }
5582 _ => panic!("expected inline source map"),
5583 }
5584 }
5585
5586 #[test]
5587 fn source_mapping_url_charset_variant() {
5588 let map_json = r#"{"version":3}"#;
5589 let encoded = base64_encode_simple(map_json);
5590 let input =
5591 format!("x\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,{encoded}");
5592 let url = parse_source_mapping_url(&input);
5593 assert!(matches!(url, Some(SourceMappingUrl::Inline(_))));
5594 }
5595
5596 #[test]
5597 fn source_mapping_url_invalid_base64_falls_through_to_external() {
5598 let input = "x\n//# sourceMappingURL=data:application/json;base64,!!!invalid!!!";
5600 let url = parse_source_mapping_url(input);
5601 assert!(matches!(url, Some(SourceMappingUrl::External(_))));
5603 }
5604
5605 #[test]
5606 fn from_json_lines_with_extensions_preserved() {
5607 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","x_custom":99}"#;
5608 let sm = SourceMap::from_json_lines(json, 0, 1).unwrap();
5609 assert!(sm.extensions.contains_key("x_custom"));
5610 }
5611
5612 fn base64_encode_simple(input: &str) -> String {
5614 const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
5615 let bytes = input.as_bytes();
5616 let mut result = String::new();
5617 for chunk in bytes.chunks(3) {
5618 let b0 = chunk[0] as u32;
5619 let b1 = if chunk.len() > 1 { chunk[1] as u32 } else { 0 };
5620 let b2 = if chunk.len() > 2 { chunk[2] as u32 } else { 0 };
5621 let n = (b0 << 16) | (b1 << 8) | b2;
5622 result.push(CHARS[((n >> 18) & 0x3F) as usize] as char);
5623 result.push(CHARS[((n >> 12) & 0x3F) as usize] as char);
5624 if chunk.len() > 1 {
5625 result.push(CHARS[((n >> 6) & 0x3F) as usize] as char);
5626 } else {
5627 result.push('=');
5628 }
5629 if chunk.len() > 2 {
5630 result.push(CHARS[(n & 0x3F) as usize] as char);
5631 } else {
5632 result.push('=');
5633 }
5634 }
5635 result
5636 }
5637
5638 #[test]
5641 fn mappings_iter_matches_decode() {
5642 let vlq = "AAAA;AACA,EAAA;AACA";
5643 let iter_mappings: Vec<Mapping> = MappingsIter::new(vlq).collect::<Result<_, _>>().unwrap();
5644 let (decoded, _) = decode_mappings(vlq).unwrap();
5645 assert_eq!(iter_mappings.len(), decoded.len());
5646 for (a, b) in iter_mappings.iter().zip(decoded.iter()) {
5647 assert_eq!(a.generated_line, b.generated_line);
5648 assert_eq!(a.generated_column, b.generated_column);
5649 assert_eq!(a.source, b.source);
5650 assert_eq!(a.original_line, b.original_line);
5651 assert_eq!(a.original_column, b.original_column);
5652 assert_eq!(a.name, b.name);
5653 }
5654 }
5655
5656 #[test]
5657 fn mappings_iter_empty() {
5658 let mappings: Vec<Mapping> = MappingsIter::new("").collect::<Result<_, _>>().unwrap();
5659 assert!(mappings.is_empty());
5660 }
5661
5662 #[test]
5663 fn mappings_iter_generated_only() {
5664 let vlq = "A,AAAA";
5665 let mappings: Vec<Mapping> = MappingsIter::new(vlq).collect::<Result<_, _>>().unwrap();
5666 assert_eq!(mappings.len(), 2);
5667 assert_eq!(mappings[0].source, u32::MAX);
5668 assert_eq!(mappings[1].source, 0);
5669 }
5670
5671 #[test]
5672 fn mappings_iter_with_names() {
5673 let vlq = "AAAAA";
5674 let mappings: Vec<Mapping> = MappingsIter::new(vlq).collect::<Result<_, _>>().unwrap();
5675 assert_eq!(mappings.len(), 1);
5676 assert_eq!(mappings[0].name, 0);
5677 }
5678
5679 #[test]
5680 fn mappings_iter_multiple_lines() {
5681 let vlq = "AAAA;AACA;AACA";
5682 let mappings: Vec<Mapping> = MappingsIter::new(vlq).collect::<Result<_, _>>().unwrap();
5683 assert_eq!(mappings.len(), 3);
5684 assert_eq!(mappings[0].generated_line, 0);
5685 assert_eq!(mappings[1].generated_line, 1);
5686 assert_eq!(mappings[2].generated_line, 2);
5687 }
5688 #[test]
5691 fn range_mappings_basic_decode() {
5692 let json = r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA,CAAC,GAAG","rangeMappings":"A,C"}"#;
5693 let sm = SourceMap::from_json(json).unwrap();
5694 assert!(sm.all_mappings()[0].is_range_mapping);
5695 assert!(!sm.all_mappings()[1].is_range_mapping);
5696 assert!(sm.all_mappings()[2].is_range_mapping);
5697 }
5698
5699 #[test]
5700 fn range_mapping_lookup_with_delta() {
5701 let json = r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA,GAAG","rangeMappings":"A"}"#;
5702 let sm = SourceMap::from_json(json).unwrap();
5703 assert_eq!(sm.original_position_for(0, 0).unwrap().column, 0);
5704 assert_eq!(sm.original_position_for(0, 1).unwrap().column, 1);
5705 assert_eq!(sm.original_position_for(0, 2).unwrap().column, 2);
5706 assert_eq!(sm.original_position_for(0, 3).unwrap().column, 3);
5707 }
5708
5709 #[test]
5710 fn range_mapping_cross_line() {
5711 let json = r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA","rangeMappings":"A"}"#;
5712 let sm = SourceMap::from_json(json).unwrap();
5713 assert_eq!(sm.original_position_for(1, 5).unwrap().line, 1);
5714 assert_eq!(sm.original_position_for(1, 5).unwrap().column, 0);
5715 assert_eq!(sm.original_position_for(2, 10).unwrap().line, 2);
5716 }
5717
5718 #[test]
5719 fn range_mapping_encode_roundtrip() {
5720 let json = r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA,CAAC,GAAG","rangeMappings":"A,C"}"#;
5721 assert_eq!(
5722 SourceMap::from_json(json)
5723 .unwrap()
5724 .encode_range_mappings()
5725 .unwrap(),
5726 "A,C"
5727 );
5728 }
5729
5730 #[test]
5731 fn no_range_mappings_test() {
5732 let sm = SourceMap::from_json(
5733 r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA"}"#,
5734 )
5735 .unwrap();
5736 assert!(!sm.has_range_mappings());
5737 assert!(sm.encode_range_mappings().is_none());
5738 }
5739
5740 #[test]
5741 fn range_mappings_multi_line_test() {
5742 let sm = SourceMap::from_json(r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA,CAAC;AAAA","rangeMappings":"A;A"}"#).unwrap();
5743 assert!(sm.all_mappings()[0].is_range_mapping);
5744 assert!(!sm.all_mappings()[1].is_range_mapping);
5745 assert!(sm.all_mappings()[2].is_range_mapping);
5746 }
5747
5748 #[test]
5749 fn range_mappings_json_roundtrip() {
5750 let sm = SourceMap::from_json(r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA,CAAC,GAAG","rangeMappings":"A,C"}"#).unwrap();
5751 let output = sm.to_json();
5752 assert!(output.contains("rangeMappings"));
5753 assert_eq!(
5754 SourceMap::from_json(&output).unwrap().range_mapping_count(),
5755 2
5756 );
5757 }
5758
5759 #[test]
5760 fn range_mappings_absent_from_json_test() {
5761 assert!(
5762 !SourceMap::from_json(
5763 r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA"}"#
5764 )
5765 .unwrap()
5766 .to_json()
5767 .contains("rangeMappings")
5768 );
5769 }
5770
5771 #[test]
5772 fn range_mapping_fallback_test() {
5773 let sm = SourceMap::from_json(r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA;KACK","rangeMappings":"A"}"#).unwrap();
5774 let loc = sm.original_position_for(1, 2).unwrap();
5775 assert_eq!(loc.line, 1);
5776 assert_eq!(loc.column, 0);
5777 }
5778
5779 #[test]
5780 fn range_mapping_no_fallback_non_range() {
5781 assert!(
5782 SourceMap::from_json(
5783 r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA"}"#
5784 )
5785 .unwrap()
5786 .original_position_for(1, 5)
5787 .is_none()
5788 );
5789 }
5790
5791 #[test]
5792 fn range_mapping_from_vlq_test() {
5793 let sm = SourceMap::from_vlq_with_range_mappings(
5794 "AAAA,CAAC",
5795 vec!["input.js".into()],
5796 vec![],
5797 None,
5798 None,
5799 vec![],
5800 vec![],
5801 None,
5802 Some("A"),
5803 )
5804 .unwrap();
5805 assert!(sm.all_mappings()[0].is_range_mapping);
5806 assert!(!sm.all_mappings()[1].is_range_mapping);
5807 }
5808
5809 #[test]
5810 fn range_mapping_encode_multi_line_test() {
5811 let sm = SourceMap::from_json(r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA,CAAC;AAAA,CAAC","rangeMappings":"A;B"}"#).unwrap();
5812 assert!(sm.all_mappings()[0].is_range_mapping);
5813 assert!(!sm.all_mappings()[1].is_range_mapping);
5814 assert!(!sm.all_mappings()[2].is_range_mapping);
5815 assert!(sm.all_mappings()[3].is_range_mapping);
5816 assert_eq!(sm.encode_range_mappings().unwrap(), "A;B");
5817 }
5818
5819 #[test]
5820 fn range_mapping_from_parts_test() {
5821 let sm = SourceMap::from_parts(
5822 None,
5823 None,
5824 vec!["input.js".into()],
5825 vec![],
5826 vec![],
5827 vec![
5828 Mapping {
5829 generated_line: 0,
5830 generated_column: 0,
5831 source: 0,
5832 original_line: 0,
5833 original_column: 0,
5834 name: NO_NAME,
5835 is_range_mapping: true,
5836 },
5837 Mapping {
5838 generated_line: 0,
5839 generated_column: 5,
5840 source: 0,
5841 original_line: 0,
5842 original_column: 5,
5843 name: NO_NAME,
5844 is_range_mapping: false,
5845 },
5846 ],
5847 vec![],
5848 None,
5849 None,
5850 );
5851 assert_eq!(sm.original_position_for(0, 2).unwrap().column, 2);
5852 assert_eq!(sm.original_position_for(0, 6).unwrap().column, 5);
5853 }
5854
5855 #[test]
5856 fn range_mapping_indexed_test() {
5857 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();
5858 assert!(sm.has_range_mappings());
5859 assert_eq!(sm.original_position_for(1, 3).unwrap().line, 1);
5860 }
5861
5862 #[test]
5863 fn range_mapping_empty_string_test() {
5864 assert!(!SourceMap::from_json(r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA","rangeMappings":""}"#).unwrap().has_range_mappings());
5865 }
5866
5867 #[test]
5868 fn range_mapping_lub_no_underflow() {
5869 let json = r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"KAAK","rangeMappings":"A"}"#;
5872 let sm = SourceMap::from_json(json).unwrap();
5873
5874 let loc = sm.original_position_for_with_bias(0, 2, Bias::LeastUpperBound);
5875 assert!(loc.is_some());
5876 let loc = loc.unwrap();
5877 assert_eq!(loc.line, 0);
5879 assert_eq!(loc.column, 5);
5880 }
5881}