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