1use std::cell::{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
34pub mod js_identifiers;
35pub mod source_view;
36pub mod utils;
37
38pub use source_view::SourceView;
39
40const NO_SOURCE: u32 = u32::MAX;
43const NO_NAME: u32 = u32::MAX;
44
45#[derive(Debug, Clone, Copy)]
53pub struct Mapping {
54 pub generated_line: u32,
56 pub generated_column: u32,
58 pub source: u32,
60 pub original_line: u32,
62 pub original_column: u32,
64 pub name: u32,
66 pub is_range_mapping: bool,
68}
69
70#[derive(Debug, Clone)]
75pub struct OriginalLocation {
76 pub source: u32,
78 pub line: u32,
80 pub column: u32,
82 pub name: Option<u32>,
84}
85
86#[derive(Debug, Clone)]
90pub struct GeneratedLocation {
91 pub line: u32,
93 pub column: u32,
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
103pub enum Bias {
104 #[default]
106 GreatestLowerBound,
107 LeastUpperBound,
109}
110
111#[derive(Debug, Clone)]
116pub struct MappedRange {
117 pub source: u32,
119 pub original_start_line: u32,
121 pub original_start_column: u32,
123 pub original_end_line: u32,
125 pub original_end_column: u32,
127}
128
129#[derive(Debug)]
131pub enum ParseError {
132 Json(serde_json::Error),
134 Vlq(DecodeError),
136 InvalidVersion(u32),
138 Scopes(srcmap_scopes::ScopesError),
140 NestedIndexMap,
142 SectionsNotOrdered,
144}
145
146impl fmt::Display for ParseError {
147 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148 match self {
149 Self::Json(e) => write!(f, "JSON parse error: {e}"),
150 Self::Vlq(e) => write!(f, "VLQ decode error: {e}"),
151 Self::InvalidVersion(v) => write!(f, "unsupported source map version: {v}"),
152 Self::Scopes(e) => write!(f, "scopes decode error: {e}"),
153 Self::NestedIndexMap => write!(f, "section map must not be an indexed source map"),
154 Self::SectionsNotOrdered => {
155 write!(f, "sections must be in ascending (line, column) order")
156 }
157 }
158 }
159}
160
161impl std::error::Error for ParseError {
162 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
163 match self {
164 Self::Json(e) => Some(e),
165 Self::Vlq(e) => Some(e),
166 Self::Scopes(e) => Some(e),
167 Self::InvalidVersion(_) | Self::NestedIndexMap | Self::SectionsNotOrdered => None,
168 }
169 }
170}
171
172impl From<serde_json::Error> for ParseError {
173 fn from(e: serde_json::Error) -> Self {
174 Self::Json(e)
175 }
176}
177
178impl From<DecodeError> for ParseError {
179 fn from(e: DecodeError) -> Self {
180 Self::Vlq(e)
181 }
182}
183
184impl From<srcmap_scopes::ScopesError> for ParseError {
185 fn from(e: srcmap_scopes::ScopesError) -> Self {
186 Self::Scopes(e)
187 }
188}
189
190pub fn resolve_sources(raw_sources: &[Option<String>], source_root: &str) -> Vec<String> {
194 raw_sources
195 .iter()
196 .map(|s| match s {
197 Some(s) if !source_root.is_empty() => format!("{source_root}{s}"),
198 Some(s) => s.clone(),
199 None => String::new(),
200 })
201 .collect()
202}
203
204fn build_source_map(sources: &[String]) -> HashMap<String, u32> {
206 sources
207 .iter()
208 .enumerate()
209 .map(|(i, s)| (s.clone(), i as u32))
210 .collect()
211}
212
213#[derive(Deserialize)]
216struct RawSourceMap<'a> {
217 version: u32,
218 #[serde(default)]
219 file: Option<String>,
220 #[serde(default, rename = "sourceRoot")]
221 source_root: Option<String>,
222 #[serde(default)]
223 sources: Vec<Option<String>>,
224 #[serde(default, rename = "sourcesContent")]
225 sources_content: Option<Vec<Option<String>>>,
226 #[serde(default)]
227 names: Vec<String>,
228 #[serde(default, borrow)]
229 mappings: &'a str,
230 #[serde(default, rename = "ignoreList")]
231 ignore_list: Option<Vec<u32>>,
232 #[serde(default, rename = "x_google_ignoreList")]
234 x_google_ignore_list: Option<Vec<u32>>,
235 #[serde(default, rename = "debugId", alias = "debug_id")]
238 debug_id: Option<String>,
239 #[serde(default, borrow)]
241 scopes: Option<&'a str>,
242 #[serde(default, borrow, rename = "rangeMappings")]
244 range_mappings: Option<&'a str>,
245 #[serde(default)]
247 sections: Option<Vec<RawSection>>,
248 #[serde(flatten)]
250 extensions: HashMap<String, serde_json::Value>,
251}
252
253#[derive(Deserialize)]
255struct RawSection {
256 offset: RawOffset,
257 map: Box<serde_json::value::RawValue>,
258}
259
260#[derive(Deserialize)]
261struct RawOffset {
262 line: u32,
263 column: u32,
264}
265
266#[derive(Deserialize)]
272pub struct RawSourceMapLite<'a> {
273 pub version: u32,
274 #[serde(default)]
275 pub file: Option<String>,
276 #[serde(default, rename = "sourceRoot")]
277 pub source_root: Option<String>,
278 #[serde(default)]
279 pub sources: Vec<Option<String>>,
280 #[serde(default)]
281 pub names: Vec<String>,
282 #[serde(default, borrow)]
283 pub mappings: &'a str,
284 #[serde(default, rename = "ignoreList")]
285 pub ignore_list: Option<Vec<u32>>,
286 #[serde(default, rename = "x_google_ignoreList")]
287 pub x_google_ignore_list: Option<Vec<u32>>,
288 #[serde(default, rename = "debugId", alias = "debug_id")]
289 pub debug_id: Option<String>,
290 #[serde(default, borrow)]
291 pub scopes: Option<&'a str>,
292 #[serde(default, borrow, rename = "rangeMappings")]
293 pub range_mappings: Option<&'a str>,
294 #[serde(default)]
297 pub sections: Option<Vec<serde_json::Value>>,
298}
299
300#[derive(Debug, Clone)]
325pub struct SourceMap {
326 pub file: Option<String>,
327 pub source_root: Option<String>,
328 pub sources: Vec<String>,
329 pub sources_content: Vec<Option<String>>,
330 pub names: Vec<String>,
331 pub ignore_list: Vec<u32>,
332 pub extensions: HashMap<String, serde_json::Value>,
334 pub debug_id: Option<String>,
336 pub scopes: Option<ScopeInfo>,
338
339 mappings: Vec<Mapping>,
341
342 line_offsets: Vec<u32>,
345
346 reverse_index: OnceCell<Vec<u32>>,
349
350 source_map: HashMap<String, u32>,
352
353 has_range_mappings: bool,
355}
356
357impl SourceMap {
358 pub fn from_json(json: &str) -> Result<Self, ParseError> {
361 Self::from_json_inner(json, true)
362 }
363
364 pub fn from_json_no_content(json: &str) -> Result<Self, ParseError> {
368 let raw: RawSourceMapLite<'_> = serde_json::from_str(json)?;
369
370 if raw.version != 3 {
371 return Err(ParseError::InvalidVersion(raw.version));
372 }
373
374 let source_root = raw.source_root.as_deref().unwrap_or("");
375 let sources = resolve_sources(&raw.sources, source_root);
376 let source_map = build_source_map(&sources);
377 let (mut mappings, line_offsets) = decode_mappings(raw.mappings)?;
378
379 if let Some(range_mappings_str) = raw.range_mappings
380 && !range_mappings_str.is_empty()
381 {
382 decode_range_mappings(range_mappings_str, &mut mappings, &line_offsets)?;
383 }
384
385 let num_sources = sources.len();
386 let scopes = match raw.scopes {
387 Some(scopes_str) if !scopes_str.is_empty() => Some(srcmap_scopes::decode_scopes(
388 scopes_str,
389 &raw.names,
390 num_sources,
391 )?),
392 _ => None,
393 };
394
395 let ignore_list = match raw.ignore_list {
396 Some(list) => list,
397 None => raw.x_google_ignore_list.unwrap_or_default(),
398 };
399
400 let has_range_mappings = mappings.iter().any(|m| m.is_range_mapping);
401
402 Ok(Self {
403 file: raw.file,
404 source_root: raw.source_root,
405 sources,
406 sources_content: Vec::new(),
407 names: raw.names,
408 ignore_list,
409 extensions: HashMap::new(),
410 debug_id: raw.debug_id,
411 scopes,
412 mappings,
413 line_offsets,
414 reverse_index: OnceCell::new(),
415 source_map,
416 has_range_mappings,
417 })
418 }
419
420 fn from_json_inner(json: &str, allow_sections: bool) -> Result<Self, ParseError> {
422 let raw: RawSourceMap<'_> = serde_json::from_str(json)?;
423
424 if raw.version != 3 {
425 return Err(ParseError::InvalidVersion(raw.version));
426 }
427
428 if let Some(sections) = raw.sections {
430 if !allow_sections {
431 return Err(ParseError::NestedIndexMap);
432 }
433 return Self::from_sections(raw.file, sections);
434 }
435
436 Self::from_regular(raw)
437 }
438
439 fn from_regular(raw: RawSourceMap<'_>) -> Result<Self, ParseError> {
441 let source_root = raw.source_root.as_deref().unwrap_or("");
442 let sources = resolve_sources(&raw.sources, source_root);
443 let sources_content = raw.sources_content.unwrap_or_default();
444 let source_map = build_source_map(&sources);
445
446 let (mut mappings, line_offsets) = decode_mappings(raw.mappings)?;
448
449 if let Some(range_mappings_str) = raw.range_mappings
451 && !range_mappings_str.is_empty()
452 {
453 decode_range_mappings(range_mappings_str, &mut mappings, &line_offsets)?;
454 }
455
456 let num_sources = sources.len();
458 let scopes = match raw.scopes {
459 Some(scopes_str) if !scopes_str.is_empty() => Some(srcmap_scopes::decode_scopes(
460 scopes_str,
461 &raw.names,
462 num_sources,
463 )?),
464 _ => None,
465 };
466
467 let ignore_list = match raw.ignore_list {
469 Some(list) => list,
470 None => raw.x_google_ignore_list.unwrap_or_default(),
471 };
472
473 let extensions: HashMap<String, serde_json::Value> = raw
475 .extensions
476 .into_iter()
477 .filter(|(k, _)| k.starts_with("x_") || k.starts_with("x-"))
478 .collect();
479
480 let has_range_mappings = mappings.iter().any(|m| m.is_range_mapping);
481
482 Ok(Self {
483 file: raw.file,
484 source_root: raw.source_root,
485 sources,
486 sources_content,
487 names: raw.names,
488 ignore_list,
489 extensions,
490 debug_id: raw.debug_id,
491 scopes,
492 mappings,
493 line_offsets,
494 reverse_index: OnceCell::new(),
495 source_map,
496 has_range_mappings,
497 })
498 }
499
500 fn from_sections(file: Option<String>, sections: Vec<RawSection>) -> Result<Self, ParseError> {
502 let mut all_sources: Vec<String> = Vec::new();
503 let mut all_sources_content: Vec<Option<String>> = Vec::new();
504 let mut all_names: Vec<String> = Vec::new();
505 let mut all_mappings: Vec<Mapping> = Vec::new();
506 let mut all_ignore_list: Vec<u32> = Vec::new();
507 let mut max_line: u32 = 0;
508
509 let mut source_index_map: HashMap<String, u32> = HashMap::new();
511 let mut name_index_map: HashMap<String, u32> = HashMap::new();
512
513 for i in 1..sections.len() {
515 let prev = §ions[i - 1].offset;
516 let curr = §ions[i].offset;
517 if (curr.line, curr.column) <= (prev.line, prev.column) {
518 return Err(ParseError::SectionsNotOrdered);
519 }
520 }
521
522 for section in §ions {
523 let sub = Self::from_json_inner(section.map.get(), false)?;
525
526 let line_offset = section.offset.line;
527 let col_offset = section.offset.column;
528
529 let source_remap: Vec<u32> = sub
531 .sources
532 .iter()
533 .enumerate()
534 .map(|(i, s)| {
535 if let Some(&existing) = source_index_map.get(s) {
536 existing
537 } else {
538 let idx = all_sources.len() as u32;
539 all_sources.push(s.clone());
540 let content = sub.sources_content.get(i).cloned().unwrap_or(None);
542 all_sources_content.push(content);
543 source_index_map.insert(s.clone(), idx);
544 idx
545 }
546 })
547 .collect();
548
549 let name_remap: Vec<u32> = sub
551 .names
552 .iter()
553 .map(|n| {
554 if let Some(&existing) = name_index_map.get(n) {
555 existing
556 } else {
557 let idx = all_names.len() as u32;
558 all_names.push(n.clone());
559 name_index_map.insert(n.clone(), idx);
560 idx
561 }
562 })
563 .collect();
564
565 for &idx in &sub.ignore_list {
567 let global_idx = source_remap[idx as usize];
568 if !all_ignore_list.contains(&global_idx) {
569 all_ignore_list.push(global_idx);
570 }
571 }
572
573 for m in &sub.mappings {
575 let gen_line = m.generated_line + line_offset;
576 let gen_col = if m.generated_line == 0 {
577 m.generated_column + col_offset
578 } else {
579 m.generated_column
580 };
581
582 all_mappings.push(Mapping {
583 generated_line: gen_line,
584 generated_column: gen_col,
585 source: if m.source == NO_SOURCE {
586 NO_SOURCE
587 } else {
588 source_remap[m.source as usize]
589 },
590 original_line: m.original_line,
591 original_column: m.original_column,
592 name: if m.name == NO_NAME {
593 NO_NAME
594 } else {
595 name_remap[m.name as usize]
596 },
597 is_range_mapping: m.is_range_mapping,
598 });
599
600 if gen_line > max_line {
601 max_line = gen_line;
602 }
603 }
604 }
605
606 all_mappings.sort_unstable_by(|a, b| {
608 a.generated_line
609 .cmp(&b.generated_line)
610 .then(a.generated_column.cmp(&b.generated_column))
611 });
612
613 let line_count = if all_mappings.is_empty() {
615 0
616 } else {
617 max_line as usize + 1
618 };
619 let mut line_offsets: Vec<u32> = vec![0; line_count + 1];
620 let mut current_line: usize = 0;
621 for (i, m) in all_mappings.iter().enumerate() {
622 while current_line < m.generated_line as usize {
623 current_line += 1;
624 if current_line < line_offsets.len() {
625 line_offsets[current_line] = i as u32;
626 }
627 }
628 }
629 if !line_offsets.is_empty() {
631 let last = all_mappings.len() as u32;
632 for offset in line_offsets.iter_mut().skip(current_line + 1) {
633 *offset = last;
634 }
635 }
636
637 let source_map = build_source_map(&all_sources);
638 let has_range_mappings = all_mappings.iter().any(|m| m.is_range_mapping);
639
640 Ok(Self {
641 file,
642 source_root: None,
643 sources: all_sources,
644 sources_content: all_sources_content,
645 names: all_names,
646 ignore_list: all_ignore_list,
647 extensions: HashMap::new(),
648 debug_id: None,
649 scopes: None, mappings: all_mappings,
651 line_offsets,
652 reverse_index: OnceCell::new(),
653 source_map,
654 has_range_mappings,
655 })
656 }
657
658 pub fn original_position_for(&self, line: u32, column: u32) -> Option<OriginalLocation> {
663 self.original_position_for_with_bias(line, column, Bias::GreatestLowerBound)
664 }
665
666 pub fn original_position_for_with_bias(
672 &self,
673 line: u32,
674 column: u32,
675 bias: Bias,
676 ) -> Option<OriginalLocation> {
677 let line_idx = line as usize;
678 if line_idx + 1 >= self.line_offsets.len() {
679 return self.range_mapping_fallback(line, column);
680 }
681
682 let start = self.line_offsets[line_idx] as usize;
683 let end = self.line_offsets[line_idx + 1] as usize;
684
685 if start == end {
686 return self.range_mapping_fallback(line, column);
687 }
688
689 let line_mappings = &self.mappings[start..end];
690
691 let idx = match bias {
692 Bias::GreatestLowerBound => {
693 match line_mappings.binary_search_by_key(&column, |m| m.generated_column) {
694 Ok(i) => i,
695 Err(0) => return self.range_mapping_fallback(line, column),
696 Err(i) => i - 1,
697 }
698 }
699 Bias::LeastUpperBound => {
700 match line_mappings.binary_search_by_key(&column, |m| m.generated_column) {
701 Ok(i) => i,
702 Err(i) => {
703 if i >= line_mappings.len() {
704 return None;
705 }
706 i
707 }
708 }
709 }
710 };
711
712 let mapping = &line_mappings[idx];
713
714 if mapping.source == NO_SOURCE {
715 return None;
716 }
717
718 if mapping.is_range_mapping && column >= mapping.generated_column {
719 let column_delta = column - mapping.generated_column;
720 return Some(OriginalLocation {
721 source: mapping.source,
722 line: mapping.original_line,
723 column: mapping.original_column + column_delta,
724 name: if mapping.name == NO_NAME {
725 None
726 } else {
727 Some(mapping.name)
728 },
729 });
730 }
731
732 Some(OriginalLocation {
733 source: mapping.source,
734 line: mapping.original_line,
735 column: mapping.original_column,
736 name: if mapping.name == NO_NAME {
737 None
738 } else {
739 Some(mapping.name)
740 },
741 })
742 }
743
744 fn range_mapping_fallback(&self, line: u32, column: u32) -> Option<OriginalLocation> {
749 let line_idx = line as usize;
750 let search_end = if line_idx + 1 < self.line_offsets.len() {
751 self.line_offsets[line_idx] as usize
752 } else {
753 self.mappings.len()
754 };
755 if search_end == 0 {
756 return None;
757 }
758 let last_mapping = &self.mappings[search_end - 1];
759 if !last_mapping.is_range_mapping || last_mapping.source == NO_SOURCE {
760 return None;
761 }
762 let line_delta = line - last_mapping.generated_line;
763 let column_delta = if line_delta == 0 {
764 column.saturating_sub(last_mapping.generated_column)
765 } else {
766 0
767 };
768 Some(OriginalLocation {
769 source: last_mapping.source,
770 line: last_mapping.original_line + line_delta,
771 column: last_mapping.original_column + column_delta,
772 name: if last_mapping.name == NO_NAME {
773 None
774 } else {
775 Some(last_mapping.name)
776 },
777 })
778 }
779
780 pub fn generated_position_for(
785 &self,
786 source: &str,
787 line: u32,
788 column: u32,
789 ) -> Option<GeneratedLocation> {
790 self.generated_position_for_with_bias(source, line, column, Bias::LeastUpperBound)
791 }
792
793 pub fn generated_position_for_with_bias(
799 &self,
800 source: &str,
801 line: u32,
802 column: u32,
803 bias: Bias,
804 ) -> Option<GeneratedLocation> {
805 let &source_idx = self.source_map.get(source)?;
806
807 let reverse_index = self
808 .reverse_index
809 .get_or_init(|| build_reverse_index(&self.mappings));
810
811 let idx = reverse_index.partition_point(|&i| {
813 let m = &self.mappings[i as usize];
814 (m.source, m.original_line, m.original_column) < (source_idx, line, column)
815 });
816
817 match bias {
818 Bias::GreatestLowerBound => {
819 if idx < reverse_index.len() {
823 let mapping = &self.mappings[reverse_index[idx] as usize];
824 if mapping.source == source_idx
825 && mapping.original_line == line
826 && mapping.original_column == column
827 {
828 return Some(GeneratedLocation {
829 line: mapping.generated_line,
830 column: mapping.generated_column,
831 });
832 }
833 }
834 if idx == 0 {
836 return None;
837 }
838 let mapping = &self.mappings[reverse_index[idx - 1] as usize];
839 if mapping.source != source_idx {
840 return None;
841 }
842 Some(GeneratedLocation {
843 line: mapping.generated_line,
844 column: mapping.generated_column,
845 })
846 }
847 Bias::LeastUpperBound => {
848 if idx >= reverse_index.len() {
849 return None;
850 }
851 let mapping = &self.mappings[reverse_index[idx] as usize];
852 if mapping.source != source_idx {
853 return None;
854 }
855 Some(GeneratedLocation {
856 line: mapping.generated_line,
857 column: mapping.generated_column,
858 })
859 }
860 }
861 }
862
863 pub fn all_generated_positions_for(
868 &self,
869 source: &str,
870 line: u32,
871 column: u32,
872 ) -> Vec<GeneratedLocation> {
873 let Some(&source_idx) = self.source_map.get(source) else {
874 return Vec::new();
875 };
876
877 let reverse_index = self
878 .reverse_index
879 .get_or_init(|| build_reverse_index(&self.mappings));
880
881 let start = reverse_index.partition_point(|&i| {
883 let m = &self.mappings[i as usize];
884 (m.source, m.original_line, m.original_column) < (source_idx, line, column)
885 });
886
887 let mut results = Vec::new();
888
889 for &ri in &reverse_index[start..] {
890 let m = &self.mappings[ri as usize];
891 if m.source != source_idx || m.original_line != line || m.original_column != column {
892 break;
893 }
894 results.push(GeneratedLocation {
895 line: m.generated_line,
896 column: m.generated_column,
897 });
898 }
899
900 results
901 }
902
903 pub fn map_range(
909 &self,
910 start_line: u32,
911 start_column: u32,
912 end_line: u32,
913 end_column: u32,
914 ) -> Option<MappedRange> {
915 let start = self.original_position_for(start_line, start_column)?;
916 let end = self.original_position_for(end_line, end_column)?;
917
918 if start.source != end.source {
920 return None;
921 }
922
923 Some(MappedRange {
924 source: start.source,
925 original_start_line: start.line,
926 original_start_column: start.column,
927 original_end_line: end.line,
928 original_end_column: end.column,
929 })
930 }
931
932 #[inline]
939 pub fn source(&self, index: u32) -> &str {
940 &self.sources[index as usize]
941 }
942
943 #[inline]
945 pub fn get_source(&self, index: u32) -> Option<&str> {
946 self.sources.get(index as usize).map(|s| s.as_str())
947 }
948
949 #[inline]
956 pub fn name(&self, index: u32) -> &str {
957 &self.names[index as usize]
958 }
959
960 #[inline]
962 pub fn get_name(&self, index: u32) -> Option<&str> {
963 self.names.get(index as usize).map(|s| s.as_str())
964 }
965
966 #[inline]
968 pub fn source_index(&self, name: &str) -> Option<u32> {
969 self.source_map.get(name).copied()
970 }
971
972 #[inline]
974 pub fn mapping_count(&self) -> usize {
975 self.mappings.len()
976 }
977
978 #[inline]
980 pub fn line_count(&self) -> usize {
981 self.line_offsets.len().saturating_sub(1)
982 }
983
984 #[inline]
986 pub fn mappings_for_line(&self, line: u32) -> &[Mapping] {
987 let line_idx = line as usize;
988 if line_idx + 1 >= self.line_offsets.len() {
989 return &[];
990 }
991 let start = self.line_offsets[line_idx] as usize;
992 let end = self.line_offsets[line_idx + 1] as usize;
993 &self.mappings[start..end]
994 }
995
996 #[inline]
998 pub fn all_mappings(&self) -> &[Mapping] {
999 &self.mappings
1000 }
1001
1002 pub fn to_json(&self) -> String {
1007 self.to_json_with_options(false)
1008 }
1009
1010 pub fn to_json_with_options(&self, exclude_content: bool) -> String {
1014 let mappings = self.encode_mappings();
1015
1016 let scopes_encoded = if let Some(ref scopes_info) = self.scopes {
1018 let mut names_clone = self.names.clone();
1019 let s = srcmap_scopes::encode_scopes(scopes_info, &mut names_clone);
1020 Some((s, names_clone))
1021 } else {
1022 None
1023 };
1024 let names_for_json = match &scopes_encoded {
1025 Some((_, expanded_names)) => expanded_names,
1026 None => &self.names,
1027 };
1028
1029 let source_root_prefix = self.source_root.as_deref().unwrap_or("");
1030
1031 let mut json = String::with_capacity(256 + mappings.len());
1032 json.push_str(r#"{"version":3"#);
1033
1034 if let Some(ref file) = self.file {
1035 json.push_str(r#","file":"#);
1036 json_quote_into(&mut json, file);
1037 }
1038
1039 if let Some(ref root) = self.source_root {
1040 json.push_str(r#","sourceRoot":"#);
1041 json_quote_into(&mut json, root);
1042 }
1043
1044 json.push_str(r#","sources":["#);
1046 for (i, s) in self.sources.iter().enumerate() {
1047 if i > 0 {
1048 json.push(',');
1049 }
1050 let source_name = if !source_root_prefix.is_empty() {
1051 s.strip_prefix(source_root_prefix).unwrap_or(s)
1052 } else {
1053 s
1054 };
1055 json_quote_into(&mut json, source_name);
1056 }
1057 json.push(']');
1058
1059 if !exclude_content
1060 && !self.sources_content.is_empty()
1061 && self.sources_content.iter().any(|c| c.is_some())
1062 {
1063 json.push_str(r#","sourcesContent":["#);
1064 for (i, c) in self.sources_content.iter().enumerate() {
1065 if i > 0 {
1066 json.push(',');
1067 }
1068 match c {
1069 Some(content) => json_quote_into(&mut json, content),
1070 None => json.push_str("null"),
1071 }
1072 }
1073 json.push(']');
1074 }
1075
1076 json.push_str(r#","names":["#);
1077 for (i, n) in names_for_json.iter().enumerate() {
1078 if i > 0 {
1079 json.push(',');
1080 }
1081 json_quote_into(&mut json, n);
1082 }
1083 json.push(']');
1084
1085 json.push_str(r#","mappings":""#);
1087 json.push_str(&mappings);
1088 json.push('"');
1089
1090 if let Some(range_mappings) = self.encode_range_mappings() {
1091 json.push_str(r#","rangeMappings":""#);
1093 json.push_str(&range_mappings);
1094 json.push('"');
1095 }
1096
1097 if !self.ignore_list.is_empty() {
1098 use std::fmt::Write;
1099 json.push_str(r#","ignoreList":["#);
1100 for (i, &idx) in self.ignore_list.iter().enumerate() {
1101 if i > 0 {
1102 json.push(',');
1103 }
1104 let _ = write!(json, "{idx}");
1105 }
1106 json.push(']');
1107 }
1108
1109 if let Some(ref id) = self.debug_id {
1110 json.push_str(r#","debugId":"#);
1111 json_quote_into(&mut json, id);
1112 }
1113
1114 if let Some((ref s, _)) = scopes_encoded {
1116 json.push_str(r#","scopes":"#);
1117 json_quote_into(&mut json, s);
1118 }
1119
1120 let mut ext_keys: Vec<&String> = self.extensions.keys().collect();
1122 ext_keys.sort();
1123 for key in ext_keys {
1124 if let Some(val) = self.extensions.get(key) {
1125 json.push(',');
1126 json_quote_into(&mut json, key);
1127 json.push(':');
1128 json.push_str(&serde_json::to_string(val).unwrap_or_default());
1129 }
1130 }
1131
1132 json.push('}');
1133 json
1134 }
1135
1136 #[allow(clippy::too_many_arguments)]
1142 pub fn from_parts(
1143 file: Option<String>,
1144 source_root: Option<String>,
1145 sources: Vec<String>,
1146 sources_content: Vec<Option<String>>,
1147 names: Vec<String>,
1148 mappings: Vec<Mapping>,
1149 ignore_list: Vec<u32>,
1150 debug_id: Option<String>,
1151 scopes: Option<ScopeInfo>,
1152 ) -> Self {
1153 let line_count = mappings.last().map_or(0, |m| m.generated_line as usize + 1);
1155 let mut line_offsets: Vec<u32> = vec![0; line_count + 1];
1156 let mut current_line: usize = 0;
1157 for (i, m) in mappings.iter().enumerate() {
1158 while current_line < m.generated_line as usize {
1159 current_line += 1;
1160 if current_line < line_offsets.len() {
1161 line_offsets[current_line] = i as u32;
1162 }
1163 }
1164 }
1165 if !line_offsets.is_empty() {
1167 let last = mappings.len() as u32;
1168 for offset in line_offsets.iter_mut().skip(current_line + 1) {
1169 *offset = last;
1170 }
1171 }
1172
1173 let source_map = build_source_map(&sources);
1174 let has_range_mappings = mappings.iter().any(|m| m.is_range_mapping);
1175
1176 Self {
1177 file,
1178 source_root,
1179 sources,
1180 sources_content,
1181 names,
1182 ignore_list,
1183 extensions: HashMap::new(),
1184 debug_id,
1185 scopes,
1186 mappings,
1187 line_offsets,
1188 reverse_index: OnceCell::new(),
1189 source_map,
1190 has_range_mappings,
1191 }
1192 }
1193
1194 #[allow(clippy::too_many_arguments)]
1200 pub fn from_vlq(
1201 mappings_str: &str,
1202 sources: Vec<String>,
1203 names: Vec<String>,
1204 file: Option<String>,
1205 source_root: Option<String>,
1206 sources_content: Vec<Option<String>>,
1207 ignore_list: Vec<u32>,
1208 debug_id: Option<String>,
1209 ) -> Result<Self, ParseError> {
1210 Self::from_vlq_with_range_mappings(
1211 mappings_str,
1212 sources,
1213 names,
1214 file,
1215 source_root,
1216 sources_content,
1217 ignore_list,
1218 debug_id,
1219 None,
1220 )
1221 }
1222
1223 #[allow(clippy::too_many_arguments)]
1226 pub fn from_vlq_with_range_mappings(
1227 mappings_str: &str,
1228 sources: Vec<String>,
1229 names: Vec<String>,
1230 file: Option<String>,
1231 source_root: Option<String>,
1232 sources_content: Vec<Option<String>>,
1233 ignore_list: Vec<u32>,
1234 debug_id: Option<String>,
1235 range_mappings_str: Option<&str>,
1236 ) -> Result<Self, ParseError> {
1237 let (mut mappings, line_offsets) = decode_mappings(mappings_str)?;
1238 if let Some(rm_str) = range_mappings_str
1239 && !rm_str.is_empty()
1240 {
1241 decode_range_mappings(rm_str, &mut mappings, &line_offsets)?;
1242 }
1243 let source_map = build_source_map(&sources);
1244 let has_range_mappings = mappings.iter().any(|m| m.is_range_mapping);
1245 Ok(Self {
1246 file,
1247 source_root,
1248 sources,
1249 sources_content,
1250 names,
1251 ignore_list,
1252 extensions: HashMap::new(),
1253 debug_id,
1254 scopes: None,
1255 mappings,
1256 line_offsets,
1257 reverse_index: OnceCell::new(),
1258 source_map,
1259 has_range_mappings,
1260 })
1261 }
1262
1263 pub fn builder() -> SourceMapBuilder {
1290 SourceMapBuilder::new()
1291 }
1292
1293 pub fn from_json_lines(json: &str, start_line: u32, end_line: u32) -> Result<Self, ParseError> {
1299 let raw: RawSourceMap<'_> = serde_json::from_str(json)?;
1300
1301 if raw.version != 3 {
1302 return Err(ParseError::InvalidVersion(raw.version));
1303 }
1304
1305 let source_root = raw.source_root.as_deref().unwrap_or("");
1306 let sources = resolve_sources(&raw.sources, source_root);
1307 let sources_content = raw.sources_content.unwrap_or_default();
1308 let source_map = build_source_map(&sources);
1309
1310 let (mappings, line_offsets) = decode_mappings_range(raw.mappings, start_line, end_line)?;
1312
1313 let num_sources = sources.len();
1315 let scopes = match raw.scopes {
1316 Some(scopes_str) if !scopes_str.is_empty() => Some(srcmap_scopes::decode_scopes(
1317 scopes_str,
1318 &raw.names,
1319 num_sources,
1320 )?),
1321 _ => None,
1322 };
1323
1324 let ignore_list = match raw.ignore_list {
1325 Some(list) => list,
1326 None => raw.x_google_ignore_list.unwrap_or_default(),
1327 };
1328
1329 let extensions: HashMap<String, serde_json::Value> = raw
1331 .extensions
1332 .into_iter()
1333 .filter(|(k, _)| k.starts_with("x_") || k.starts_with("x-"))
1334 .collect();
1335
1336 let has_range_mappings = mappings.iter().any(|m| m.is_range_mapping);
1337
1338 Ok(Self {
1339 file: raw.file,
1340 source_root: raw.source_root,
1341 sources,
1342 sources_content,
1343 names: raw.names,
1344 ignore_list,
1345 extensions,
1346 debug_id: raw.debug_id,
1347 scopes,
1348 mappings,
1349 line_offsets,
1350 reverse_index: OnceCell::new(),
1351 source_map,
1352 has_range_mappings,
1353 })
1354 }
1355
1356 pub fn encode_mappings(&self) -> String {
1358 if self.mappings.is_empty() {
1359 return String::new();
1360 }
1361
1362 let mut out: Vec<u8> = Vec::with_capacity(self.mappings.len() * 6);
1363
1364 let mut prev_gen_col: i64 = 0;
1365 let mut prev_source: i64 = 0;
1366 let mut prev_orig_line: i64 = 0;
1367 let mut prev_orig_col: i64 = 0;
1368 let mut prev_name: i64 = 0;
1369 let mut prev_gen_line: u32 = 0;
1370 let mut first_in_line = true;
1371
1372 for m in &self.mappings {
1373 while prev_gen_line < m.generated_line {
1374 out.push(b';');
1375 prev_gen_line += 1;
1376 prev_gen_col = 0;
1377 first_in_line = true;
1378 }
1379
1380 if !first_in_line {
1381 out.push(b',');
1382 }
1383 first_in_line = false;
1384
1385 srcmap_codec::vlq_encode(&mut out, m.generated_column as i64 - prev_gen_col);
1386 prev_gen_col = m.generated_column as i64;
1387
1388 if m.source != NO_SOURCE {
1389 srcmap_codec::vlq_encode(&mut out, m.source as i64 - prev_source);
1390 prev_source = m.source as i64;
1391
1392 srcmap_codec::vlq_encode(&mut out, m.original_line as i64 - prev_orig_line);
1393 prev_orig_line = m.original_line as i64;
1394
1395 srcmap_codec::vlq_encode(&mut out, m.original_column as i64 - prev_orig_col);
1396 prev_orig_col = m.original_column as i64;
1397
1398 if m.name != NO_NAME {
1399 srcmap_codec::vlq_encode(&mut out, m.name as i64 - prev_name);
1400 prev_name = m.name as i64;
1401 }
1402 }
1403 }
1404
1405 debug_assert!(out.is_ascii());
1408 unsafe { String::from_utf8_unchecked(out) }
1409 }
1410
1411 pub fn encode_range_mappings(&self) -> Option<String> {
1412 if !self.has_range_mappings {
1413 return None;
1414 }
1415 let line_count = self.line_offsets.len().saturating_sub(1);
1416 let mut out: Vec<u8> = Vec::new();
1417 for line_idx in 0..line_count {
1418 if line_idx > 0 {
1419 out.push(b';');
1420 }
1421 let start = self.line_offsets[line_idx] as usize;
1422 let end = self.line_offsets[line_idx + 1] as usize;
1423 let mut prev_offset: u64 = 0;
1424 let mut first_on_line = true;
1425 for (i, mapping) in self.mappings[start..end].iter().enumerate() {
1426 if mapping.is_range_mapping {
1427 if !first_on_line {
1428 out.push(b',');
1429 }
1430 first_on_line = false;
1431 vlq_encode_unsigned(&mut out, i as u64 - prev_offset);
1432 prev_offset = i as u64;
1433 }
1434 }
1435 }
1436 while out.last() == Some(&b';') {
1437 out.pop();
1438 }
1439 if out.is_empty() {
1440 return None;
1441 }
1442 debug_assert!(out.is_ascii());
1445 Some(unsafe { String::from_utf8_unchecked(out) })
1446 }
1447
1448 #[inline]
1449 pub fn has_range_mappings(&self) -> bool {
1450 self.has_range_mappings
1451 }
1452
1453 #[inline]
1454 pub fn range_mapping_count(&self) -> usize {
1455 self.mappings.iter().filter(|m| m.is_range_mapping).count()
1456 }
1457}
1458
1459#[derive(Debug, Clone, Copy)]
1463struct VlqState {
1464 source_index: i64,
1465 original_line: i64,
1466 original_column: i64,
1467 name_index: i64,
1468}
1469
1470#[derive(Debug, Clone)]
1472struct LineInfo {
1473 byte_offset: usize,
1475 byte_end: usize,
1477 state: VlqState,
1479}
1480
1481#[derive(Debug)]
1503pub struct LazySourceMap {
1504 pub file: Option<String>,
1505 pub source_root: Option<String>,
1506 pub sources: Vec<String>,
1507 pub sources_content: Vec<Option<String>>,
1508 pub names: Vec<String>,
1509 pub ignore_list: Vec<u32>,
1510 pub extensions: HashMap<String, serde_json::Value>,
1511 pub debug_id: Option<String>,
1512 pub scopes: Option<ScopeInfo>,
1513
1514 raw_mappings: String,
1516
1517 line_info: Vec<LineInfo>,
1520
1521 decoded_lines: RefCell<HashMap<u32, Vec<Mapping>>>,
1523
1524 source_map: HashMap<String, u32>,
1526
1527 fast_scan: bool,
1530
1531 decode_watermark: Cell<u32>,
1534 decode_state: Cell<VlqState>,
1535}
1536
1537impl LazySourceMap {
1538 pub fn from_json(json: &str) -> Result<Self, ParseError> {
1543 let raw: RawSourceMap<'_> = serde_json::from_str(json)?;
1544
1545 if raw.version != 3 {
1546 return Err(ParseError::InvalidVersion(raw.version));
1547 }
1548
1549 let source_root = raw.source_root.as_deref().unwrap_or("");
1550 let sources = resolve_sources(&raw.sources, source_root);
1551 let sources_content = raw.sources_content.unwrap_or_default();
1552 let source_map = build_source_map(&sources);
1553
1554 let raw_mappings = raw.mappings.to_string();
1557 let line_info = prescan_mappings(&raw_mappings)?;
1558
1559 let num_sources = sources.len();
1561 let scopes = match raw.scopes {
1562 Some(scopes_str) if !scopes_str.is_empty() => Some(srcmap_scopes::decode_scopes(
1563 scopes_str,
1564 &raw.names,
1565 num_sources,
1566 )?),
1567 _ => None,
1568 };
1569
1570 let ignore_list = match raw.ignore_list {
1571 Some(list) => list,
1572 None => raw.x_google_ignore_list.unwrap_or_default(),
1573 };
1574
1575 let extensions: HashMap<String, serde_json::Value> = raw
1577 .extensions
1578 .into_iter()
1579 .filter(|(k, _)| k.starts_with("x_") || k.starts_with("x-"))
1580 .collect();
1581
1582 Ok(Self {
1583 file: raw.file,
1584 source_root: raw.source_root,
1585 sources,
1586 sources_content,
1587 names: raw.names,
1588 ignore_list,
1589 extensions,
1590 debug_id: raw.debug_id,
1591 scopes,
1592 raw_mappings,
1593 line_info,
1594 decoded_lines: RefCell::new(HashMap::new()),
1595 source_map,
1596 fast_scan: false,
1597 decode_watermark: Cell::new(0),
1598 decode_state: Cell::new(VlqState { source_index: 0, original_line: 0, original_column: 0, name_index: 0 }),
1599 })
1600 }
1601
1602 pub fn from_json_no_content(json: &str) -> Result<Self, ParseError> {
1610 let raw: RawSourceMapLite<'_> = serde_json::from_str(json)?;
1611
1612 if raw.version != 3 {
1613 return Err(ParseError::InvalidVersion(raw.version));
1614 }
1615
1616 if raw.sections.is_some() {
1619 return Err(ParseError::NestedIndexMap);
1620 }
1621
1622 let source_root = raw.source_root.as_deref().unwrap_or("");
1623 let sources = resolve_sources(&raw.sources, source_root);
1624 let source_map = build_source_map(&sources);
1625
1626 let raw_mappings = raw.mappings.to_string();
1627 let line_info = prescan_mappings(&raw_mappings)?;
1628
1629 let num_sources = sources.len();
1630 let scopes = match raw.scopes {
1631 Some(scopes_str) if !scopes_str.is_empty() => Some(srcmap_scopes::decode_scopes(
1632 scopes_str,
1633 &raw.names,
1634 num_sources,
1635 )?),
1636 _ => None,
1637 };
1638
1639 let ignore_list = match raw.ignore_list {
1640 Some(list) => list,
1641 None => raw.x_google_ignore_list.unwrap_or_default(),
1642 };
1643
1644 Ok(Self {
1645 file: raw.file,
1646 source_root: raw.source_root,
1647 sources,
1648 sources_content: Vec::new(),
1649 names: raw.names,
1650 ignore_list,
1651 extensions: HashMap::new(),
1652 debug_id: raw.debug_id,
1653 scopes,
1654 raw_mappings,
1655 line_info,
1656 decoded_lines: RefCell::new(HashMap::new()),
1657 source_map,
1658 fast_scan: false,
1659 decode_watermark: Cell::new(0),
1660 decode_state: Cell::new(VlqState { source_index: 0, original_line: 0, original_column: 0, name_index: 0 }),
1661 })
1662 }
1663
1664 pub fn from_vlq(
1669 mappings: &str,
1670 sources: Vec<String>,
1671 names: Vec<String>,
1672 file: Option<String>,
1673 source_root: Option<String>,
1674 ignore_list: Vec<u32>,
1675 debug_id: Option<String>,
1676 ) -> Result<Self, ParseError> {
1677 let source_map = build_source_map(&sources);
1678 let raw_mappings = mappings.to_string();
1679 let line_info = prescan_mappings(&raw_mappings)?;
1680
1681 Ok(Self {
1682 file,
1683 source_root,
1684 sources,
1685 sources_content: Vec::new(),
1686 names,
1687 ignore_list,
1688 extensions: HashMap::new(),
1689 debug_id,
1690 scopes: None,
1691 raw_mappings,
1692 line_info,
1693 decoded_lines: RefCell::new(HashMap::new()),
1694 source_map,
1695 fast_scan: false,
1696 decode_watermark: Cell::new(0),
1697 decode_state: Cell::new(VlqState { source_index: 0, original_line: 0, original_column: 0, name_index: 0 }),
1698 })
1699 }
1700
1701 pub fn from_json_fast(json: &str) -> Result<Self, ParseError> {
1711 let raw: RawSourceMapLite<'_> = serde_json::from_str(json)?;
1712
1713 if raw.version != 3 {
1714 return Err(ParseError::InvalidVersion(raw.version));
1715 }
1716
1717 if raw.sections.is_some() {
1720 return Err(ParseError::NestedIndexMap);
1721 }
1722
1723 let source_root = raw.source_root.as_deref().unwrap_or("");
1724 let sources = resolve_sources(&raw.sources, source_root);
1725 let source_map = build_source_map(&sources);
1726 let raw_mappings = raw.mappings.to_string();
1727
1728 let line_info = fast_scan_lines(&raw_mappings);
1730
1731 let ignore_list = match raw.ignore_list {
1732 Some(list) => list,
1733 None => raw.x_google_ignore_list.unwrap_or_default(),
1734 };
1735
1736 Ok(Self {
1737 file: raw.file,
1738 source_root: raw.source_root,
1739 sources,
1740 sources_content: Vec::new(),
1741 names: raw.names,
1742 ignore_list,
1743 extensions: HashMap::new(),
1744 debug_id: raw.debug_id,
1745 scopes: None,
1746 raw_mappings,
1747 line_info,
1748 decoded_lines: RefCell::new(HashMap::new()),
1749 source_map,
1750 fast_scan: true,
1751 decode_watermark: Cell::new(0),
1752 decode_state: Cell::new(VlqState { source_index: 0, original_line: 0, original_column: 0, name_index: 0 }),
1753 })
1754 }
1755
1756 fn decode_line_with_state(
1762 &self,
1763 line: u32,
1764 mut state: VlqState,
1765 ) -> Result<(Vec<Mapping>, VlqState), DecodeError> {
1766 let line_idx = line as usize;
1767 if line_idx >= self.line_info.len() {
1768 return Ok((Vec::new(), state));
1769 }
1770
1771 let info = &self.line_info[line_idx];
1772 let bytes = self.raw_mappings.as_bytes();
1773 let end = info.byte_end;
1774
1775 let mut mappings = Vec::new();
1776 let mut source_index = state.source_index;
1777 let mut original_line = state.original_line;
1778 let mut original_column = state.original_column;
1779 let mut name_index = state.name_index;
1780 let mut generated_column: i64 = 0;
1781 let mut pos = info.byte_offset;
1782
1783 while pos < end {
1784 let byte = bytes[pos];
1785 if byte == b',' {
1786 pos += 1;
1787 continue;
1788 }
1789
1790 generated_column += vlq_fast(bytes, &mut pos)?;
1791
1792 if pos < end && bytes[pos] != b',' && bytes[pos] != b';' {
1793 source_index += vlq_fast(bytes, &mut pos)?;
1794 if pos >= end || bytes[pos] == b',' || bytes[pos] == b';' {
1795 return Err(DecodeError::InvalidSegmentLength { fields: 2, offset: pos });
1796 }
1797 original_line += vlq_fast(bytes, &mut pos)?;
1798 if pos >= end || bytes[pos] == b',' || bytes[pos] == b';' {
1799 return Err(DecodeError::InvalidSegmentLength { fields: 3, offset: pos });
1800 }
1801 original_column += vlq_fast(bytes, &mut pos)?;
1802
1803 let name = if pos < end && bytes[pos] != b',' && bytes[pos] != b';' {
1804 name_index += vlq_fast(bytes, &mut pos)?;
1805 name_index as u32
1806 } else {
1807 NO_NAME
1808 };
1809
1810 mappings.push(Mapping {
1811 generated_line: line,
1812 generated_column: generated_column as u32,
1813 source: source_index as u32,
1814 original_line: original_line as u32,
1815 original_column: original_column as u32,
1816 name,
1817 is_range_mapping: false,
1818 });
1819 } else {
1820 mappings.push(Mapping {
1821 generated_line: line,
1822 generated_column: generated_column as u32,
1823 source: NO_SOURCE,
1824 original_line: 0,
1825 original_column: 0,
1826 name: NO_NAME,
1827 is_range_mapping: false,
1828 });
1829 }
1830 }
1831
1832 state.source_index = source_index;
1833 state.original_line = original_line;
1834 state.original_column = original_column;
1835 state.name_index = name_index;
1836 Ok((mappings, state))
1837 }
1838
1839 pub fn decode_line(&self, line: u32) -> Result<Vec<Mapping>, DecodeError> {
1844 if let Some(cached) = self.decoded_lines.borrow().get(&line) {
1846 return Ok(cached.clone());
1847 }
1848
1849 let line_idx = line as usize;
1850 if line_idx >= self.line_info.len() {
1851 return Ok(Vec::new());
1852 }
1853
1854 if self.fast_scan {
1855 let watermark = self.decode_watermark.get();
1860 let start = if line >= watermark { watermark } else { 0 };
1861 let mut state = if line >= watermark {
1862 self.decode_state.get()
1863 } else {
1864 VlqState { source_index: 0, original_line: 0, original_column: 0, name_index: 0 }
1865 };
1866
1867 for l in start..=line {
1868 let info = &self.line_info[l as usize];
1869 if self.decoded_lines.borrow().contains_key(&l) {
1870 let bytes = self.raw_mappings.as_bytes();
1872 state = walk_vlq_state(bytes, info.byte_offset, info.byte_end, state)?;
1873 } else {
1874 let (mappings, new_state) = self.decode_line_with_state(l, state)?;
1875 state = new_state;
1876 self.decoded_lines.borrow_mut().insert(l, mappings);
1877 }
1878 }
1879
1880 if line + 1 > self.decode_watermark.get() {
1882 self.decode_watermark.set(line + 1);
1883 self.decode_state.set(state);
1884 }
1885
1886 let cached = self.decoded_lines.borrow().get(&line).cloned();
1887 return Ok(cached.unwrap_or_default());
1888 }
1889
1890 let state = self.line_info[line_idx].state;
1892 let (mappings, _) = self.decode_line_with_state(line, state)?;
1893 self.decoded_lines.borrow_mut().insert(line, mappings.clone());
1894 Ok(mappings)
1895 }
1896
1897 pub fn original_position_for(&self, line: u32, column: u32) -> Option<OriginalLocation> {
1902 let line_mappings = self.decode_line(line).ok()?;
1903
1904 if line_mappings.is_empty() {
1905 return None;
1906 }
1907
1908 let idx = match line_mappings.binary_search_by_key(&column, |m| m.generated_column) {
1910 Ok(i) => i,
1911 Err(0) => return None,
1912 Err(i) => i - 1,
1913 };
1914
1915 let mapping = &line_mappings[idx];
1916
1917 if mapping.source == NO_SOURCE {
1918 return None;
1919 }
1920
1921 Some(OriginalLocation {
1922 source: mapping.source,
1923 line: mapping.original_line,
1924 column: mapping.original_column,
1925 name: if mapping.name == NO_NAME {
1926 None
1927 } else {
1928 Some(mapping.name)
1929 },
1930 })
1931 }
1932
1933 #[inline]
1935 pub fn line_count(&self) -> usize {
1936 self.line_info.len()
1937 }
1938
1939 #[inline]
1946 pub fn source(&self, index: u32) -> &str {
1947 &self.sources[index as usize]
1948 }
1949
1950 #[inline]
1952 pub fn get_source(&self, index: u32) -> Option<&str> {
1953 self.sources.get(index as usize).map(|s| s.as_str())
1954 }
1955
1956 #[inline]
1963 pub fn name(&self, index: u32) -> &str {
1964 &self.names[index as usize]
1965 }
1966
1967 #[inline]
1969 pub fn get_name(&self, index: u32) -> Option<&str> {
1970 self.names.get(index as usize).map(|s| s.as_str())
1971 }
1972
1973 #[inline]
1975 pub fn source_index(&self, name: &str) -> Option<u32> {
1976 self.source_map.get(name).copied()
1977 }
1978
1979 pub fn mappings_for_line(&self, line: u32) -> Vec<Mapping> {
1981 self.decode_line(line).unwrap_or_default()
1982 }
1983
1984 pub fn into_sourcemap(self) -> Result<SourceMap, ParseError> {
1988 let (mappings, line_offsets) = decode_mappings(&self.raw_mappings)?;
1989 let has_range_mappings = mappings.iter().any(|m| m.is_range_mapping);
1990
1991 Ok(SourceMap {
1992 file: self.file,
1993 source_root: self.source_root,
1994 sources: self.sources.clone(),
1995 sources_content: self.sources_content,
1996 names: self.names,
1997 ignore_list: self.ignore_list,
1998 extensions: self.extensions,
1999 debug_id: self.debug_id,
2000 scopes: self.scopes,
2001 mappings,
2002 line_offsets,
2003 reverse_index: OnceCell::new(),
2004 source_map: self.source_map,
2005 has_range_mappings,
2006 })
2007 }
2008}
2009
2010fn prescan_mappings(input: &str) -> Result<Vec<LineInfo>, DecodeError> {
2013 if input.is_empty() {
2014 return Ok(Vec::new());
2015 }
2016
2017 let bytes = input.as_bytes();
2018 let len = bytes.len();
2019
2020 let line_count = bytes.iter().filter(|&&b| b == b';').count() + 1;
2022 let mut line_info: Vec<LineInfo> = Vec::with_capacity(line_count);
2023
2024 let mut source_index: i64 = 0;
2025 let mut original_line: i64 = 0;
2026 let mut original_column: i64 = 0;
2027 let mut name_index: i64 = 0;
2028 let mut pos: usize = 0;
2029
2030 loop {
2031 let line_start = pos;
2032 let state = VlqState {
2033 source_index,
2034 original_line,
2035 original_column,
2036 name_index,
2037 };
2038
2039 let mut saw_semicolon = false;
2040
2041 while pos < len {
2043 let byte = bytes[pos];
2044
2045 if byte == b';' {
2046 pos += 1;
2047 saw_semicolon = true;
2048 break;
2049 }
2050
2051 if byte == b',' {
2052 pos += 1;
2053 continue;
2054 }
2055
2056 vlq_fast(bytes, &mut pos)?;
2058
2059 if pos < len && bytes[pos] != b',' && bytes[pos] != b';' {
2060 source_index += vlq_fast(bytes, &mut pos)?;
2062
2063 if pos >= len || bytes[pos] == b',' || bytes[pos] == b';' {
2065 return Err(DecodeError::InvalidSegmentLength {
2066 fields: 2,
2067 offset: pos,
2068 });
2069 }
2070
2071 original_line += vlq_fast(bytes, &mut pos)?;
2073
2074 if pos >= len || bytes[pos] == b',' || bytes[pos] == b';' {
2076 return Err(DecodeError::InvalidSegmentLength {
2077 fields: 3,
2078 offset: pos,
2079 });
2080 }
2081
2082 original_column += vlq_fast(bytes, &mut pos)?;
2084
2085 if pos < len && bytes[pos] != b',' && bytes[pos] != b';' {
2087 name_index += vlq_fast(bytes, &mut pos)?;
2088 }
2089 }
2090 }
2091
2092 let byte_end = if saw_semicolon { pos - 1 } else { pos };
2094
2095 line_info.push(LineInfo {
2096 byte_offset: line_start,
2097 byte_end,
2098 state,
2099 });
2100
2101 if !saw_semicolon {
2102 break;
2103 }
2104 }
2105
2106 Ok(line_info)
2107}
2108
2109fn walk_vlq_state(
2111 bytes: &[u8],
2112 start: usize,
2113 end: usize,
2114 mut state: VlqState,
2115) -> Result<VlqState, DecodeError> {
2116 let mut pos = start;
2117 while pos < end {
2118 let byte = bytes[pos];
2119 if byte == b',' {
2120 pos += 1;
2121 continue;
2122 }
2123
2124 vlq_fast(bytes, &mut pos)?;
2126
2127 if pos < end && bytes[pos] != b',' && bytes[pos] != b';' {
2128 state.source_index += vlq_fast(bytes, &mut pos)?;
2129 if pos >= end || bytes[pos] == b',' || bytes[pos] == b';' {
2130 return Err(DecodeError::InvalidSegmentLength { fields: 2, offset: pos });
2131 }
2132 state.original_line += vlq_fast(bytes, &mut pos)?;
2133 if pos >= end || bytes[pos] == b',' || bytes[pos] == b';' {
2134 return Err(DecodeError::InvalidSegmentLength { fields: 3, offset: pos });
2135 }
2136 state.original_column += vlq_fast(bytes, &mut pos)?;
2137 if pos < end && bytes[pos] != b',' && bytes[pos] != b';' {
2138 state.name_index += vlq_fast(bytes, &mut pos)?;
2139 }
2140 }
2141 }
2142 Ok(state)
2143}
2144
2145fn fast_scan_lines(input: &str) -> Vec<LineInfo> {
2148 if input.is_empty() {
2149 return Vec::new();
2150 }
2151
2152 let bytes = input.as_bytes();
2153 let len = bytes.len();
2154 let zero_state = VlqState {
2155 source_index: 0,
2156 original_line: 0,
2157 original_column: 0,
2158 name_index: 0,
2159 };
2160
2161 let mut line_info = Vec::new();
2163 let mut pos = 0;
2164 loop {
2165 let line_start = pos;
2166
2167 while pos < len && bytes[pos] != b';' {
2169 pos += 1;
2170 }
2171
2172 line_info.push(LineInfo {
2173 byte_offset: line_start,
2174 byte_end: pos,
2175 state: zero_state, });
2177
2178 if pos >= len {
2179 break;
2180 }
2181 pos += 1; }
2183
2184 line_info
2185}
2186
2187#[derive(Debug, Clone, PartialEq, Eq)]
2189pub enum SourceMappingUrl {
2190 Inline(String),
2192 External(String),
2194}
2195
2196pub fn parse_source_mapping_url(source: &str) -> Option<SourceMappingUrl> {
2202 for line in source.lines().rev() {
2204 let trimmed = line.trim();
2205 let url = if let Some(rest) = trimmed.strip_prefix("//# sourceMappingURL=") {
2206 rest.trim()
2207 } else if let Some(rest) = trimmed.strip_prefix("//@ sourceMappingURL=") {
2208 rest.trim()
2209 } else if let Some(rest) = trimmed.strip_prefix("/*# sourceMappingURL=") {
2210 rest.trim_end_matches("*/").trim()
2211 } else if let Some(rest) = trimmed.strip_prefix("/*@ sourceMappingURL=") {
2212 rest.trim_end_matches("*/").trim()
2213 } else {
2214 continue;
2215 };
2216
2217 if url.is_empty() {
2218 continue;
2219 }
2220
2221 if let Some(base64_data) = url
2223 .strip_prefix("data:application/json;base64,")
2224 .or_else(|| url.strip_prefix("data:application/json;charset=utf-8;base64,"))
2225 .or_else(|| url.strip_prefix("data:application/json;charset=UTF-8;base64,"))
2226 {
2227 let decoded = base64_decode(base64_data);
2229 if let Some(json) = decoded {
2230 return Some(SourceMappingUrl::Inline(json));
2231 }
2232 }
2233
2234 return Some(SourceMappingUrl::External(url.to_string()));
2235 }
2236
2237 None
2238}
2239
2240fn base64_decode(input: &str) -> Option<String> {
2242 let input = input.trim();
2243 let bytes: Vec<u8> = input.bytes().filter(|b| !b.is_ascii_whitespace()).collect();
2244
2245 let mut output = Vec::with_capacity(bytes.len() * 3 / 4);
2246
2247 for chunk in bytes.chunks(4) {
2248 let mut buf = [0u8; 4];
2249 let mut len = 0;
2250
2251 for &b in chunk {
2252 if b == b'=' {
2253 break;
2254 }
2255 let val = match b {
2256 b'A'..=b'Z' => b - b'A',
2257 b'a'..=b'z' => b - b'a' + 26,
2258 b'0'..=b'9' => b - b'0' + 52,
2259 b'+' => 62,
2260 b'/' => 63,
2261 _ => return None,
2262 };
2263 buf[len] = val;
2264 len += 1;
2265 }
2266
2267 if len >= 2 {
2268 output.push((buf[0] << 2) | (buf[1] >> 4));
2269 }
2270 if len >= 3 {
2271 output.push((buf[1] << 4) | (buf[2] >> 2));
2272 }
2273 if len >= 4 {
2274 output.push((buf[2] << 6) | buf[3]);
2275 }
2276 }
2277
2278 String::from_utf8(output).ok()
2279}
2280
2281pub fn validate_deep(sm: &SourceMap) -> Vec<String> {
2286 let mut warnings = Vec::new();
2287
2288 let mut prev_line: u32 = 0;
2290 let mut prev_col: u32 = 0;
2291 let mappings = sm.all_mappings();
2292 for m in mappings {
2293 if m.generated_line < prev_line
2294 || (m.generated_line == prev_line && m.generated_column < prev_col)
2295 {
2296 warnings.push(format!(
2297 "mappings out of order at {}:{}",
2298 m.generated_line, m.generated_column
2299 ));
2300 }
2301 prev_line = m.generated_line;
2302 prev_col = m.generated_column;
2303 }
2304
2305 for m in mappings {
2307 if m.source != NO_SOURCE && m.source as usize >= sm.sources.len() {
2308 warnings.push(format!(
2309 "source index {} out of bounds (max {})",
2310 m.source,
2311 sm.sources.len()
2312 ));
2313 }
2314 if m.name != NO_NAME && m.name as usize >= sm.names.len() {
2315 warnings.push(format!(
2316 "name index {} out of bounds (max {})",
2317 m.name,
2318 sm.names.len()
2319 ));
2320 }
2321 }
2322
2323 for &idx in &sm.ignore_list {
2325 if idx as usize >= sm.sources.len() {
2326 warnings.push(format!(
2327 "ignoreList index {} out of bounds (max {})",
2328 idx,
2329 sm.sources.len()
2330 ));
2331 }
2332 }
2333
2334 let mut referenced_sources = std::collections::HashSet::new();
2336 for m in mappings {
2337 if m.source != NO_SOURCE {
2338 referenced_sources.insert(m.source);
2339 }
2340 }
2341 for (i, source) in sm.sources.iter().enumerate() {
2342 if !referenced_sources.contains(&(i as u32)) {
2343 warnings.push(format!("source \"{source}\" (index {i}) is unreferenced"));
2344 }
2345 }
2346
2347 warnings
2348}
2349
2350fn json_quote_into(out: &mut String, s: &str) {
2352 let bytes = s.as_bytes();
2353 out.push('"');
2354
2355 let mut start = 0;
2356 for (i, &b) in bytes.iter().enumerate() {
2357 let escape = match b {
2358 b'"' => "\\\"",
2359 b'\\' => "\\\\",
2360 b'\n' => "\\n",
2361 b'\r' => "\\r",
2362 b'\t' => "\\t",
2363 0x00..=0x08 | 0x0b | 0x0c | 0x0e..=0x1f => {
2365 if start < i {
2366 out.push_str(&s[start..i]);
2367 }
2368 use std::fmt::Write;
2369 let _ = write!(out, "\\u{:04x}", b);
2370 start = i + 1;
2371 continue;
2372 }
2373 _ => continue,
2374 };
2375 if start < i {
2376 out.push_str(&s[start..i]);
2377 }
2378 out.push_str(escape);
2379 start = i + 1;
2380 }
2381
2382 if start < bytes.len() {
2383 out.push_str(&s[start..]);
2384 }
2385
2386 out.push('"');
2387}
2388
2389const B64: [u8; 128] = {
2393 let mut table = [0xFFu8; 128];
2394 let chars = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2395 let mut i = 0u8;
2396 while i < 64 {
2397 table[chars[i as usize] as usize] = i;
2398 i += 1;
2399 }
2400 table
2401};
2402
2403#[inline(always)]
2406fn vlq_fast(bytes: &[u8], pos: &mut usize) -> Result<i64, DecodeError> {
2407 let p = *pos;
2408 if p >= bytes.len() {
2409 return Err(DecodeError::UnexpectedEof { offset: p });
2410 }
2411
2412 let b0 = bytes[p];
2413 if b0 >= 128 {
2414 return Err(DecodeError::InvalidBase64 {
2415 byte: b0,
2416 offset: p,
2417 });
2418 }
2419 let d0 = B64[b0 as usize];
2420 if d0 == 0xFF {
2421 return Err(DecodeError::InvalidBase64 {
2422 byte: b0,
2423 offset: p,
2424 });
2425 }
2426
2427 if (d0 & 0x20) == 0 {
2429 *pos = p + 1;
2430 let val = (d0 >> 1) as i64;
2431 return Ok(if (d0 & 1) != 0 { -val } else { val });
2432 }
2433
2434 let mut result: u64 = (d0 & 0x1F) as u64;
2436 let mut shift: u32 = 5;
2437 let mut i = p + 1;
2438
2439 loop {
2440 if i >= bytes.len() {
2441 return Err(DecodeError::UnexpectedEof { offset: i });
2442 }
2443 let b = bytes[i];
2444 if b >= 128 {
2445 return Err(DecodeError::InvalidBase64 { byte: b, offset: i });
2446 }
2447 let d = B64[b as usize];
2448 if d == 0xFF {
2449 return Err(DecodeError::InvalidBase64 { byte: b, offset: i });
2450 }
2451 i += 1;
2452
2453 if shift >= 60 {
2454 return Err(DecodeError::VlqOverflow { offset: p });
2455 }
2456
2457 result += ((d & 0x1F) as u64) << shift;
2458 shift += 5;
2459
2460 if (d & 0x20) == 0 {
2461 break;
2462 }
2463 }
2464
2465 *pos = i;
2466 let value = if (result & 1) == 1 {
2467 -((result >> 1) as i64)
2468 } else {
2469 (result >> 1) as i64
2470 };
2471 Ok(value)
2472}
2473
2474#[inline(always)]
2475fn vlq_unsigned_fast(bytes: &[u8], pos: &mut usize) -> Result<u64, DecodeError> {
2476 let p = *pos;
2477 if p >= bytes.len() {
2478 return Err(DecodeError::UnexpectedEof { offset: p });
2479 }
2480 let b0 = bytes[p];
2481 if b0 >= 128 {
2482 return Err(DecodeError::InvalidBase64 {
2483 byte: b0,
2484 offset: p,
2485 });
2486 }
2487 let d0 = B64[b0 as usize];
2488 if d0 == 0xFF {
2489 return Err(DecodeError::InvalidBase64 {
2490 byte: b0,
2491 offset: p,
2492 });
2493 }
2494 if (d0 & 0x20) == 0 {
2495 *pos = p + 1;
2496 return Ok(d0 as u64);
2497 }
2498 let mut result: u64 = (d0 & 0x1F) as u64;
2499 let mut shift: u32 = 5;
2500 let mut i = p + 1;
2501 loop {
2502 if i >= bytes.len() {
2503 return Err(DecodeError::UnexpectedEof { offset: i });
2504 }
2505 let b = bytes[i];
2506 if b >= 128 {
2507 return Err(DecodeError::InvalidBase64 { byte: b, offset: i });
2508 }
2509 let d = B64[b as usize];
2510 if d == 0xFF {
2511 return Err(DecodeError::InvalidBase64 { byte: b, offset: i });
2512 }
2513 i += 1;
2514 if shift >= 60 {
2515 return Err(DecodeError::VlqOverflow { offset: p });
2516 }
2517 result |= ((d & 0x1F) as u64) << shift;
2518 shift += 5;
2519 if (d & 0x20) == 0 {
2520 break;
2521 }
2522 }
2523 *pos = i;
2524 Ok(result)
2525}
2526
2527fn decode_range_mappings(
2528 input: &str,
2529 mappings: &mut [Mapping],
2530 line_offsets: &[u32],
2531) -> Result<(), DecodeError> {
2532 let bytes = input.as_bytes();
2533 let len = bytes.len();
2534 let mut pos: usize = 0;
2535 let mut generated_line: usize = 0;
2536 while pos < len {
2537 let line_start = if generated_line + 1 < line_offsets.len() {
2538 line_offsets[generated_line] as usize
2539 } else {
2540 break;
2541 };
2542 let line_end = if generated_line + 2 < line_offsets.len() {
2544 line_offsets[generated_line + 1] as usize
2545 } else {
2546 mappings.len()
2547 };
2548 let mut mapping_index: u64 = 0;
2549 while pos < len {
2550 let byte = bytes[pos];
2551 if byte == b';' {
2552 pos += 1;
2553 break;
2554 }
2555 if byte == b',' {
2556 pos += 1;
2557 continue;
2558 }
2559 let offset = vlq_unsigned_fast(bytes, &mut pos)?;
2560 mapping_index += offset;
2561 let abs_idx = line_start + mapping_index as usize;
2562 if abs_idx < line_end {
2563 mappings[abs_idx].is_range_mapping = true;
2564 }
2565 }
2566 generated_line += 1;
2567 }
2568 Ok(())
2569}
2570
2571fn decode_mappings(input: &str) -> Result<(Vec<Mapping>, Vec<u32>), DecodeError> {
2572 if input.is_empty() {
2573 return Ok((Vec::new(), vec![0]));
2574 }
2575
2576 let bytes = input.as_bytes();
2577 let len = bytes.len();
2578
2579 let mut semicolons = 0usize;
2581 let mut commas = 0usize;
2582 for &b in bytes {
2583 semicolons += (b == b';') as usize;
2584 commas += (b == b',') as usize;
2585 }
2586 let line_count = semicolons + 1;
2587 let approx_segments = commas + line_count;
2588
2589 let mut mappings: Vec<Mapping> = Vec::with_capacity(approx_segments);
2590 let mut line_offsets: Vec<u32> = Vec::with_capacity(line_count + 1);
2591
2592 let mut source_index: i64 = 0;
2593 let mut original_line: i64 = 0;
2594 let mut original_column: i64 = 0;
2595 let mut name_index: i64 = 0;
2596 let mut generated_line: u32 = 0;
2597 let mut pos: usize = 0;
2598
2599 loop {
2600 line_offsets.push(mappings.len() as u32);
2601 let mut generated_column: i64 = 0;
2602 let mut saw_semicolon = false;
2603
2604 while pos < len {
2605 let byte = bytes[pos];
2606
2607 if byte == b';' {
2608 pos += 1;
2609 saw_semicolon = true;
2610 break;
2611 }
2612
2613 if byte == b',' {
2614 pos += 1;
2615 continue;
2616 }
2617
2618 generated_column += vlq_fast(bytes, &mut pos)?;
2620
2621 if pos < len && bytes[pos] != b',' && bytes[pos] != b';' {
2622 source_index += vlq_fast(bytes, &mut pos)?;
2624
2625 if pos >= len || bytes[pos] == b',' || bytes[pos] == b';' {
2627 return Err(DecodeError::InvalidSegmentLength {
2628 fields: 2,
2629 offset: pos,
2630 });
2631 }
2632
2633 original_line += vlq_fast(bytes, &mut pos)?;
2635
2636 if pos >= len || bytes[pos] == b',' || bytes[pos] == b';' {
2638 return Err(DecodeError::InvalidSegmentLength {
2639 fields: 3,
2640 offset: pos,
2641 });
2642 }
2643
2644 original_column += vlq_fast(bytes, &mut pos)?;
2646
2647 let name = if pos < len && bytes[pos] != b',' && bytes[pos] != b';' {
2649 name_index += vlq_fast(bytes, &mut pos)?;
2650 name_index as u32
2651 } else {
2652 NO_NAME
2653 };
2654
2655 mappings.push(Mapping {
2656 generated_line,
2657 generated_column: generated_column as u32,
2658 source: source_index as u32,
2659 original_line: original_line as u32,
2660 original_column: original_column as u32,
2661 name,
2662 is_range_mapping: false,
2663 });
2664 } else {
2665 mappings.push(Mapping {
2667 generated_line,
2668 generated_column: generated_column as u32,
2669 source: NO_SOURCE,
2670 original_line: 0,
2671 original_column: 0,
2672 name: NO_NAME,
2673 is_range_mapping: false,
2674 });
2675 }
2676 }
2677
2678 if !saw_semicolon {
2679 break;
2680 }
2681 generated_line += 1;
2682 }
2683
2684 line_offsets.push(mappings.len() as u32);
2686
2687 Ok((mappings, line_offsets))
2688}
2689
2690fn decode_mappings_range(
2697 input: &str,
2698 start_line: u32,
2699 end_line: u32,
2700) -> Result<(Vec<Mapping>, Vec<u32>), DecodeError> {
2701 let actual_lines = if input.is_empty() {
2704 0u32
2705 } else {
2706 input.as_bytes().iter().filter(|&&b| b == b';').count() as u32 + 1
2707 };
2708 let end_line = end_line.min(actual_lines);
2709
2710 if input.is_empty() || start_line >= end_line {
2711 return Ok((Vec::new(), vec![0; end_line as usize + 1]));
2712 }
2713
2714 let bytes = input.as_bytes();
2715 let len = bytes.len();
2716
2717 let mut mappings: Vec<Mapping> = Vec::new();
2718
2719 let mut source_index: i64 = 0;
2720 let mut original_line: i64 = 0;
2721 let mut original_column: i64 = 0;
2722 let mut name_index: i64 = 0;
2723 let mut generated_line: u32 = 0;
2724 let mut pos: usize = 0;
2725
2726 let mut line_starts: Vec<(u32, u32)> = Vec::new();
2729
2730 loop {
2731 let in_range = generated_line >= start_line && generated_line < end_line;
2732 if in_range {
2733 line_starts.push((generated_line, mappings.len() as u32));
2734 }
2735
2736 let mut generated_column: i64 = 0;
2737 let mut saw_semicolon = false;
2738
2739 while pos < len {
2740 let byte = bytes[pos];
2741
2742 if byte == b';' {
2743 pos += 1;
2744 saw_semicolon = true;
2745 break;
2746 }
2747
2748 if byte == b',' {
2749 pos += 1;
2750 continue;
2751 }
2752
2753 generated_column += vlq_fast(bytes, &mut pos)?;
2755
2756 if pos < len && bytes[pos] != b',' && bytes[pos] != b';' {
2757 source_index += vlq_fast(bytes, &mut pos)?;
2759
2760 if pos >= len || bytes[pos] == b',' || bytes[pos] == b';' {
2762 return Err(DecodeError::InvalidSegmentLength {
2763 fields: 2,
2764 offset: pos,
2765 });
2766 }
2767
2768 original_line += vlq_fast(bytes, &mut pos)?;
2770
2771 if pos >= len || bytes[pos] == b',' || bytes[pos] == b';' {
2773 return Err(DecodeError::InvalidSegmentLength {
2774 fields: 3,
2775 offset: pos,
2776 });
2777 }
2778
2779 original_column += vlq_fast(bytes, &mut pos)?;
2781
2782 let name = if pos < len && bytes[pos] != b',' && bytes[pos] != b';' {
2784 name_index += vlq_fast(bytes, &mut pos)?;
2785 name_index as u32
2786 } else {
2787 NO_NAME
2788 };
2789
2790 if in_range {
2791 mappings.push(Mapping {
2792 generated_line,
2793 generated_column: generated_column as u32,
2794 source: source_index as u32,
2795 original_line: original_line as u32,
2796 original_column: original_column as u32,
2797 name,
2798 is_range_mapping: false,
2799 });
2800 }
2801 } else {
2802 if in_range {
2804 mappings.push(Mapping {
2805 generated_line,
2806 generated_column: generated_column as u32,
2807 source: NO_SOURCE,
2808 original_line: 0,
2809 original_column: 0,
2810 name: NO_NAME,
2811 is_range_mapping: false,
2812 });
2813 }
2814 }
2815 }
2816
2817 if !saw_semicolon {
2818 break;
2819 }
2820 generated_line += 1;
2821
2822 if generated_line >= end_line {
2824 break;
2825 }
2826 }
2827
2828 let total = mappings.len() as u32;
2832 let mut line_offsets: Vec<u32> = vec![total; end_line as usize + 1];
2833
2834 for i in 0..=start_line as usize {
2837 if i < line_offsets.len() {
2838 line_offsets[i] = 0;
2839 }
2840 }
2841
2842 for &(line, offset) in &line_starts {
2844 line_offsets[line as usize] = offset;
2845 }
2846
2847 for i in start_line as usize..=end_line as usize {
2870 if i < line_offsets.len() {
2871 line_offsets[i] = total;
2872 }
2873 }
2874
2875 for &(line, offset) in &line_starts {
2877 line_offsets[line as usize] = offset;
2878 }
2879
2880 let mut next_offset = total;
2884 for i in (start_line as usize..end_line as usize).rev() {
2885 if line_offsets[i] == total {
2886 line_offsets[i] = next_offset;
2888 } else {
2889 next_offset = line_offsets[i];
2890 }
2891 }
2892
2893 for offset in line_offsets.iter_mut().take(start_line as usize) {
2896 *offset = 0;
2897 }
2898
2899 Ok((mappings, line_offsets))
2900}
2901
2902fn build_reverse_index(mappings: &[Mapping]) -> Vec<u32> {
2904 let mut indices: Vec<u32> = (0..mappings.len() as u32)
2905 .filter(|&i| mappings[i as usize].source != NO_SOURCE)
2906 .collect();
2907
2908 indices.sort_unstable_by(|&a, &b| {
2909 let ma = &mappings[a as usize];
2910 let mb = &mappings[b as usize];
2911 ma.source
2912 .cmp(&mb.source)
2913 .then(ma.original_line.cmp(&mb.original_line))
2914 .then(ma.original_column.cmp(&mb.original_column))
2915 });
2916
2917 indices
2918}
2919
2920pub struct MappingsIter<'a> {
2940 bytes: &'a [u8],
2941 len: usize,
2942 pos: usize,
2943 source_index: i64,
2944 original_line: i64,
2945 original_column: i64,
2946 name_index: i64,
2947 generated_line: u32,
2948 generated_column: i64,
2949 done: bool,
2950}
2951
2952impl<'a> MappingsIter<'a> {
2953 pub fn new(vlq: &'a str) -> Self {
2955 let bytes = vlq.as_bytes();
2956 Self {
2957 bytes,
2958 len: bytes.len(),
2959 pos: 0,
2960 source_index: 0,
2961 original_line: 0,
2962 original_column: 0,
2963 name_index: 0,
2964 generated_line: 0,
2965 generated_column: 0,
2966 done: false,
2967 }
2968 }
2969}
2970
2971impl Iterator for MappingsIter<'_> {
2972 type Item = Result<Mapping, DecodeError>;
2973
2974 fn next(&mut self) -> Option<Self::Item> {
2975 if self.done {
2976 return None;
2977 }
2978
2979 loop {
2980 if self.pos >= self.len {
2981 self.done = true;
2982 return None;
2983 }
2984
2985 let byte = self.bytes[self.pos];
2986
2987 if byte == b';' {
2988 self.pos += 1;
2989 self.generated_line += 1;
2990 self.generated_column = 0;
2991 continue;
2992 }
2993
2994 if byte == b',' {
2995 self.pos += 1;
2996 continue;
2997 }
2998
2999 match vlq_fast(self.bytes, &mut self.pos) {
3001 Ok(delta) => self.generated_column += delta,
3002 Err(e) => {
3003 self.done = true;
3004 return Some(Err(e));
3005 }
3006 }
3007
3008 if self.pos < self.len && self.bytes[self.pos] != b',' && self.bytes[self.pos] != b';' {
3009 match vlq_fast(self.bytes, &mut self.pos) {
3011 Ok(delta) => self.source_index += delta,
3012 Err(e) => {
3013 self.done = true;
3014 return Some(Err(e));
3015 }
3016 }
3017 if self.pos >= self.len || self.bytes[self.pos] == b',' || self.bytes[self.pos] == b';' {
3019 self.done = true;
3020 return Some(Err(DecodeError::InvalidSegmentLength { fields: 2, offset: self.pos }));
3021 }
3022 match vlq_fast(self.bytes, &mut self.pos) {
3024 Ok(delta) => self.original_line += delta,
3025 Err(e) => {
3026 self.done = true;
3027 return Some(Err(e));
3028 }
3029 }
3030 if self.pos >= self.len || self.bytes[self.pos] == b',' || self.bytes[self.pos] == b';' {
3032 self.done = true;
3033 return Some(Err(DecodeError::InvalidSegmentLength { fields: 3, offset: self.pos }));
3034 }
3035 match vlq_fast(self.bytes, &mut self.pos) {
3037 Ok(delta) => self.original_column += delta,
3038 Err(e) => {
3039 self.done = true;
3040 return Some(Err(e));
3041 }
3042 }
3043
3044 let name = if self.pos < self.len
3046 && self.bytes[self.pos] != b','
3047 && self.bytes[self.pos] != b';'
3048 {
3049 match vlq_fast(self.bytes, &mut self.pos) {
3050 Ok(delta) => {
3051 self.name_index += delta;
3052 self.name_index as u32
3053 }
3054 Err(e) => {
3055 self.done = true;
3056 return Some(Err(e));
3057 }
3058 }
3059 } else {
3060 NO_NAME
3061 };
3062
3063 return Some(Ok(Mapping {
3064 generated_line: self.generated_line,
3065 generated_column: self.generated_column as u32,
3066 source: self.source_index as u32,
3067 original_line: self.original_line as u32,
3068 original_column: self.original_column as u32,
3069 name,
3070 is_range_mapping: false,
3071 }));
3072 } else {
3073 return Some(Ok(Mapping {
3075 generated_line: self.generated_line,
3076 generated_column: self.generated_column as u32,
3077 source: NO_SOURCE,
3078 original_line: 0,
3079 original_column: 0,
3080 name: NO_NAME,
3081 is_range_mapping: false,
3082 }));
3083 }
3084 }
3085 }
3086}
3087
3088pub struct SourceMapBuilder {
3095 file: Option<String>,
3096 source_root: Option<String>,
3097 sources: Vec<String>,
3098 sources_content: Vec<Option<String>>,
3099 names: Vec<String>,
3100 mappings: Vec<Mapping>,
3101 ignore_list: Vec<u32>,
3102 debug_id: Option<String>,
3103 scopes: Option<ScopeInfo>,
3104}
3105
3106impl SourceMapBuilder {
3107 pub fn new() -> Self {
3108 Self {
3109 file: None,
3110 source_root: None,
3111 sources: Vec::new(),
3112 sources_content: Vec::new(),
3113 names: Vec::new(),
3114 mappings: Vec::new(),
3115 ignore_list: Vec::new(),
3116 debug_id: None,
3117 scopes: None,
3118 }
3119 }
3120
3121 pub fn file(mut self, file: impl Into<String>) -> Self {
3122 self.file = Some(file.into());
3123 self
3124 }
3125
3126 pub fn source_root(mut self, root: impl Into<String>) -> Self {
3127 self.source_root = Some(root.into());
3128 self
3129 }
3130
3131 pub fn sources(mut self, sources: impl IntoIterator<Item = impl Into<String>>) -> Self {
3132 self.sources = sources.into_iter().map(Into::into).collect();
3133 self
3134 }
3135
3136 pub fn sources_content(
3137 mut self,
3138 content: impl IntoIterator<Item = Option<impl Into<String>>>,
3139 ) -> Self {
3140 self.sources_content = content.into_iter().map(|c| c.map(Into::into)).collect();
3141 self
3142 }
3143
3144 pub fn names(mut self, names: impl IntoIterator<Item = impl Into<String>>) -> Self {
3145 self.names = names.into_iter().map(Into::into).collect();
3146 self
3147 }
3148
3149 pub fn mappings(mut self, mappings: impl IntoIterator<Item = Mapping>) -> Self {
3150 self.mappings = mappings.into_iter().collect();
3151 self
3152 }
3153
3154 pub fn ignore_list(mut self, list: impl IntoIterator<Item = u32>) -> Self {
3155 self.ignore_list = list.into_iter().collect();
3156 self
3157 }
3158
3159 pub fn debug_id(mut self, id: impl Into<String>) -> Self {
3160 self.debug_id = Some(id.into());
3161 self
3162 }
3163
3164 pub fn scopes(mut self, scopes: ScopeInfo) -> Self {
3165 self.scopes = Some(scopes);
3166 self
3167 }
3168
3169 pub fn build(self) -> SourceMap {
3173 SourceMap::from_parts(
3174 self.file,
3175 self.source_root,
3176 self.sources,
3177 self.sources_content,
3178 self.names,
3179 self.mappings,
3180 self.ignore_list,
3181 self.debug_id,
3182 self.scopes,
3183 )
3184 }
3185}
3186
3187impl Default for SourceMapBuilder {
3188 fn default() -> Self {
3189 Self::new()
3190 }
3191}
3192
3193#[cfg(test)]
3196mod tests {
3197 use super::*;
3198
3199 fn simple_map() -> &'static str {
3200 r#"{"version":3,"sources":["input.js"],"names":["hello"],"mappings":"AAAA;AACA,EAAA;AACA"}"#
3201 }
3202
3203 #[test]
3204 fn parse_basic() {
3205 let sm = SourceMap::from_json(simple_map()).unwrap();
3206 assert_eq!(sm.sources, vec!["input.js"]);
3207 assert_eq!(sm.names, vec!["hello"]);
3208 assert_eq!(sm.line_count(), 3);
3209 assert!(sm.mapping_count() > 0);
3210 }
3211
3212 #[test]
3213 fn to_json_roundtrip() {
3214 let json = simple_map();
3215 let sm = SourceMap::from_json(json).unwrap();
3216 let output = sm.to_json();
3217
3218 let sm2 = SourceMap::from_json(&output).unwrap();
3220 assert_eq!(sm2.sources, sm.sources);
3221 assert_eq!(sm2.names, sm.names);
3222 assert_eq!(sm2.mapping_count(), sm.mapping_count());
3223 assert_eq!(sm2.line_count(), sm.line_count());
3224
3225 for m in sm.all_mappings() {
3227 let loc1 = sm.original_position_for(m.generated_line, m.generated_column);
3228 let loc2 = sm2.original_position_for(m.generated_line, m.generated_column);
3229 match (loc1, loc2) {
3230 (Some(a), Some(b)) => {
3231 assert_eq!(a.source, b.source);
3232 assert_eq!(a.line, b.line);
3233 assert_eq!(a.column, b.column);
3234 assert_eq!(a.name, b.name);
3235 }
3236 (None, None) => {}
3237 _ => panic!(
3238 "lookup mismatch at ({}, {})",
3239 m.generated_line, m.generated_column
3240 ),
3241 }
3242 }
3243 }
3244
3245 #[test]
3246 fn to_json_roundtrip_large() {
3247 let json = generate_test_sourcemap(50, 10, 3);
3248 let sm = SourceMap::from_json(&json).unwrap();
3249 let output = sm.to_json();
3250 let sm2 = SourceMap::from_json(&output).unwrap();
3251
3252 assert_eq!(sm2.mapping_count(), sm.mapping_count());
3253
3254 for line in (0..sm.line_count() as u32).step_by(5) {
3256 for col in [0u32, 10, 20, 50] {
3257 let a = sm.original_position_for(line, col);
3258 let b = sm2.original_position_for(line, col);
3259 match (a, b) {
3260 (Some(a), Some(b)) => {
3261 assert_eq!(a.source, b.source);
3262 assert_eq!(a.line, b.line);
3263 assert_eq!(a.column, b.column);
3264 }
3265 (None, None) => {}
3266 _ => panic!("mismatch at ({line}, {col})"),
3267 }
3268 }
3269 }
3270 }
3271
3272 #[test]
3273 fn to_json_preserves_fields() {
3274 let json = r#"{"version":3,"file":"out.js","sourceRoot":"src/","sources":["app.ts"],"sourcesContent":["const x = 1;"],"names":["x"],"mappings":"AAAAA","ignoreList":[0]}"#;
3275 let sm = SourceMap::from_json(json).unwrap();
3276 let output = sm.to_json();
3277
3278 assert!(output.contains(r#""file":"out.js""#));
3279 assert!(output.contains(r#""sourceRoot":"src/""#));
3280 assert!(output.contains(r#""sourcesContent":["const x = 1;"]"#));
3281 assert!(output.contains(r#""ignoreList":[0]"#));
3282
3283 let sm2 = SourceMap::from_json(&output).unwrap();
3285 assert_eq!(sm2.file.as_deref(), Some("out.js"));
3286 assert_eq!(sm2.ignore_list, vec![0]);
3287 }
3288
3289 #[test]
3290 fn original_position_for_exact_match() {
3291 let sm = SourceMap::from_json(simple_map()).unwrap();
3292 let loc = sm.original_position_for(0, 0).unwrap();
3293 assert_eq!(loc.source, 0);
3294 assert_eq!(loc.line, 0);
3295 assert_eq!(loc.column, 0);
3296 }
3297
3298 #[test]
3299 fn original_position_for_column_within_segment() {
3300 let sm = SourceMap::from_json(simple_map()).unwrap();
3301 let loc = sm.original_position_for(1, 5);
3303 assert!(loc.is_some());
3304 }
3305
3306 #[test]
3307 fn original_position_for_nonexistent_line() {
3308 let sm = SourceMap::from_json(simple_map()).unwrap();
3309 assert!(sm.original_position_for(999, 0).is_none());
3310 }
3311
3312 #[test]
3313 fn original_position_for_before_first_mapping() {
3314 let sm = SourceMap::from_json(simple_map()).unwrap();
3316 let loc = sm.original_position_for(1, 0);
3317 let _ = loc;
3320 }
3321
3322 #[test]
3323 fn generated_position_for_basic() {
3324 let sm = SourceMap::from_json(simple_map()).unwrap();
3325 let loc = sm.generated_position_for("input.js", 0, 0).unwrap();
3326 assert_eq!(loc.line, 0);
3327 assert_eq!(loc.column, 0);
3328 }
3329
3330 #[test]
3331 fn generated_position_for_unknown_source() {
3332 let sm = SourceMap::from_json(simple_map()).unwrap();
3333 assert!(sm.generated_position_for("nonexistent.js", 0, 0).is_none());
3334 }
3335
3336 #[test]
3337 fn parse_invalid_version() {
3338 let json = r#"{"version":2,"sources":[],"names":[],"mappings":""}"#;
3339 let err = SourceMap::from_json(json).unwrap_err();
3340 assert!(matches!(err, ParseError::InvalidVersion(2)));
3341 }
3342
3343 #[test]
3344 fn parse_empty_mappings() {
3345 let json = r#"{"version":3,"sources":[],"names":[],"mappings":""}"#;
3346 let sm = SourceMap::from_json(json).unwrap();
3347 assert_eq!(sm.mapping_count(), 0);
3348 assert!(sm.original_position_for(0, 0).is_none());
3349 }
3350
3351 #[test]
3352 fn parse_with_source_root() {
3353 let json = r#"{"version":3,"sourceRoot":"src/","sources":["foo.js"],"names":[],"mappings":"AAAA"}"#;
3354 let sm = SourceMap::from_json(json).unwrap();
3355 assert_eq!(sm.sources, vec!["src/foo.js"]);
3356 }
3357
3358 #[test]
3359 fn parse_with_sources_content() {
3360 let json = r#"{"version":3,"sources":["a.js"],"sourcesContent":["var x = 1;"],"names":[],"mappings":"AAAA"}"#;
3361 let sm = SourceMap::from_json(json).unwrap();
3362 assert_eq!(sm.sources_content, vec![Some("var x = 1;".to_string())]);
3363 }
3364
3365 #[test]
3366 fn mappings_for_line() {
3367 let sm = SourceMap::from_json(simple_map()).unwrap();
3368 let line0 = sm.mappings_for_line(0);
3369 assert!(!line0.is_empty());
3370 let empty = sm.mappings_for_line(999);
3371 assert!(empty.is_empty());
3372 }
3373
3374 #[test]
3375 fn large_sourcemap_lookup() {
3376 let json = generate_test_sourcemap(500, 20, 5);
3378 let sm = SourceMap::from_json(&json).unwrap();
3379
3380 for line in [0, 10, 100, 250, 499] {
3382 let mappings = sm.mappings_for_line(line);
3383 if let Some(m) = mappings.first() {
3384 let loc = sm.original_position_for(line, m.generated_column);
3385 assert!(loc.is_some(), "lookup failed for line {line}");
3386 }
3387 }
3388 }
3389
3390 #[test]
3391 fn reverse_lookup_roundtrip() {
3392 let json = generate_test_sourcemap(100, 10, 3);
3393 let sm = SourceMap::from_json(&json).unwrap();
3394
3395 let mapping = &sm.mappings[50];
3397 if mapping.source != NO_SOURCE {
3398 let source_name = sm.source(mapping.source);
3399 let result = sm.generated_position_for(
3400 source_name,
3401 mapping.original_line,
3402 mapping.original_column,
3403 );
3404 assert!(result.is_some(), "reverse lookup failed");
3405 }
3406 }
3407
3408 #[test]
3409 fn all_generated_positions_for_basic() {
3410 let sm = SourceMap::from_json(simple_map()).unwrap();
3411 let results = sm.all_generated_positions_for("input.js", 0, 0);
3412 assert!(!results.is_empty(), "should find at least one position");
3413 assert_eq!(results[0].line, 0);
3414 assert_eq!(results[0].column, 0);
3415 }
3416
3417 #[test]
3418 fn all_generated_positions_for_unknown_source() {
3419 let sm = SourceMap::from_json(simple_map()).unwrap();
3420 let results = sm.all_generated_positions_for("nonexistent.js", 0, 0);
3421 assert!(results.is_empty());
3422 }
3423
3424 #[test]
3425 fn all_generated_positions_for_no_match() {
3426 let sm = SourceMap::from_json(simple_map()).unwrap();
3427 let results = sm.all_generated_positions_for("input.js", 999, 999);
3428 assert!(results.is_empty());
3429 }
3430
3431 #[test]
3432 fn encode_mappings_roundtrip() {
3433 let json = generate_test_sourcemap(50, 10, 3);
3434 let sm = SourceMap::from_json(&json).unwrap();
3435 let encoded = sm.encode_mappings();
3436 let json2 = format!(
3438 r#"{{"version":3,"sources":{sources},"names":{names},"mappings":"{mappings}"}}"#,
3439 sources = serde_json::to_string(&sm.sources).unwrap(),
3440 names = serde_json::to_string(&sm.names).unwrap(),
3441 mappings = encoded,
3442 );
3443 let sm2 = SourceMap::from_json(&json2).unwrap();
3444 assert_eq!(sm2.mapping_count(), sm.mapping_count());
3445 }
3446
3447 #[test]
3448 fn indexed_source_map() {
3449 let json = r#"{
3450 "version": 3,
3451 "file": "bundle.js",
3452 "sections": [
3453 {
3454 "offset": {"line": 0, "column": 0},
3455 "map": {
3456 "version": 3,
3457 "sources": ["a.js"],
3458 "names": ["foo"],
3459 "mappings": "AAAAA"
3460 }
3461 },
3462 {
3463 "offset": {"line": 10, "column": 0},
3464 "map": {
3465 "version": 3,
3466 "sources": ["b.js"],
3467 "names": ["bar"],
3468 "mappings": "AAAAA"
3469 }
3470 }
3471 ]
3472 }"#;
3473
3474 let sm = SourceMap::from_json(json).unwrap();
3475
3476 assert_eq!(sm.sources.len(), 2);
3478 assert!(sm.sources.contains(&"a.js".to_string()));
3479 assert!(sm.sources.contains(&"b.js".to_string()));
3480
3481 assert_eq!(sm.names.len(), 2);
3483 assert!(sm.names.contains(&"foo".to_string()));
3484 assert!(sm.names.contains(&"bar".to_string()));
3485
3486 let loc = sm.original_position_for(0, 0).unwrap();
3488 assert_eq!(sm.source(loc.source), "a.js");
3489 assert_eq!(loc.line, 0);
3490 assert_eq!(loc.column, 0);
3491
3492 let loc = sm.original_position_for(10, 0).unwrap();
3494 assert_eq!(sm.source(loc.source), "b.js");
3495 assert_eq!(loc.line, 0);
3496 assert_eq!(loc.column, 0);
3497 }
3498
3499 #[test]
3500 fn indexed_source_map_shared_sources() {
3501 let json = r#"{
3503 "version": 3,
3504 "sections": [
3505 {
3506 "offset": {"line": 0, "column": 0},
3507 "map": {
3508 "version": 3,
3509 "sources": ["shared.js"],
3510 "names": [],
3511 "mappings": "AAAA"
3512 }
3513 },
3514 {
3515 "offset": {"line": 5, "column": 0},
3516 "map": {
3517 "version": 3,
3518 "sources": ["shared.js"],
3519 "names": [],
3520 "mappings": "AACA"
3521 }
3522 }
3523 ]
3524 }"#;
3525
3526 let sm = SourceMap::from_json(json).unwrap();
3527
3528 assert_eq!(sm.sources.len(), 1);
3530 assert_eq!(sm.sources[0], "shared.js");
3531
3532 let loc0 = sm.original_position_for(0, 0).unwrap();
3534 let loc5 = sm.original_position_for(5, 0).unwrap();
3535 assert_eq!(loc0.source, loc5.source);
3536 }
3537
3538 #[test]
3539 fn parse_ignore_list() {
3540 let json = r#"{"version":3,"sources":["app.js","node_modules/lib.js"],"names":[],"mappings":"AAAA;ACAA","ignoreList":[1]}"#;
3541 let sm = SourceMap::from_json(json).unwrap();
3542 assert_eq!(sm.ignore_list, vec![1]);
3543 }
3544
3545 fn build_sourcemap_json(
3547 sources: &[&str],
3548 names: &[&str],
3549 mappings_data: &[Vec<Vec<i64>>],
3550 ) -> String {
3551 let converted: Vec<Vec<srcmap_codec::Segment>> = mappings_data
3552 .iter()
3553 .map(|line| {
3554 line.iter()
3555 .map(|seg| srcmap_codec::Segment::from(seg.as_slice()))
3556 .collect()
3557 })
3558 .collect();
3559 let encoded = srcmap_codec::encode(&converted);
3560 format!(
3561 r#"{{"version":3,"sources":[{}],"names":[{}],"mappings":"{}"}}"#,
3562 sources
3563 .iter()
3564 .map(|s| format!("\"{s}\""))
3565 .collect::<Vec<_>>()
3566 .join(","),
3567 names
3568 .iter()
3569 .map(|n| format!("\"{n}\""))
3570 .collect::<Vec<_>>()
3571 .join(","),
3572 encoded,
3573 )
3574 }
3575
3576 #[test]
3579 fn decode_multiple_consecutive_semicolons() {
3580 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;;;AACA"}"#;
3581 let sm = SourceMap::from_json(json).unwrap();
3582 assert_eq!(sm.line_count(), 4);
3583 assert!(sm.mappings_for_line(1).is_empty());
3584 assert!(sm.mappings_for_line(2).is_empty());
3585 assert!(!sm.mappings_for_line(0).is_empty());
3586 assert!(!sm.mappings_for_line(3).is_empty());
3587 }
3588
3589 #[test]
3590 fn decode_trailing_semicolons() {
3591 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;;"}"#;
3592 let sm = SourceMap::from_json(json).unwrap();
3593 assert_eq!(sm.line_count(), 3);
3594 assert!(!sm.mappings_for_line(0).is_empty());
3595 assert!(sm.mappings_for_line(1).is_empty());
3596 assert!(sm.mappings_for_line(2).is_empty());
3597 }
3598
3599 #[test]
3600 fn decode_leading_comma() {
3601 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":",AAAA"}"#;
3602 let sm = SourceMap::from_json(json).unwrap();
3603 assert_eq!(sm.mapping_count(), 1);
3604 let m = &sm.all_mappings()[0];
3605 assert_eq!(m.generated_line, 0);
3606 assert_eq!(m.generated_column, 0);
3607 }
3608
3609 #[test]
3610 fn decode_single_field_segments() {
3611 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A,C"}"#;
3612 let sm = SourceMap::from_json(json).unwrap();
3613 assert_eq!(sm.mapping_count(), 2);
3614 for m in sm.all_mappings() {
3615 assert_eq!(m.source, NO_SOURCE);
3616 }
3617 assert_eq!(sm.all_mappings()[0].generated_column, 0);
3618 assert_eq!(sm.all_mappings()[1].generated_column, 1);
3619 assert!(sm.original_position_for(0, 0).is_none());
3620 assert!(sm.original_position_for(0, 1).is_none());
3621 }
3622
3623 #[test]
3624 fn decode_five_field_segments_with_names() {
3625 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0, 0], vec![10, 0, 0, 5, 1]]];
3626 let json = build_sourcemap_json(&["app.js"], &["foo", "bar"], &mappings_data);
3627 let sm = SourceMap::from_json(&json).unwrap();
3628 assert_eq!(sm.mapping_count(), 2);
3629 assert_eq!(sm.all_mappings()[0].name, 0);
3630 assert_eq!(sm.all_mappings()[1].name, 1);
3631
3632 let loc = sm.original_position_for(0, 0).unwrap();
3633 assert_eq!(loc.name, Some(0));
3634 assert_eq!(sm.name(0), "foo");
3635
3636 let loc = sm.original_position_for(0, 10).unwrap();
3637 assert_eq!(loc.name, Some(1));
3638 assert_eq!(sm.name(1), "bar");
3639 }
3640
3641 #[test]
3642 fn decode_large_vlq_values() {
3643 let mappings_data = vec![vec![vec![500_i64, 0, 1000, 2000]]];
3644 let json = build_sourcemap_json(&["big.js"], &[], &mappings_data);
3645 let sm = SourceMap::from_json(&json).unwrap();
3646 assert_eq!(sm.mapping_count(), 1);
3647 let m = &sm.all_mappings()[0];
3648 assert_eq!(m.generated_column, 500);
3649 assert_eq!(m.original_line, 1000);
3650 assert_eq!(m.original_column, 2000);
3651
3652 let loc = sm.original_position_for(0, 500).unwrap();
3653 assert_eq!(loc.line, 1000);
3654 assert_eq!(loc.column, 2000);
3655 }
3656
3657 #[test]
3658 fn decode_only_semicolons() {
3659 let json = r#"{"version":3,"sources":[],"names":[],"mappings":";;;"}"#;
3660 let sm = SourceMap::from_json(json).unwrap();
3661 assert_eq!(sm.line_count(), 4);
3662 assert_eq!(sm.mapping_count(), 0);
3663 for line in 0..4 {
3664 assert!(sm.mappings_for_line(line).is_empty());
3665 }
3666 }
3667
3668 #[test]
3669 fn decode_mixed_single_and_four_field_segments() {
3670 let mappings_data = vec![vec![srcmap_codec::Segment::four(5, 0, 0, 0)]];
3671 let four_field_encoded = srcmap_codec::encode(&mappings_data);
3672 let combined_mappings = format!("A,{four_field_encoded}");
3673 let json = format!(
3674 r#"{{"version":3,"sources":["x.js"],"names":[],"mappings":"{combined_mappings}"}}"#,
3675 );
3676 let sm = SourceMap::from_json(&json).unwrap();
3677 assert_eq!(sm.mapping_count(), 2);
3678 assert_eq!(sm.all_mappings()[0].source, NO_SOURCE);
3679 assert_eq!(sm.all_mappings()[1].source, 0);
3680 }
3681
3682 #[test]
3685 fn parse_missing_optional_fields() {
3686 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
3687 let sm = SourceMap::from_json(json).unwrap();
3688 assert!(sm.file.is_none());
3689 assert!(sm.source_root.is_none());
3690 assert!(sm.sources_content.is_empty());
3691 assert!(sm.ignore_list.is_empty());
3692 }
3693
3694 #[test]
3695 fn parse_with_file_field() {
3696 let json =
3697 r#"{"version":3,"file":"output.js","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
3698 let sm = SourceMap::from_json(json).unwrap();
3699 assert_eq!(sm.file.as_deref(), Some("output.js"));
3700 }
3701
3702 #[test]
3703 fn parse_null_entries_in_sources() {
3704 let json = r#"{"version":3,"sources":["a.js",null,"c.js"],"names":[],"mappings":"AAAA"}"#;
3705 let sm = SourceMap::from_json(json).unwrap();
3706 assert_eq!(sm.sources.len(), 3);
3707 assert_eq!(sm.sources[0], "a.js");
3708 assert_eq!(sm.sources[1], "");
3709 assert_eq!(sm.sources[2], "c.js");
3710 }
3711
3712 #[test]
3713 fn parse_null_entries_in_sources_with_source_root() {
3714 let json = r#"{"version":3,"sourceRoot":"lib/","sources":["a.js",null],"names":[],"mappings":"AAAA"}"#;
3715 let sm = SourceMap::from_json(json).unwrap();
3716 assert_eq!(sm.sources[0], "lib/a.js");
3717 assert_eq!(sm.sources[1], "");
3718 }
3719
3720 #[test]
3721 fn parse_empty_names_array() {
3722 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
3723 let sm = SourceMap::from_json(json).unwrap();
3724 assert!(sm.names.is_empty());
3725 }
3726
3727 #[test]
3728 fn parse_invalid_json() {
3729 let result = SourceMap::from_json("not valid json");
3730 assert!(result.is_err());
3731 assert!(matches!(result.unwrap_err(), ParseError::Json(_)));
3732 }
3733
3734 #[test]
3735 fn parse_json_missing_version() {
3736 let result = SourceMap::from_json(r#"{"sources":[],"names":[],"mappings":""}"#);
3737 assert!(result.is_err());
3738 }
3739
3740 #[test]
3741 fn parse_multiple_sources_overlapping_original_positions() {
3742 let mappings_data = vec![vec![vec![0_i64, 0, 5, 10], vec![10, 1, 5, 10]]];
3743 let json = build_sourcemap_json(&["a.js", "b.js"], &[], &mappings_data);
3744 let sm = SourceMap::from_json(&json).unwrap();
3745
3746 let loc0 = sm.original_position_for(0, 0).unwrap();
3747 assert_eq!(loc0.source, 0);
3748 assert_eq!(sm.source(loc0.source), "a.js");
3749
3750 let loc1 = sm.original_position_for(0, 10).unwrap();
3751 assert_eq!(loc1.source, 1);
3752 assert_eq!(sm.source(loc1.source), "b.js");
3753
3754 assert_eq!(loc0.line, loc1.line);
3755 assert_eq!(loc0.column, loc1.column);
3756 }
3757
3758 #[test]
3759 fn parse_sources_content_with_null_entries() {
3760 let json = r#"{"version":3,"sources":["a.js","b.js"],"sourcesContent":["content a",null],"names":[],"mappings":"AAAA"}"#;
3761 let sm = SourceMap::from_json(json).unwrap();
3762 assert_eq!(sm.sources_content.len(), 2);
3763 assert_eq!(sm.sources_content[0], Some("content a".to_string()));
3764 assert_eq!(sm.sources_content[1], None);
3765 }
3766
3767 #[test]
3768 fn parse_empty_sources_and_names() {
3769 let json = r#"{"version":3,"sources":[],"names":[],"mappings":""}"#;
3770 let sm = SourceMap::from_json(json).unwrap();
3771 assert!(sm.sources.is_empty());
3772 assert!(sm.names.is_empty());
3773 assert_eq!(sm.mapping_count(), 0);
3774 }
3775
3776 #[test]
3779 fn lookup_exact_match() {
3780 let mappings_data = vec![vec![
3781 vec![0_i64, 0, 10, 20],
3782 vec![5, 0, 10, 25],
3783 vec![15, 0, 11, 0],
3784 ]];
3785 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3786 let sm = SourceMap::from_json(&json).unwrap();
3787
3788 let loc = sm.original_position_for(0, 5).unwrap();
3789 assert_eq!(loc.line, 10);
3790 assert_eq!(loc.column, 25);
3791 }
3792
3793 #[test]
3794 fn lookup_before_first_segment() {
3795 let mappings_data = vec![vec![vec![5_i64, 0, 0, 0]]];
3796 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3797 let sm = SourceMap::from_json(&json).unwrap();
3798
3799 assert!(sm.original_position_for(0, 0).is_none());
3800 assert!(sm.original_position_for(0, 4).is_none());
3801 }
3802
3803 #[test]
3804 fn lookup_between_segments() {
3805 let mappings_data = vec![vec![
3806 vec![0_i64, 0, 1, 0],
3807 vec![10, 0, 2, 0],
3808 vec![20, 0, 3, 0],
3809 ]];
3810 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3811 let sm = SourceMap::from_json(&json).unwrap();
3812
3813 let loc = sm.original_position_for(0, 7).unwrap();
3814 assert_eq!(loc.line, 1);
3815 assert_eq!(loc.column, 0);
3816
3817 let loc = sm.original_position_for(0, 15).unwrap();
3818 assert_eq!(loc.line, 2);
3819 assert_eq!(loc.column, 0);
3820 }
3821
3822 #[test]
3823 fn lookup_after_last_segment() {
3824 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0], vec![10, 0, 1, 5]]];
3825 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3826 let sm = SourceMap::from_json(&json).unwrap();
3827
3828 let loc = sm.original_position_for(0, 100).unwrap();
3829 assert_eq!(loc.line, 1);
3830 assert_eq!(loc.column, 5);
3831 }
3832
3833 #[test]
3834 fn lookup_empty_lines_no_mappings() {
3835 let mappings_data = vec![
3836 vec![vec![0_i64, 0, 0, 0]],
3837 vec![],
3838 vec![vec![0_i64, 0, 2, 0]],
3839 ];
3840 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3841 let sm = SourceMap::from_json(&json).unwrap();
3842
3843 assert!(sm.original_position_for(1, 0).is_none());
3844 assert!(sm.original_position_for(1, 10).is_none());
3845 assert!(sm.original_position_for(0, 0).is_some());
3846 assert!(sm.original_position_for(2, 0).is_some());
3847 }
3848
3849 #[test]
3850 fn lookup_line_with_single_mapping() {
3851 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0]]];
3852 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3853 let sm = SourceMap::from_json(&json).unwrap();
3854
3855 let loc = sm.original_position_for(0, 0).unwrap();
3856 assert_eq!(loc.line, 0);
3857 assert_eq!(loc.column, 0);
3858
3859 let loc = sm.original_position_for(0, 50).unwrap();
3860 assert_eq!(loc.line, 0);
3861 assert_eq!(loc.column, 0);
3862 }
3863
3864 #[test]
3865 fn lookup_column_0_vs_column_nonzero() {
3866 let mappings_data = vec![vec![vec![0_i64, 0, 10, 0], vec![8, 0, 20, 5]]];
3867 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3868 let sm = SourceMap::from_json(&json).unwrap();
3869
3870 let loc0 = sm.original_position_for(0, 0).unwrap();
3871 assert_eq!(loc0.line, 10);
3872 assert_eq!(loc0.column, 0);
3873
3874 let loc8 = sm.original_position_for(0, 8).unwrap();
3875 assert_eq!(loc8.line, 20);
3876 assert_eq!(loc8.column, 5);
3877
3878 let loc4 = sm.original_position_for(0, 4).unwrap();
3879 assert_eq!(loc4.line, 10);
3880 }
3881
3882 #[test]
3883 fn lookup_beyond_last_line() {
3884 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0]]];
3885 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3886 let sm = SourceMap::from_json(&json).unwrap();
3887
3888 assert!(sm.original_position_for(1, 0).is_none());
3889 assert!(sm.original_position_for(100, 0).is_none());
3890 }
3891
3892 #[test]
3893 fn lookup_single_field_returns_none() {
3894 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A"}"#;
3895 let sm = SourceMap::from_json(json).unwrap();
3896 assert_eq!(sm.mapping_count(), 1);
3897 assert!(sm.original_position_for(0, 0).is_none());
3898 }
3899
3900 #[test]
3903 fn reverse_lookup_exact_match() {
3904 let mappings_data = vec![
3905 vec![vec![0_i64, 0, 0, 0]],
3906 vec![vec![4, 0, 1, 0], vec![10, 0, 1, 8]],
3907 vec![vec![0, 0, 2, 0]],
3908 ];
3909 let json = build_sourcemap_json(&["main.js"], &[], &mappings_data);
3910 let sm = SourceMap::from_json(&json).unwrap();
3911
3912 let loc = sm.generated_position_for("main.js", 1, 8).unwrap();
3913 assert_eq!(loc.line, 1);
3914 assert_eq!(loc.column, 10);
3915 }
3916
3917 #[test]
3918 fn reverse_lookup_no_match() {
3919 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0], vec![10, 0, 0, 10]]];
3920 let json = build_sourcemap_json(&["main.js"], &[], &mappings_data);
3921 let sm = SourceMap::from_json(&json).unwrap();
3922
3923 assert!(sm.generated_position_for("main.js", 99, 0).is_none());
3924 }
3925
3926 #[test]
3927 fn reverse_lookup_unknown_source() {
3928 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0]]];
3929 let json = build_sourcemap_json(&["main.js"], &[], &mappings_data);
3930 let sm = SourceMap::from_json(&json).unwrap();
3931
3932 assert!(sm.generated_position_for("unknown.js", 0, 0).is_none());
3933 }
3934
3935 #[test]
3936 fn reverse_lookup_multiple_mappings_same_original() {
3937 let mappings_data = vec![vec![vec![0_i64, 0, 5, 10]], vec![vec![20, 0, 5, 10]]];
3938 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3939 let sm = SourceMap::from_json(&json).unwrap();
3940
3941 let loc = sm.generated_position_for("src.js", 5, 10);
3942 assert!(loc.is_some());
3943 let loc = loc.unwrap();
3944 assert!(
3945 (loc.line == 0 && loc.column == 0) || (loc.line == 1 && loc.column == 20),
3946 "Expected (0,0) or (1,20), got ({},{})",
3947 loc.line,
3948 loc.column
3949 );
3950 }
3951
3952 #[test]
3953 fn reverse_lookup_with_multiple_sources() {
3954 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0], vec![10, 1, 0, 0]]];
3955 let json = build_sourcemap_json(&["a.js", "b.js"], &[], &mappings_data);
3956 let sm = SourceMap::from_json(&json).unwrap();
3957
3958 let loc_a = sm.generated_position_for("a.js", 0, 0).unwrap();
3959 assert_eq!(loc_a.line, 0);
3960 assert_eq!(loc_a.column, 0);
3961
3962 let loc_b = sm.generated_position_for("b.js", 0, 0).unwrap();
3963 assert_eq!(loc_b.line, 0);
3964 assert_eq!(loc_b.column, 10);
3965 }
3966
3967 #[test]
3968 fn reverse_lookup_skips_single_field_segments() {
3969 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A,KAAAA"}"#;
3970 let sm = SourceMap::from_json(json).unwrap();
3971
3972 let loc = sm.generated_position_for("a.js", 0, 0).unwrap();
3973 assert_eq!(loc.line, 0);
3974 assert_eq!(loc.column, 5);
3975 }
3976
3977 #[test]
3978 fn reverse_lookup_finds_each_original_line() {
3979 let mappings_data = vec![
3980 vec![vec![0_i64, 0, 0, 0]],
3981 vec![vec![0, 0, 1, 0]],
3982 vec![vec![0, 0, 2, 0]],
3983 vec![vec![0, 0, 3, 0]],
3984 ];
3985 let json = build_sourcemap_json(&["x.js"], &[], &mappings_data);
3986 let sm = SourceMap::from_json(&json).unwrap();
3987
3988 for orig_line in 0..4 {
3989 let loc = sm.generated_position_for("x.js", orig_line, 0).unwrap();
3990 assert_eq!(
3991 loc.line, orig_line,
3992 "reverse lookup for orig line {orig_line}"
3993 );
3994 assert_eq!(loc.column, 0);
3995 }
3996 }
3997
3998 #[test]
4001 fn parse_with_ignore_list_multiple() {
4002 let json = r#"{"version":3,"sources":["app.js","node_modules/lib.js","vendor.js"],"names":[],"mappings":"AAAA","ignoreList":[1,2]}"#;
4003 let sm = SourceMap::from_json(json).unwrap();
4004 assert_eq!(sm.ignore_list, vec![1, 2]);
4005 }
4006
4007 #[test]
4008 fn parse_with_empty_ignore_list() {
4009 let json =
4010 r#"{"version":3,"sources":["app.js"],"names":[],"mappings":"AAAA","ignoreList":[]}"#;
4011 let sm = SourceMap::from_json(json).unwrap();
4012 assert!(sm.ignore_list.is_empty());
4013 }
4014
4015 #[test]
4016 fn parse_without_ignore_list_field() {
4017 let json = r#"{"version":3,"sources":["app.js"],"names":[],"mappings":"AAAA"}"#;
4018 let sm = SourceMap::from_json(json).unwrap();
4019 assert!(sm.ignore_list.is_empty());
4020 }
4021
4022 #[test]
4025 fn source_index_lookup() {
4026 let json = r#"{"version":3,"sources":["a.js","b.js","c.js"],"names":[],"mappings":"AAAA"}"#;
4027 let sm = SourceMap::from_json(json).unwrap();
4028 assert_eq!(sm.source_index("a.js"), Some(0));
4029 assert_eq!(sm.source_index("b.js"), Some(1));
4030 assert_eq!(sm.source_index("c.js"), Some(2));
4031 assert_eq!(sm.source_index("d.js"), None);
4032 }
4033
4034 #[test]
4035 fn all_mappings_returns_complete_list() {
4036 let mappings_data = vec![
4037 vec![vec![0_i64, 0, 0, 0], vec![5, 0, 0, 5]],
4038 vec![vec![0, 0, 1, 0]],
4039 ];
4040 let json = build_sourcemap_json(&["x.js"], &[], &mappings_data);
4041 let sm = SourceMap::from_json(&json).unwrap();
4042 assert_eq!(sm.all_mappings().len(), 3);
4043 assert_eq!(sm.mapping_count(), 3);
4044 }
4045
4046 #[test]
4047 fn line_count_matches_decoded_lines() {
4048 let mappings_data = vec![
4049 vec![vec![0_i64, 0, 0, 0]],
4050 vec![],
4051 vec![vec![0_i64, 0, 2, 0]],
4052 vec![],
4053 vec![],
4054 ];
4055 let json = build_sourcemap_json(&["x.js"], &[], &mappings_data);
4056 let sm = SourceMap::from_json(&json).unwrap();
4057 assert_eq!(sm.line_count(), 5);
4058 }
4059
4060 #[test]
4061 fn parse_error_display() {
4062 let err = ParseError::InvalidVersion(5);
4063 assert_eq!(format!("{err}"), "unsupported source map version: 5");
4064
4065 let json_err = SourceMap::from_json("{}").unwrap_err();
4066 let display = format!("{json_err}");
4067 assert!(display.contains("JSON parse error") || display.contains("missing field"));
4068 }
4069
4070 #[test]
4071 fn original_position_name_none_for_four_field() {
4072 let mappings_data = vec![vec![vec![0_i64, 0, 5, 10]]];
4073 let json = build_sourcemap_json(&["a.js"], &["unused_name"], &mappings_data);
4074 let sm = SourceMap::from_json(&json).unwrap();
4075
4076 let loc = sm.original_position_for(0, 0).unwrap();
4077 assert!(loc.name.is_none());
4078 }
4079
4080 #[test]
4081 fn forward_and_reverse_roundtrip_comprehensive() {
4082 let mappings_data = vec![
4083 vec![vec![0_i64, 0, 0, 0], vec![10, 0, 0, 10], vec![20, 1, 5, 0]],
4084 vec![vec![0, 0, 1, 0], vec![5, 1, 6, 3]],
4085 vec![vec![0, 0, 2, 0]],
4086 ];
4087 let json = build_sourcemap_json(&["a.js", "b.js"], &[], &mappings_data);
4088 let sm = SourceMap::from_json(&json).unwrap();
4089
4090 for m in sm.all_mappings() {
4091 if m.source == NO_SOURCE {
4092 continue;
4093 }
4094 let source_name = sm.source(m.source);
4095
4096 let orig = sm
4097 .original_position_for(m.generated_line, m.generated_column)
4098 .unwrap();
4099 assert_eq!(orig.source, m.source);
4100 assert_eq!(orig.line, m.original_line);
4101 assert_eq!(orig.column, m.original_column);
4102
4103 let gen_loc = sm
4104 .generated_position_for(source_name, m.original_line, m.original_column)
4105 .unwrap();
4106 assert_eq!(gen_loc.line, m.generated_line);
4107 assert_eq!(gen_loc.column, m.generated_column);
4108 }
4109 }
4110
4111 #[test]
4116 fn source_root_with_multiple_sources() {
4117 let json = r#"{"version":3,"sourceRoot":"lib/","sources":["a.js","b.js","c.js"],"names":[],"mappings":"AAAA,KACA,KACA"}"#;
4118 let sm = SourceMap::from_json(json).unwrap();
4119 assert_eq!(sm.sources, vec!["lib/a.js", "lib/b.js", "lib/c.js"]);
4120 }
4121
4122 #[test]
4123 fn source_root_empty_string() {
4124 let json =
4125 r#"{"version":3,"sourceRoot":"","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
4126 let sm = SourceMap::from_json(json).unwrap();
4127 assert_eq!(sm.sources, vec!["a.js"]);
4128 }
4129
4130 #[test]
4131 fn source_root_preserved_in_to_json() {
4132 let json =
4133 r#"{"version":3,"sourceRoot":"src/","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
4134 let sm = SourceMap::from_json(json).unwrap();
4135 let output = sm.to_json();
4136 assert!(output.contains(r#""sourceRoot":"src/""#));
4137 }
4138
4139 #[test]
4140 fn source_root_reverse_lookup_uses_prefixed_name() {
4141 let json =
4142 r#"{"version":3,"sourceRoot":"src/","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
4143 let sm = SourceMap::from_json(json).unwrap();
4144 assert!(sm.generated_position_for("src/a.js", 0, 0).is_some());
4146 assert!(sm.generated_position_for("a.js", 0, 0).is_none());
4147 }
4148
4149 #[test]
4150 fn source_root_with_trailing_slash() {
4151 let json =
4152 r#"{"version":3,"sourceRoot":"src/","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
4153 let sm = SourceMap::from_json(json).unwrap();
4154 assert_eq!(sm.sources[0], "src/a.js");
4155 }
4156
4157 #[test]
4158 fn source_root_without_trailing_slash() {
4159 let json =
4160 r#"{"version":3,"sourceRoot":"src","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
4161 let sm = SourceMap::from_json(json).unwrap();
4162 assert_eq!(sm.sources[0], "srca.js");
4164 let output = sm.to_json();
4166 let sm2 = SourceMap::from_json(&output).unwrap();
4167 assert_eq!(sm2.sources[0], "srca.js");
4168 }
4169
4170 #[test]
4173 fn parse_empty_json_object() {
4174 let result = SourceMap::from_json("{}");
4176 assert!(result.is_err());
4177 }
4178
4179 #[test]
4180 fn parse_version_0() {
4181 let json = r#"{"version":0,"sources":[],"names":[],"mappings":""}"#;
4182 assert!(matches!(
4183 SourceMap::from_json(json).unwrap_err(),
4184 ParseError::InvalidVersion(0)
4185 ));
4186 }
4187
4188 #[test]
4189 fn parse_version_4() {
4190 let json = r#"{"version":4,"sources":[],"names":[],"mappings":""}"#;
4191 assert!(matches!(
4192 SourceMap::from_json(json).unwrap_err(),
4193 ParseError::InvalidVersion(4)
4194 ));
4195 }
4196
4197 #[test]
4198 fn parse_extra_unknown_fields_ignored() {
4199 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","x_custom_field":true,"x_debug":{"foo":"bar"}}"#;
4200 let sm = SourceMap::from_json(json).unwrap();
4201 assert_eq!(sm.mapping_count(), 1);
4202 }
4203
4204 #[test]
4205 fn parse_vlq_error_propagated() {
4206 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AA!A"}"#;
4208 let result = SourceMap::from_json(json);
4209 assert!(result.is_err());
4210 assert!(matches!(result.unwrap_err(), ParseError::Vlq(_)));
4211 }
4212
4213 #[test]
4214 fn parse_truncated_vlq_error() {
4215 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"g"}"#;
4217 let result = SourceMap::from_json(json);
4218 assert!(result.is_err());
4219 }
4220
4221 #[test]
4224 fn to_json_produces_valid_json() {
4225 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]}"#;
4226 let sm = SourceMap::from_json(json).unwrap();
4227 let output = sm.to_json();
4228 let _: serde_json::Value = serde_json::from_str(&output).unwrap();
4230 }
4231
4232 #[test]
4233 fn to_json_escapes_special_chars() {
4234 let json = r#"{"version":3,"sources":["path/with\"quotes.js"],"sourcesContent":["line1\nline2\ttab\\backslash"],"names":[],"mappings":"AAAA"}"#;
4235 let sm = SourceMap::from_json(json).unwrap();
4236 let output = sm.to_json();
4237 let _: serde_json::Value = serde_json::from_str(&output).unwrap();
4238 let sm2 = SourceMap::from_json(&output).unwrap();
4239 assert_eq!(
4240 sm2.sources_content[0].as_deref(),
4241 Some("line1\nline2\ttab\\backslash")
4242 );
4243 }
4244
4245 #[test]
4246 fn to_json_empty_map() {
4247 let json = r#"{"version":3,"sources":[],"names":[],"mappings":""}"#;
4248 let sm = SourceMap::from_json(json).unwrap();
4249 let output = sm.to_json();
4250 let sm2 = SourceMap::from_json(&output).unwrap();
4251 assert_eq!(sm2.mapping_count(), 0);
4252 assert!(sm2.sources.is_empty());
4253 }
4254
4255 #[test]
4256 fn to_json_roundtrip_with_names() {
4257 let mappings_data = vec![vec![
4258 vec![0_i64, 0, 0, 0, 0],
4259 vec![10, 0, 0, 10, 1],
4260 vec![20, 0, 1, 0, 2],
4261 ]];
4262 let json = build_sourcemap_json(&["src.js"], &["foo", "bar", "baz"], &mappings_data);
4263 let sm = SourceMap::from_json(&json).unwrap();
4264 let output = sm.to_json();
4265 let sm2 = SourceMap::from_json(&output).unwrap();
4266
4267 for m in sm2.all_mappings() {
4268 if m.source != NO_SOURCE && m.name != NO_NAME {
4269 let loc = sm2
4270 .original_position_for(m.generated_line, m.generated_column)
4271 .unwrap();
4272 assert!(loc.name.is_some());
4273 }
4274 }
4275 }
4276
4277 #[test]
4280 fn indexed_source_map_column_offset() {
4281 let json = r#"{
4282 "version": 3,
4283 "sections": [
4284 {
4285 "offset": {"line": 0, "column": 10},
4286 "map": {
4287 "version": 3,
4288 "sources": ["a.js"],
4289 "names": [],
4290 "mappings": "AAAA"
4291 }
4292 }
4293 ]
4294 }"#;
4295 let sm = SourceMap::from_json(json).unwrap();
4296 let loc = sm.original_position_for(0, 10).unwrap();
4298 assert_eq!(loc.line, 0);
4299 assert_eq!(loc.column, 0);
4300 assert!(sm.original_position_for(0, 0).is_none());
4302 }
4303
4304 #[test]
4305 fn indexed_source_map_column_offset_only_first_line() {
4306 let json = r#"{
4308 "version": 3,
4309 "sections": [
4310 {
4311 "offset": {"line": 0, "column": 20},
4312 "map": {
4313 "version": 3,
4314 "sources": ["a.js"],
4315 "names": [],
4316 "mappings": "AAAA;AAAA"
4317 }
4318 }
4319 ]
4320 }"#;
4321 let sm = SourceMap::from_json(json).unwrap();
4322 let loc = sm.original_position_for(0, 20).unwrap();
4324 assert_eq!(loc.column, 0);
4325 let loc = sm.original_position_for(1, 0).unwrap();
4327 assert_eq!(loc.column, 0);
4328 }
4329
4330 #[test]
4331 fn indexed_source_map_empty_section() {
4332 let json = r#"{
4333 "version": 3,
4334 "sections": [
4335 {
4336 "offset": {"line": 0, "column": 0},
4337 "map": {
4338 "version": 3,
4339 "sources": [],
4340 "names": [],
4341 "mappings": ""
4342 }
4343 },
4344 {
4345 "offset": {"line": 5, "column": 0},
4346 "map": {
4347 "version": 3,
4348 "sources": ["b.js"],
4349 "names": [],
4350 "mappings": "AAAA"
4351 }
4352 }
4353 ]
4354 }"#;
4355 let sm = SourceMap::from_json(json).unwrap();
4356 assert_eq!(sm.sources.len(), 1);
4357 let loc = sm.original_position_for(5, 0).unwrap();
4358 assert_eq!(sm.source(loc.source), "b.js");
4359 }
4360
4361 #[test]
4362 fn indexed_source_map_with_sources_content() {
4363 let json = r#"{
4364 "version": 3,
4365 "sections": [
4366 {
4367 "offset": {"line": 0, "column": 0},
4368 "map": {
4369 "version": 3,
4370 "sources": ["a.js"],
4371 "sourcesContent": ["var a = 1;"],
4372 "names": [],
4373 "mappings": "AAAA"
4374 }
4375 },
4376 {
4377 "offset": {"line": 5, "column": 0},
4378 "map": {
4379 "version": 3,
4380 "sources": ["b.js"],
4381 "sourcesContent": ["var b = 2;"],
4382 "names": [],
4383 "mappings": "AAAA"
4384 }
4385 }
4386 ]
4387 }"#;
4388 let sm = SourceMap::from_json(json).unwrap();
4389 assert_eq!(sm.sources_content.len(), 2);
4390 assert_eq!(sm.sources_content[0], Some("var a = 1;".to_string()));
4391 assert_eq!(sm.sources_content[1], Some("var b = 2;".to_string()));
4392 }
4393
4394 #[test]
4395 fn indexed_source_map_with_ignore_list() {
4396 let json = r#"{
4397 "version": 3,
4398 "sections": [
4399 {
4400 "offset": {"line": 0, "column": 0},
4401 "map": {
4402 "version": 3,
4403 "sources": ["app.js", "vendor.js"],
4404 "names": [],
4405 "mappings": "AAAA",
4406 "ignoreList": [1]
4407 }
4408 }
4409 ]
4410 }"#;
4411 let sm = SourceMap::from_json(json).unwrap();
4412 assert!(!sm.ignore_list.is_empty());
4413 }
4414
4415 #[test]
4418 fn lookup_max_column_on_line() {
4419 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0]]];
4420 let json = build_sourcemap_json(&["a.js"], &[], &mappings_data);
4421 let sm = SourceMap::from_json(&json).unwrap();
4422 let loc = sm.original_position_for(0, u32::MAX - 1).unwrap();
4424 assert_eq!(loc.line, 0);
4425 assert_eq!(loc.column, 0);
4426 }
4427
4428 #[test]
4429 fn mappings_for_line_beyond_end() {
4430 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
4431 let sm = SourceMap::from_json(json).unwrap();
4432 assert!(sm.mappings_for_line(u32::MAX).is_empty());
4433 }
4434
4435 #[test]
4436 fn source_with_unicode_path() {
4437 let json =
4438 r#"{"version":3,"sources":["src/日本語.ts"],"names":["変数"],"mappings":"AAAAA"}"#;
4439 let sm = SourceMap::from_json(json).unwrap();
4440 assert_eq!(sm.sources[0], "src/日本語.ts");
4441 assert_eq!(sm.names[0], "変数");
4442 let loc = sm.original_position_for(0, 0).unwrap();
4443 assert_eq!(sm.source(loc.source), "src/日本語.ts");
4444 assert_eq!(sm.name(loc.name.unwrap()), "変数");
4445 }
4446
4447 #[test]
4448 fn to_json_roundtrip_unicode_sources() {
4449 let json = r#"{"version":3,"sources":["src/日本語.ts"],"sourcesContent":["const 変数 = 1;"],"names":["変数"],"mappings":"AAAAA"}"#;
4450 let sm = SourceMap::from_json(json).unwrap();
4451 let output = sm.to_json();
4452 let _: serde_json::Value = serde_json::from_str(&output).unwrap();
4453 let sm2 = SourceMap::from_json(&output).unwrap();
4454 assert_eq!(sm2.sources[0], "src/日本語.ts");
4455 assert_eq!(sm2.sources_content[0], Some("const 変数 = 1;".to_string()));
4456 }
4457
4458 #[test]
4459 fn many_sources_lookup() {
4460 let sources: Vec<String> = (0..100).map(|i| format!("src/file{i}.js")).collect();
4462 let source_strs: Vec<&str> = sources.iter().map(|s| s.as_str()).collect();
4463 let mappings_data = vec![
4464 sources
4465 .iter()
4466 .enumerate()
4467 .map(|(i, _)| vec![(i * 10) as i64, i as i64, 0, 0])
4468 .collect::<Vec<_>>(),
4469 ];
4470 let json = build_sourcemap_json(&source_strs, &[], &mappings_data);
4471 let sm = SourceMap::from_json(&json).unwrap();
4472
4473 for (i, src) in sources.iter().enumerate() {
4474 assert_eq!(sm.source_index(src), Some(i as u32));
4475 }
4476 }
4477
4478 #[test]
4479 fn clone_sourcemap() {
4480 let json = r#"{"version":3,"sources":["a.js"],"names":["x"],"mappings":"AAAAA"}"#;
4481 let sm = SourceMap::from_json(json).unwrap();
4482 let sm2 = sm.clone();
4483 assert_eq!(sm2.sources, sm.sources);
4484 assert_eq!(sm2.mapping_count(), sm.mapping_count());
4485 let loc = sm2.original_position_for(0, 0).unwrap();
4486 assert_eq!(sm2.source(loc.source), "a.js");
4487 }
4488
4489 #[test]
4490 fn parse_debug_id() {
4491 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","debugId":"85314830-023f-4cf1-a267-535f4e37bb17"}"#;
4492 let sm = SourceMap::from_json(json).unwrap();
4493 assert_eq!(
4494 sm.debug_id.as_deref(),
4495 Some("85314830-023f-4cf1-a267-535f4e37bb17")
4496 );
4497 }
4498
4499 #[test]
4500 fn parse_debug_id_snake_case() {
4501 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","debug_id":"85314830-023f-4cf1-a267-535f4e37bb17"}"#;
4502 let sm = SourceMap::from_json(json).unwrap();
4503 assert_eq!(
4504 sm.debug_id.as_deref(),
4505 Some("85314830-023f-4cf1-a267-535f4e37bb17")
4506 );
4507 }
4508
4509 #[test]
4510 fn parse_no_debug_id() {
4511 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
4512 let sm = SourceMap::from_json(json).unwrap();
4513 assert_eq!(sm.debug_id, None);
4514 }
4515
4516 #[test]
4517 fn debug_id_roundtrip() {
4518 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","debugId":"85314830-023f-4cf1-a267-535f4e37bb17"}"#;
4519 let sm = SourceMap::from_json(json).unwrap();
4520 let output = sm.to_json();
4521 assert!(output.contains(r#""debugId":"85314830-023f-4cf1-a267-535f4e37bb17""#));
4522 let sm2 = SourceMap::from_json(&output).unwrap();
4523 assert_eq!(sm.debug_id, sm2.debug_id);
4524 }
4525
4526 #[test]
4527 fn debug_id_not_in_json_when_absent() {
4528 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
4529 let sm = SourceMap::from_json(json).unwrap();
4530 let output = sm.to_json();
4531 assert!(!output.contains("debugId"));
4532 }
4533
4534 fn generate_test_sourcemap(lines: usize, segs_per_line: usize, num_sources: usize) -> String {
4536 let sources: Vec<String> = (0..num_sources)
4537 .map(|i| format!("src/file{i}.js"))
4538 .collect();
4539 let names: Vec<String> = (0..20).map(|i| format!("var{i}")).collect();
4540
4541 let mut mappings_parts = Vec::with_capacity(lines);
4542 let mut gen_col;
4543 let mut src: i64 = 0;
4544 let mut src_line: i64 = 0;
4545 let mut src_col: i64;
4546 let mut name: i64 = 0;
4547
4548 for _ in 0..lines {
4549 gen_col = 0i64;
4550 let mut line_parts = Vec::with_capacity(segs_per_line);
4551
4552 for s in 0..segs_per_line {
4553 let gc_delta = 2 + (s as i64 * 3) % 20;
4554 gen_col += gc_delta;
4555
4556 let src_delta = if s % 7 == 0 { 1 } else { 0 };
4557 src = (src + src_delta) % num_sources as i64;
4558
4559 src_line += 1;
4560 src_col = (s as i64 * 5 + 1) % 30;
4561
4562 let has_name = s % 4 == 0;
4563 if has_name {
4564 name = (name + 1) % names.len() as i64;
4565 }
4566
4567 let segment = if has_name {
4569 srcmap_codec::Segment::five(gen_col, src, src_line, src_col, name)
4570 } else {
4571 srcmap_codec::Segment::four(gen_col, src, src_line, src_col)
4572 };
4573
4574 line_parts.push(segment);
4575 }
4576
4577 mappings_parts.push(line_parts);
4578 }
4579
4580 let encoded = srcmap_codec::encode(&mappings_parts);
4581
4582 format!(
4583 r#"{{"version":3,"sources":[{}],"names":[{}],"mappings":"{}"}}"#,
4584 sources
4585 .iter()
4586 .map(|s| format!("\"{s}\""))
4587 .collect::<Vec<_>>()
4588 .join(","),
4589 names
4590 .iter()
4591 .map(|n| format!("\"{n}\""))
4592 .collect::<Vec<_>>()
4593 .join(","),
4594 encoded,
4595 )
4596 }
4597
4598 fn bias_map() -> &'static str {
4603 r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA,KAAK,KAAK"}"#
4605 }
4606
4607 #[test]
4608 fn original_position_glb_exact_match() {
4609 let sm = SourceMap::from_json(bias_map()).unwrap();
4610 let loc = sm
4611 .original_position_for_with_bias(0, 5, Bias::GreatestLowerBound)
4612 .unwrap();
4613 assert_eq!(loc.column, 5);
4614 }
4615
4616 #[test]
4617 fn original_position_glb_snaps_left() {
4618 let sm = SourceMap::from_json(bias_map()).unwrap();
4619 let loc = sm
4621 .original_position_for_with_bias(0, 7, Bias::GreatestLowerBound)
4622 .unwrap();
4623 assert_eq!(loc.column, 5);
4624 }
4625
4626 #[test]
4627 fn original_position_lub_exact_match() {
4628 let sm = SourceMap::from_json(bias_map()).unwrap();
4629 let loc = sm
4630 .original_position_for_with_bias(0, 5, Bias::LeastUpperBound)
4631 .unwrap();
4632 assert_eq!(loc.column, 5);
4633 }
4634
4635 #[test]
4636 fn original_position_lub_snaps_right() {
4637 let sm = SourceMap::from_json(bias_map()).unwrap();
4638 let loc = sm
4640 .original_position_for_with_bias(0, 3, Bias::LeastUpperBound)
4641 .unwrap();
4642 assert_eq!(loc.column, 5);
4643 }
4644
4645 #[test]
4646 fn original_position_lub_before_first() {
4647 let sm = SourceMap::from_json(bias_map()).unwrap();
4648 let loc = sm
4650 .original_position_for_with_bias(0, 0, Bias::LeastUpperBound)
4651 .unwrap();
4652 assert_eq!(loc.column, 0);
4653 }
4654
4655 #[test]
4656 fn original_position_lub_after_last() {
4657 let sm = SourceMap::from_json(bias_map()).unwrap();
4658 let loc = sm.original_position_for_with_bias(0, 15, Bias::LeastUpperBound);
4660 assert!(loc.is_none());
4661 }
4662
4663 #[test]
4664 fn original_position_glb_before_first() {
4665 let sm = SourceMap::from_json(bias_map()).unwrap();
4666 let loc = sm
4668 .original_position_for_with_bias(0, 0, Bias::GreatestLowerBound)
4669 .unwrap();
4670 assert_eq!(loc.column, 0);
4671 }
4672
4673 #[test]
4674 fn generated_position_lub() {
4675 let sm = SourceMap::from_json(bias_map()).unwrap();
4676 let loc = sm
4678 .generated_position_for_with_bias("input.js", 0, 3, Bias::LeastUpperBound)
4679 .unwrap();
4680 assert_eq!(loc.column, 5);
4681 }
4682
4683 #[test]
4684 fn generated_position_glb() {
4685 let sm = SourceMap::from_json(bias_map()).unwrap();
4686 let loc = sm
4688 .generated_position_for_with_bias("input.js", 0, 7, Bias::GreatestLowerBound)
4689 .unwrap();
4690 assert_eq!(loc.column, 5);
4691 }
4692
4693 #[test]
4696 fn map_range_basic() {
4697 let sm = SourceMap::from_json(bias_map()).unwrap();
4698 let range = sm.map_range(0, 0, 0, 10).unwrap();
4699 assert_eq!(range.source, 0);
4700 assert_eq!(range.original_start_line, 0);
4701 assert_eq!(range.original_start_column, 0);
4702 assert_eq!(range.original_end_line, 0);
4703 assert_eq!(range.original_end_column, 10);
4704 }
4705
4706 #[test]
4707 fn map_range_no_mapping() {
4708 let sm = SourceMap::from_json(bias_map()).unwrap();
4709 let range = sm.map_range(0, 0, 5, 0);
4711 assert!(range.is_none());
4712 }
4713
4714 #[test]
4715 fn map_range_different_sources() {
4716 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":[],"mappings":"AAAA;ACAA"}"#;
4718 let sm = SourceMap::from_json(json).unwrap();
4719 let range = sm.map_range(0, 0, 1, 0);
4721 assert!(range.is_none());
4722 }
4723
4724 #[test]
4727 fn extension_fields_preserved() {
4728 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","x_facebook_sources":[[{"names":["<global>"]}]],"x_google_linecount":42}"#;
4729 let sm = SourceMap::from_json(json).unwrap();
4730
4731 assert!(sm.extensions.contains_key("x_facebook_sources"));
4732 assert!(sm.extensions.contains_key("x_google_linecount"));
4733 assert_eq!(
4734 sm.extensions.get("x_google_linecount"),
4735 Some(&serde_json::json!(42))
4736 );
4737
4738 let output = sm.to_json();
4740 assert!(output.contains("x_facebook_sources"));
4741 assert!(output.contains("x_google_linecount"));
4742 }
4743
4744 #[test]
4745 fn x_google_ignorelist_fallback() {
4746 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":[],"mappings":"AAAA","x_google_ignoreList":[1]}"#;
4747 let sm = SourceMap::from_json(json).unwrap();
4748 assert_eq!(sm.ignore_list, vec![1]);
4749 }
4750
4751 #[test]
4752 fn ignorelist_takes_precedence_over_x_google() {
4753 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":[],"mappings":"AAAA","ignoreList":[0],"x_google_ignoreList":[1]}"#;
4754 let sm = SourceMap::from_json(json).unwrap();
4755 assert_eq!(sm.ignore_list, vec![0]);
4756 }
4757
4758 #[test]
4759 fn source_mapping_url_external() {
4760 let source = "var a = 1;\n//# sourceMappingURL=app.js.map\n";
4761 let result = parse_source_mapping_url(source).unwrap();
4762 assert_eq!(result, SourceMappingUrl::External("app.js.map".to_string()));
4763 }
4764
4765 #[test]
4766 fn source_mapping_url_inline() {
4767 let json = r#"{"version":3,"sources":[],"names":[],"mappings":""}"#;
4768 let b64 = base64_encode_simple(json);
4769 let source =
4770 format!("var a = 1;\n//# sourceMappingURL=data:application/json;base64,{b64}\n");
4771 match parse_source_mapping_url(&source).unwrap() {
4772 SourceMappingUrl::Inline(decoded) => {
4773 assert_eq!(decoded, json);
4774 }
4775 _ => panic!("expected inline"),
4776 }
4777 }
4778
4779 #[test]
4780 fn source_mapping_url_at_sign() {
4781 let source = "var a = 1;\n//@ sourceMappingURL=old-style.map";
4782 let result = parse_source_mapping_url(source).unwrap();
4783 assert_eq!(
4784 result,
4785 SourceMappingUrl::External("old-style.map".to_string())
4786 );
4787 }
4788
4789 #[test]
4790 fn source_mapping_url_css_comment() {
4791 let source = "body { }\n/*# sourceMappingURL=styles.css.map */";
4792 let result = parse_source_mapping_url(source).unwrap();
4793 assert_eq!(
4794 result,
4795 SourceMappingUrl::External("styles.css.map".to_string())
4796 );
4797 }
4798
4799 #[test]
4800 fn source_mapping_url_none() {
4801 let source = "var a = 1;";
4802 assert!(parse_source_mapping_url(source).is_none());
4803 }
4804
4805 #[test]
4806 fn exclude_content_option() {
4807 let json = r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":[],"mappings":"AAAA"}"#;
4808 let sm = SourceMap::from_json(json).unwrap();
4809
4810 let with_content = sm.to_json();
4811 assert!(with_content.contains("sourcesContent"));
4812
4813 let without_content = sm.to_json_with_options(true);
4814 assert!(!without_content.contains("sourcesContent"));
4815 }
4816
4817 #[test]
4818 fn validate_deep_clean_map() {
4819 let sm = SourceMap::from_json(simple_map()).unwrap();
4820 let warnings = validate_deep(&sm);
4821 assert!(warnings.is_empty(), "unexpected warnings: {warnings:?}");
4822 }
4823
4824 #[test]
4825 fn validate_deep_unreferenced_source() {
4826 let json =
4828 r#"{"version":3,"sources":["used.js","unused.js"],"names":[],"mappings":"AAAA"}"#;
4829 let sm = SourceMap::from_json(json).unwrap();
4830 let warnings = validate_deep(&sm);
4831 assert!(warnings.iter().any(|w| w.contains("unused.js")));
4832 }
4833
4834 #[test]
4837 fn from_parts_basic() {
4838 let mappings = vec![
4839 Mapping {
4840 generated_line: 0,
4841 generated_column: 0,
4842 source: 0,
4843 original_line: 0,
4844 original_column: 0,
4845 name: NO_NAME,
4846 is_range_mapping: false,
4847 },
4848 Mapping {
4849 generated_line: 1,
4850 generated_column: 4,
4851 source: 0,
4852 original_line: 1,
4853 original_column: 2,
4854 name: NO_NAME,
4855 is_range_mapping: false,
4856 },
4857 ];
4858
4859 let sm = SourceMap::from_parts(
4860 Some("out.js".to_string()),
4861 None,
4862 vec!["input.js".to_string()],
4863 vec![Some("var x = 1;".to_string())],
4864 vec![],
4865 mappings,
4866 vec![],
4867 None,
4868 None,
4869 );
4870
4871 assert_eq!(sm.line_count(), 2);
4872 assert_eq!(sm.mapping_count(), 2);
4873
4874 let loc = sm.original_position_for(0, 0).unwrap();
4875 assert_eq!(loc.source, 0);
4876 assert_eq!(loc.line, 0);
4877 assert_eq!(loc.column, 0);
4878
4879 let loc = sm.original_position_for(1, 4).unwrap();
4880 assert_eq!(loc.line, 1);
4881 assert_eq!(loc.column, 2);
4882 }
4883
4884 #[test]
4885 fn from_parts_empty() {
4886 let sm = SourceMap::from_parts(
4887 None,
4888 None,
4889 vec![],
4890 vec![],
4891 vec![],
4892 vec![],
4893 vec![],
4894 None,
4895 None,
4896 );
4897 assert_eq!(sm.line_count(), 0);
4898 assert_eq!(sm.mapping_count(), 0);
4899 assert!(sm.original_position_for(0, 0).is_none());
4900 }
4901
4902 #[test]
4903 fn from_parts_with_names() {
4904 let mappings = vec![Mapping {
4905 generated_line: 0,
4906 generated_column: 0,
4907 source: 0,
4908 original_line: 0,
4909 original_column: 0,
4910 name: 0,
4911 is_range_mapping: false,
4912 }];
4913
4914 let sm = SourceMap::from_parts(
4915 None,
4916 None,
4917 vec!["input.js".to_string()],
4918 vec![],
4919 vec!["myVar".to_string()],
4920 mappings,
4921 vec![],
4922 None,
4923 None,
4924 );
4925
4926 let loc = sm.original_position_for(0, 0).unwrap();
4927 assert_eq!(loc.name, Some(0));
4928 assert_eq!(sm.name(0), "myVar");
4929 }
4930
4931 #[test]
4932 fn from_parts_roundtrip_via_json() {
4933 let json = generate_test_sourcemap(50, 10, 3);
4934 let sm = SourceMap::from_json(&json).unwrap();
4935
4936 let sm2 = SourceMap::from_parts(
4937 sm.file.clone(),
4938 sm.source_root.clone(),
4939 sm.sources.clone(),
4940 sm.sources_content.clone(),
4941 sm.names.clone(),
4942 sm.all_mappings().to_vec(),
4943 sm.ignore_list.clone(),
4944 sm.debug_id.clone(),
4945 None,
4946 );
4947
4948 assert_eq!(sm2.mapping_count(), sm.mapping_count());
4949 assert_eq!(sm2.line_count(), sm.line_count());
4950
4951 for m in sm.all_mappings() {
4953 if m.source != NO_SOURCE {
4954 let a = sm.original_position_for(m.generated_line, m.generated_column);
4955 let b = sm2.original_position_for(m.generated_line, m.generated_column);
4956 match (a, b) {
4957 (Some(a), Some(b)) => {
4958 assert_eq!(a.source, b.source);
4959 assert_eq!(a.line, b.line);
4960 assert_eq!(a.column, b.column);
4961 }
4962 (None, None) => {}
4963 _ => panic!("mismatch at ({}, {})", m.generated_line, m.generated_column),
4964 }
4965 }
4966 }
4967 }
4968
4969 #[test]
4970 fn from_parts_reverse_lookup() {
4971 let mappings = vec![
4972 Mapping {
4973 generated_line: 0,
4974 generated_column: 0,
4975 source: 0,
4976 original_line: 10,
4977 original_column: 5,
4978 name: NO_NAME,
4979 is_range_mapping: false,
4980 },
4981 Mapping {
4982 generated_line: 1,
4983 generated_column: 8,
4984 source: 0,
4985 original_line: 20,
4986 original_column: 0,
4987 name: NO_NAME,
4988 is_range_mapping: false,
4989 },
4990 ];
4991
4992 let sm = SourceMap::from_parts(
4993 None,
4994 None,
4995 vec!["src.js".to_string()],
4996 vec![],
4997 vec![],
4998 mappings,
4999 vec![],
5000 None,
5001 None,
5002 );
5003
5004 let loc = sm.generated_position_for("src.js", 10, 5).unwrap();
5005 assert_eq!(loc.line, 0);
5006 assert_eq!(loc.column, 0);
5007
5008 let loc = sm.generated_position_for("src.js", 20, 0).unwrap();
5009 assert_eq!(loc.line, 1);
5010 assert_eq!(loc.column, 8);
5011 }
5012
5013 #[test]
5014 fn from_parts_sparse_lines() {
5015 let mappings = vec![
5016 Mapping {
5017 generated_line: 0,
5018 generated_column: 0,
5019 source: 0,
5020 original_line: 0,
5021 original_column: 0,
5022 name: NO_NAME,
5023 is_range_mapping: false,
5024 },
5025 Mapping {
5026 generated_line: 5,
5027 generated_column: 0,
5028 source: 0,
5029 original_line: 5,
5030 original_column: 0,
5031 name: NO_NAME,
5032 is_range_mapping: false,
5033 },
5034 ];
5035
5036 let sm = SourceMap::from_parts(
5037 None,
5038 None,
5039 vec!["src.js".to_string()],
5040 vec![],
5041 vec![],
5042 mappings,
5043 vec![],
5044 None,
5045 None,
5046 );
5047
5048 assert_eq!(sm.line_count(), 6);
5049 assert!(sm.original_position_for(0, 0).is_some());
5050 assert!(sm.original_position_for(2, 0).is_none());
5051 assert!(sm.original_position_for(5, 0).is_some());
5052 }
5053
5054 #[test]
5057 fn from_json_lines_basic() {
5058 let json = generate_test_sourcemap(10, 5, 2);
5059 let sm_full = SourceMap::from_json(&json).unwrap();
5060
5061 let sm_partial = SourceMap::from_json_lines(&json, 3, 7).unwrap();
5063
5064 for line in 3..7u32 {
5066 let full_mappings = sm_full.mappings_for_line(line);
5067 let partial_mappings = sm_partial.mappings_for_line(line);
5068 assert_eq!(
5069 full_mappings.len(),
5070 partial_mappings.len(),
5071 "line {line} mapping count mismatch"
5072 );
5073 for (a, b) in full_mappings.iter().zip(partial_mappings.iter()) {
5074 assert_eq!(a.generated_column, b.generated_column);
5075 assert_eq!(a.source, b.source);
5076 assert_eq!(a.original_line, b.original_line);
5077 assert_eq!(a.original_column, b.original_column);
5078 assert_eq!(a.name, b.name);
5079 }
5080 }
5081 }
5082
5083 #[test]
5084 fn from_json_lines_first_lines() {
5085 let json = generate_test_sourcemap(10, 5, 2);
5086 let sm_full = SourceMap::from_json(&json).unwrap();
5087 let sm_partial = SourceMap::from_json_lines(&json, 0, 3).unwrap();
5088
5089 for line in 0..3u32 {
5090 let full_mappings = sm_full.mappings_for_line(line);
5091 let partial_mappings = sm_partial.mappings_for_line(line);
5092 assert_eq!(full_mappings.len(), partial_mappings.len());
5093 }
5094 }
5095
5096 #[test]
5097 fn from_json_lines_last_lines() {
5098 let json = generate_test_sourcemap(10, 5, 2);
5099 let sm_full = SourceMap::from_json(&json).unwrap();
5100 let sm_partial = SourceMap::from_json_lines(&json, 7, 10).unwrap();
5101
5102 for line in 7..10u32 {
5103 let full_mappings = sm_full.mappings_for_line(line);
5104 let partial_mappings = sm_partial.mappings_for_line(line);
5105 assert_eq!(full_mappings.len(), partial_mappings.len(), "line {line}");
5106 }
5107 }
5108
5109 #[test]
5110 fn from_json_lines_empty_range() {
5111 let json = generate_test_sourcemap(10, 5, 2);
5112 let sm = SourceMap::from_json_lines(&json, 5, 5).unwrap();
5113 assert_eq!(sm.mapping_count(), 0);
5114 }
5115
5116 #[test]
5117 fn from_json_lines_beyond_end() {
5118 let json = generate_test_sourcemap(5, 3, 1);
5119 let sm = SourceMap::from_json_lines(&json, 3, 100).unwrap();
5121 assert!(sm.mapping_count() > 0);
5123 }
5124
5125 #[test]
5126 fn from_json_lines_single_line() {
5127 let json = generate_test_sourcemap(10, 5, 2);
5128 let sm_full = SourceMap::from_json(&json).unwrap();
5129 let sm_partial = SourceMap::from_json_lines(&json, 5, 6).unwrap();
5130
5131 let full_mappings = sm_full.mappings_for_line(5);
5132 let partial_mappings = sm_partial.mappings_for_line(5);
5133 assert_eq!(full_mappings.len(), partial_mappings.len());
5134 }
5135
5136 #[test]
5139 fn lazy_basic_lookup() {
5140 let json = r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA;AACA"}"#;
5141 let sm = LazySourceMap::from_json(json).unwrap();
5142
5143 assert_eq!(sm.line_count(), 2);
5144 assert_eq!(sm.sources, vec!["input.js"]);
5145
5146 let loc = sm.original_position_for(0, 0).unwrap();
5147 assert_eq!(sm.source(loc.source), "input.js");
5148 assert_eq!(loc.line, 0);
5149 assert_eq!(loc.column, 0);
5150 }
5151
5152 #[test]
5153 fn lazy_multiple_lines() {
5154 let json = generate_test_sourcemap(20, 5, 3);
5155 let sm_eager = SourceMap::from_json(&json).unwrap();
5156 let sm_lazy = LazySourceMap::from_json(&json).unwrap();
5157
5158 assert_eq!(sm_lazy.line_count(), sm_eager.line_count());
5159
5160 for m in sm_eager.all_mappings() {
5162 if m.source == NO_SOURCE {
5163 continue;
5164 }
5165 let eager_loc = sm_eager
5166 .original_position_for(m.generated_line, m.generated_column)
5167 .unwrap();
5168 let lazy_loc = sm_lazy
5169 .original_position_for(m.generated_line, m.generated_column)
5170 .unwrap();
5171 assert_eq!(eager_loc.source, lazy_loc.source);
5172 assert_eq!(eager_loc.line, lazy_loc.line);
5173 assert_eq!(eager_loc.column, lazy_loc.column);
5174 assert_eq!(eager_loc.name, lazy_loc.name);
5175 }
5176 }
5177
5178 #[test]
5179 fn lazy_empty_mappings() {
5180 let json = r#"{"version":3,"sources":[],"names":[],"mappings":""}"#;
5181 let sm = LazySourceMap::from_json(json).unwrap();
5182 assert_eq!(sm.line_count(), 0);
5183 assert!(sm.original_position_for(0, 0).is_none());
5184 }
5185
5186 #[test]
5187 fn lazy_empty_lines() {
5188 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;;;AACA"}"#;
5189 let sm = LazySourceMap::from_json(json).unwrap();
5190 assert_eq!(sm.line_count(), 4);
5191
5192 assert!(sm.original_position_for(0, 0).is_some());
5193 assert!(sm.original_position_for(1, 0).is_none());
5194 assert!(sm.original_position_for(2, 0).is_none());
5195 assert!(sm.original_position_for(3, 0).is_some());
5196 }
5197
5198 #[test]
5199 fn lazy_decode_line_caching() {
5200 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,KACA;AACA"}"#;
5201 let sm = LazySourceMap::from_json(json).unwrap();
5202
5203 let line0_a = sm.decode_line(0).unwrap();
5205 let line0_b = sm.decode_line(0).unwrap();
5207 assert_eq!(line0_a.len(), line0_b.len());
5208 assert_eq!(line0_a[0].generated_column, line0_b[0].generated_column);
5209 }
5210
5211 #[test]
5212 fn lazy_with_names() {
5213 let json = r#"{"version":3,"sources":["input.js"],"names":["foo","bar"],"mappings":"AAAAA,KACAC"}"#;
5214 let sm = LazySourceMap::from_json(json).unwrap();
5215
5216 let loc = sm.original_position_for(0, 0).unwrap();
5217 assert_eq!(loc.name, Some(0));
5218 assert_eq!(sm.name(0), "foo");
5219
5220 let loc = sm.original_position_for(0, 5).unwrap();
5221 assert_eq!(loc.name, Some(1));
5222 assert_eq!(sm.name(1), "bar");
5223 }
5224
5225 #[test]
5226 fn lazy_nonexistent_line() {
5227 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
5228 let sm = LazySourceMap::from_json(json).unwrap();
5229 assert!(sm.original_position_for(99, 0).is_none());
5230 let line = sm.decode_line(99).unwrap();
5231 assert!(line.is_empty());
5232 }
5233
5234 #[test]
5235 fn lazy_into_sourcemap() {
5236 let json = generate_test_sourcemap(20, 5, 3);
5237 let sm_eager = SourceMap::from_json(&json).unwrap();
5238 let sm_lazy = LazySourceMap::from_json(&json).unwrap();
5239 let sm_converted = sm_lazy.into_sourcemap().unwrap();
5240
5241 assert_eq!(sm_converted.mapping_count(), sm_eager.mapping_count());
5242 assert_eq!(sm_converted.line_count(), sm_eager.line_count());
5243
5244 for m in sm_eager.all_mappings() {
5246 let a = sm_eager.original_position_for(m.generated_line, m.generated_column);
5247 let b = sm_converted.original_position_for(m.generated_line, m.generated_column);
5248 match (a, b) {
5249 (Some(a), Some(b)) => {
5250 assert_eq!(a.source, b.source);
5251 assert_eq!(a.line, b.line);
5252 assert_eq!(a.column, b.column);
5253 }
5254 (None, None) => {}
5255 _ => panic!("mismatch at ({}, {})", m.generated_line, m.generated_column),
5256 }
5257 }
5258 }
5259
5260 #[test]
5261 fn lazy_source_index_lookup() {
5262 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":[],"mappings":"AAAA;ACAA"}"#;
5263 let sm = LazySourceMap::from_json(json).unwrap();
5264 assert_eq!(sm.source_index("a.js"), Some(0));
5265 assert_eq!(sm.source_index("b.js"), Some(1));
5266 assert_eq!(sm.source_index("c.js"), None);
5267 }
5268
5269 #[test]
5270 fn lazy_mappings_for_line() {
5271 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,KACA;AACA"}"#;
5272 let sm = LazySourceMap::from_json(json).unwrap();
5273
5274 let line0 = sm.mappings_for_line(0);
5275 assert_eq!(line0.len(), 2);
5276
5277 let line1 = sm.mappings_for_line(1);
5278 assert_eq!(line1.len(), 1);
5279
5280 let line99 = sm.mappings_for_line(99);
5281 assert!(line99.is_empty());
5282 }
5283
5284 #[test]
5285 fn lazy_large_map_selective_decode() {
5286 let json = generate_test_sourcemap(100, 10, 5);
5288 let sm_eager = SourceMap::from_json(&json).unwrap();
5289 let sm_lazy = LazySourceMap::from_json(&json).unwrap();
5290
5291 for line in [50, 75] {
5293 let eager_mappings = sm_eager.mappings_for_line(line);
5294 let lazy_mappings = sm_lazy.mappings_for_line(line);
5295 assert_eq!(
5296 eager_mappings.len(),
5297 lazy_mappings.len(),
5298 "line {line} count mismatch"
5299 );
5300 for (a, b) in eager_mappings.iter().zip(lazy_mappings.iter()) {
5301 assert_eq!(a.generated_column, b.generated_column);
5302 assert_eq!(a.source, b.source);
5303 assert_eq!(a.original_line, b.original_line);
5304 assert_eq!(a.original_column, b.original_column);
5305 assert_eq!(a.name, b.name);
5306 }
5307 }
5308 }
5309
5310 #[test]
5311 fn lazy_single_field_segments() {
5312 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A,KAAAA"}"#;
5313 let sm = LazySourceMap::from_json(json).unwrap();
5314
5315 assert!(sm.original_position_for(0, 0).is_none());
5317 let loc = sm.original_position_for(0, 5).unwrap();
5319 assert_eq!(loc.source, 0);
5320 }
5321
5322 #[test]
5325 fn parse_error_display_vlq() {
5326 let err = ParseError::Vlq(srcmap_codec::DecodeError::UnexpectedEof { offset: 3 });
5327 assert!(err.to_string().contains("VLQ decode error"));
5328 }
5329
5330 #[test]
5331 fn parse_error_display_scopes() {
5332 let err = ParseError::Scopes(srcmap_scopes::ScopesError::UnclosedScope);
5333 assert!(err.to_string().contains("scopes decode error"));
5334 }
5335
5336 #[test]
5337 fn indexed_map_with_names_in_sections() {
5338 let json = r#"{
5339 "version": 3,
5340 "sections": [
5341 {
5342 "offset": {"line": 0, "column": 0},
5343 "map": {
5344 "version": 3,
5345 "sources": ["a.js"],
5346 "names": ["foo"],
5347 "mappings": "AAAAA"
5348 }
5349 },
5350 {
5351 "offset": {"line": 1, "column": 0},
5352 "map": {
5353 "version": 3,
5354 "sources": ["a.js"],
5355 "names": ["foo"],
5356 "mappings": "AAAAA"
5357 }
5358 }
5359 ]
5360 }"#;
5361 let sm = SourceMap::from_json(json).unwrap();
5362 assert_eq!(sm.sources.len(), 1);
5364 assert_eq!(sm.names.len(), 1);
5365 }
5366
5367 #[test]
5368 fn indexed_map_with_ignore_list() {
5369 let json = r#"{
5370 "version": 3,
5371 "sections": [
5372 {
5373 "offset": {"line": 0, "column": 0},
5374 "map": {
5375 "version": 3,
5376 "sources": ["vendor.js"],
5377 "names": [],
5378 "mappings": "AAAA",
5379 "ignoreList": [0]
5380 }
5381 }
5382 ]
5383 }"#;
5384 let sm = SourceMap::from_json(json).unwrap();
5385 assert_eq!(sm.ignore_list, vec![0]);
5386 }
5387
5388 #[test]
5389 fn indexed_map_with_generated_only_segment() {
5390 let json = r#"{
5392 "version": 3,
5393 "sections": [
5394 {
5395 "offset": {"line": 0, "column": 0},
5396 "map": {
5397 "version": 3,
5398 "sources": ["a.js"],
5399 "names": [],
5400 "mappings": "A,AAAA"
5401 }
5402 }
5403 ]
5404 }"#;
5405 let sm = SourceMap::from_json(json).unwrap();
5406 assert!(sm.mapping_count() >= 1);
5407 }
5408
5409 #[test]
5410 fn indexed_map_empty_mappings() {
5411 let json = r#"{
5412 "version": 3,
5413 "sections": [
5414 {
5415 "offset": {"line": 0, "column": 0},
5416 "map": {
5417 "version": 3,
5418 "sources": [],
5419 "names": [],
5420 "mappings": ""
5421 }
5422 }
5423 ]
5424 }"#;
5425 let sm = SourceMap::from_json(json).unwrap();
5426 assert_eq!(sm.mapping_count(), 0);
5427 }
5428
5429 #[test]
5430 fn generated_position_glb_exact_match() {
5431 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAE,OAAO"}"#;
5432 let sm = SourceMap::from_json(json).unwrap();
5433
5434 let loc = sm.generated_position_for_with_bias("a.js", 0, 0, Bias::GreatestLowerBound);
5435 assert!(loc.is_some());
5436 assert_eq!(loc.unwrap().column, 0);
5437 }
5438
5439 #[test]
5440 fn generated_position_glb_no_exact_match() {
5441 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAE"}"#;
5442 let sm = SourceMap::from_json(json).unwrap();
5443
5444 let loc = sm.generated_position_for_with_bias("a.js", 0, 0, Bias::GreatestLowerBound);
5446 assert!(loc.is_some());
5447 }
5448
5449 #[test]
5450 fn generated_position_glb_wrong_source() {
5451 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":[],"mappings":"AAAA,KCCA"}"#;
5452 let sm = SourceMap::from_json(json).unwrap();
5453
5454 let loc = sm.generated_position_for_with_bias("b.js", 5, 0, Bias::GreatestLowerBound);
5456 if let Some(l) = loc {
5459 assert_eq!(l.line, 0);
5461 }
5462 }
5463
5464 #[test]
5465 fn generated_position_lub_wrong_source() {
5466 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
5467 let sm = SourceMap::from_json(json).unwrap();
5468
5469 let loc =
5471 sm.generated_position_for_with_bias("nonexistent.js", 0, 0, Bias::LeastUpperBound);
5472 assert!(loc.is_none());
5473 }
5474
5475 #[test]
5476 fn to_json_with_ignore_list() {
5477 let json = r#"{"version":3,"sources":["vendor.js"],"names":[],"mappings":"AAAA","ignoreList":[0]}"#;
5478 let sm = SourceMap::from_json(json).unwrap();
5479 let output = sm.to_json();
5480 assert!(output.contains("\"ignoreList\":[0]"));
5481 }
5482
5483 #[test]
5484 fn to_json_with_extensions() {
5485 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","x_custom":"test_value"}"#;
5486 let sm = SourceMap::from_json(json).unwrap();
5487 let output = sm.to_json();
5488 assert!(output.contains("x_custom"));
5489 assert!(output.contains("test_value"));
5490 }
5491
5492 #[test]
5493 fn from_parts_empty_mappings() {
5494 let sm = SourceMap::from_parts(
5495 None,
5496 None,
5497 vec!["a.js".to_string()],
5498 vec![Some("content".to_string())],
5499 vec![],
5500 vec![],
5501 vec![],
5502 None,
5503 None,
5504 );
5505 assert_eq!(sm.mapping_count(), 0);
5506 assert_eq!(sm.sources, vec!["a.js"]);
5507 }
5508
5509 #[test]
5510 fn from_vlq_basic() {
5511 let sm = SourceMap::from_vlq(
5512 "AAAA;AACA",
5513 vec!["a.js".to_string()],
5514 vec![],
5515 Some("out.js".to_string()),
5516 None,
5517 vec![Some("content".to_string())],
5518 vec![],
5519 None,
5520 )
5521 .unwrap();
5522
5523 assert_eq!(sm.file.as_deref(), Some("out.js"));
5524 assert_eq!(sm.sources, vec!["a.js"]);
5525 let loc = sm.original_position_for(0, 0).unwrap();
5526 assert_eq!(sm.source(loc.source), "a.js");
5527 assert_eq!(loc.line, 0);
5528 }
5529
5530 #[test]
5531 fn from_json_lines_basic_coverage() {
5532 let json =
5533 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA"}"#;
5534 let sm = SourceMap::from_json_lines(json, 1, 3).unwrap();
5535 assert!(sm.original_position_for(1, 0).is_some());
5537 assert!(sm.original_position_for(2, 0).is_some());
5538 }
5539
5540 #[test]
5541 fn from_json_lines_with_source_root() {
5542 let json = r#"{"version":3,"sourceRoot":"src/","sources":["a.js"],"names":[],"mappings":"AAAA;AACA"}"#;
5543 let sm = SourceMap::from_json_lines(json, 0, 2).unwrap();
5544 assert_eq!(sm.sources[0], "src/a.js");
5545 }
5546
5547 #[test]
5548 fn from_json_lines_with_null_source() {
5549 let json = r#"{"version":3,"sources":[null,"a.js"],"names":[],"mappings":"AAAA,KCCA"}"#;
5550 let sm = SourceMap::from_json_lines(json, 0, 1).unwrap();
5551 assert_eq!(sm.sources.len(), 2);
5552 }
5553
5554 #[test]
5555 fn json_escaping_special_chars_sourcemap() {
5556 let json = r#"{"version":3,"sources":["path/with\nnewline.js"],"sourcesContent":["line1\r\nline2\t\"quoted\"\\\u0001"],"names":[],"mappings":"AAAA"}"#;
5559 let sm = SourceMap::from_json(json).unwrap();
5560 let output = sm.to_json();
5562 let sm2 = SourceMap::from_json(&output).unwrap();
5563 assert_eq!(sm.sources[0], sm2.sources[0]);
5564 assert_eq!(sm.sources_content[0], sm2.sources_content[0]);
5565 }
5566
5567 #[test]
5568 fn to_json_exclude_content() {
5569 let json = r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":[],"mappings":"AAAA"}"#;
5570 let sm = SourceMap::from_json(json).unwrap();
5571 let output = sm.to_json_with_options(true);
5572 assert!(!output.contains("sourcesContent"));
5573 let output_with = sm.to_json_with_options(false);
5574 assert!(output_with.contains("sourcesContent"));
5575 }
5576
5577 #[test]
5578 fn encode_mappings_with_name() {
5579 let json = r#"{"version":3,"sources":["a.js"],"names":["foo"],"mappings":"AAAAA"}"#;
5581 let sm = SourceMap::from_json(json).unwrap();
5582 let encoded = sm.encode_mappings();
5583 assert_eq!(encoded, "AAAAA");
5584 }
5585
5586 #[test]
5587 fn encode_mappings_generated_only() {
5588 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A,AAAA"}"#;
5590 let sm = SourceMap::from_json(json).unwrap();
5591 let encoded = sm.encode_mappings();
5592 let roundtrip = SourceMap::from_json(&format!(
5593 r#"{{"version":3,"sources":["a.js"],"names":[],"mappings":"{}"}}"#,
5594 encoded
5595 ))
5596 .unwrap();
5597 assert_eq!(roundtrip.mapping_count(), sm.mapping_count());
5598 }
5599
5600 #[test]
5601 fn map_range_single_result() {
5602 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAC,OAAO"}"#;
5603 let sm = SourceMap::from_json(json).unwrap();
5604 let result = sm.map_range(0, 0, 0, 1);
5606 assert!(result.is_some());
5607 let range = result.unwrap();
5608 assert_eq!(range.source, 0);
5609 }
5610
5611 #[test]
5612 fn scopes_in_from_json() {
5613 let info = srcmap_scopes::ScopeInfo {
5615 scopes: vec![Some(srcmap_scopes::OriginalScope {
5616 start: srcmap_scopes::Position { line: 0, column: 0 },
5617 end: srcmap_scopes::Position { line: 5, column: 0 },
5618 name: None,
5619 kind: None,
5620 is_stack_frame: false,
5621 variables: vec![],
5622 children: vec![],
5623 })],
5624 ranges: vec![],
5625 };
5626 let mut names = vec![];
5627 let scopes_str = srcmap_scopes::encode_scopes(&info, &mut names);
5628
5629 let json = format!(
5630 r#"{{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","scopes":"{scopes_str}"}}"#
5631 );
5632
5633 let sm = SourceMap::from_json(&json).unwrap();
5634 assert!(sm.scopes.is_some());
5635 }
5636
5637 #[test]
5638 fn from_json_lines_with_scopes() {
5639 let info = srcmap_scopes::ScopeInfo {
5640 scopes: vec![Some(srcmap_scopes::OriginalScope {
5641 start: srcmap_scopes::Position { line: 0, column: 0 },
5642 end: srcmap_scopes::Position { line: 5, column: 0 },
5643 name: None,
5644 kind: None,
5645 is_stack_frame: false,
5646 variables: vec![],
5647 children: vec![],
5648 })],
5649 ranges: vec![],
5650 };
5651 let mut names = vec![];
5652 let scopes_str = srcmap_scopes::encode_scopes(&info, &mut names);
5653 let json = format!(
5654 r#"{{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;AACA","scopes":"{scopes_str}"}}"#
5655 );
5656 let sm = SourceMap::from_json_lines(&json, 0, 2).unwrap();
5657 assert!(sm.scopes.is_some());
5658 }
5659
5660 #[test]
5661 fn from_json_lines_with_extensions() {
5662 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","x_custom":"val","not_x":"skip"}"#;
5663 let sm = SourceMap::from_json_lines(json, 0, 1).unwrap();
5664 assert!(sm.extensions.contains_key("x_custom"));
5665 assert!(!sm.extensions.contains_key("not_x"));
5666 }
5667
5668 #[test]
5669 fn lazy_sourcemap_version_error() {
5670 let json = r#"{"version":2,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
5671 let err = LazySourceMap::from_json(json).unwrap_err();
5672 assert!(matches!(err, ParseError::InvalidVersion(2)));
5673 }
5674
5675 #[test]
5676 fn lazy_sourcemap_with_source_root() {
5677 let json =
5678 r#"{"version":3,"sourceRoot":"src/","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
5679 let sm = LazySourceMap::from_json(json).unwrap();
5680 assert_eq!(sm.sources[0], "src/a.js");
5681 }
5682
5683 #[test]
5684 fn lazy_sourcemap_with_ignore_list_and_extensions() {
5685 let json = r#"{"version":3,"sources":["v.js"],"names":[],"mappings":"AAAA","ignoreList":[0],"x_custom":"val","not_x":"skip"}"#;
5686 let sm = LazySourceMap::from_json(json).unwrap();
5687 assert_eq!(sm.ignore_list, vec![0]);
5688 assert!(sm.extensions.contains_key("x_custom"));
5689 assert!(!sm.extensions.contains_key("not_x"));
5690 }
5691
5692 #[test]
5693 fn lazy_sourcemap_with_scopes() {
5694 let info = srcmap_scopes::ScopeInfo {
5695 scopes: vec![Some(srcmap_scopes::OriginalScope {
5696 start: srcmap_scopes::Position { line: 0, column: 0 },
5697 end: srcmap_scopes::Position { line: 5, column: 0 },
5698 name: None,
5699 kind: None,
5700 is_stack_frame: false,
5701 variables: vec![],
5702 children: vec![],
5703 })],
5704 ranges: vec![],
5705 };
5706 let mut names = vec![];
5707 let scopes_str = srcmap_scopes::encode_scopes(&info, &mut names);
5708 let json = format!(
5709 r#"{{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","scopes":"{scopes_str}"}}"#
5710 );
5711 let sm = LazySourceMap::from_json(&json).unwrap();
5712 assert!(sm.scopes.is_some());
5713 }
5714
5715 #[test]
5716 fn lazy_sourcemap_null_source() {
5717 let json = r#"{"version":3,"sources":[null,"a.js"],"names":[],"mappings":"AAAA,KCCA"}"#;
5718 let sm = LazySourceMap::from_json(json).unwrap();
5719 assert_eq!(sm.sources.len(), 2);
5720 }
5721
5722 #[test]
5723 fn indexed_map_multi_line_section() {
5724 let json = r#"{
5726 "version": 3,
5727 "sections": [
5728 {
5729 "offset": {"line": 0, "column": 0},
5730 "map": {
5731 "version": 3,
5732 "sources": ["a.js"],
5733 "names": [],
5734 "mappings": "AAAA;AACA;AACA"
5735 }
5736 },
5737 {
5738 "offset": {"line": 5, "column": 0},
5739 "map": {
5740 "version": 3,
5741 "sources": ["b.js"],
5742 "names": [],
5743 "mappings": "AAAA;AACA"
5744 }
5745 }
5746 ]
5747 }"#;
5748 let sm = SourceMap::from_json(json).unwrap();
5749 assert!(sm.original_position_for(0, 0).is_some());
5750 assert!(sm.original_position_for(5, 0).is_some());
5751 }
5752
5753 #[test]
5754 fn source_mapping_url_extraction() {
5755 let input = "var x = 1;\n//# sourceMappingURL=bundle.js.map";
5757 let url = parse_source_mapping_url(input);
5758 assert!(matches!(url, Some(SourceMappingUrl::External(ref s)) if s == "bundle.js.map"));
5759
5760 let input = "body { }\n/*# sourceMappingURL=style.css.map */";
5762 let url = parse_source_mapping_url(input);
5763 assert!(matches!(url, Some(SourceMappingUrl::External(ref s)) if s == "style.css.map"));
5764
5765 let input = "var x;\n//@ sourceMappingURL=old-style.map";
5767 let url = parse_source_mapping_url(input);
5768 assert!(matches!(url, Some(SourceMappingUrl::External(ref s)) if s == "old-style.map"));
5769
5770 let input = "body{}\n/*@ sourceMappingURL=old-css.map */";
5772 let url = parse_source_mapping_url(input);
5773 assert!(matches!(url, Some(SourceMappingUrl::External(ref s)) if s == "old-css.map"));
5774
5775 let input = "var x = 1;";
5777 let url = parse_source_mapping_url(input);
5778 assert!(url.is_none());
5779
5780 let input = "//# sourceMappingURL=";
5782 let url = parse_source_mapping_url(input);
5783 assert!(url.is_none());
5784
5785 let map_json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
5787 let encoded = base64_encode_simple(map_json);
5788 let input = format!("var x;\n//# sourceMappingURL=data:application/json;base64,{encoded}");
5789 let url = parse_source_mapping_url(&input);
5790 assert!(matches!(url, Some(SourceMappingUrl::Inline(_))));
5791 }
5792
5793 #[test]
5794 fn validate_deep_unreferenced_coverage() {
5795 let sm = SourceMap::from_parts(
5797 None,
5798 None,
5799 vec!["used.js".to_string(), "unused.js".to_string()],
5800 vec![None, None],
5801 vec![],
5802 vec![Mapping {
5803 generated_line: 0,
5804 generated_column: 0,
5805 source: 0,
5806 original_line: 0,
5807 original_column: 0,
5808 name: NO_NAME,
5809 is_range_mapping: false,
5810 }],
5811 vec![],
5812 None,
5813 None,
5814 );
5815 let warnings = validate_deep(&sm);
5816 assert!(warnings.iter().any(|w| w.contains("unreferenced")));
5817 }
5818
5819 #[test]
5820 fn from_json_lines_generated_only_segment() {
5821 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A,AAAA;AACA"}"#;
5823 let sm = SourceMap::from_json_lines(json, 0, 2).unwrap();
5824 assert!(sm.mapping_count() >= 2);
5825 }
5826
5827 #[test]
5828 fn from_json_lines_with_names() {
5829 let json = r#"{"version":3,"sources":["a.js"],"names":["foo"],"mappings":"AAAAA;AACAA"}"#;
5830 let sm = SourceMap::from_json_lines(json, 0, 2).unwrap();
5831 let loc = sm.original_position_for(0, 0).unwrap();
5832 assert_eq!(loc.name, Some(0));
5833 }
5834
5835 #[test]
5836 fn from_parts_with_line_gap() {
5837 let sm = SourceMap::from_parts(
5839 None,
5840 None,
5841 vec!["a.js".to_string()],
5842 vec![None],
5843 vec![],
5844 vec![
5845 Mapping {
5846 generated_line: 0,
5847 generated_column: 0,
5848 source: 0,
5849 original_line: 0,
5850 original_column: 0,
5851 name: NO_NAME,
5852 is_range_mapping: false,
5853 },
5854 Mapping {
5855 generated_line: 5,
5856 generated_column: 0,
5857 source: 0,
5858 original_line: 5,
5859 original_column: 0,
5860 name: NO_NAME,
5861 is_range_mapping: false,
5862 },
5863 ],
5864 vec![],
5865 None,
5866 None,
5867 );
5868 assert!(sm.original_position_for(0, 0).is_some());
5869 assert!(sm.original_position_for(5, 0).is_some());
5870 assert!(sm.original_position_for(1, 0).is_none());
5872 }
5873
5874 #[test]
5875 fn lazy_decode_line_with_names_and_generated_only() {
5876 let json = r#"{"version":3,"sources":["a.js"],"names":["fn"],"mappings":"A,AAAAC"}"#;
5878 let sm = LazySourceMap::from_json(json).unwrap();
5879 let line = sm.decode_line(0).unwrap();
5880 assert!(line.len() >= 2);
5881 assert_eq!(line[0].source, NO_SOURCE);
5883 assert_ne!(line[1].name, NO_NAME);
5885 }
5886
5887 #[test]
5888 fn generated_position_glb_source_mismatch() {
5889 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":[],"mappings":"AAAA,KCCA"}"#;
5891 let sm = SourceMap::from_json(json).unwrap();
5892
5893 let loc = sm.generated_position_for_with_bias("a.js", 100, 0, Bias::LeastUpperBound);
5895 assert!(loc.is_none());
5896
5897 let loc = sm.generated_position_for_with_bias("b.js", 0, 0, Bias::GreatestLowerBound);
5901 assert!(loc.is_none());
5902
5903 let loc = sm.generated_position_for_with_bias("b.js", 1, 0, Bias::GreatestLowerBound);
5905 assert!(loc.is_some());
5906
5907 let loc = sm.generated_position_for_with_bias("b.js", 99, 0, Bias::LeastUpperBound);
5909 assert!(loc.is_none());
5910 }
5911
5912 #[test]
5915 fn from_json_invalid_scopes_error() {
5916 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","scopes":"!!invalid!!"}"#;
5918 let err = SourceMap::from_json(json).unwrap_err();
5919 assert!(matches!(err, ParseError::Scopes(_)));
5920 }
5921
5922 #[test]
5923 fn lazy_from_json_invalid_scopes_error() {
5924 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","scopes":"!!invalid!!"}"#;
5925 let err = LazySourceMap::from_json(json).unwrap_err();
5926 assert!(matches!(err, ParseError::Scopes(_)));
5927 }
5928
5929 #[test]
5930 fn from_json_lines_invalid_scopes_error() {
5931 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","scopes":"!!invalid!!"}"#;
5932 let err = SourceMap::from_json_lines(json, 0, 1).unwrap_err();
5933 assert!(matches!(err, ParseError::Scopes(_)));
5934 }
5935
5936 #[test]
5937 fn from_json_lines_invalid_version() {
5938 let json = r#"{"version":2,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
5939 let err = SourceMap::from_json_lines(json, 0, 1).unwrap_err();
5940 assert!(matches!(err, ParseError::InvalidVersion(2)));
5941 }
5942
5943 #[test]
5944 fn indexed_map_with_ignore_list_remapped() {
5945 let json = r#"{
5947 "version": 3,
5948 "sections": [{
5949 "offset": {"line": 0, "column": 0},
5950 "map": {
5951 "version": 3,
5952 "sources": ["a.js", "b.js"],
5953 "names": [],
5954 "mappings": "AAAA;ACAA",
5955 "ignoreList": [1]
5956 }
5957 }, {
5958 "offset": {"line": 5, "column": 0},
5959 "map": {
5960 "version": 3,
5961 "sources": ["b.js", "c.js"],
5962 "names": [],
5963 "mappings": "AAAA;ACAA",
5964 "ignoreList": [0]
5965 }
5966 }]
5967 }"#;
5968 let sm = SourceMap::from_json(json).unwrap();
5969 assert!(!sm.ignore_list.is_empty());
5971 }
5972
5973 #[test]
5974 fn to_json_with_debug_id() {
5975 let sm = SourceMap::from_parts(
5976 Some("out.js".to_string()),
5977 None,
5978 vec!["a.js".to_string()],
5979 vec![None],
5980 vec![],
5981 vec![Mapping {
5982 generated_line: 0,
5983 generated_column: 0,
5984 source: 0,
5985 original_line: 0,
5986 original_column: 0,
5987 name: NO_NAME,
5988 is_range_mapping: false,
5989 }],
5990 vec![],
5991 Some("abc-123".to_string()),
5992 None,
5993 );
5994 let json = sm.to_json();
5995 assert!(json.contains(r#""debugId":"abc-123""#));
5996 }
5997
5998 #[test]
5999 fn to_json_with_ignore_list_and_extensions() {
6000 let mut sm = SourceMap::from_parts(
6001 None,
6002 None,
6003 vec!["a.js".to_string(), "b.js".to_string()],
6004 vec![None, None],
6005 vec![],
6006 vec![Mapping {
6007 generated_line: 0,
6008 generated_column: 0,
6009 source: 0,
6010 original_line: 0,
6011 original_column: 0,
6012 name: NO_NAME,
6013 is_range_mapping: false,
6014 }],
6015 vec![1],
6016 None,
6017 None,
6018 );
6019 sm.extensions
6020 .insert("x_test".to_string(), serde_json::json!(42));
6021 let json = sm.to_json();
6022 assert!(json.contains("\"ignoreList\":[1]"));
6023 assert!(json.contains("\"x_test\":42"));
6024 }
6025
6026 #[test]
6027 fn from_vlq_with_all_options() {
6028 let sm = SourceMap::from_vlq(
6029 "AAAA;AACA",
6030 vec!["a.js".to_string()],
6031 vec![],
6032 Some("out.js".to_string()),
6033 Some("src/".to_string()),
6034 vec![Some("content".to_string())],
6035 vec![0],
6036 Some("debug-123".to_string()),
6037 )
6038 .unwrap();
6039 assert_eq!(sm.source(0), "a.js");
6040 assert!(sm.original_position_for(0, 0).is_some());
6041 assert!(sm.original_position_for(1, 0).is_some());
6042 }
6043
6044 #[test]
6045 fn lazy_into_sourcemap_roundtrip() {
6046 let json = r#"{"version":3,"sources":["a.js"],"names":["x"],"mappings":"AAAAA;AACAA"}"#;
6047 let lazy = LazySourceMap::from_json(json).unwrap();
6048 let sm = lazy.into_sourcemap().unwrap();
6049 assert!(sm.original_position_for(0, 0).is_some());
6050 assert!(sm.original_position_for(1, 0).is_some());
6051 assert_eq!(sm.name(0), "x");
6052 }
6053
6054 #[test]
6055 fn lazy_original_position_for_no_match() {
6056 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"KAAA"}"#;
6058 let sm = LazySourceMap::from_json(json).unwrap();
6059 assert!(sm.original_position_for(0, 0).is_none());
6061 }
6062
6063 #[test]
6064 fn lazy_original_position_for_empty_line() {
6065 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":";AAAA"}"#;
6066 let sm = LazySourceMap::from_json(json).unwrap();
6067 assert!(sm.original_position_for(0, 0).is_none());
6069 assert!(sm.original_position_for(1, 0).is_some());
6071 }
6072
6073 #[test]
6074 fn lazy_original_position_generated_only() {
6075 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A;AAAA"}"#;
6077 let sm = LazySourceMap::from_json(json).unwrap();
6078 assert!(sm.original_position_for(0, 0).is_none());
6080 assert!(sm.original_position_for(1, 0).is_some());
6082 }
6083
6084 #[test]
6085 fn from_json_lines_null_source() {
6086 let json = r#"{"version":3,"sources":[null,"a.js"],"names":[],"mappings":"ACAA"}"#;
6087 let sm = SourceMap::from_json_lines(json, 0, 1).unwrap();
6088 assert!(sm.mapping_count() >= 1);
6089 }
6090
6091 #[test]
6092 fn from_json_lines_with_source_root_prefix() {
6093 let json =
6094 r#"{"version":3,"sourceRoot":"lib/","sources":["b.js"],"names":[],"mappings":"AAAA"}"#;
6095 let sm = SourceMap::from_json_lines(json, 0, 1).unwrap();
6096 assert_eq!(sm.source(0), "lib/b.js");
6097 }
6098
6099 #[test]
6100 fn generated_position_for_glb_idx_zero() {
6101 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAKA"}"#;
6105 let sm = SourceMap::from_json(json).unwrap();
6106 let loc = sm.generated_position_for_with_bias("a.js", 0, 0, Bias::GreatestLowerBound);
6107 assert!(loc.is_none());
6108 }
6109
6110 #[test]
6111 fn from_json_lines_with_ignore_list() {
6112 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":[],"mappings":"AAAA;ACAA","ignoreList":[1]}"#;
6113 let sm = SourceMap::from_json_lines(json, 0, 2).unwrap();
6114 assert_eq!(sm.ignore_list, vec![1]);
6115 }
6116
6117 #[test]
6118 fn validate_deep_out_of_order_mappings() {
6119 let sm = SourceMap::from_parts(
6121 None,
6122 None,
6123 vec!["a.js".to_string()],
6124 vec![None],
6125 vec![],
6126 vec![
6127 Mapping {
6128 generated_line: 1,
6129 generated_column: 0,
6130 source: 0,
6131 original_line: 0,
6132 original_column: 0,
6133 name: NO_NAME,
6134 is_range_mapping: false,
6135 },
6136 Mapping {
6137 generated_line: 0,
6138 generated_column: 0,
6139 source: 0,
6140 original_line: 0,
6141 original_column: 0,
6142 name: NO_NAME,
6143 is_range_mapping: false,
6144 },
6145 ],
6146 vec![],
6147 None,
6148 None,
6149 );
6150 let warnings = validate_deep(&sm);
6151 assert!(warnings.iter().any(|w| w.contains("out of order")));
6152 }
6153
6154 #[test]
6155 fn validate_deep_out_of_bounds_source() {
6156 let sm = SourceMap::from_parts(
6157 None,
6158 None,
6159 vec!["a.js".to_string()],
6160 vec![None],
6161 vec![],
6162 vec![Mapping {
6163 generated_line: 0,
6164 generated_column: 0,
6165 source: 5,
6166 original_line: 0,
6167 original_column: 0,
6168 name: NO_NAME,
6169 is_range_mapping: false,
6170 }],
6171 vec![],
6172 None,
6173 None,
6174 );
6175 let warnings = validate_deep(&sm);
6176 assert!(
6177 warnings
6178 .iter()
6179 .any(|w| w.contains("source index") && w.contains("out of bounds"))
6180 );
6181 }
6182
6183 #[test]
6184 fn validate_deep_out_of_bounds_name() {
6185 let sm = SourceMap::from_parts(
6186 None,
6187 None,
6188 vec!["a.js".to_string()],
6189 vec![None],
6190 vec!["foo".to_string()],
6191 vec![Mapping {
6192 generated_line: 0,
6193 generated_column: 0,
6194 source: 0,
6195 original_line: 0,
6196 original_column: 0,
6197 name: 5,
6198 is_range_mapping: false,
6199 }],
6200 vec![],
6201 None,
6202 None,
6203 );
6204 let warnings = validate_deep(&sm);
6205 assert!(
6206 warnings
6207 .iter()
6208 .any(|w| w.contains("name index") && w.contains("out of bounds"))
6209 );
6210 }
6211
6212 #[test]
6213 fn validate_deep_out_of_bounds_ignore_list() {
6214 let sm = SourceMap::from_parts(
6215 None,
6216 None,
6217 vec!["a.js".to_string()],
6218 vec![None],
6219 vec![],
6220 vec![Mapping {
6221 generated_line: 0,
6222 generated_column: 0,
6223 source: 0,
6224 original_line: 0,
6225 original_column: 0,
6226 name: NO_NAME,
6227 is_range_mapping: false,
6228 }],
6229 vec![10],
6230 None,
6231 None,
6232 );
6233 let warnings = validate_deep(&sm);
6234 assert!(
6235 warnings
6236 .iter()
6237 .any(|w| w.contains("ignoreList") && w.contains("out of bounds"))
6238 );
6239 }
6240
6241 #[test]
6242 fn source_mapping_url_inline_decoded() {
6243 let map_json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
6245 let encoded = base64_encode_simple(map_json);
6246 let input = format!("var x;\n//# sourceMappingURL=data:application/json;base64,{encoded}");
6247 let url = parse_source_mapping_url(&input);
6248 match url {
6249 Some(SourceMappingUrl::Inline(json)) => {
6250 assert!(json.contains("version"));
6251 assert!(json.contains("AAAA"));
6252 }
6253 _ => panic!("expected inline source map"),
6254 }
6255 }
6256
6257 #[test]
6258 fn source_mapping_url_charset_variant() {
6259 let map_json = r#"{"version":3}"#;
6260 let encoded = base64_encode_simple(map_json);
6261 let input =
6262 format!("x\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,{encoded}");
6263 let url = parse_source_mapping_url(&input);
6264 assert!(matches!(url, Some(SourceMappingUrl::Inline(_))));
6265 }
6266
6267 #[test]
6268 fn source_mapping_url_invalid_base64_falls_through_to_external() {
6269 let input = "x\n//# sourceMappingURL=data:application/json;base64,!!!invalid!!!";
6271 let url = parse_source_mapping_url(input);
6272 assert!(matches!(url, Some(SourceMappingUrl::External(_))));
6274 }
6275
6276 #[test]
6277 fn from_json_lines_with_extensions_preserved() {
6278 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","x_custom":99}"#;
6279 let sm = SourceMap::from_json_lines(json, 0, 1).unwrap();
6280 assert!(sm.extensions.contains_key("x_custom"));
6281 }
6282
6283 fn base64_encode_simple(input: &str) -> String {
6285 const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
6286 let bytes = input.as_bytes();
6287 let mut result = String::new();
6288 for chunk in bytes.chunks(3) {
6289 let b0 = chunk[0] as u32;
6290 let b1 = if chunk.len() > 1 { chunk[1] as u32 } else { 0 };
6291 let b2 = if chunk.len() > 2 { chunk[2] as u32 } else { 0 };
6292 let n = (b0 << 16) | (b1 << 8) | b2;
6293 result.push(CHARS[((n >> 18) & 0x3F) as usize] as char);
6294 result.push(CHARS[((n >> 12) & 0x3F) as usize] as char);
6295 if chunk.len() > 1 {
6296 result.push(CHARS[((n >> 6) & 0x3F) as usize] as char);
6297 } else {
6298 result.push('=');
6299 }
6300 if chunk.len() > 2 {
6301 result.push(CHARS[(n & 0x3F) as usize] as char);
6302 } else {
6303 result.push('=');
6304 }
6305 }
6306 result
6307 }
6308
6309 #[test]
6312 fn mappings_iter_matches_decode() {
6313 let vlq = "AAAA;AACA,EAAA;AACA";
6314 let iter_mappings: Vec<Mapping> = MappingsIter::new(vlq).collect::<Result<_, _>>().unwrap();
6315 let (decoded, _) = decode_mappings(vlq).unwrap();
6316 assert_eq!(iter_mappings.len(), decoded.len());
6317 for (a, b) in iter_mappings.iter().zip(decoded.iter()) {
6318 assert_eq!(a.generated_line, b.generated_line);
6319 assert_eq!(a.generated_column, b.generated_column);
6320 assert_eq!(a.source, b.source);
6321 assert_eq!(a.original_line, b.original_line);
6322 assert_eq!(a.original_column, b.original_column);
6323 assert_eq!(a.name, b.name);
6324 }
6325 }
6326
6327 #[test]
6328 fn mappings_iter_empty() {
6329 let mappings: Vec<Mapping> = MappingsIter::new("").collect::<Result<_, _>>().unwrap();
6330 assert!(mappings.is_empty());
6331 }
6332
6333 #[test]
6334 fn mappings_iter_generated_only() {
6335 let vlq = "A,AAAA";
6336 let mappings: Vec<Mapping> = MappingsIter::new(vlq).collect::<Result<_, _>>().unwrap();
6337 assert_eq!(mappings.len(), 2);
6338 assert_eq!(mappings[0].source, u32::MAX);
6339 assert_eq!(mappings[1].source, 0);
6340 }
6341
6342 #[test]
6343 fn mappings_iter_with_names() {
6344 let vlq = "AAAAA";
6345 let mappings: Vec<Mapping> = MappingsIter::new(vlq).collect::<Result<_, _>>().unwrap();
6346 assert_eq!(mappings.len(), 1);
6347 assert_eq!(mappings[0].name, 0);
6348 }
6349
6350 #[test]
6351 fn mappings_iter_multiple_lines() {
6352 let vlq = "AAAA;AACA;AACA";
6353 let mappings: Vec<Mapping> = MappingsIter::new(vlq).collect::<Result<_, _>>().unwrap();
6354 assert_eq!(mappings.len(), 3);
6355 assert_eq!(mappings[0].generated_line, 0);
6356 assert_eq!(mappings[1].generated_line, 1);
6357 assert_eq!(mappings[2].generated_line, 2);
6358 }
6359 #[test]
6362 fn range_mappings_basic_decode() {
6363 let json = r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA,CAAC,GAAG","rangeMappings":"A,C"}"#;
6364 let sm = SourceMap::from_json(json).unwrap();
6365 assert!(sm.all_mappings()[0].is_range_mapping);
6366 assert!(!sm.all_mappings()[1].is_range_mapping);
6367 assert!(sm.all_mappings()[2].is_range_mapping);
6368 }
6369
6370 #[test]
6371 fn range_mapping_lookup_with_delta() {
6372 let json = r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA,GAAG","rangeMappings":"A"}"#;
6373 let sm = SourceMap::from_json(json).unwrap();
6374 assert_eq!(sm.original_position_for(0, 0).unwrap().column, 0);
6375 assert_eq!(sm.original_position_for(0, 1).unwrap().column, 1);
6376 assert_eq!(sm.original_position_for(0, 2).unwrap().column, 2);
6377 assert_eq!(sm.original_position_for(0, 3).unwrap().column, 3);
6378 }
6379
6380 #[test]
6381 fn range_mapping_cross_line() {
6382 let json = r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA","rangeMappings":"A"}"#;
6383 let sm = SourceMap::from_json(json).unwrap();
6384 assert_eq!(sm.original_position_for(1, 5).unwrap().line, 1);
6385 assert_eq!(sm.original_position_for(1, 5).unwrap().column, 0);
6386 assert_eq!(sm.original_position_for(2, 10).unwrap().line, 2);
6387 }
6388
6389 #[test]
6390 fn range_mapping_encode_roundtrip() {
6391 let json = r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA,CAAC,GAAG","rangeMappings":"A,C"}"#;
6392 assert_eq!(
6393 SourceMap::from_json(json)
6394 .unwrap()
6395 .encode_range_mappings()
6396 .unwrap(),
6397 "A,C"
6398 );
6399 }
6400
6401 #[test]
6402 fn no_range_mappings_test() {
6403 let sm = SourceMap::from_json(
6404 r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA"}"#,
6405 )
6406 .unwrap();
6407 assert!(!sm.has_range_mappings());
6408 assert!(sm.encode_range_mappings().is_none());
6409 }
6410
6411 #[test]
6412 fn range_mappings_multi_line_test() {
6413 let sm = SourceMap::from_json(r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA,CAAC;AAAA","rangeMappings":"A;A"}"#).unwrap();
6414 assert!(sm.all_mappings()[0].is_range_mapping);
6415 assert!(!sm.all_mappings()[1].is_range_mapping);
6416 assert!(sm.all_mappings()[2].is_range_mapping);
6417 }
6418
6419 #[test]
6420 fn range_mappings_json_roundtrip() {
6421 let sm = SourceMap::from_json(r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA,CAAC,GAAG","rangeMappings":"A,C"}"#).unwrap();
6422 let output = sm.to_json();
6423 assert!(output.contains("rangeMappings"));
6424 assert_eq!(
6425 SourceMap::from_json(&output).unwrap().range_mapping_count(),
6426 2
6427 );
6428 }
6429
6430 #[test]
6431 fn range_mappings_absent_from_json_test() {
6432 assert!(
6433 !SourceMap::from_json(
6434 r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA"}"#
6435 )
6436 .unwrap()
6437 .to_json()
6438 .contains("rangeMappings")
6439 );
6440 }
6441
6442 #[test]
6443 fn range_mapping_fallback_test() {
6444 let sm = SourceMap::from_json(r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA;KACK","rangeMappings":"A"}"#).unwrap();
6445 let loc = sm.original_position_for(1, 2).unwrap();
6446 assert_eq!(loc.line, 1);
6447 assert_eq!(loc.column, 0);
6448 }
6449
6450 #[test]
6451 fn range_mapping_no_fallback_non_range() {
6452 assert!(
6453 SourceMap::from_json(
6454 r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA"}"#
6455 )
6456 .unwrap()
6457 .original_position_for(1, 5)
6458 .is_none()
6459 );
6460 }
6461
6462 #[test]
6463 fn range_mapping_from_vlq_test() {
6464 let sm = SourceMap::from_vlq_with_range_mappings(
6465 "AAAA,CAAC",
6466 vec!["input.js".into()],
6467 vec![],
6468 None,
6469 None,
6470 vec![],
6471 vec![],
6472 None,
6473 Some("A"),
6474 )
6475 .unwrap();
6476 assert!(sm.all_mappings()[0].is_range_mapping);
6477 assert!(!sm.all_mappings()[1].is_range_mapping);
6478 }
6479
6480 #[test]
6481 fn range_mapping_encode_multi_line_test() {
6482 let sm = SourceMap::from_json(r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA,CAAC;AAAA,CAAC","rangeMappings":"A;B"}"#).unwrap();
6483 assert!(sm.all_mappings()[0].is_range_mapping);
6484 assert!(!sm.all_mappings()[1].is_range_mapping);
6485 assert!(!sm.all_mappings()[2].is_range_mapping);
6486 assert!(sm.all_mappings()[3].is_range_mapping);
6487 assert_eq!(sm.encode_range_mappings().unwrap(), "A;B");
6488 }
6489
6490 #[test]
6491 fn range_mapping_from_parts_test() {
6492 let sm = SourceMap::from_parts(
6493 None,
6494 None,
6495 vec!["input.js".into()],
6496 vec![],
6497 vec![],
6498 vec![
6499 Mapping {
6500 generated_line: 0,
6501 generated_column: 0,
6502 source: 0,
6503 original_line: 0,
6504 original_column: 0,
6505 name: NO_NAME,
6506 is_range_mapping: true,
6507 },
6508 Mapping {
6509 generated_line: 0,
6510 generated_column: 5,
6511 source: 0,
6512 original_line: 0,
6513 original_column: 5,
6514 name: NO_NAME,
6515 is_range_mapping: false,
6516 },
6517 ],
6518 vec![],
6519 None,
6520 None,
6521 );
6522 assert_eq!(sm.original_position_for(0, 2).unwrap().column, 2);
6523 assert_eq!(sm.original_position_for(0, 6).unwrap().column, 5);
6524 }
6525
6526 #[test]
6527 fn range_mapping_indexed_test() {
6528 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();
6529 assert!(sm.has_range_mappings());
6530 assert_eq!(sm.original_position_for(1, 3).unwrap().line, 1);
6531 }
6532
6533 #[test]
6534 fn range_mapping_empty_string_test() {
6535 assert!(!SourceMap::from_json(r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA","rangeMappings":""}"#).unwrap().has_range_mappings());
6536 }
6537
6538 #[test]
6539 fn range_mapping_lub_no_underflow() {
6540 let json = r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"KAAK","rangeMappings":"A"}"#;
6543 let sm = SourceMap::from_json(json).unwrap();
6544
6545 let loc = sm.original_position_for_with_bias(0, 2, Bias::LeastUpperBound);
6546 assert!(loc.is_some());
6547 let loc = loc.unwrap();
6548 assert_eq!(loc.line, 0);
6550 assert_eq!(loc.column, 5);
6551 }
6552
6553 #[test]
6556 fn builder_basic() {
6557 let sm = SourceMap::builder()
6558 .file("output.js")
6559 .sources(["input.ts"])
6560 .sources_content([Some("let x = 1;")])
6561 .names(["x"])
6562 .mappings([Mapping {
6563 generated_line: 0,
6564 generated_column: 0,
6565 source: 0,
6566 original_line: 0,
6567 original_column: 4,
6568 name: 0,
6569 is_range_mapping: false,
6570 }])
6571 .build();
6572
6573 assert_eq!(sm.file.as_deref(), Some("output.js"));
6574 assert_eq!(sm.sources, vec!["input.ts"]);
6575 assert_eq!(sm.sources_content, vec![Some("let x = 1;".to_string())]);
6576 assert_eq!(sm.names, vec!["x"]);
6577 assert_eq!(sm.mapping_count(), 1);
6578
6579 let loc = sm.original_position_for(0, 0).unwrap();
6580 assert_eq!(sm.source(loc.source), "input.ts");
6581 assert_eq!(loc.column, 4);
6582 assert_eq!(sm.name(loc.name.unwrap()), "x");
6583 }
6584
6585 #[test]
6586 fn builder_empty() {
6587 let sm = SourceMap::builder().build();
6588 assert_eq!(sm.mapping_count(), 0);
6589 assert_eq!(sm.sources.len(), 0);
6590 assert_eq!(sm.names.len(), 0);
6591 assert!(sm.file.is_none());
6592 }
6593
6594 #[test]
6595 fn builder_multiple_sources() {
6596 let sm = SourceMap::builder()
6597 .sources(["a.ts", "b.ts", "c.ts"])
6598 .sources_content([Some("// a"), Some("// b"), None])
6599 .mappings([
6600 Mapping {
6601 generated_line: 0,
6602 generated_column: 0,
6603 source: 0,
6604 original_line: 0,
6605 original_column: 0,
6606 name: u32::MAX,
6607 is_range_mapping: false,
6608 },
6609 Mapping {
6610 generated_line: 1,
6611 generated_column: 0,
6612 source: 1,
6613 original_line: 0,
6614 original_column: 0,
6615 name: u32::MAX,
6616 is_range_mapping: false,
6617 },
6618 Mapping {
6619 generated_line: 2,
6620 generated_column: 0,
6621 source: 2,
6622 original_line: 0,
6623 original_column: 0,
6624 name: u32::MAX,
6625 is_range_mapping: false,
6626 },
6627 ])
6628 .build();
6629
6630 assert_eq!(sm.sources.len(), 3);
6631 assert_eq!(sm.mapping_count(), 3);
6632 assert_eq!(sm.line_count(), 3);
6633
6634 let loc0 = sm.original_position_for(0, 0).unwrap();
6635 assert_eq!(sm.source(loc0.source), "a.ts");
6636
6637 let loc1 = sm.original_position_for(1, 0).unwrap();
6638 assert_eq!(sm.source(loc1.source), "b.ts");
6639
6640 let loc2 = sm.original_position_for(2, 0).unwrap();
6641 assert_eq!(sm.source(loc2.source), "c.ts");
6642 }
6643
6644 #[test]
6645 fn builder_with_iterators() {
6646 let source_names: Vec<String> = (0..5).map(|i| format!("mod_{i}.ts")).collect();
6647 let mappings = (0..5u32).map(|i| Mapping {
6648 generated_line: i,
6649 generated_column: 0,
6650 source: i,
6651 original_line: i,
6652 original_column: 0,
6653 name: u32::MAX,
6654 is_range_mapping: false,
6655 });
6656
6657 let sm = SourceMap::builder()
6658 .sources(source_names.iter().map(|s| s.as_str()))
6659 .mappings(mappings)
6660 .build();
6661
6662 assert_eq!(sm.sources.len(), 5);
6663 assert_eq!(sm.mapping_count(), 5);
6664 for i in 0..5u32 {
6665 let loc = sm.original_position_for(i, 0).unwrap();
6666 assert_eq!(sm.source(loc.source), format!("mod_{i}.ts"));
6667 }
6668 }
6669
6670 #[test]
6671 fn builder_ignore_list_and_debug_id() {
6672 let sm = SourceMap::builder()
6673 .sources(["app.ts", "node_modules/lib.js"])
6674 .ignore_list([1])
6675 .debug_id("85314830-023f-4cf1-a267-535f4e37bb17")
6676 .build();
6677
6678 assert_eq!(sm.ignore_list, vec![1]);
6679 assert_eq!(
6680 sm.debug_id.as_deref(),
6681 Some("85314830-023f-4cf1-a267-535f4e37bb17")
6682 );
6683 }
6684
6685 #[test]
6686 fn builder_range_mappings() {
6687 let sm = SourceMap::builder()
6688 .sources(["input.ts"])
6689 .mappings([
6690 Mapping {
6691 generated_line: 0,
6692 generated_column: 0,
6693 source: 0,
6694 original_line: 0,
6695 original_column: 0,
6696 name: u32::MAX,
6697 is_range_mapping: true,
6698 },
6699 Mapping {
6700 generated_line: 0,
6701 generated_column: 10,
6702 source: 0,
6703 original_line: 5,
6704 original_column: 0,
6705 name: u32::MAX,
6706 is_range_mapping: false,
6707 },
6708 ])
6709 .build();
6710
6711 assert!(sm.has_range_mappings());
6712 assert_eq!(sm.mapping_count(), 2);
6713 }
6714
6715 #[test]
6716 fn builder_json_roundtrip() {
6717 let sm = SourceMap::builder()
6718 .file("out.js")
6719 .source_root("/src/")
6720 .sources(["a.ts", "b.ts"])
6721 .sources_content([Some("// a"), Some("// b")])
6722 .names(["foo", "bar"])
6723 .mappings([
6724 Mapping {
6725 generated_line: 0,
6726 generated_column: 0,
6727 source: 0,
6728 original_line: 0,
6729 original_column: 0,
6730 name: 0,
6731 is_range_mapping: false,
6732 },
6733 Mapping {
6734 generated_line: 1,
6735 generated_column: 5,
6736 source: 1,
6737 original_line: 3,
6738 original_column: 2,
6739 name: 1,
6740 is_range_mapping: false,
6741 },
6742 ])
6743 .build();
6744
6745 let json = sm.to_json();
6746 let sm2 = SourceMap::from_json(&json).unwrap();
6747
6748 assert_eq!(sm2.file, sm.file);
6749 assert_eq!(sm2.sources, vec!["/src/a.ts", "/src/b.ts"]);
6751 assert_eq!(sm2.names, sm.names);
6752 assert_eq!(sm2.mapping_count(), sm.mapping_count());
6753
6754 for m in sm.all_mappings() {
6755 let a = sm.original_position_for(m.generated_line, m.generated_column);
6756 let b = sm2.original_position_for(m.generated_line, m.generated_column);
6757 match (a, b) {
6758 (Some(a), Some(b)) => {
6759 assert_eq!(a.source, b.source);
6760 assert_eq!(a.line, b.line);
6761 assert_eq!(a.column, b.column);
6762 assert_eq!(a.name, b.name);
6763 }
6764 (None, None) => {}
6765 _ => panic!("lookup mismatch"),
6766 }
6767 }
6768 }
6769
6770 #[test]
6773 fn range_mapping_fallback_column_underflow() {
6774 let json = r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"KAAK","rangeMappings":"A"}"#;
6777 let sm = SourceMap::from_json(json).unwrap();
6778 let loc = sm.original_position_for(0, 2);
6781 assert!(loc.is_none());
6783 }
6784
6785 #[test]
6786 fn range_mapping_fallback_cross_line_column_zero() {
6787 let json = r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"UAAU","rangeMappings":"A"}"#;
6791 let sm = SourceMap::from_json(json).unwrap();
6792 let loc = sm.original_position_for(1, 0).unwrap();
6793 assert_eq!(loc.line, 1);
6794 assert_eq!(loc.column, 10);
6795 }
6796
6797 #[test]
6798 fn vlq_overflow_at_shift_60() {
6799 let overflow_vlq = "ggggggggggggggA"; let json = format!(
6805 r#"{{"version":3,"sources":["a.js"],"names":[],"mappings":"{}"}}"#,
6806 overflow_vlq
6807 );
6808 let result = SourceMap::from_json(&json);
6809 assert!(result.is_err());
6810 assert!(matches!(result.unwrap_err(), ParseError::Vlq(_)));
6811 }
6812
6813 #[test]
6814 fn lazy_sourcemap_rejects_indexed_maps() {
6815 let json = r#"{"version":3,"sections":[{"offset":{"line":0,"column":0},"map":{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}}]}"#;
6816 let result = LazySourceMap::from_json_fast(json);
6817 assert!(result.is_err());
6818 assert!(matches!(result.unwrap_err(), ParseError::NestedIndexMap));
6819
6820 let result = LazySourceMap::from_json_no_content(json);
6821 assert!(result.is_err());
6822 assert!(matches!(result.unwrap_err(), ParseError::NestedIndexMap));
6823 }
6824
6825 #[test]
6826 fn lazy_sourcemap_regular_map_still_works() {
6827 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;AACA"}"#;
6828 let sm = LazySourceMap::from_json_fast(json).unwrap();
6829 let loc = sm.original_position_for(0, 0).unwrap();
6830 assert_eq!(sm.source(loc.source), "a.js");
6831 assert_eq!(loc.line, 0);
6832 }
6833
6834 #[test]
6835 fn lazy_sourcemap_get_source_name_bounds() {
6836 let json = r#"{"version":3,"sources":["a.js"],"names":["foo"],"mappings":"AAAAA"}"#;
6837 let sm = LazySourceMap::from_json_fast(json).unwrap();
6838 assert_eq!(sm.get_source(0), Some("a.js"));
6839 assert_eq!(sm.get_source(1), None);
6840 assert_eq!(sm.get_source(u32::MAX), None);
6841 assert_eq!(sm.get_name(0), Some("foo"));
6842 assert_eq!(sm.get_name(1), None);
6843 assert_eq!(sm.get_name(u32::MAX), None);
6844 }
6845
6846 #[test]
6847 fn lazy_sourcemap_backward_seek() {
6848 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA"}"#;
6850 let sm = LazySourceMap::from_json_fast(json).unwrap();
6851
6852 let loc3 = sm.original_position_for(3, 0).unwrap();
6854 assert_eq!(loc3.line, 3);
6855
6856 let loc1 = sm.original_position_for(1, 0).unwrap();
6858 assert_eq!(loc1.line, 1);
6859
6860 let loc4 = sm.original_position_for(4, 0).unwrap();
6862 assert_eq!(loc4.line, 4);
6863
6864 let loc0 = sm.original_position_for(0, 0).unwrap();
6866 assert_eq!(loc0.line, 0);
6867 }
6868
6869 #[test]
6870 fn lazy_sourcemap_fast_scan_vs_prescan_consistency() {
6871 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":["x","y"],"mappings":"AAAAA,KACAC;ACAAD,KACAC"}"#;
6873 let fast = LazySourceMap::from_json_fast(json).unwrap();
6874 let prescan = LazySourceMap::from_json_no_content(json).unwrap();
6875
6876 for line in 0..2 {
6877 for col in [0, 5, 10] {
6878 let a = fast.original_position_for(line, col);
6879 let b = prescan.original_position_for(line, col);
6880 match (&a, &b) {
6881 (Some(a), Some(b)) => {
6882 assert_eq!(a.source, b.source, "line={line}, col={col}");
6883 assert_eq!(a.line, b.line, "line={line}, col={col}");
6884 assert_eq!(a.column, b.column, "line={line}, col={col}");
6885 assert_eq!(a.name, b.name, "line={line}, col={col}");
6886 }
6887 (None, None) => {}
6888 _ => panic!("mismatch at line={line}, col={col}: {a:?} vs {b:?}"),
6889 }
6890 }
6891 }
6892 }
6893
6894 #[test]
6895 fn mappings_iter_rejects_two_field_segment() {
6896 let result: Result<Vec<_>, _> = MappingsIter::new("AA").collect();
6898 assert!(result.is_err());
6899 assert!(matches!(
6900 result.unwrap_err(),
6901 DecodeError::InvalidSegmentLength { fields: 2, .. }
6902 ));
6903 }
6904
6905 #[test]
6906 fn mappings_iter_rejects_three_field_segment() {
6907 let result: Result<Vec<_>, _> = MappingsIter::new("AAA").collect();
6909 assert!(result.is_err());
6910 assert!(matches!(
6911 result.unwrap_err(),
6912 DecodeError::InvalidSegmentLength { fields: 3, .. }
6913 ));
6914 }
6915
6916 #[test]
6917 fn decode_mappings_range_caps_end_line() {
6918 let mappings = "AAAA;AACA";
6920 let (result, offsets) = decode_mappings_range(mappings, 0, 1_000_000).unwrap();
6921 assert_eq!(result.len(), 2);
6923 assert!(offsets.len() <= 3); }
6925
6926 #[test]
6927 fn decode_range_mappings_cross_line_bound_check() {
6928 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;AAAA","rangeMappings":"E"}"#;
6931 let sm = SourceMap::from_json(json).unwrap();
6932 assert!(!sm.all_mappings()[1].is_range_mapping);
6935 }
6936
6937 #[test]
6938 fn fast_scan_lines_empty() {
6939 let result = fast_scan_lines("");
6940 assert!(result.is_empty());
6941 }
6942
6943 #[test]
6944 fn fast_scan_lines_no_semicolons() {
6945 let result = fast_scan_lines("AAAA,CAAC");
6946 assert_eq!(result.len(), 1);
6947 assert_eq!(result[0].byte_offset, 0);
6948 assert_eq!(result[0].byte_end, 9);
6949 }
6950
6951 #[test]
6952 fn fast_scan_lines_only_semicolons() {
6953 let result = fast_scan_lines(";;;");
6954 assert_eq!(result.len(), 4);
6955 for info in &result {
6956 assert_eq!(info.byte_offset, info.byte_end); }
6958 }
6959}