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