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, Default)]
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 #[allow(
1625 clippy::too_many_arguments,
1626 reason = "private constructor centralizes shared LazySourceMap setup"
1627 )]
1628 fn new_inner(
1629 file: Option<String>,
1630 source_root: Option<String>,
1631 sources: Vec<String>,
1632 sources_content: Vec<Option<String>>,
1633 names: Vec<String>,
1634 ignore_list: Vec<u32>,
1635 extensions: HashMap<String, serde_json::Value>,
1636 debug_id: Option<String>,
1637 scopes: Option<ScopeInfo>,
1638 raw_mappings: String,
1639 line_info: Vec<LineInfo>,
1640 source_map: HashMap<String, u32>,
1641 fast_scan: bool,
1642 ) -> Self {
1643 Self {
1644 file,
1645 source_root,
1646 sources,
1647 sources_content,
1648 names,
1649 ignore_list,
1650 extensions,
1651 debug_id,
1652 scopes,
1653 raw_mappings,
1654 line_info,
1655 decoded_lines: RefCell::new(HashMap::new()),
1656 source_map,
1657 fast_scan,
1658 decode_watermark: Cell::new(0),
1659 decode_state: Cell::new(VlqState::default()),
1660 }
1661 }
1662
1663 pub fn from_json(json: &str) -> Result<Self, ParseError> {
1668 let raw: RawSourceMap<'_> = serde_json::from_str(json)?;
1669
1670 if raw.version != 3 {
1671 return Err(ParseError::InvalidVersion(raw.version));
1672 }
1673
1674 let source_root = raw.source_root.as_deref().unwrap_or("");
1675 let sources = resolve_sources(&raw.sources, source_root);
1676 let sources_content = raw.sources_content.unwrap_or_default();
1677 let source_map = build_source_map(&sources);
1678
1679 let raw_mappings = raw.mappings.to_string();
1682 let line_info = prescan_mappings(&raw_mappings)?;
1683
1684 let num_sources = sources.len();
1686 let scopes = match raw.scopes {
1687 Some(scopes_str) if !scopes_str.is_empty() => {
1688 Some(srcmap_scopes::decode_scopes(scopes_str, &raw.names, num_sources)?)
1689 }
1690 _ => None,
1691 };
1692
1693 let ignore_list = match raw.ignore_list {
1694 Some(list) => list,
1695 None => raw.x_google_ignore_list.unwrap_or_default(),
1696 };
1697
1698 let extensions: HashMap<String, serde_json::Value> = raw
1700 .extensions
1701 .into_iter()
1702 .filter(|(k, _)| k.starts_with("x_") || k.starts_with("x-"))
1703 .collect();
1704
1705 Ok(Self::new_inner(
1706 raw.file,
1707 raw.source_root,
1708 sources,
1709 sources_content,
1710 raw.names,
1711 ignore_list,
1712 extensions,
1713 raw.debug_id,
1714 scopes,
1715 raw_mappings,
1716 line_info,
1717 source_map,
1718 false,
1719 ))
1720 }
1721
1722 pub fn from_json_no_content(json: &str) -> Result<Self, ParseError> {
1730 let raw: RawSourceMapLite<'_> = serde_json::from_str(json)?;
1731
1732 if raw.version != 3 {
1733 return Err(ParseError::InvalidVersion(raw.version));
1734 }
1735
1736 if raw.sections.is_some() {
1739 return Err(ParseError::NestedIndexMap);
1740 }
1741
1742 let source_root = raw.source_root.as_deref().unwrap_or("");
1743 let sources = resolve_sources(&raw.sources, source_root);
1744 let source_map = build_source_map(&sources);
1745
1746 let raw_mappings = raw.mappings.to_string();
1747 let line_info = prescan_mappings(&raw_mappings)?;
1748
1749 let num_sources = sources.len();
1750 let scopes = match raw.scopes {
1751 Some(scopes_str) if !scopes_str.is_empty() => {
1752 Some(srcmap_scopes::decode_scopes(scopes_str, &raw.names, num_sources)?)
1753 }
1754 _ => None,
1755 };
1756
1757 let ignore_list = match raw.ignore_list {
1758 Some(list) => list,
1759 None => raw.x_google_ignore_list.unwrap_or_default(),
1760 };
1761
1762 Ok(Self::new_inner(
1763 raw.file,
1764 raw.source_root,
1765 sources,
1766 Vec::new(),
1767 raw.names,
1768 ignore_list,
1769 HashMap::new(),
1770 raw.debug_id,
1771 scopes,
1772 raw_mappings,
1773 line_info,
1774 source_map,
1775 false,
1776 ))
1777 }
1778
1779 pub fn from_vlq(
1784 mappings: &str,
1785 sources: Vec<String>,
1786 names: Vec<String>,
1787 file: Option<String>,
1788 source_root: Option<String>,
1789 ignore_list: Vec<u32>,
1790 debug_id: Option<String>,
1791 ) -> Result<Self, ParseError> {
1792 let source_map = build_source_map(&sources);
1793 let raw_mappings = mappings.to_string();
1794 let line_info = prescan_mappings(&raw_mappings)?;
1795
1796 Ok(Self::new_inner(
1797 file,
1798 source_root,
1799 sources,
1800 Vec::new(),
1801 names,
1802 ignore_list,
1803 HashMap::new(),
1804 debug_id,
1805 None,
1806 raw_mappings,
1807 line_info,
1808 source_map,
1809 false,
1810 ))
1811 }
1812
1813 pub fn from_json_fast(json: &str) -> Result<Self, ParseError> {
1823 let raw: RawSourceMapLite<'_> = serde_json::from_str(json)?;
1824
1825 if raw.version != 3 {
1826 return Err(ParseError::InvalidVersion(raw.version));
1827 }
1828
1829 if raw.sections.is_some() {
1832 return Err(ParseError::NestedIndexMap);
1833 }
1834
1835 let source_root = raw.source_root.as_deref().unwrap_or("");
1836 let sources = resolve_sources(&raw.sources, source_root);
1837 let source_map = build_source_map(&sources);
1838 let raw_mappings = raw.mappings.to_string();
1839
1840 let line_info = fast_scan_lines(&raw_mappings);
1842
1843 let ignore_list = match raw.ignore_list {
1844 Some(list) => list,
1845 None => raw.x_google_ignore_list.unwrap_or_default(),
1846 };
1847
1848 Ok(Self::new_inner(
1849 raw.file,
1850 raw.source_root,
1851 sources,
1852 Vec::new(),
1853 raw.names,
1854 ignore_list,
1855 HashMap::new(),
1856 raw.debug_id,
1857 None,
1858 raw_mappings,
1859 line_info,
1860 source_map,
1861 true,
1862 ))
1863 }
1864
1865 fn decode_line_with_state(
1871 &self,
1872 line: u32,
1873 mut state: VlqState,
1874 ) -> Result<(Vec<Mapping>, VlqState), DecodeError> {
1875 let line_idx = line as usize;
1876 if line_idx >= self.line_info.len() {
1877 return Ok((Vec::new(), state));
1878 }
1879
1880 let info = &self.line_info[line_idx];
1881 let bytes = self.raw_mappings.as_bytes();
1882 let end = info.byte_end;
1883
1884 let mut mappings = Vec::new();
1885 let mut source_index = state.source_index;
1886 let mut original_line = state.original_line;
1887 let mut original_column = state.original_column;
1888 let mut name_index = state.name_index;
1889 let mut generated_column: i64 = 0;
1890 let mut pos = info.byte_offset;
1891
1892 while pos < end {
1893 let byte = bytes[pos];
1894 if byte == b',' {
1895 pos += 1;
1896 continue;
1897 }
1898
1899 generated_column += vlq_fast(bytes, &mut pos)?;
1900
1901 if pos < end && bytes[pos] != b',' && bytes[pos] != b';' {
1902 source_index += vlq_fast(bytes, &mut pos)?;
1903 if pos >= end || bytes[pos] == b',' || bytes[pos] == b';' {
1904 return Err(DecodeError::InvalidSegmentLength { fields: 2, offset: pos });
1905 }
1906 original_line += vlq_fast(bytes, &mut pos)?;
1907 if pos >= end || bytes[pos] == b',' || bytes[pos] == b';' {
1908 return Err(DecodeError::InvalidSegmentLength { fields: 3, offset: pos });
1909 }
1910 original_column += vlq_fast(bytes, &mut pos)?;
1911
1912 let name = if pos < end && bytes[pos] != b',' && bytes[pos] != b';' {
1913 name_index += vlq_fast(bytes, &mut pos)?;
1914 name_index as u32
1915 } else {
1916 NO_NAME
1917 };
1918
1919 mappings.push(Mapping {
1920 generated_line: line,
1921 generated_column: generated_column as u32,
1922 source: source_index as u32,
1923 original_line: original_line as u32,
1924 original_column: original_column as u32,
1925 name,
1926 is_range_mapping: false,
1927 });
1928 } else {
1929 mappings.push(Mapping {
1930 generated_line: line,
1931 generated_column: generated_column as u32,
1932 source: NO_SOURCE,
1933 original_line: 0,
1934 original_column: 0,
1935 name: NO_NAME,
1936 is_range_mapping: false,
1937 });
1938 }
1939 }
1940
1941 state.source_index = source_index;
1942 state.original_line = original_line;
1943 state.original_column = original_column;
1944 state.name_index = name_index;
1945 Ok((mappings, state))
1946 }
1947
1948 pub fn decode_line(&self, line: u32) -> Result<Vec<Mapping>, DecodeError> {
1953 if let Some(cached) = self.decoded_lines.borrow().get(&line) {
1955 return Ok(cached.clone());
1956 }
1957
1958 let line_idx = line as usize;
1959 if line_idx >= self.line_info.len() {
1960 return Ok(Vec::new());
1961 }
1962
1963 if self.fast_scan {
1964 let watermark = self.decode_watermark.get();
1969 let start = if line >= watermark { watermark } else { 0 };
1970 let mut state = if line >= watermark {
1971 self.decode_state.get()
1972 } else {
1973 VlqState { source_index: 0, original_line: 0, original_column: 0, name_index: 0 }
1974 };
1975
1976 for l in start..=line {
1977 let info = &self.line_info[l as usize];
1978 if self.decoded_lines.borrow().contains_key(&l) {
1979 let bytes = self.raw_mappings.as_bytes();
1981 state = walk_vlq_state(bytes, info.byte_offset, info.byte_end, state)?;
1982 } else {
1983 let (mappings, new_state) = self.decode_line_with_state(l, state)?;
1984 state = new_state;
1985 self.decoded_lines.borrow_mut().insert(l, mappings);
1986 }
1987 }
1988
1989 if line + 1 > self.decode_watermark.get() {
1991 self.decode_watermark.set(line + 1);
1992 self.decode_state.set(state);
1993 }
1994
1995 let cached = self.decoded_lines.borrow().get(&line).cloned();
1996 return Ok(cached.unwrap_or_default());
1997 }
1998
1999 let state = self.line_info[line_idx].state;
2001 let (mappings, _) = self.decode_line_with_state(line, state)?;
2002 self.decoded_lines.borrow_mut().insert(line, mappings.clone());
2003 Ok(mappings)
2004 }
2005
2006 pub fn original_position_for(&self, line: u32, column: u32) -> Option<OriginalLocation> {
2011 let line_mappings = self.decode_line(line).ok()?;
2012
2013 if line_mappings.is_empty() {
2014 return None;
2015 }
2016
2017 let idx = match line_mappings.binary_search_by_key(&column, |m| m.generated_column) {
2019 Ok(i) => i,
2020 Err(0) => return None,
2021 Err(i) => i - 1,
2022 };
2023
2024 let mapping = &line_mappings[idx];
2025
2026 if mapping.source == NO_SOURCE {
2027 return None;
2028 }
2029
2030 Some(OriginalLocation {
2031 source: mapping.source,
2032 line: mapping.original_line,
2033 column: mapping.original_column,
2034 name: if mapping.name == NO_NAME { None } else { Some(mapping.name) },
2035 })
2036 }
2037
2038 #[inline]
2040 pub fn line_count(&self) -> usize {
2041 self.line_info.len()
2042 }
2043
2044 #[inline]
2051 pub fn source(&self, index: u32) -> &str {
2052 &self.sources[index as usize]
2053 }
2054
2055 #[inline]
2057 pub fn get_source(&self, index: u32) -> Option<&str> {
2058 self.sources.get(index as usize).map(|s| s.as_str())
2059 }
2060
2061 #[inline]
2068 pub fn name(&self, index: u32) -> &str {
2069 &self.names[index as usize]
2070 }
2071
2072 #[inline]
2074 pub fn get_name(&self, index: u32) -> Option<&str> {
2075 self.names.get(index as usize).map(|s| s.as_str())
2076 }
2077
2078 #[inline]
2080 pub fn source_index(&self, name: &str) -> Option<u32> {
2081 self.source_map.get(name).copied()
2082 }
2083
2084 pub fn mappings_for_line(&self, line: u32) -> Vec<Mapping> {
2086 self.decode_line(line).unwrap_or_default()
2087 }
2088
2089 pub fn into_sourcemap(self) -> Result<SourceMap, ParseError> {
2093 let (mappings, line_offsets) = decode_mappings(&self.raw_mappings)?;
2094 let has_range_mappings = mappings.iter().any(|m| m.is_range_mapping);
2095
2096 Ok(SourceMap {
2097 file: self.file,
2098 source_root: self.source_root,
2099 sources: self.sources.clone(),
2100 sources_content: self.sources_content,
2101 names: self.names,
2102 ignore_list: self.ignore_list,
2103 extensions: self.extensions,
2104 debug_id: self.debug_id,
2105 scopes: self.scopes,
2106 mappings,
2107 line_offsets,
2108 reverse_index: OnceCell::new(),
2109 source_map: self.source_map,
2110 has_range_mappings,
2111 })
2112 }
2113}
2114
2115fn prescan_mappings(input: &str) -> Result<Vec<LineInfo>, DecodeError> {
2118 if input.is_empty() {
2119 return Ok(Vec::new());
2120 }
2121
2122 let bytes = input.as_bytes();
2123 let len = bytes.len();
2124
2125 let line_count = bytes.iter().filter(|&&b| b == b';').count() + 1;
2127 let mut line_info: Vec<LineInfo> = Vec::with_capacity(line_count);
2128
2129 let mut state = VlqState::default();
2130 let mut pos: usize = 0;
2131
2132 loop {
2133 let line_start = pos;
2134 let line_state = state;
2135 while pos < len && bytes[pos] != b';' {
2136 pos += 1;
2137 }
2138 let byte_end = pos;
2139 state = walk_vlq_state(bytes, line_start, byte_end, state)?;
2140
2141 line_info.push(LineInfo { byte_offset: line_start, byte_end, state: line_state });
2142
2143 if pos >= len {
2144 break;
2145 }
2146 pos += 1;
2147 }
2148
2149 Ok(line_info)
2150}
2151
2152fn walk_vlq_state(
2154 bytes: &[u8],
2155 start: usize,
2156 end: usize,
2157 mut state: VlqState,
2158) -> Result<VlqState, DecodeError> {
2159 let mut pos = start;
2160 while pos < end {
2161 let byte = bytes[pos];
2162 if byte == b',' {
2163 pos += 1;
2164 continue;
2165 }
2166
2167 vlq_fast(bytes, &mut pos)?;
2169
2170 if pos < end && bytes[pos] != b',' && bytes[pos] != b';' {
2171 state.source_index += vlq_fast(bytes, &mut pos)?;
2172 if pos >= end || bytes[pos] == b',' || bytes[pos] == b';' {
2173 return Err(DecodeError::InvalidSegmentLength { fields: 2, offset: pos });
2174 }
2175 state.original_line += vlq_fast(bytes, &mut pos)?;
2176 if pos >= end || bytes[pos] == b',' || bytes[pos] == b';' {
2177 return Err(DecodeError::InvalidSegmentLength { fields: 3, offset: pos });
2178 }
2179 state.original_column += vlq_fast(bytes, &mut pos)?;
2180 if pos < end && bytes[pos] != b',' && bytes[pos] != b';' {
2181 state.name_index += vlq_fast(bytes, &mut pos)?;
2182 }
2183 }
2184 }
2185 Ok(state)
2186}
2187
2188fn fast_scan_lines(input: &str) -> Vec<LineInfo> {
2191 if input.is_empty() {
2192 return Vec::new();
2193 }
2194
2195 let bytes = input.as_bytes();
2196 let len = bytes.len();
2197 let zero_state =
2198 VlqState { source_index: 0, original_line: 0, original_column: 0, name_index: 0 };
2199
2200 let mut line_info = Vec::new();
2202 let mut pos = 0;
2203 loop {
2204 let line_start = pos;
2205
2206 while pos < len && bytes[pos] != b';' {
2208 pos += 1;
2209 }
2210
2211 line_info.push(LineInfo {
2212 byte_offset: line_start,
2213 byte_end: pos,
2214 state: zero_state, });
2216
2217 if pos >= len {
2218 break;
2219 }
2220 pos += 1; }
2222
2223 line_info
2224}
2225
2226#[derive(Debug, Clone, PartialEq, Eq)]
2228pub enum SourceMappingUrl {
2229 Inline(String),
2231 External(String),
2233}
2234
2235pub fn parse_source_mapping_url(source: &str) -> Option<SourceMappingUrl> {
2241 for line in source.lines().rev() {
2243 let trimmed = line.trim();
2244 let url = if let Some(rest) = trimmed.strip_prefix("//# sourceMappingURL=") {
2245 rest.trim()
2246 } else if let Some(rest) = trimmed.strip_prefix("//@ sourceMappingURL=") {
2247 rest.trim()
2248 } else if let Some(rest) = trimmed.strip_prefix("/*# sourceMappingURL=") {
2249 rest.trim_end_matches("*/").trim()
2250 } else if let Some(rest) = trimmed.strip_prefix("/*@ sourceMappingURL=") {
2251 rest.trim_end_matches("*/").trim()
2252 } else {
2253 continue;
2254 };
2255
2256 if url.is_empty() {
2257 continue;
2258 }
2259
2260 if let Some(base64_data) = url
2262 .strip_prefix("data:application/json;base64,")
2263 .or_else(|| url.strip_prefix("data:application/json;charset=utf-8;base64,"))
2264 .or_else(|| url.strip_prefix("data:application/json;charset=UTF-8;base64,"))
2265 {
2266 let decoded = base64_decode(base64_data);
2268 if let Some(json) = decoded {
2269 return Some(SourceMappingUrl::Inline(json));
2270 }
2271 }
2272
2273 return Some(SourceMappingUrl::External(url.to_string()));
2274 }
2275
2276 None
2277}
2278
2279fn percent_decode(input: &str) -> String {
2282 let mut output = Vec::with_capacity(input.len());
2283 let bytes = input.as_bytes();
2284 let mut i = 0;
2285 while i < bytes.len() {
2286 if bytes[i] == b'%'
2287 && i + 2 < bytes.len()
2288 && let (Some(hi), Some(lo)) = (hex_val(bytes[i + 1]), hex_val(bytes[i + 2]))
2289 {
2290 output.push((hi << 4) | lo);
2291 i += 3;
2292 continue;
2293 }
2294 output.push(bytes[i]);
2295 i += 1;
2296 }
2297 String::from_utf8(output).unwrap_or_else(|_| input.to_string())
2298}
2299
2300fn hex_val(b: u8) -> Option<u8> {
2301 match b {
2302 b'0'..=b'9' => Some(b - b'0'),
2303 b'a'..=b'f' => Some(b - b'a' + 10),
2304 b'A'..=b'F' => Some(b - b'A' + 10),
2305 _ => None,
2306 }
2307}
2308
2309fn base64_decode(input: &str) -> Option<String> {
2310 let input = input.trim();
2311 let bytes: Vec<u8> = input.bytes().filter(|b| !b.is_ascii_whitespace()).collect();
2312
2313 let mut output = Vec::with_capacity(bytes.len() * 3 / 4);
2314
2315 for chunk in bytes.chunks(4) {
2316 let mut buf = [0u8; 4];
2317 let mut len = 0;
2318
2319 for &b in chunk {
2320 if b == b'=' {
2321 break;
2322 }
2323 let val = match b {
2324 b'A'..=b'Z' => b - b'A',
2325 b'a'..=b'z' => b - b'a' + 26,
2326 b'0'..=b'9' => b - b'0' + 52,
2327 b'+' => 62,
2328 b'/' => 63,
2329 _ => return None,
2330 };
2331 buf[len] = val;
2332 len += 1;
2333 }
2334
2335 if len >= 2 {
2336 output.push((buf[0] << 2) | (buf[1] >> 4));
2337 }
2338 if len >= 3 {
2339 output.push((buf[1] << 4) | (buf[2] >> 2));
2340 }
2341 if len >= 4 {
2342 output.push((buf[2] << 6) | buf[3]);
2343 }
2344 }
2345
2346 String::from_utf8(output).ok()
2347}
2348
2349pub fn validate_deep(sm: &SourceMap) -> Vec<String> {
2354 let mut warnings = Vec::new();
2355
2356 let mut prev_line: u32 = 0;
2358 let mut prev_col: u32 = 0;
2359 let mappings = sm.all_mappings();
2360 for m in mappings {
2361 if m.generated_line < prev_line
2362 || (m.generated_line == prev_line && m.generated_column < prev_col)
2363 {
2364 warnings.push(format!(
2365 "mappings out of order at {}:{}",
2366 m.generated_line, m.generated_column
2367 ));
2368 }
2369 prev_line = m.generated_line;
2370 prev_col = m.generated_column;
2371 }
2372
2373 for m in mappings {
2375 if m.source != NO_SOURCE && m.source as usize >= sm.sources.len() {
2376 warnings.push(format!(
2377 "source index {} out of bounds (max {})",
2378 m.source,
2379 sm.sources.len()
2380 ));
2381 }
2382 if m.name != NO_NAME && m.name as usize >= sm.names.len() {
2383 warnings.push(format!("name index {} out of bounds (max {})", m.name, sm.names.len()));
2384 }
2385 }
2386
2387 for &idx in &sm.ignore_list {
2389 if idx as usize >= sm.sources.len() {
2390 warnings.push(format!(
2391 "ignoreList index {} out of bounds (max {})",
2392 idx,
2393 sm.sources.len()
2394 ));
2395 }
2396 }
2397
2398 let mut referenced_sources = std::collections::HashSet::new();
2400 for m in mappings {
2401 if m.source != NO_SOURCE {
2402 referenced_sources.insert(m.source);
2403 }
2404 }
2405 for (i, source) in sm.sources.iter().enumerate() {
2406 if !referenced_sources.contains(&(i as u32)) {
2407 warnings.push(format!("source \"{source}\" (index {i}) is unreferenced"));
2408 }
2409 }
2410
2411 warnings
2412}
2413
2414fn json_quote_into(out: &mut String, s: &str) {
2416 let bytes = s.as_bytes();
2417 out.push('"');
2418
2419 let mut start = 0;
2420 for (i, &b) in bytes.iter().enumerate() {
2421 let escape = match b {
2422 b'"' => "\\\"",
2423 b'\\' => "\\\\",
2424 b'\n' => "\\n",
2425 b'\r' => "\\r",
2426 b'\t' => "\\t",
2427 0x00..=0x08 | 0x0b | 0x0c | 0x0e..=0x1f => {
2429 if start < i {
2430 out.push_str(&s[start..i]);
2431 }
2432 use std::fmt::Write;
2433 let _ = write!(out, "\\u{:04x}", b);
2434 start = i + 1;
2435 continue;
2436 }
2437 _ => continue,
2438 };
2439 if start < i {
2440 out.push_str(&s[start..i]);
2441 }
2442 out.push_str(escape);
2443 start = i + 1;
2444 }
2445
2446 if start < bytes.len() {
2447 out.push_str(&s[start..]);
2448 }
2449
2450 out.push('"');
2451}
2452
2453const B64: [u8; 128] = {
2457 let mut table = [0xFFu8; 128];
2458 let chars = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2459 let mut i = 0u8;
2460 while i < 64 {
2461 table[chars[i as usize] as usize] = i;
2462 i += 1;
2463 }
2464 table
2465};
2466
2467#[inline(always)]
2470fn vlq_fast(bytes: &[u8], pos: &mut usize) -> Result<i64, DecodeError> {
2471 let p = *pos;
2472 if p >= bytes.len() {
2473 return Err(DecodeError::UnexpectedEof { offset: p });
2474 }
2475
2476 let b0 = bytes[p];
2477 if b0 >= 128 {
2478 return Err(DecodeError::InvalidBase64 { byte: b0, offset: p });
2479 }
2480 let d0 = B64[b0 as usize];
2481 if d0 == 0xFF {
2482 return Err(DecodeError::InvalidBase64 { byte: b0, offset: p });
2483 }
2484
2485 if (d0 & 0x20) == 0 {
2487 *pos = p + 1;
2488 let val = (d0 >> 1) as i64;
2489 return Ok(if (d0 & 1) != 0 { -val } else { val });
2490 }
2491
2492 let mut result: u64 = (d0 & 0x1F) as u64;
2494 let mut shift: u32 = 5;
2495 let mut i = p + 1;
2496
2497 loop {
2498 if i >= bytes.len() {
2499 return Err(DecodeError::UnexpectedEof { offset: i });
2500 }
2501 let b = bytes[i];
2502 if b >= 128 {
2503 return Err(DecodeError::InvalidBase64 { byte: b, offset: i });
2504 }
2505 let d = B64[b as usize];
2506 if d == 0xFF {
2507 return Err(DecodeError::InvalidBase64 { byte: b, offset: i });
2508 }
2509 i += 1;
2510
2511 if shift >= 60 {
2512 return Err(DecodeError::VlqOverflow { offset: p });
2513 }
2514
2515 result += ((d & 0x1F) as u64) << shift;
2516 shift += 5;
2517
2518 if (d & 0x20) == 0 {
2519 break;
2520 }
2521 }
2522
2523 *pos = i;
2524 let value = if (result & 1) == 1 { -((result >> 1) as i64) } else { (result >> 1) as i64 };
2525 Ok(value)
2526}
2527
2528#[inline(always)]
2529fn vlq_unsigned_fast(bytes: &[u8], pos: &mut usize) -> Result<u64, DecodeError> {
2530 let p = *pos;
2531 if p >= bytes.len() {
2532 return Err(DecodeError::UnexpectedEof { offset: p });
2533 }
2534 let b0 = bytes[p];
2535 if b0 >= 128 {
2536 return Err(DecodeError::InvalidBase64 { byte: b0, offset: p });
2537 }
2538 let d0 = B64[b0 as usize];
2539 if d0 == 0xFF {
2540 return Err(DecodeError::InvalidBase64 { byte: b0, offset: p });
2541 }
2542 if (d0 & 0x20) == 0 {
2543 *pos = p + 1;
2544 return Ok(d0 as u64);
2545 }
2546 let mut result: u64 = (d0 & 0x1F) as u64;
2547 let mut shift: u32 = 5;
2548 let mut i = p + 1;
2549 loop {
2550 if i >= bytes.len() {
2551 return Err(DecodeError::UnexpectedEof { offset: i });
2552 }
2553 let b = bytes[i];
2554 if b >= 128 {
2555 return Err(DecodeError::InvalidBase64 { byte: b, offset: i });
2556 }
2557 let d = B64[b as usize];
2558 if d == 0xFF {
2559 return Err(DecodeError::InvalidBase64 { byte: b, offset: i });
2560 }
2561 i += 1;
2562 if shift >= 60 {
2563 return Err(DecodeError::VlqOverflow { offset: p });
2564 }
2565 result |= ((d & 0x1F) as u64) << shift;
2566 shift += 5;
2567 if (d & 0x20) == 0 {
2568 break;
2569 }
2570 }
2571 *pos = i;
2572 Ok(result)
2573}
2574
2575fn decode_range_mappings(
2576 input: &str,
2577 mappings: &mut [Mapping],
2578 line_offsets: &[u32],
2579) -> Result<(), DecodeError> {
2580 let bytes = input.as_bytes();
2581 let len = bytes.len();
2582 let mut pos: usize = 0;
2583 let mut generated_line: usize = 0;
2584 while pos < len {
2585 let line_start = if generated_line + 1 < line_offsets.len() {
2586 line_offsets[generated_line] as usize
2587 } else {
2588 break;
2589 };
2590 let line_end = if generated_line + 2 < line_offsets.len() {
2592 line_offsets[generated_line + 1] as usize
2593 } else {
2594 mappings.len()
2595 };
2596 let mut mapping_index: u64 = 0;
2597 while pos < len {
2598 let byte = bytes[pos];
2599 if byte == b';' {
2600 pos += 1;
2601 break;
2602 }
2603 if byte == b',' {
2604 pos += 1;
2605 continue;
2606 }
2607 let offset = vlq_unsigned_fast(bytes, &mut pos)?;
2608 mapping_index += offset;
2609 let abs_idx = line_start + mapping_index as usize;
2610 if abs_idx < line_end {
2611 mappings[abs_idx].is_range_mapping = true;
2612 }
2613 }
2614 generated_line += 1;
2615 }
2616 Ok(())
2617}
2618
2619#[derive(Default)]
2620struct MappingsDecodeState {
2621 source_index: i64,
2622 original_line: i64,
2623 original_column: i64,
2624 name_index: i64,
2625}
2626
2627fn decode_mapping_segment(
2628 bytes: &[u8],
2629 pos: &mut usize,
2630 generated_line: u32,
2631 generated_column: &mut i64,
2632 state: &mut MappingsDecodeState,
2633) -> Result<Mapping, DecodeError> {
2634 *generated_column += vlq_fast(bytes, pos)?;
2635
2636 if *pos < bytes.len() && bytes[*pos] != b',' && bytes[*pos] != b';' {
2637 state.source_index += vlq_fast(bytes, pos)?;
2638
2639 if *pos >= bytes.len() || bytes[*pos] == b',' || bytes[*pos] == b';' {
2641 return Err(DecodeError::InvalidSegmentLength { fields: 2, offset: *pos });
2642 }
2643
2644 state.original_line += vlq_fast(bytes, pos)?;
2645
2646 if *pos >= bytes.len() || bytes[*pos] == b',' || bytes[*pos] == b';' {
2648 return Err(DecodeError::InvalidSegmentLength { fields: 3, offset: *pos });
2649 }
2650
2651 state.original_column += vlq_fast(bytes, pos)?;
2652
2653 let name = if *pos < bytes.len() && bytes[*pos] != b',' && bytes[*pos] != b';' {
2654 state.name_index += vlq_fast(bytes, pos)?;
2655 state.name_index as u32
2656 } else {
2657 NO_NAME
2658 };
2659
2660 Ok(Mapping {
2661 generated_line,
2662 generated_column: *generated_column as u32,
2663 source: state.source_index as u32,
2664 original_line: state.original_line as u32,
2665 original_column: state.original_column as u32,
2666 name,
2667 is_range_mapping: false,
2668 })
2669 } else {
2670 Ok(Mapping {
2671 generated_line,
2672 generated_column: *generated_column as u32,
2673 source: NO_SOURCE,
2674 original_line: 0,
2675 original_column: 0,
2676 name: NO_NAME,
2677 is_range_mapping: false,
2678 })
2679 }
2680}
2681
2682fn build_range_line_offsets(
2683 start_line: u32,
2684 end_line: u32,
2685 line_starts: &[(u32, u32)],
2686 total: u32,
2687) -> Vec<u32> {
2688 let mut line_offsets: Vec<u32> = vec![total; end_line as usize + 1];
2689
2690 for offset in line_offsets.iter_mut().take(start_line as usize + 1) {
2691 *offset = 0;
2692 }
2693
2694 for &(line, offset) in line_starts {
2695 line_offsets[line as usize] = offset;
2696 }
2697
2698 let mut next_offset = total;
2699 for i in (start_line as usize..end_line as usize).rev() {
2700 if line_offsets[i] == total {
2701 line_offsets[i] = next_offset;
2702 } else {
2703 next_offset = line_offsets[i];
2704 }
2705 }
2706
2707 line_offsets
2708}
2709
2710fn decode_mappings(input: &str) -> Result<(Vec<Mapping>, Vec<u32>), DecodeError> {
2711 if input.is_empty() {
2712 return Ok((Vec::new(), vec![0]));
2713 }
2714
2715 let bytes = input.as_bytes();
2716 let len = bytes.len();
2717
2718 let mut semicolons = 0usize;
2720 let mut commas = 0usize;
2721 for &b in bytes {
2722 semicolons += (b == b';') as usize;
2723 commas += (b == b',') as usize;
2724 }
2725 let line_count = semicolons + 1;
2726 let approx_segments = commas + line_count;
2727
2728 let mut mappings: Vec<Mapping> = Vec::with_capacity(approx_segments);
2729 let mut line_offsets: Vec<u32> = Vec::with_capacity(line_count + 1);
2730
2731 let mut state = MappingsDecodeState::default();
2732 let mut generated_line: u32 = 0;
2733 let mut pos: usize = 0;
2734
2735 loop {
2736 line_offsets.push(mappings.len() as u32);
2737 let mut generated_column: i64 = 0;
2738 let mut saw_semicolon = false;
2739
2740 while pos < len {
2741 let byte = bytes[pos];
2742
2743 if byte == b';' {
2744 pos += 1;
2745 saw_semicolon = true;
2746 break;
2747 }
2748
2749 if byte == b',' {
2750 pos += 1;
2751 continue;
2752 }
2753
2754 mappings.push(decode_mapping_segment(
2755 bytes,
2756 &mut pos,
2757 generated_line,
2758 &mut generated_column,
2759 &mut state,
2760 )?);
2761 }
2762
2763 if !saw_semicolon {
2764 break;
2765 }
2766 generated_line += 1;
2767 }
2768
2769 line_offsets.push(mappings.len() as u32);
2771
2772 Ok((mappings, line_offsets))
2773}
2774
2775fn decode_mappings_range(
2782 input: &str,
2783 start_line: u32,
2784 end_line: u32,
2785) -> Result<(Vec<Mapping>, Vec<u32>), DecodeError> {
2786 let actual_lines = if input.is_empty() {
2789 0u32
2790 } else {
2791 input.as_bytes().iter().filter(|&&b| b == b';').count() as u32 + 1
2792 };
2793 let end_line = end_line.min(actual_lines);
2794
2795 if input.is_empty() || start_line >= end_line {
2796 return Ok((Vec::new(), vec![0; end_line as usize + 1]));
2797 }
2798
2799 let bytes = input.as_bytes();
2800 let len = bytes.len();
2801
2802 let mut mappings: Vec<Mapping> = Vec::new();
2803
2804 let mut state = MappingsDecodeState::default();
2805 let mut generated_line: u32 = 0;
2806 let mut pos: usize = 0;
2807
2808 let mut line_starts: Vec<(u32, u32)> =
2809 Vec::with_capacity((end_line - start_line).min(actual_lines) as usize);
2810
2811 loop {
2812 let in_range = generated_line >= start_line && generated_line < end_line;
2813 if in_range {
2814 line_starts.push((generated_line, mappings.len() as u32));
2815 }
2816
2817 let mut generated_column: i64 = 0;
2818 let mut saw_semicolon = false;
2819
2820 while pos < len {
2821 let byte = bytes[pos];
2822
2823 if byte == b';' {
2824 pos += 1;
2825 saw_semicolon = true;
2826 break;
2827 }
2828
2829 if byte == b',' {
2830 pos += 1;
2831 continue;
2832 }
2833
2834 let mapping = decode_mapping_segment(
2835 bytes,
2836 &mut pos,
2837 generated_line,
2838 &mut generated_column,
2839 &mut state,
2840 )?;
2841 if in_range {
2842 mappings.push(mapping);
2843 }
2844 }
2845
2846 if !saw_semicolon {
2847 break;
2848 }
2849 generated_line += 1;
2850
2851 if generated_line >= end_line {
2853 break;
2854 }
2855 }
2856
2857 let total = mappings.len() as u32;
2858 Ok((mappings, build_range_line_offsets(start_line, end_line, &line_starts, total)))
2859}
2860
2861fn build_reverse_index(mappings: &[Mapping]) -> Vec<u32> {
2863 let mut indices: Vec<u32> =
2864 (0..mappings.len() as u32).filter(|&i| mappings[i as usize].source != NO_SOURCE).collect();
2865
2866 indices.sort_unstable_by(|&a, &b| {
2867 let ma = &mappings[a as usize];
2868 let mb = &mappings[b as usize];
2869 ma.source
2870 .cmp(&mb.source)
2871 .then(ma.original_line.cmp(&mb.original_line))
2872 .then(ma.original_column.cmp(&mb.original_column))
2873 .then(ma.generated_line.cmp(&mb.generated_line))
2874 .then(ma.generated_column.cmp(&mb.generated_column))
2875 });
2876
2877 indices
2878}
2879
2880pub struct MappingsIter<'a> {
2900 bytes: &'a [u8],
2901 len: usize,
2902 pos: usize,
2903 source_index: i64,
2904 original_line: i64,
2905 original_column: i64,
2906 name_index: i64,
2907 generated_line: u32,
2908 generated_column: i64,
2909 done: bool,
2910}
2911
2912impl<'a> MappingsIter<'a> {
2913 pub fn new(vlq: &'a str) -> Self {
2915 let bytes = vlq.as_bytes();
2916 Self {
2917 bytes,
2918 len: bytes.len(),
2919 pos: 0,
2920 source_index: 0,
2921 original_line: 0,
2922 original_column: 0,
2923 name_index: 0,
2924 generated_line: 0,
2925 generated_column: 0,
2926 done: false,
2927 }
2928 }
2929}
2930
2931impl Iterator for MappingsIter<'_> {
2932 type Item = Result<Mapping, DecodeError>;
2933
2934 fn next(&mut self) -> Option<Self::Item> {
2935 if self.done {
2936 return None;
2937 }
2938
2939 loop {
2940 if self.pos >= self.len {
2941 self.done = true;
2942 return None;
2943 }
2944
2945 let byte = self.bytes[self.pos];
2946
2947 if byte == b';' {
2948 self.pos += 1;
2949 self.generated_line += 1;
2950 self.generated_column = 0;
2951 continue;
2952 }
2953
2954 if byte == b',' {
2955 self.pos += 1;
2956 continue;
2957 }
2958
2959 match vlq_fast(self.bytes, &mut self.pos) {
2961 Ok(delta) => self.generated_column += delta,
2962 Err(e) => {
2963 self.done = true;
2964 return Some(Err(e));
2965 }
2966 }
2967
2968 if self.pos < self.len && self.bytes[self.pos] != b',' && self.bytes[self.pos] != b';' {
2969 match vlq_fast(self.bytes, &mut self.pos) {
2971 Ok(delta) => self.source_index += delta,
2972 Err(e) => {
2973 self.done = true;
2974 return Some(Err(e));
2975 }
2976 }
2977 if self.pos >= self.len
2979 || self.bytes[self.pos] == b','
2980 || self.bytes[self.pos] == b';'
2981 {
2982 self.done = true;
2983 return Some(Err(DecodeError::InvalidSegmentLength {
2984 fields: 2,
2985 offset: self.pos,
2986 }));
2987 }
2988 match vlq_fast(self.bytes, &mut self.pos) {
2990 Ok(delta) => self.original_line += delta,
2991 Err(e) => {
2992 self.done = true;
2993 return Some(Err(e));
2994 }
2995 }
2996 if self.pos >= self.len
2998 || self.bytes[self.pos] == b','
2999 || self.bytes[self.pos] == b';'
3000 {
3001 self.done = true;
3002 return Some(Err(DecodeError::InvalidSegmentLength {
3003 fields: 3,
3004 offset: self.pos,
3005 }));
3006 }
3007 match vlq_fast(self.bytes, &mut self.pos) {
3009 Ok(delta) => self.original_column += delta,
3010 Err(e) => {
3011 self.done = true;
3012 return Some(Err(e));
3013 }
3014 }
3015
3016 let name = if self.pos < self.len
3018 && self.bytes[self.pos] != b','
3019 && self.bytes[self.pos] != b';'
3020 {
3021 match vlq_fast(self.bytes, &mut self.pos) {
3022 Ok(delta) => {
3023 self.name_index += delta;
3024 self.name_index as u32
3025 }
3026 Err(e) => {
3027 self.done = true;
3028 return Some(Err(e));
3029 }
3030 }
3031 } else {
3032 NO_NAME
3033 };
3034
3035 return Some(Ok(Mapping {
3036 generated_line: self.generated_line,
3037 generated_column: self.generated_column as u32,
3038 source: self.source_index as u32,
3039 original_line: self.original_line as u32,
3040 original_column: self.original_column as u32,
3041 name,
3042 is_range_mapping: false,
3043 }));
3044 } else {
3045 return Some(Ok(Mapping {
3047 generated_line: self.generated_line,
3048 generated_column: self.generated_column as u32,
3049 source: NO_SOURCE,
3050 original_line: 0,
3051 original_column: 0,
3052 name: NO_NAME,
3053 is_range_mapping: false,
3054 }));
3055 }
3056 }
3057 }
3058}
3059
3060#[must_use]
3067pub struct SourceMapBuilder {
3068 file: Option<String>,
3069 source_root: Option<String>,
3070 sources: Vec<String>,
3071 sources_content: Vec<Option<String>>,
3072 names: Vec<String>,
3073 mappings: Vec<Mapping>,
3074 ignore_list: Vec<u32>,
3075 debug_id: Option<String>,
3076 scopes: Option<ScopeInfo>,
3077}
3078
3079impl SourceMapBuilder {
3080 pub fn new() -> Self {
3081 Self {
3082 file: None,
3083 source_root: None,
3084 sources: Vec::new(),
3085 sources_content: Vec::new(),
3086 names: Vec::new(),
3087 mappings: Vec::new(),
3088 ignore_list: Vec::new(),
3089 debug_id: None,
3090 scopes: None,
3091 }
3092 }
3093
3094 pub fn file(mut self, file: impl Into<String>) -> Self {
3095 self.file = Some(file.into());
3096 self
3097 }
3098
3099 pub fn source_root(mut self, root: impl Into<String>) -> Self {
3100 self.source_root = Some(root.into());
3101 self
3102 }
3103
3104 pub fn sources(mut self, sources: impl IntoIterator<Item = impl Into<String>>) -> Self {
3105 self.sources = sources.into_iter().map(Into::into).collect();
3106 self
3107 }
3108
3109 pub fn sources_content(
3110 mut self,
3111 content: impl IntoIterator<Item = Option<impl Into<String>>>,
3112 ) -> Self {
3113 self.sources_content = content.into_iter().map(|c| c.map(Into::into)).collect();
3114 self
3115 }
3116
3117 pub fn names(mut self, names: impl IntoIterator<Item = impl Into<String>>) -> Self {
3118 self.names = names.into_iter().map(Into::into).collect();
3119 self
3120 }
3121
3122 pub fn mappings(mut self, mappings: impl IntoIterator<Item = Mapping>) -> Self {
3123 self.mappings = mappings.into_iter().collect();
3124 self
3125 }
3126
3127 pub fn ignore_list(mut self, list: impl IntoIterator<Item = u32>) -> Self {
3128 self.ignore_list = list.into_iter().collect();
3129 self
3130 }
3131
3132 pub fn debug_id(mut self, id: impl Into<String>) -> Self {
3133 self.debug_id = Some(id.into());
3134 self
3135 }
3136
3137 pub fn scopes(mut self, scopes: ScopeInfo) -> Self {
3138 self.scopes = Some(scopes);
3139 self
3140 }
3141
3142 pub fn build(self) -> SourceMap {
3146 SourceMap::from_parts(
3147 self.file,
3148 self.source_root,
3149 self.sources,
3150 self.sources_content,
3151 self.names,
3152 self.mappings,
3153 self.ignore_list,
3154 self.debug_id,
3155 self.scopes,
3156 )
3157 }
3158}
3159
3160impl Default for SourceMapBuilder {
3161 fn default() -> Self {
3162 Self::new()
3163 }
3164}
3165
3166#[cfg(test)]
3169mod tests {
3170 use super::*;
3171
3172 fn simple_map() -> &'static str {
3173 r#"{"version":3,"sources":["input.js"],"names":["hello"],"mappings":"AAAA;AACA,EAAA;AACA"}"#
3174 }
3175
3176 #[test]
3177 fn parse_basic() {
3178 let sm = SourceMap::from_json(simple_map()).unwrap();
3179 assert_eq!(sm.sources, vec!["input.js"]);
3180 assert_eq!(sm.names, vec!["hello"]);
3181 assert_eq!(sm.line_count(), 3);
3182 assert!(sm.mapping_count() > 0);
3183 }
3184
3185 #[test]
3186 fn to_json_roundtrip() {
3187 let json = simple_map();
3188 let sm = SourceMap::from_json(json).unwrap();
3189 let output = sm.to_json();
3190
3191 let sm2 = SourceMap::from_json(&output).unwrap();
3193 assert_eq!(sm2.sources, sm.sources);
3194 assert_eq!(sm2.names, sm.names);
3195 assert_eq!(sm2.mapping_count(), sm.mapping_count());
3196 assert_eq!(sm2.line_count(), sm.line_count());
3197
3198 for m in sm.all_mappings() {
3200 let loc1 = sm.original_position_for(m.generated_line, m.generated_column);
3201 let loc2 = sm2.original_position_for(m.generated_line, m.generated_column);
3202 match (loc1, loc2) {
3203 (Some(a), Some(b)) => {
3204 assert_eq!(a.source, b.source);
3205 assert_eq!(a.line, b.line);
3206 assert_eq!(a.column, b.column);
3207 assert_eq!(a.name, b.name);
3208 }
3209 (None, None) => {}
3210 _ => panic!("lookup mismatch at ({}, {})", m.generated_line, m.generated_column),
3211 }
3212 }
3213 }
3214
3215 #[test]
3216 fn to_json_roundtrip_large() {
3217 let json = generate_test_sourcemap(50, 10, 3);
3218 let sm = SourceMap::from_json(&json).unwrap();
3219 let output = sm.to_json();
3220 let sm2 = SourceMap::from_json(&output).unwrap();
3221
3222 assert_eq!(sm2.mapping_count(), sm.mapping_count());
3223
3224 for line in (0..sm.line_count() as u32).step_by(5) {
3226 for col in [0u32, 10, 20, 50] {
3227 let a = sm.original_position_for(line, col);
3228 let b = sm2.original_position_for(line, col);
3229 match (a, b) {
3230 (Some(a), Some(b)) => {
3231 assert_eq!(a.source, b.source);
3232 assert_eq!(a.line, b.line);
3233 assert_eq!(a.column, b.column);
3234 }
3235 (None, None) => {}
3236 _ => panic!("mismatch at ({line}, {col})"),
3237 }
3238 }
3239 }
3240 }
3241
3242 #[test]
3243 fn to_json_preserves_fields() {
3244 let json = r#"{"version":3,"file":"out.js","sourceRoot":"src/","sources":["app.ts"],"sourcesContent":["const x = 1;"],"names":["x"],"mappings":"AAAAA","ignoreList":[0]}"#;
3245 let sm = SourceMap::from_json(json).unwrap();
3246 let output = sm.to_json();
3247
3248 assert!(output.contains(r#""file":"out.js""#));
3249 assert!(output.contains(r#""sourceRoot":"src/""#));
3250 assert!(output.contains(r#""sourcesContent":["const x = 1;"]"#));
3251 assert!(output.contains(r#""ignoreList":[0]"#));
3252
3253 let sm2 = SourceMap::from_json(&output).unwrap();
3255 assert_eq!(sm2.file.as_deref(), Some("out.js"));
3256 assert_eq!(sm2.ignore_list, vec![0]);
3257 }
3258
3259 #[test]
3260 fn original_position_for_exact_match() {
3261 let sm = SourceMap::from_json(simple_map()).unwrap();
3262 let loc = sm.original_position_for(0, 0).unwrap();
3263 assert_eq!(loc.source, 0);
3264 assert_eq!(loc.line, 0);
3265 assert_eq!(loc.column, 0);
3266 }
3267
3268 #[test]
3269 fn original_position_for_column_within_segment() {
3270 let sm = SourceMap::from_json(simple_map()).unwrap();
3271 let loc = sm.original_position_for(1, 5);
3273 assert!(loc.is_some());
3274 }
3275
3276 #[test]
3277 fn original_position_for_nonexistent_line() {
3278 let sm = SourceMap::from_json(simple_map()).unwrap();
3279 assert!(sm.original_position_for(999, 0).is_none());
3280 }
3281
3282 #[test]
3283 fn original_position_for_before_first_mapping() {
3284 let sm = SourceMap::from_json(simple_map()).unwrap();
3286 let loc = sm.original_position_for(1, 0);
3287 let _ = loc;
3290 }
3291
3292 #[test]
3293 fn generated_position_for_basic() {
3294 let sm = SourceMap::from_json(simple_map()).unwrap();
3295 let loc = sm.generated_position_for("input.js", 0, 0).unwrap();
3296 assert_eq!(loc.line, 0);
3297 assert_eq!(loc.column, 0);
3298 }
3299
3300 #[test]
3301 fn generated_position_for_unknown_source() {
3302 let sm = SourceMap::from_json(simple_map()).unwrap();
3303 assert!(sm.generated_position_for("nonexistent.js", 0, 0).is_none());
3304 }
3305
3306 #[test]
3307 fn parse_invalid_version() {
3308 let json = r#"{"version":2,"sources":[],"names":[],"mappings":""}"#;
3309 let err = SourceMap::from_json(json).unwrap_err();
3310 assert!(matches!(err, ParseError::InvalidVersion(2)));
3311 }
3312
3313 #[test]
3314 fn parse_empty_mappings() {
3315 let json = r#"{"version":3,"sources":[],"names":[],"mappings":""}"#;
3316 let sm = SourceMap::from_json(json).unwrap();
3317 assert_eq!(sm.mapping_count(), 0);
3318 assert!(sm.original_position_for(0, 0).is_none());
3319 }
3320
3321 #[test]
3322 fn parse_with_source_root() {
3323 let json = r#"{"version":3,"sourceRoot":"src/","sources":["foo.js"],"names":[],"mappings":"AAAA"}"#;
3324 let sm = SourceMap::from_json(json).unwrap();
3325 assert_eq!(sm.sources, vec!["src/foo.js"]);
3326 }
3327
3328 #[test]
3329 fn parse_with_sources_content() {
3330 let json = r#"{"version":3,"sources":["a.js"],"sourcesContent":["var x = 1;"],"names":[],"mappings":"AAAA"}"#;
3331 let sm = SourceMap::from_json(json).unwrap();
3332 assert_eq!(sm.sources_content, vec![Some("var x = 1;".to_string())]);
3333 }
3334
3335 #[test]
3336 fn mappings_for_line() {
3337 let sm = SourceMap::from_json(simple_map()).unwrap();
3338 let line0 = sm.mappings_for_line(0);
3339 assert!(!line0.is_empty());
3340 let empty = sm.mappings_for_line(999);
3341 assert!(empty.is_empty());
3342 }
3343
3344 #[test]
3345 fn large_sourcemap_lookup() {
3346 let json = generate_test_sourcemap(500, 20, 5);
3348 let sm = SourceMap::from_json(&json).unwrap();
3349
3350 for line in [0, 10, 100, 250, 499] {
3352 let mappings = sm.mappings_for_line(line);
3353 if let Some(m) = mappings.first() {
3354 let loc = sm.original_position_for(line, m.generated_column);
3355 assert!(loc.is_some(), "lookup failed for line {line}");
3356 }
3357 }
3358 }
3359
3360 #[test]
3361 fn reverse_lookup_roundtrip() {
3362 let json = generate_test_sourcemap(100, 10, 3);
3363 let sm = SourceMap::from_json(&json).unwrap();
3364
3365 let mapping = &sm.mappings[50];
3367 if mapping.source != NO_SOURCE {
3368 let source_name = sm.source(mapping.source);
3369 let result = sm.generated_position_for(
3370 source_name,
3371 mapping.original_line,
3372 mapping.original_column,
3373 );
3374 assert!(result.is_some(), "reverse lookup failed");
3375 }
3376 }
3377
3378 #[test]
3379 fn all_generated_positions_for_basic() {
3380 let sm = SourceMap::from_json(simple_map()).unwrap();
3381 let results = sm.all_generated_positions_for("input.js", 0, 0);
3382 assert!(!results.is_empty(), "should find at least one position");
3383 assert_eq!(results[0].line, 0);
3384 assert_eq!(results[0].column, 0);
3385 }
3386
3387 #[test]
3388 fn all_generated_positions_for_unknown_source() {
3389 let sm = SourceMap::from_json(simple_map()).unwrap();
3390 let results = sm.all_generated_positions_for("nonexistent.js", 0, 0);
3391 assert!(results.is_empty());
3392 }
3393
3394 #[test]
3395 fn all_generated_positions_for_no_match() {
3396 let sm = SourceMap::from_json(simple_map()).unwrap();
3397 let results = sm.all_generated_positions_for("input.js", 999, 999);
3398 assert!(results.is_empty());
3399 }
3400
3401 #[test]
3402 fn encode_mappings_roundtrip() {
3403 let json = generate_test_sourcemap(50, 10, 3);
3404 let sm = SourceMap::from_json(&json).unwrap();
3405 let encoded = sm.encode_mappings();
3406 let json2 = format!(
3408 r#"{{"version":3,"sources":{sources},"names":{names},"mappings":"{mappings}"}}"#,
3409 sources = serde_json::to_string(&sm.sources).unwrap(),
3410 names = serde_json::to_string(&sm.names).unwrap(),
3411 mappings = encoded,
3412 );
3413 let sm2 = SourceMap::from_json(&json2).unwrap();
3414 assert_eq!(sm2.mapping_count(), sm.mapping_count());
3415 }
3416
3417 #[test]
3418 fn indexed_source_map() {
3419 let json = r#"{
3420 "version": 3,
3421 "file": "bundle.js",
3422 "sections": [
3423 {
3424 "offset": {"line": 0, "column": 0},
3425 "map": {
3426 "version": 3,
3427 "sources": ["a.js"],
3428 "names": ["foo"],
3429 "mappings": "AAAAA"
3430 }
3431 },
3432 {
3433 "offset": {"line": 10, "column": 0},
3434 "map": {
3435 "version": 3,
3436 "sources": ["b.js"],
3437 "names": ["bar"],
3438 "mappings": "AAAAA"
3439 }
3440 }
3441 ]
3442 }"#;
3443
3444 let sm = SourceMap::from_json(json).unwrap();
3445
3446 assert_eq!(sm.sources.len(), 2);
3448 assert!(sm.sources.contains(&"a.js".to_string()));
3449 assert!(sm.sources.contains(&"b.js".to_string()));
3450
3451 assert_eq!(sm.names.len(), 2);
3453 assert!(sm.names.contains(&"foo".to_string()));
3454 assert!(sm.names.contains(&"bar".to_string()));
3455
3456 let loc = sm.original_position_for(0, 0).unwrap();
3458 assert_eq!(sm.source(loc.source), "a.js");
3459 assert_eq!(loc.line, 0);
3460 assert_eq!(loc.column, 0);
3461
3462 let loc = sm.original_position_for(10, 0).unwrap();
3464 assert_eq!(sm.source(loc.source), "b.js");
3465 assert_eq!(loc.line, 0);
3466 assert_eq!(loc.column, 0);
3467 }
3468
3469 #[test]
3470 fn indexed_source_map_shared_sources() {
3471 let json = r#"{
3473 "version": 3,
3474 "sections": [
3475 {
3476 "offset": {"line": 0, "column": 0},
3477 "map": {
3478 "version": 3,
3479 "sources": ["shared.js"],
3480 "names": [],
3481 "mappings": "AAAA"
3482 }
3483 },
3484 {
3485 "offset": {"line": 5, "column": 0},
3486 "map": {
3487 "version": 3,
3488 "sources": ["shared.js"],
3489 "names": [],
3490 "mappings": "AACA"
3491 }
3492 }
3493 ]
3494 }"#;
3495
3496 let sm = SourceMap::from_json(json).unwrap();
3497
3498 assert_eq!(sm.sources.len(), 1);
3500 assert_eq!(sm.sources[0], "shared.js");
3501
3502 let loc0 = sm.original_position_for(0, 0).unwrap();
3504 let loc5 = sm.original_position_for(5, 0).unwrap();
3505 assert_eq!(loc0.source, loc5.source);
3506 }
3507
3508 #[test]
3509 fn parse_ignore_list() {
3510 let json = r#"{"version":3,"sources":["app.js","node_modules/lib.js"],"names":[],"mappings":"AAAA;ACAA","ignoreList":[1]}"#;
3511 let sm = SourceMap::from_json(json).unwrap();
3512 assert_eq!(sm.ignore_list, vec![1]);
3513 }
3514
3515 fn build_sourcemap_json(
3517 sources: &[&str],
3518 names: &[&str],
3519 mappings_data: &[Vec<Vec<i64>>],
3520 ) -> String {
3521 let converted: Vec<Vec<srcmap_codec::Segment>> = mappings_data
3522 .iter()
3523 .map(|line| {
3524 line.iter().map(|seg| srcmap_codec::Segment::from(seg.as_slice())).collect()
3525 })
3526 .collect();
3527 let encoded = srcmap_codec::encode(&converted);
3528 format!(
3529 r#"{{"version":3,"sources":[{}],"names":[{}],"mappings":"{}"}}"#,
3530 sources.iter().map(|s| format!("\"{s}\"")).collect::<Vec<_>>().join(","),
3531 names.iter().map(|n| format!("\"{n}\"")).collect::<Vec<_>>().join(","),
3532 encoded,
3533 )
3534 }
3535
3536 #[test]
3539 fn decode_multiple_consecutive_semicolons() {
3540 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;;;AACA"}"#;
3541 let sm = SourceMap::from_json(json).unwrap();
3542 assert_eq!(sm.line_count(), 4);
3543 assert!(sm.mappings_for_line(1).is_empty());
3544 assert!(sm.mappings_for_line(2).is_empty());
3545 assert!(!sm.mappings_for_line(0).is_empty());
3546 assert!(!sm.mappings_for_line(3).is_empty());
3547 }
3548
3549 #[test]
3550 fn decode_trailing_semicolons() {
3551 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;;"}"#;
3552 let sm = SourceMap::from_json(json).unwrap();
3553 assert_eq!(sm.line_count(), 3);
3554 assert!(!sm.mappings_for_line(0).is_empty());
3555 assert!(sm.mappings_for_line(1).is_empty());
3556 assert!(sm.mappings_for_line(2).is_empty());
3557 }
3558
3559 #[test]
3560 fn decode_leading_comma() {
3561 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":",AAAA"}"#;
3562 let sm = SourceMap::from_json(json).unwrap();
3563 assert_eq!(sm.mapping_count(), 1);
3564 let m = &sm.all_mappings()[0];
3565 assert_eq!(m.generated_line, 0);
3566 assert_eq!(m.generated_column, 0);
3567 }
3568
3569 #[test]
3570 fn decode_single_field_segments() {
3571 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A,C"}"#;
3572 let sm = SourceMap::from_json(json).unwrap();
3573 assert_eq!(sm.mapping_count(), 2);
3574 for m in sm.all_mappings() {
3575 assert_eq!(m.source, NO_SOURCE);
3576 }
3577 assert_eq!(sm.all_mappings()[0].generated_column, 0);
3578 assert_eq!(sm.all_mappings()[1].generated_column, 1);
3579 assert!(sm.original_position_for(0, 0).is_none());
3580 assert!(sm.original_position_for(0, 1).is_none());
3581 }
3582
3583 #[test]
3584 fn decode_five_field_segments_with_names() {
3585 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0, 0], vec![10, 0, 0, 5, 1]]];
3586 let json = build_sourcemap_json(&["app.js"], &["foo", "bar"], &mappings_data);
3587 let sm = SourceMap::from_json(&json).unwrap();
3588 assert_eq!(sm.mapping_count(), 2);
3589 assert_eq!(sm.all_mappings()[0].name, 0);
3590 assert_eq!(sm.all_mappings()[1].name, 1);
3591
3592 let loc = sm.original_position_for(0, 0).unwrap();
3593 assert_eq!(loc.name, Some(0));
3594 assert_eq!(sm.name(0), "foo");
3595
3596 let loc = sm.original_position_for(0, 10).unwrap();
3597 assert_eq!(loc.name, Some(1));
3598 assert_eq!(sm.name(1), "bar");
3599 }
3600
3601 #[test]
3602 fn decode_large_vlq_values() {
3603 let mappings_data = vec![vec![vec![500_i64, 0, 1000, 2000]]];
3604 let json = build_sourcemap_json(&["big.js"], &[], &mappings_data);
3605 let sm = SourceMap::from_json(&json).unwrap();
3606 assert_eq!(sm.mapping_count(), 1);
3607 let m = &sm.all_mappings()[0];
3608 assert_eq!(m.generated_column, 500);
3609 assert_eq!(m.original_line, 1000);
3610 assert_eq!(m.original_column, 2000);
3611
3612 let loc = sm.original_position_for(0, 500).unwrap();
3613 assert_eq!(loc.line, 1000);
3614 assert_eq!(loc.column, 2000);
3615 }
3616
3617 #[test]
3618 fn decode_only_semicolons() {
3619 let json = r#"{"version":3,"sources":[],"names":[],"mappings":";;;"}"#;
3620 let sm = SourceMap::from_json(json).unwrap();
3621 assert_eq!(sm.line_count(), 4);
3622 assert_eq!(sm.mapping_count(), 0);
3623 for line in 0..4 {
3624 assert!(sm.mappings_for_line(line).is_empty());
3625 }
3626 }
3627
3628 #[test]
3629 fn decode_mixed_single_and_four_field_segments() {
3630 let mappings_data = vec![vec![srcmap_codec::Segment::four(5, 0, 0, 0)]];
3631 let four_field_encoded = srcmap_codec::encode(&mappings_data);
3632 let combined_mappings = format!("A,{four_field_encoded}");
3633 let json = format!(
3634 r#"{{"version":3,"sources":["x.js"],"names":[],"mappings":"{combined_mappings}"}}"#,
3635 );
3636 let sm = SourceMap::from_json(&json).unwrap();
3637 assert_eq!(sm.mapping_count(), 2);
3638 assert_eq!(sm.all_mappings()[0].source, NO_SOURCE);
3639 assert_eq!(sm.all_mappings()[1].source, 0);
3640 }
3641
3642 #[test]
3645 fn parse_missing_optional_fields() {
3646 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
3647 let sm = SourceMap::from_json(json).unwrap();
3648 assert!(sm.file.is_none());
3649 assert!(sm.source_root.is_none());
3650 assert!(sm.sources_content.is_empty());
3651 assert!(sm.ignore_list.is_empty());
3652 }
3653
3654 #[test]
3655 fn parse_with_file_field() {
3656 let json =
3657 r#"{"version":3,"file":"output.js","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
3658 let sm = SourceMap::from_json(json).unwrap();
3659 assert_eq!(sm.file.as_deref(), Some("output.js"));
3660 }
3661
3662 #[test]
3663 fn parse_null_entries_in_sources() {
3664 let json = r#"{"version":3,"sources":["a.js",null,"c.js"],"names":[],"mappings":"AAAA"}"#;
3665 let sm = SourceMap::from_json(json).unwrap();
3666 assert_eq!(sm.sources.len(), 3);
3667 assert_eq!(sm.sources[0], "a.js");
3668 assert_eq!(sm.sources[1], "");
3669 assert_eq!(sm.sources[2], "c.js");
3670 }
3671
3672 #[test]
3673 fn parse_null_entries_in_sources_with_source_root() {
3674 let json = r#"{"version":3,"sourceRoot":"lib/","sources":["a.js",null],"names":[],"mappings":"AAAA"}"#;
3675 let sm = SourceMap::from_json(json).unwrap();
3676 assert_eq!(sm.sources[0], "lib/a.js");
3677 assert_eq!(sm.sources[1], "");
3678 }
3679
3680 #[test]
3681 fn parse_empty_names_array() {
3682 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
3683 let sm = SourceMap::from_json(json).unwrap();
3684 assert!(sm.names.is_empty());
3685 }
3686
3687 #[test]
3688 fn parse_invalid_json() {
3689 let result = SourceMap::from_json("not valid json");
3690 assert!(result.is_err());
3691 assert!(matches!(result.unwrap_err(), ParseError::Json(_)));
3692 }
3693
3694 #[test]
3695 fn parse_json_missing_version() {
3696 let result = SourceMap::from_json(r#"{"sources":[],"names":[],"mappings":""}"#);
3697 assert!(result.is_err());
3698 }
3699
3700 #[test]
3701 fn parse_multiple_sources_overlapping_original_positions() {
3702 let mappings_data = vec![vec![vec![0_i64, 0, 5, 10], vec![10, 1, 5, 10]]];
3703 let json = build_sourcemap_json(&["a.js", "b.js"], &[], &mappings_data);
3704 let sm = SourceMap::from_json(&json).unwrap();
3705
3706 let loc0 = sm.original_position_for(0, 0).unwrap();
3707 assert_eq!(loc0.source, 0);
3708 assert_eq!(sm.source(loc0.source), "a.js");
3709
3710 let loc1 = sm.original_position_for(0, 10).unwrap();
3711 assert_eq!(loc1.source, 1);
3712 assert_eq!(sm.source(loc1.source), "b.js");
3713
3714 assert_eq!(loc0.line, loc1.line);
3715 assert_eq!(loc0.column, loc1.column);
3716 }
3717
3718 #[test]
3719 fn parse_sources_content_with_null_entries() {
3720 let json = r#"{"version":3,"sources":["a.js","b.js"],"sourcesContent":["content a",null],"names":[],"mappings":"AAAA"}"#;
3721 let sm = SourceMap::from_json(json).unwrap();
3722 assert_eq!(sm.sources_content.len(), 2);
3723 assert_eq!(sm.sources_content[0], Some("content a".to_string()));
3724 assert_eq!(sm.sources_content[1], None);
3725 }
3726
3727 #[test]
3728 fn parse_empty_sources_and_names() {
3729 let json = r#"{"version":3,"sources":[],"names":[],"mappings":""}"#;
3730 let sm = SourceMap::from_json(json).unwrap();
3731 assert!(sm.sources.is_empty());
3732 assert!(sm.names.is_empty());
3733 assert_eq!(sm.mapping_count(), 0);
3734 }
3735
3736 #[test]
3739 fn lookup_exact_match() {
3740 let mappings_data =
3741 vec![vec![vec![0_i64, 0, 10, 20], vec![5, 0, 10, 25], vec![15, 0, 11, 0]]];
3742 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3743 let sm = SourceMap::from_json(&json).unwrap();
3744
3745 let loc = sm.original_position_for(0, 5).unwrap();
3746 assert_eq!(loc.line, 10);
3747 assert_eq!(loc.column, 25);
3748 }
3749
3750 #[test]
3751 fn lookup_before_first_segment() {
3752 let mappings_data = vec![vec![vec![5_i64, 0, 0, 0]]];
3753 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3754 let sm = SourceMap::from_json(&json).unwrap();
3755
3756 assert!(sm.original_position_for(0, 0).is_none());
3757 assert!(sm.original_position_for(0, 4).is_none());
3758 }
3759
3760 #[test]
3761 fn lookup_between_segments() {
3762 let mappings_data = vec![vec![vec![0_i64, 0, 1, 0], vec![10, 0, 2, 0], vec![20, 0, 3, 0]]];
3763 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3764 let sm = SourceMap::from_json(&json).unwrap();
3765
3766 let loc = sm.original_position_for(0, 7).unwrap();
3767 assert_eq!(loc.line, 1);
3768 assert_eq!(loc.column, 0);
3769
3770 let loc = sm.original_position_for(0, 15).unwrap();
3771 assert_eq!(loc.line, 2);
3772 assert_eq!(loc.column, 0);
3773 }
3774
3775 #[test]
3776 fn lookup_after_last_segment() {
3777 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0], vec![10, 0, 1, 5]]];
3778 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3779 let sm = SourceMap::from_json(&json).unwrap();
3780
3781 let loc = sm.original_position_for(0, 100).unwrap();
3782 assert_eq!(loc.line, 1);
3783 assert_eq!(loc.column, 5);
3784 }
3785
3786 #[test]
3787 fn lookup_empty_lines_no_mappings() {
3788 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0]], vec![], vec![vec![0_i64, 0, 2, 0]]];
3789 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3790 let sm = SourceMap::from_json(&json).unwrap();
3791
3792 assert!(sm.original_position_for(1, 0).is_none());
3793 assert!(sm.original_position_for(1, 10).is_none());
3794 assert!(sm.original_position_for(0, 0).is_some());
3795 assert!(sm.original_position_for(2, 0).is_some());
3796 }
3797
3798 #[test]
3799 fn lookup_line_with_single_mapping() {
3800 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0]]];
3801 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3802 let sm = SourceMap::from_json(&json).unwrap();
3803
3804 let loc = sm.original_position_for(0, 0).unwrap();
3805 assert_eq!(loc.line, 0);
3806 assert_eq!(loc.column, 0);
3807
3808 let loc = sm.original_position_for(0, 50).unwrap();
3809 assert_eq!(loc.line, 0);
3810 assert_eq!(loc.column, 0);
3811 }
3812
3813 #[test]
3814 fn lookup_column_0_vs_column_nonzero() {
3815 let mappings_data = vec![vec![vec![0_i64, 0, 10, 0], vec![8, 0, 20, 5]]];
3816 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3817 let sm = SourceMap::from_json(&json).unwrap();
3818
3819 let loc0 = sm.original_position_for(0, 0).unwrap();
3820 assert_eq!(loc0.line, 10);
3821 assert_eq!(loc0.column, 0);
3822
3823 let loc8 = sm.original_position_for(0, 8).unwrap();
3824 assert_eq!(loc8.line, 20);
3825 assert_eq!(loc8.column, 5);
3826
3827 let loc4 = sm.original_position_for(0, 4).unwrap();
3828 assert_eq!(loc4.line, 10);
3829 }
3830
3831 #[test]
3832 fn lookup_beyond_last_line() {
3833 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0]]];
3834 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3835 let sm = SourceMap::from_json(&json).unwrap();
3836
3837 assert!(sm.original_position_for(1, 0).is_none());
3838 assert!(sm.original_position_for(100, 0).is_none());
3839 }
3840
3841 #[test]
3842 fn lookup_single_field_returns_none() {
3843 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A"}"#;
3844 let sm = SourceMap::from_json(json).unwrap();
3845 assert_eq!(sm.mapping_count(), 1);
3846 assert!(sm.original_position_for(0, 0).is_none());
3847 }
3848
3849 #[test]
3852 fn reverse_lookup_exact_match() {
3853 let mappings_data = vec![
3854 vec![vec![0_i64, 0, 0, 0]],
3855 vec![vec![4, 0, 1, 0], vec![10, 0, 1, 8]],
3856 vec![vec![0, 0, 2, 0]],
3857 ];
3858 let json = build_sourcemap_json(&["main.js"], &[], &mappings_data);
3859 let sm = SourceMap::from_json(&json).unwrap();
3860
3861 let loc = sm.generated_position_for("main.js", 1, 8).unwrap();
3862 assert_eq!(loc.line, 1);
3863 assert_eq!(loc.column, 10);
3864 }
3865
3866 #[test]
3867 fn reverse_lookup_no_match() {
3868 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0], vec![10, 0, 0, 10]]];
3869 let json = build_sourcemap_json(&["main.js"], &[], &mappings_data);
3870 let sm = SourceMap::from_json(&json).unwrap();
3871
3872 assert!(sm.generated_position_for("main.js", 99, 0).is_none());
3873 }
3874
3875 #[test]
3876 fn reverse_lookup_unknown_source() {
3877 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0]]];
3878 let json = build_sourcemap_json(&["main.js"], &[], &mappings_data);
3879 let sm = SourceMap::from_json(&json).unwrap();
3880
3881 assert!(sm.generated_position_for("unknown.js", 0, 0).is_none());
3882 }
3883
3884 #[test]
3885 fn reverse_lookup_multiple_mappings_same_original() {
3886 let mappings_data = vec![vec![vec![0_i64, 0, 5, 10]], vec![vec![20, 0, 5, 10]]];
3887 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
3888 let sm = SourceMap::from_json(&json).unwrap();
3889
3890 let loc = sm.generated_position_for("src.js", 5, 10);
3891 assert!(loc.is_some());
3892 let loc = loc.unwrap();
3893 assert!(
3894 (loc.line == 0 && loc.column == 0) || (loc.line == 1 && loc.column == 20),
3895 "Expected (0,0) or (1,20), got ({},{})",
3896 loc.line,
3897 loc.column
3898 );
3899 }
3900
3901 #[test]
3902 fn reverse_lookup_with_multiple_sources() {
3903 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0], vec![10, 1, 0, 0]]];
3904 let json = build_sourcemap_json(&["a.js", "b.js"], &[], &mappings_data);
3905 let sm = SourceMap::from_json(&json).unwrap();
3906
3907 let loc_a = sm.generated_position_for("a.js", 0, 0).unwrap();
3908 assert_eq!(loc_a.line, 0);
3909 assert_eq!(loc_a.column, 0);
3910
3911 let loc_b = sm.generated_position_for("b.js", 0, 0).unwrap();
3912 assert_eq!(loc_b.line, 0);
3913 assert_eq!(loc_b.column, 10);
3914 }
3915
3916 #[test]
3917 fn reverse_lookup_skips_single_field_segments() {
3918 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A,KAAAA"}"#;
3919 let sm = SourceMap::from_json(json).unwrap();
3920
3921 let loc = sm.generated_position_for("a.js", 0, 0).unwrap();
3922 assert_eq!(loc.line, 0);
3923 assert_eq!(loc.column, 5);
3924 }
3925
3926 #[test]
3927 fn reverse_lookup_finds_each_original_line() {
3928 let mappings_data = vec![
3929 vec![vec![0_i64, 0, 0, 0]],
3930 vec![vec![0, 0, 1, 0]],
3931 vec![vec![0, 0, 2, 0]],
3932 vec![vec![0, 0, 3, 0]],
3933 ];
3934 let json = build_sourcemap_json(&["x.js"], &[], &mappings_data);
3935 let sm = SourceMap::from_json(&json).unwrap();
3936
3937 for orig_line in 0..4 {
3938 let loc = sm.generated_position_for("x.js", orig_line, 0).unwrap();
3939 assert_eq!(loc.line, orig_line, "reverse lookup for orig line {orig_line}");
3940 assert_eq!(loc.column, 0);
3941 }
3942 }
3943
3944 #[test]
3947 fn parse_with_ignore_list_multiple() {
3948 let json = r#"{"version":3,"sources":["app.js","node_modules/lib.js","vendor.js"],"names":[],"mappings":"AAAA","ignoreList":[1,2]}"#;
3949 let sm = SourceMap::from_json(json).unwrap();
3950 assert_eq!(sm.ignore_list, vec![1, 2]);
3951 }
3952
3953 #[test]
3954 fn parse_with_empty_ignore_list() {
3955 let json =
3956 r#"{"version":3,"sources":["app.js"],"names":[],"mappings":"AAAA","ignoreList":[]}"#;
3957 let sm = SourceMap::from_json(json).unwrap();
3958 assert!(sm.ignore_list.is_empty());
3959 }
3960
3961 #[test]
3962 fn parse_without_ignore_list_field() {
3963 let json = r#"{"version":3,"sources":["app.js"],"names":[],"mappings":"AAAA"}"#;
3964 let sm = SourceMap::from_json(json).unwrap();
3965 assert!(sm.ignore_list.is_empty());
3966 }
3967
3968 #[test]
3971 fn source_index_lookup() {
3972 let json = r#"{"version":3,"sources":["a.js","b.js","c.js"],"names":[],"mappings":"AAAA"}"#;
3973 let sm = SourceMap::from_json(json).unwrap();
3974 assert_eq!(sm.source_index("a.js"), Some(0));
3975 assert_eq!(sm.source_index("b.js"), Some(1));
3976 assert_eq!(sm.source_index("c.js"), Some(2));
3977 assert_eq!(sm.source_index("d.js"), None);
3978 }
3979
3980 #[test]
3981 fn all_mappings_returns_complete_list() {
3982 let mappings_data =
3983 vec![vec![vec![0_i64, 0, 0, 0], vec![5, 0, 0, 5]], vec![vec![0, 0, 1, 0]]];
3984 let json = build_sourcemap_json(&["x.js"], &[], &mappings_data);
3985 let sm = SourceMap::from_json(&json).unwrap();
3986 assert_eq!(sm.all_mappings().len(), 3);
3987 assert_eq!(sm.mapping_count(), 3);
3988 }
3989
3990 #[test]
3991 fn line_count_matches_decoded_lines() {
3992 let mappings_data =
3993 vec![vec![vec![0_i64, 0, 0, 0]], vec![], vec![vec![0_i64, 0, 2, 0]], vec![], vec![]];
3994 let json = build_sourcemap_json(&["x.js"], &[], &mappings_data);
3995 let sm = SourceMap::from_json(&json).unwrap();
3996 assert_eq!(sm.line_count(), 5);
3997 }
3998
3999 #[test]
4000 fn parse_error_display() {
4001 let err = ParseError::InvalidVersion(5);
4002 assert_eq!(format!("{err}"), "unsupported source map version: 5");
4003
4004 let json_err = SourceMap::from_json("{}").unwrap_err();
4005 let display = format!("{json_err}");
4006 assert!(display.contains("JSON parse error") || display.contains("missing field"));
4007 }
4008
4009 #[test]
4010 fn original_position_name_none_for_four_field() {
4011 let mappings_data = vec![vec![vec![0_i64, 0, 5, 10]]];
4012 let json = build_sourcemap_json(&["a.js"], &["unused_name"], &mappings_data);
4013 let sm = SourceMap::from_json(&json).unwrap();
4014
4015 let loc = sm.original_position_for(0, 0).unwrap();
4016 assert!(loc.name.is_none());
4017 }
4018
4019 #[test]
4020 fn forward_and_reverse_roundtrip_comprehensive() {
4021 let mappings_data = vec![
4022 vec![vec![0_i64, 0, 0, 0], vec![10, 0, 0, 10], vec![20, 1, 5, 0]],
4023 vec![vec![0, 0, 1, 0], vec![5, 1, 6, 3]],
4024 vec![vec![0, 0, 2, 0]],
4025 ];
4026 let json = build_sourcemap_json(&["a.js", "b.js"], &[], &mappings_data);
4027 let sm = SourceMap::from_json(&json).unwrap();
4028
4029 for m in sm.all_mappings() {
4030 if m.source == NO_SOURCE {
4031 continue;
4032 }
4033 let source_name = sm.source(m.source);
4034
4035 let orig = sm.original_position_for(m.generated_line, m.generated_column).unwrap();
4036 assert_eq!(orig.source, m.source);
4037 assert_eq!(orig.line, m.original_line);
4038 assert_eq!(orig.column, m.original_column);
4039
4040 let gen_loc =
4041 sm.generated_position_for(source_name, m.original_line, m.original_column).unwrap();
4042 assert_eq!(gen_loc.line, m.generated_line);
4043 assert_eq!(gen_loc.column, m.generated_column);
4044 }
4045 }
4046
4047 #[test]
4052 fn source_root_with_multiple_sources() {
4053 let json = r#"{"version":3,"sourceRoot":"lib/","sources":["a.js","b.js","c.js"],"names":[],"mappings":"AAAA,KACA,KACA"}"#;
4054 let sm = SourceMap::from_json(json).unwrap();
4055 assert_eq!(sm.sources, vec!["lib/a.js", "lib/b.js", "lib/c.js"]);
4056 }
4057
4058 #[test]
4059 fn source_root_empty_string() {
4060 let json =
4061 r#"{"version":3,"sourceRoot":"","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
4062 let sm = SourceMap::from_json(json).unwrap();
4063 assert_eq!(sm.sources, vec!["a.js"]);
4064 }
4065
4066 #[test]
4067 fn source_root_preserved_in_to_json() {
4068 let json =
4069 r#"{"version":3,"sourceRoot":"src/","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
4070 let sm = SourceMap::from_json(json).unwrap();
4071 let output = sm.to_json();
4072 assert!(output.contains(r#""sourceRoot":"src/""#));
4073 }
4074
4075 #[test]
4076 fn source_root_reverse_lookup_uses_prefixed_name() {
4077 let json =
4078 r#"{"version":3,"sourceRoot":"src/","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
4079 let sm = SourceMap::from_json(json).unwrap();
4080 assert!(sm.generated_position_for("src/a.js", 0, 0).is_some());
4082 assert!(sm.generated_position_for("a.js", 0, 0).is_none());
4083 }
4084
4085 #[test]
4086 fn source_root_with_trailing_slash() {
4087 let json =
4088 r#"{"version":3,"sourceRoot":"src/","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
4089 let sm = SourceMap::from_json(json).unwrap();
4090 assert_eq!(sm.sources[0], "src/a.js");
4091 }
4092
4093 #[test]
4094 fn source_root_without_trailing_slash() {
4095 let json =
4096 r#"{"version":3,"sourceRoot":"src","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
4097 let sm = SourceMap::from_json(json).unwrap();
4098 assert_eq!(sm.sources[0], "srca.js");
4100 let output = sm.to_json();
4102 let sm2 = SourceMap::from_json(&output).unwrap();
4103 assert_eq!(sm2.sources[0], "srca.js");
4104 }
4105
4106 #[test]
4109 fn parse_empty_json_object() {
4110 let result = SourceMap::from_json("{}");
4112 assert!(result.is_err());
4113 }
4114
4115 #[test]
4116 fn parse_version_0() {
4117 let json = r#"{"version":0,"sources":[],"names":[],"mappings":""}"#;
4118 assert!(matches!(SourceMap::from_json(json).unwrap_err(), ParseError::InvalidVersion(0)));
4119 }
4120
4121 #[test]
4122 fn parse_version_4() {
4123 let json = r#"{"version":4,"sources":[],"names":[],"mappings":""}"#;
4124 assert!(matches!(SourceMap::from_json(json).unwrap_err(), ParseError::InvalidVersion(4)));
4125 }
4126
4127 #[test]
4128 fn parse_extra_unknown_fields_ignored() {
4129 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","x_custom_field":true,"x_debug":{"foo":"bar"}}"#;
4130 let sm = SourceMap::from_json(json).unwrap();
4131 assert_eq!(sm.mapping_count(), 1);
4132 }
4133
4134 #[test]
4135 fn parse_vlq_error_propagated() {
4136 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AA!A"}"#;
4138 let result = SourceMap::from_json(json);
4139 assert!(result.is_err());
4140 assert!(matches!(result.unwrap_err(), ParseError::Vlq(_)));
4141 }
4142
4143 #[test]
4144 fn parse_truncated_vlq_error() {
4145 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"g"}"#;
4147 let result = SourceMap::from_json(json);
4148 assert!(result.is_err());
4149 }
4150
4151 #[test]
4154 fn to_json_produces_valid_json() {
4155 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]}"#;
4156 let sm = SourceMap::from_json(json).unwrap();
4157 let output = sm.to_json();
4158 let _: serde_json::Value = serde_json::from_str(&output).unwrap();
4160 }
4161
4162 #[test]
4163 fn to_json_escapes_special_chars() {
4164 let json = r#"{"version":3,"sources":["path/with\"quotes.js"],"sourcesContent":["line1\nline2\ttab\\backslash"],"names":[],"mappings":"AAAA"}"#;
4165 let sm = SourceMap::from_json(json).unwrap();
4166 let output = sm.to_json();
4167 let _: serde_json::Value = serde_json::from_str(&output).unwrap();
4168 let sm2 = SourceMap::from_json(&output).unwrap();
4169 assert_eq!(sm2.sources_content[0].as_deref(), Some("line1\nline2\ttab\\backslash"));
4170 }
4171
4172 #[test]
4173 fn to_json_empty_map() {
4174 let json = r#"{"version":3,"sources":[],"names":[],"mappings":""}"#;
4175 let sm = SourceMap::from_json(json).unwrap();
4176 let output = sm.to_json();
4177 let sm2 = SourceMap::from_json(&output).unwrap();
4178 assert_eq!(sm2.mapping_count(), 0);
4179 assert!(sm2.sources.is_empty());
4180 }
4181
4182 #[test]
4183 fn to_json_roundtrip_with_names() {
4184 let mappings_data =
4185 vec![vec![vec![0_i64, 0, 0, 0, 0], vec![10, 0, 0, 10, 1], vec![20, 0, 1, 0, 2]]];
4186 let json = build_sourcemap_json(&["src.js"], &["foo", "bar", "baz"], &mappings_data);
4187 let sm = SourceMap::from_json(&json).unwrap();
4188 let output = sm.to_json();
4189 let sm2 = SourceMap::from_json(&output).unwrap();
4190
4191 for m in sm2.all_mappings() {
4192 if m.source != NO_SOURCE && m.name != NO_NAME {
4193 let loc = sm2.original_position_for(m.generated_line, m.generated_column).unwrap();
4194 assert!(loc.name.is_some());
4195 }
4196 }
4197 }
4198
4199 #[test]
4202 fn indexed_source_map_column_offset() {
4203 let json = r#"{
4204 "version": 3,
4205 "sections": [
4206 {
4207 "offset": {"line": 0, "column": 10},
4208 "map": {
4209 "version": 3,
4210 "sources": ["a.js"],
4211 "names": [],
4212 "mappings": "AAAA"
4213 }
4214 }
4215 ]
4216 }"#;
4217 let sm = SourceMap::from_json(json).unwrap();
4218 let loc = sm.original_position_for(0, 10).unwrap();
4220 assert_eq!(loc.line, 0);
4221 assert_eq!(loc.column, 0);
4222 assert!(sm.original_position_for(0, 0).is_none());
4224 }
4225
4226 #[test]
4227 fn indexed_source_map_column_offset_only_first_line() {
4228 let json = r#"{
4230 "version": 3,
4231 "sections": [
4232 {
4233 "offset": {"line": 0, "column": 20},
4234 "map": {
4235 "version": 3,
4236 "sources": ["a.js"],
4237 "names": [],
4238 "mappings": "AAAA;AAAA"
4239 }
4240 }
4241 ]
4242 }"#;
4243 let sm = SourceMap::from_json(json).unwrap();
4244 let loc = sm.original_position_for(0, 20).unwrap();
4246 assert_eq!(loc.column, 0);
4247 let loc = sm.original_position_for(1, 0).unwrap();
4249 assert_eq!(loc.column, 0);
4250 }
4251
4252 #[test]
4253 fn indexed_source_map_empty_section() {
4254 let json = r#"{
4255 "version": 3,
4256 "sections": [
4257 {
4258 "offset": {"line": 0, "column": 0},
4259 "map": {
4260 "version": 3,
4261 "sources": [],
4262 "names": [],
4263 "mappings": ""
4264 }
4265 },
4266 {
4267 "offset": {"line": 5, "column": 0},
4268 "map": {
4269 "version": 3,
4270 "sources": ["b.js"],
4271 "names": [],
4272 "mappings": "AAAA"
4273 }
4274 }
4275 ]
4276 }"#;
4277 let sm = SourceMap::from_json(json).unwrap();
4278 assert_eq!(sm.sources.len(), 1);
4279 let loc = sm.original_position_for(5, 0).unwrap();
4280 assert_eq!(sm.source(loc.source), "b.js");
4281 }
4282
4283 #[test]
4284 fn indexed_source_map_with_sources_content() {
4285 let json = r#"{
4286 "version": 3,
4287 "sections": [
4288 {
4289 "offset": {"line": 0, "column": 0},
4290 "map": {
4291 "version": 3,
4292 "sources": ["a.js"],
4293 "sourcesContent": ["var a = 1;"],
4294 "names": [],
4295 "mappings": "AAAA"
4296 }
4297 },
4298 {
4299 "offset": {"line": 5, "column": 0},
4300 "map": {
4301 "version": 3,
4302 "sources": ["b.js"],
4303 "sourcesContent": ["var b = 2;"],
4304 "names": [],
4305 "mappings": "AAAA"
4306 }
4307 }
4308 ]
4309 }"#;
4310 let sm = SourceMap::from_json(json).unwrap();
4311 assert_eq!(sm.sources_content.len(), 2);
4312 assert_eq!(sm.sources_content[0], Some("var a = 1;".to_string()));
4313 assert_eq!(sm.sources_content[1], Some("var b = 2;".to_string()));
4314 }
4315
4316 #[test]
4317 fn indexed_source_map_with_ignore_list() {
4318 let json = r#"{
4319 "version": 3,
4320 "sections": [
4321 {
4322 "offset": {"line": 0, "column": 0},
4323 "map": {
4324 "version": 3,
4325 "sources": ["app.js", "vendor.js"],
4326 "names": [],
4327 "mappings": "AAAA",
4328 "ignoreList": [1]
4329 }
4330 }
4331 ]
4332 }"#;
4333 let sm = SourceMap::from_json(json).unwrap();
4334 assert!(!sm.ignore_list.is_empty());
4335 }
4336
4337 #[test]
4340 fn lookup_max_column_on_line() {
4341 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0]]];
4342 let json = build_sourcemap_json(&["a.js"], &[], &mappings_data);
4343 let sm = SourceMap::from_json(&json).unwrap();
4344 let loc = sm.original_position_for(0, u32::MAX - 1).unwrap();
4346 assert_eq!(loc.line, 0);
4347 assert_eq!(loc.column, 0);
4348 }
4349
4350 #[test]
4351 fn mappings_for_line_beyond_end() {
4352 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
4353 let sm = SourceMap::from_json(json).unwrap();
4354 assert!(sm.mappings_for_line(u32::MAX).is_empty());
4355 }
4356
4357 #[test]
4358 fn source_with_unicode_path() {
4359 let json =
4360 r#"{"version":3,"sources":["src/日本語.ts"],"names":["変数"],"mappings":"AAAAA"}"#;
4361 let sm = SourceMap::from_json(json).unwrap();
4362 assert_eq!(sm.sources[0], "src/日本語.ts");
4363 assert_eq!(sm.names[0], "変数");
4364 let loc = sm.original_position_for(0, 0).unwrap();
4365 assert_eq!(sm.source(loc.source), "src/日本語.ts");
4366 assert_eq!(sm.name(loc.name.unwrap()), "変数");
4367 }
4368
4369 #[test]
4370 fn to_json_roundtrip_unicode_sources() {
4371 let json = r#"{"version":3,"sources":["src/日本語.ts"],"sourcesContent":["const 変数 = 1;"],"names":["変数"],"mappings":"AAAAA"}"#;
4372 let sm = SourceMap::from_json(json).unwrap();
4373 let output = sm.to_json();
4374 let _: serde_json::Value = serde_json::from_str(&output).unwrap();
4375 let sm2 = SourceMap::from_json(&output).unwrap();
4376 assert_eq!(sm2.sources[0], "src/日本語.ts");
4377 assert_eq!(sm2.sources_content[0], Some("const 変数 = 1;".to_string()));
4378 }
4379
4380 #[test]
4381 fn many_sources_lookup() {
4382 let sources: Vec<String> = (0..100).map(|i| format!("src/file{i}.js")).collect();
4384 let source_strs: Vec<&str> = sources.iter().map(|s| s.as_str()).collect();
4385 let mappings_data = vec![
4386 sources
4387 .iter()
4388 .enumerate()
4389 .map(|(i, _)| vec![(i * 10) as i64, i as i64, 0, 0])
4390 .collect::<Vec<_>>(),
4391 ];
4392 let json = build_sourcemap_json(&source_strs, &[], &mappings_data);
4393 let sm = SourceMap::from_json(&json).unwrap();
4394
4395 for (i, src) in sources.iter().enumerate() {
4396 assert_eq!(sm.source_index(src), Some(i as u32));
4397 }
4398 }
4399
4400 #[test]
4401 fn clone_sourcemap() {
4402 let json = r#"{"version":3,"sources":["a.js"],"names":["x"],"mappings":"AAAAA"}"#;
4403 let sm = SourceMap::from_json(json).unwrap();
4404 let sm2 = sm.clone();
4405 assert_eq!(sm2.sources, sm.sources);
4406 assert_eq!(sm2.mapping_count(), sm.mapping_count());
4407 let loc = sm2.original_position_for(0, 0).unwrap();
4408 assert_eq!(sm2.source(loc.source), "a.js");
4409 }
4410
4411 #[test]
4412 fn parse_debug_id() {
4413 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","debugId":"85314830-023f-4cf1-a267-535f4e37bb17"}"#;
4414 let sm = SourceMap::from_json(json).unwrap();
4415 assert_eq!(sm.debug_id.as_deref(), Some("85314830-023f-4cf1-a267-535f4e37bb17"));
4416 }
4417
4418 #[test]
4419 fn parse_debug_id_snake_case() {
4420 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","debug_id":"85314830-023f-4cf1-a267-535f4e37bb17"}"#;
4421 let sm = SourceMap::from_json(json).unwrap();
4422 assert_eq!(sm.debug_id.as_deref(), Some("85314830-023f-4cf1-a267-535f4e37bb17"));
4423 }
4424
4425 #[test]
4426 fn parse_no_debug_id() {
4427 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
4428 let sm = SourceMap::from_json(json).unwrap();
4429 assert_eq!(sm.debug_id, None);
4430 }
4431
4432 #[test]
4433 fn debug_id_roundtrip() {
4434 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","debugId":"85314830-023f-4cf1-a267-535f4e37bb17"}"#;
4435 let sm = SourceMap::from_json(json).unwrap();
4436 let output = sm.to_json();
4437 assert!(output.contains(r#""debugId":"85314830-023f-4cf1-a267-535f4e37bb17""#));
4438 let sm2 = SourceMap::from_json(&output).unwrap();
4439 assert_eq!(sm.debug_id, sm2.debug_id);
4440 }
4441
4442 #[test]
4443 fn debug_id_not_in_json_when_absent() {
4444 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
4445 let sm = SourceMap::from_json(json).unwrap();
4446 let output = sm.to_json();
4447 assert!(!output.contains("debugId"));
4448 }
4449
4450 fn generate_test_sourcemap(lines: usize, segs_per_line: usize, num_sources: usize) -> String {
4452 let sources: Vec<String> = (0..num_sources).map(|i| format!("src/file{i}.js")).collect();
4453 let names: Vec<String> = (0..20).map(|i| format!("var{i}")).collect();
4454
4455 let mut mappings_parts = Vec::with_capacity(lines);
4456 let mut gen_col;
4457 let mut src: i64 = 0;
4458 let mut src_line: i64 = 0;
4459 let mut src_col: i64;
4460 let mut name: i64 = 0;
4461
4462 for _ in 0..lines {
4463 gen_col = 0i64;
4464 let mut line_parts = Vec::with_capacity(segs_per_line);
4465
4466 for s in 0..segs_per_line {
4467 let gc_delta = 2 + (s as i64 * 3) % 20;
4468 gen_col += gc_delta;
4469
4470 let src_delta = i64::from(s % 7 == 0);
4471 src = (src + src_delta) % num_sources as i64;
4472
4473 src_line += 1;
4474 src_col = (s as i64 * 5 + 1) % 30;
4475
4476 let has_name = s % 4 == 0;
4477 if has_name {
4478 name = (name + 1) % names.len() as i64;
4479 }
4480
4481 let segment = if has_name {
4483 srcmap_codec::Segment::five(gen_col, src, src_line, src_col, name)
4484 } else {
4485 srcmap_codec::Segment::four(gen_col, src, src_line, src_col)
4486 };
4487
4488 line_parts.push(segment);
4489 }
4490
4491 mappings_parts.push(line_parts);
4492 }
4493
4494 let encoded = srcmap_codec::encode(&mappings_parts);
4495
4496 format!(
4497 r#"{{"version":3,"sources":[{}],"names":[{}],"mappings":"{}"}}"#,
4498 sources.iter().map(|s| format!("\"{s}\"")).collect::<Vec<_>>().join(","),
4499 names.iter().map(|n| format!("\"{n}\"")).collect::<Vec<_>>().join(","),
4500 encoded,
4501 )
4502 }
4503
4504 fn bias_map() -> &'static str {
4509 r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA,KAAK,KAAK"}"#
4511 }
4512
4513 #[test]
4514 fn original_position_glb_exact_match() {
4515 let sm = SourceMap::from_json(bias_map()).unwrap();
4516 let loc = sm.original_position_for_with_bias(0, 5, Bias::GreatestLowerBound).unwrap();
4517 assert_eq!(loc.column, 5);
4518 }
4519
4520 #[test]
4521 fn original_position_glb_snaps_left() {
4522 let sm = SourceMap::from_json(bias_map()).unwrap();
4523 let loc = sm.original_position_for_with_bias(0, 7, Bias::GreatestLowerBound).unwrap();
4525 assert_eq!(loc.column, 5);
4526 }
4527
4528 #[test]
4529 fn original_position_lub_exact_match() {
4530 let sm = SourceMap::from_json(bias_map()).unwrap();
4531 let loc = sm.original_position_for_with_bias(0, 5, Bias::LeastUpperBound).unwrap();
4532 assert_eq!(loc.column, 5);
4533 }
4534
4535 #[test]
4536 fn original_position_lub_snaps_right() {
4537 let sm = SourceMap::from_json(bias_map()).unwrap();
4538 let loc = sm.original_position_for_with_bias(0, 3, Bias::LeastUpperBound).unwrap();
4540 assert_eq!(loc.column, 5);
4541 }
4542
4543 #[test]
4544 fn original_position_lub_before_first() {
4545 let sm = SourceMap::from_json(bias_map()).unwrap();
4546 let loc = sm.original_position_for_with_bias(0, 0, Bias::LeastUpperBound).unwrap();
4548 assert_eq!(loc.column, 0);
4549 }
4550
4551 #[test]
4552 fn original_position_lub_after_last() {
4553 let sm = SourceMap::from_json(bias_map()).unwrap();
4554 let loc = sm.original_position_for_with_bias(0, 15, Bias::LeastUpperBound);
4556 assert!(loc.is_none());
4557 }
4558
4559 #[test]
4560 fn original_position_glb_before_first() {
4561 let sm = SourceMap::from_json(bias_map()).unwrap();
4562 let loc = sm.original_position_for_with_bias(0, 0, Bias::GreatestLowerBound).unwrap();
4564 assert_eq!(loc.column, 0);
4565 }
4566
4567 #[test]
4568 fn generated_position_lub() {
4569 let sm = SourceMap::from_json(bias_map()).unwrap();
4570 let loc =
4572 sm.generated_position_for_with_bias("input.js", 0, 3, Bias::LeastUpperBound).unwrap();
4573 assert_eq!(loc.column, 5);
4574 }
4575
4576 #[test]
4577 fn generated_position_glb() {
4578 let sm = SourceMap::from_json(bias_map()).unwrap();
4579 let loc = sm
4581 .generated_position_for_with_bias("input.js", 0, 7, Bias::GreatestLowerBound)
4582 .unwrap();
4583 assert_eq!(loc.column, 5);
4584 }
4585
4586 #[test]
4587 fn generated_position_for_default_bias_is_glb() {
4588 let sm = SourceMap::from_json(bias_map()).unwrap();
4591 let glb = sm.generated_position_for("input.js", 0, 7).unwrap();
4593 let glb_explicit = sm
4594 .generated_position_for_with_bias("input.js", 0, 7, Bias::GreatestLowerBound)
4595 .unwrap();
4596 assert_eq!(glb.line, glb_explicit.line);
4597 assert_eq!(glb.column, glb_explicit.column);
4598 }
4599
4600 #[test]
4603 fn map_range_basic() {
4604 let sm = SourceMap::from_json(bias_map()).unwrap();
4605 let range = sm.map_range(0, 0, 0, 10).unwrap();
4606 assert_eq!(range.source, 0);
4607 assert_eq!(range.original_start_line, 0);
4608 assert_eq!(range.original_start_column, 0);
4609 assert_eq!(range.original_end_line, 0);
4610 assert_eq!(range.original_end_column, 10);
4611 }
4612
4613 #[test]
4614 fn map_range_no_mapping() {
4615 let sm = SourceMap::from_json(bias_map()).unwrap();
4616 let range = sm.map_range(0, 0, 5, 0);
4618 assert!(range.is_none());
4619 }
4620
4621 #[test]
4622 fn map_range_different_sources() {
4623 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":[],"mappings":"AAAA;ACAA"}"#;
4625 let sm = SourceMap::from_json(json).unwrap();
4626 let range = sm.map_range(0, 0, 1, 0);
4628 assert!(range.is_none());
4629 }
4630
4631 #[test]
4634 fn extension_fields_preserved() {
4635 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","x_facebook_sources":[[{"names":["<global>"]}]],"x_google_linecount":42}"#;
4636 let sm = SourceMap::from_json(json).unwrap();
4637
4638 assert!(sm.extensions.contains_key("x_facebook_sources"));
4639 assert!(sm.extensions.contains_key("x_google_linecount"));
4640 assert_eq!(sm.extensions.get("x_google_linecount"), Some(&serde_json::json!(42)));
4641
4642 let output = sm.to_json();
4644 assert!(output.contains("x_facebook_sources"));
4645 assert!(output.contains("x_google_linecount"));
4646 }
4647
4648 #[test]
4649 fn x_google_ignorelist_fallback() {
4650 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":[],"mappings":"AAAA","x_google_ignoreList":[1]}"#;
4651 let sm = SourceMap::from_json(json).unwrap();
4652 assert_eq!(sm.ignore_list, vec![1]);
4653 }
4654
4655 #[test]
4656 fn ignorelist_takes_precedence_over_x_google() {
4657 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":[],"mappings":"AAAA","ignoreList":[0],"x_google_ignoreList":[1]}"#;
4658 let sm = SourceMap::from_json(json).unwrap();
4659 assert_eq!(sm.ignore_list, vec![0]);
4660 }
4661
4662 #[test]
4663 fn source_mapping_url_external() {
4664 let source = "var a = 1;\n//# sourceMappingURL=app.js.map\n";
4665 let result = parse_source_mapping_url(source).unwrap();
4666 assert_eq!(result, SourceMappingUrl::External("app.js.map".to_string()));
4667 }
4668
4669 #[test]
4670 fn source_mapping_url_inline() {
4671 let json = r#"{"version":3,"sources":[],"names":[],"mappings":""}"#;
4672 let b64 = base64_encode_simple(json);
4673 let source =
4674 format!("var a = 1;\n//# sourceMappingURL=data:application/json;base64,{b64}\n");
4675 match parse_source_mapping_url(&source).unwrap() {
4676 SourceMappingUrl::Inline(decoded) => {
4677 assert_eq!(decoded, json);
4678 }
4679 SourceMappingUrl::External(_) => panic!("expected inline"),
4680 }
4681 }
4682
4683 #[test]
4684 fn source_mapping_url_at_sign() {
4685 let source = "var a = 1;\n//@ sourceMappingURL=old-style.map";
4686 let result = parse_source_mapping_url(source).unwrap();
4687 assert_eq!(result, SourceMappingUrl::External("old-style.map".to_string()));
4688 }
4689
4690 #[test]
4691 fn source_mapping_url_css_comment() {
4692 let source = "body { }\n/*# sourceMappingURL=styles.css.map */";
4693 let result = parse_source_mapping_url(source).unwrap();
4694 assert_eq!(result, SourceMappingUrl::External("styles.css.map".to_string()));
4695 }
4696
4697 #[test]
4698 fn source_mapping_url_none() {
4699 let source = "var a = 1;";
4700 assert!(parse_source_mapping_url(source).is_none());
4701 }
4702
4703 #[test]
4704 fn exclude_content_option() {
4705 let json = r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":[],"mappings":"AAAA"}"#;
4706 let sm = SourceMap::from_json(json).unwrap();
4707
4708 let with_content = sm.to_json();
4709 assert!(with_content.contains("sourcesContent"));
4710
4711 let without_content = sm.to_json_with_options(true);
4712 assert!(!without_content.contains("sourcesContent"));
4713 }
4714
4715 #[test]
4716 fn validate_deep_clean_map() {
4717 let sm = SourceMap::from_json(simple_map()).unwrap();
4718 let warnings = validate_deep(&sm);
4719 assert!(warnings.is_empty(), "unexpected warnings: {warnings:?}");
4720 }
4721
4722 #[test]
4723 fn validate_deep_unreferenced_source() {
4724 let json =
4726 r#"{"version":3,"sources":["used.js","unused.js"],"names":[],"mappings":"AAAA"}"#;
4727 let sm = SourceMap::from_json(json).unwrap();
4728 let warnings = validate_deep(&sm);
4729 assert!(warnings.iter().any(|w| w.contains("unused.js")));
4730 }
4731
4732 #[test]
4735 fn from_parts_basic() {
4736 let mappings = vec![
4737 Mapping {
4738 generated_line: 0,
4739 generated_column: 0,
4740 source: 0,
4741 original_line: 0,
4742 original_column: 0,
4743 name: NO_NAME,
4744 is_range_mapping: false,
4745 },
4746 Mapping {
4747 generated_line: 1,
4748 generated_column: 4,
4749 source: 0,
4750 original_line: 1,
4751 original_column: 2,
4752 name: NO_NAME,
4753 is_range_mapping: false,
4754 },
4755 ];
4756
4757 let sm = SourceMap::from_parts(
4758 Some("out.js".to_string()),
4759 None,
4760 vec!["input.js".to_string()],
4761 vec![Some("var x = 1;".to_string())],
4762 vec![],
4763 mappings,
4764 vec![],
4765 None,
4766 None,
4767 );
4768
4769 assert_eq!(sm.line_count(), 2);
4770 assert_eq!(sm.mapping_count(), 2);
4771
4772 let loc = sm.original_position_for(0, 0).unwrap();
4773 assert_eq!(loc.source, 0);
4774 assert_eq!(loc.line, 0);
4775 assert_eq!(loc.column, 0);
4776
4777 let loc = sm.original_position_for(1, 4).unwrap();
4778 assert_eq!(loc.line, 1);
4779 assert_eq!(loc.column, 2);
4780 }
4781
4782 #[test]
4783 fn from_parts_empty() {
4784 let sm =
4785 SourceMap::from_parts(None, None, vec![], vec![], vec![], vec![], vec![], None, None);
4786 assert_eq!(sm.line_count(), 0);
4787 assert_eq!(sm.mapping_count(), 0);
4788 assert!(sm.original_position_for(0, 0).is_none());
4789 }
4790
4791 #[test]
4792 fn from_parts_with_names() {
4793 let mappings = vec![Mapping {
4794 generated_line: 0,
4795 generated_column: 0,
4796 source: 0,
4797 original_line: 0,
4798 original_column: 0,
4799 name: 0,
4800 is_range_mapping: false,
4801 }];
4802
4803 let sm = SourceMap::from_parts(
4804 None,
4805 None,
4806 vec!["input.js".to_string()],
4807 vec![],
4808 vec!["myVar".to_string()],
4809 mappings,
4810 vec![],
4811 None,
4812 None,
4813 );
4814
4815 let loc = sm.original_position_for(0, 0).unwrap();
4816 assert_eq!(loc.name, Some(0));
4817 assert_eq!(sm.name(0), "myVar");
4818 }
4819
4820 #[test]
4821 fn from_parts_roundtrip_via_json() {
4822 let json = generate_test_sourcemap(50, 10, 3);
4823 let sm = SourceMap::from_json(&json).unwrap();
4824
4825 let sm2 = SourceMap::from_parts(
4826 sm.file.clone(),
4827 sm.source_root.clone(),
4828 sm.sources.clone(),
4829 sm.sources_content.clone(),
4830 sm.names.clone(),
4831 sm.all_mappings().to_vec(),
4832 sm.ignore_list.clone(),
4833 sm.debug_id.clone(),
4834 None,
4835 );
4836
4837 assert_eq!(sm2.mapping_count(), sm.mapping_count());
4838 assert_eq!(sm2.line_count(), sm.line_count());
4839
4840 for m in sm.all_mappings() {
4842 if m.source != NO_SOURCE {
4843 let a = sm.original_position_for(m.generated_line, m.generated_column);
4844 let b = sm2.original_position_for(m.generated_line, m.generated_column);
4845 match (a, b) {
4846 (Some(a), Some(b)) => {
4847 assert_eq!(a.source, b.source);
4848 assert_eq!(a.line, b.line);
4849 assert_eq!(a.column, b.column);
4850 }
4851 (None, None) => {}
4852 _ => panic!("mismatch at ({}, {})", m.generated_line, m.generated_column),
4853 }
4854 }
4855 }
4856 }
4857
4858 #[test]
4859 fn from_parts_reverse_lookup() {
4860 let mappings = vec![
4861 Mapping {
4862 generated_line: 0,
4863 generated_column: 0,
4864 source: 0,
4865 original_line: 10,
4866 original_column: 5,
4867 name: NO_NAME,
4868 is_range_mapping: false,
4869 },
4870 Mapping {
4871 generated_line: 1,
4872 generated_column: 8,
4873 source: 0,
4874 original_line: 20,
4875 original_column: 0,
4876 name: NO_NAME,
4877 is_range_mapping: false,
4878 },
4879 ];
4880
4881 let sm = SourceMap::from_parts(
4882 None,
4883 None,
4884 vec!["src.js".to_string()],
4885 vec![],
4886 vec![],
4887 mappings,
4888 vec![],
4889 None,
4890 None,
4891 );
4892
4893 let loc = sm.generated_position_for("src.js", 10, 5).unwrap();
4894 assert_eq!(loc.line, 0);
4895 assert_eq!(loc.column, 0);
4896
4897 let loc = sm.generated_position_for("src.js", 20, 0).unwrap();
4898 assert_eq!(loc.line, 1);
4899 assert_eq!(loc.column, 8);
4900 }
4901
4902 #[test]
4903 fn from_parts_sparse_lines() {
4904 let mappings = vec![
4905 Mapping {
4906 generated_line: 0,
4907 generated_column: 0,
4908 source: 0,
4909 original_line: 0,
4910 original_column: 0,
4911 name: NO_NAME,
4912 is_range_mapping: false,
4913 },
4914 Mapping {
4915 generated_line: 5,
4916 generated_column: 0,
4917 source: 0,
4918 original_line: 5,
4919 original_column: 0,
4920 name: NO_NAME,
4921 is_range_mapping: false,
4922 },
4923 ];
4924
4925 let sm = SourceMap::from_parts(
4926 None,
4927 None,
4928 vec!["src.js".to_string()],
4929 vec![],
4930 vec![],
4931 mappings,
4932 vec![],
4933 None,
4934 None,
4935 );
4936
4937 assert_eq!(sm.line_count(), 6);
4938 assert!(sm.original_position_for(0, 0).is_some());
4939 assert!(sm.original_position_for(2, 0).is_none());
4940 assert!(sm.original_position_for(5, 0).is_some());
4941 }
4942
4943 #[test]
4946 fn from_json_lines_basic() {
4947 let json = generate_test_sourcemap(10, 5, 2);
4948 let sm_full = SourceMap::from_json(&json).unwrap();
4949
4950 let sm_partial = SourceMap::from_json_lines(&json, 3, 7).unwrap();
4952
4953 for line in 3..7u32 {
4955 let full_mappings = sm_full.mappings_for_line(line);
4956 let partial_mappings = sm_partial.mappings_for_line(line);
4957 assert_eq!(
4958 full_mappings.len(),
4959 partial_mappings.len(),
4960 "line {line} mapping count mismatch"
4961 );
4962 for (a, b) in full_mappings.iter().zip(partial_mappings.iter()) {
4963 assert_eq!(a.generated_column, b.generated_column);
4964 assert_eq!(a.source, b.source);
4965 assert_eq!(a.original_line, b.original_line);
4966 assert_eq!(a.original_column, b.original_column);
4967 assert_eq!(a.name, b.name);
4968 }
4969 }
4970 }
4971
4972 #[test]
4973 fn from_json_lines_first_lines() {
4974 let json = generate_test_sourcemap(10, 5, 2);
4975 let sm_full = SourceMap::from_json(&json).unwrap();
4976 let sm_partial = SourceMap::from_json_lines(&json, 0, 3).unwrap();
4977
4978 for line in 0..3u32 {
4979 let full_mappings = sm_full.mappings_for_line(line);
4980 let partial_mappings = sm_partial.mappings_for_line(line);
4981 assert_eq!(full_mappings.len(), partial_mappings.len());
4982 }
4983 }
4984
4985 #[test]
4986 fn from_json_lines_last_lines() {
4987 let json = generate_test_sourcemap(10, 5, 2);
4988 let sm_full = SourceMap::from_json(&json).unwrap();
4989 let sm_partial = SourceMap::from_json_lines(&json, 7, 10).unwrap();
4990
4991 for line in 7..10u32 {
4992 let full_mappings = sm_full.mappings_for_line(line);
4993 let partial_mappings = sm_partial.mappings_for_line(line);
4994 assert_eq!(full_mappings.len(), partial_mappings.len(), "line {line}");
4995 }
4996 }
4997
4998 #[test]
4999 fn from_json_lines_empty_range() {
5000 let json = generate_test_sourcemap(10, 5, 2);
5001 let sm = SourceMap::from_json_lines(&json, 5, 5).unwrap();
5002 assert_eq!(sm.mapping_count(), 0);
5003 }
5004
5005 #[test]
5006 fn from_json_lines_beyond_end() {
5007 let json = generate_test_sourcemap(5, 3, 1);
5008 let sm = SourceMap::from_json_lines(&json, 3, 100).unwrap();
5010 assert!(sm.mapping_count() > 0);
5012 }
5013
5014 #[test]
5015 fn from_json_lines_single_line() {
5016 let json = generate_test_sourcemap(10, 5, 2);
5017 let sm_full = SourceMap::from_json(&json).unwrap();
5018 let sm_partial = SourceMap::from_json_lines(&json, 5, 6).unwrap();
5019
5020 let full_mappings = sm_full.mappings_for_line(5);
5021 let partial_mappings = sm_partial.mappings_for_line(5);
5022 assert_eq!(full_mappings.len(), partial_mappings.len());
5023 }
5024
5025 #[test]
5028 fn lazy_basic_lookup() {
5029 let json = r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA;AACA"}"#;
5030 let sm = LazySourceMap::from_json(json).unwrap();
5031
5032 assert_eq!(sm.line_count(), 2);
5033 assert_eq!(sm.sources, vec!["input.js"]);
5034
5035 let loc = sm.original_position_for(0, 0).unwrap();
5036 assert_eq!(sm.source(loc.source), "input.js");
5037 assert_eq!(loc.line, 0);
5038 assert_eq!(loc.column, 0);
5039 }
5040
5041 #[test]
5042 fn lazy_multiple_lines() {
5043 let json = generate_test_sourcemap(20, 5, 3);
5044 let sm_eager = SourceMap::from_json(&json).unwrap();
5045 let sm_lazy = LazySourceMap::from_json(&json).unwrap();
5046
5047 assert_eq!(sm_lazy.line_count(), sm_eager.line_count());
5048
5049 for m in sm_eager.all_mappings() {
5051 if m.source == NO_SOURCE {
5052 continue;
5053 }
5054 let eager_loc =
5055 sm_eager.original_position_for(m.generated_line, m.generated_column).unwrap();
5056 let lazy_loc =
5057 sm_lazy.original_position_for(m.generated_line, m.generated_column).unwrap();
5058 assert_eq!(eager_loc.source, lazy_loc.source);
5059 assert_eq!(eager_loc.line, lazy_loc.line);
5060 assert_eq!(eager_loc.column, lazy_loc.column);
5061 assert_eq!(eager_loc.name, lazy_loc.name);
5062 }
5063 }
5064
5065 #[test]
5066 fn lazy_empty_mappings() {
5067 let json = r#"{"version":3,"sources":[],"names":[],"mappings":""}"#;
5068 let sm = LazySourceMap::from_json(json).unwrap();
5069 assert_eq!(sm.line_count(), 0);
5070 assert!(sm.original_position_for(0, 0).is_none());
5071 }
5072
5073 #[test]
5074 fn lazy_empty_lines() {
5075 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;;;AACA"}"#;
5076 let sm = LazySourceMap::from_json(json).unwrap();
5077 assert_eq!(sm.line_count(), 4);
5078
5079 assert!(sm.original_position_for(0, 0).is_some());
5080 assert!(sm.original_position_for(1, 0).is_none());
5081 assert!(sm.original_position_for(2, 0).is_none());
5082 assert!(sm.original_position_for(3, 0).is_some());
5083 }
5084
5085 #[test]
5086 fn lazy_decode_line_caching() {
5087 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,KACA;AACA"}"#;
5088 let sm = LazySourceMap::from_json(json).unwrap();
5089
5090 let line0_a = sm.decode_line(0).unwrap();
5092 let line0_b = sm.decode_line(0).unwrap();
5094 assert_eq!(line0_a.len(), line0_b.len());
5095 assert_eq!(line0_a[0].generated_column, line0_b[0].generated_column);
5096 }
5097
5098 #[test]
5099 fn lazy_with_names() {
5100 let json = r#"{"version":3,"sources":["input.js"],"names":["foo","bar"],"mappings":"AAAAA,KACAC"}"#;
5101 let sm = LazySourceMap::from_json(json).unwrap();
5102
5103 let loc = sm.original_position_for(0, 0).unwrap();
5104 assert_eq!(loc.name, Some(0));
5105 assert_eq!(sm.name(0), "foo");
5106
5107 let loc = sm.original_position_for(0, 5).unwrap();
5108 assert_eq!(loc.name, Some(1));
5109 assert_eq!(sm.name(1), "bar");
5110 }
5111
5112 #[test]
5113 fn lazy_nonexistent_line() {
5114 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
5115 let sm = LazySourceMap::from_json(json).unwrap();
5116 assert!(sm.original_position_for(99, 0).is_none());
5117 let line = sm.decode_line(99).unwrap();
5118 assert!(line.is_empty());
5119 }
5120
5121 #[test]
5122 fn lazy_into_sourcemap() {
5123 let json = generate_test_sourcemap(20, 5, 3);
5124 let sm_eager = SourceMap::from_json(&json).unwrap();
5125 let sm_lazy = LazySourceMap::from_json(&json).unwrap();
5126 let sm_converted = sm_lazy.into_sourcemap().unwrap();
5127
5128 assert_eq!(sm_converted.mapping_count(), sm_eager.mapping_count());
5129 assert_eq!(sm_converted.line_count(), sm_eager.line_count());
5130
5131 for m in sm_eager.all_mappings() {
5133 let a = sm_eager.original_position_for(m.generated_line, m.generated_column);
5134 let b = sm_converted.original_position_for(m.generated_line, m.generated_column);
5135 match (a, b) {
5136 (Some(a), Some(b)) => {
5137 assert_eq!(a.source, b.source);
5138 assert_eq!(a.line, b.line);
5139 assert_eq!(a.column, b.column);
5140 }
5141 (None, None) => {}
5142 _ => panic!("mismatch at ({}, {})", m.generated_line, m.generated_column),
5143 }
5144 }
5145 }
5146
5147 #[test]
5148 fn lazy_source_index_lookup() {
5149 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":[],"mappings":"AAAA;ACAA"}"#;
5150 let sm = LazySourceMap::from_json(json).unwrap();
5151 assert_eq!(sm.source_index("a.js"), Some(0));
5152 assert_eq!(sm.source_index("b.js"), Some(1));
5153 assert_eq!(sm.source_index("c.js"), None);
5154 }
5155
5156 #[test]
5157 fn lazy_mappings_for_line() {
5158 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,KACA;AACA"}"#;
5159 let sm = LazySourceMap::from_json(json).unwrap();
5160
5161 let line0 = sm.mappings_for_line(0);
5162 assert_eq!(line0.len(), 2);
5163
5164 let line1 = sm.mappings_for_line(1);
5165 assert_eq!(line1.len(), 1);
5166
5167 let line99 = sm.mappings_for_line(99);
5168 assert!(line99.is_empty());
5169 }
5170
5171 #[test]
5172 fn lazy_large_map_selective_decode() {
5173 let json = generate_test_sourcemap(100, 10, 5);
5175 let sm_eager = SourceMap::from_json(&json).unwrap();
5176 let sm_lazy = LazySourceMap::from_json(&json).unwrap();
5177
5178 for line in [50, 75] {
5180 let eager_mappings = sm_eager.mappings_for_line(line);
5181 let lazy_mappings = sm_lazy.mappings_for_line(line);
5182 assert_eq!(eager_mappings.len(), lazy_mappings.len(), "line {line} count mismatch");
5183 for (a, b) in eager_mappings.iter().zip(lazy_mappings.iter()) {
5184 assert_eq!(a.generated_column, b.generated_column);
5185 assert_eq!(a.source, b.source);
5186 assert_eq!(a.original_line, b.original_line);
5187 assert_eq!(a.original_column, b.original_column);
5188 assert_eq!(a.name, b.name);
5189 }
5190 }
5191 }
5192
5193 #[test]
5194 fn lazy_single_field_segments() {
5195 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A,KAAAA"}"#;
5196 let sm = LazySourceMap::from_json(json).unwrap();
5197
5198 assert!(sm.original_position_for(0, 0).is_none());
5200 let loc = sm.original_position_for(0, 5).unwrap();
5202 assert_eq!(loc.source, 0);
5203 }
5204
5205 #[test]
5208 fn parse_error_display_vlq() {
5209 let err = ParseError::Vlq(srcmap_codec::DecodeError::UnexpectedEof { offset: 3 });
5210 assert!(err.to_string().contains("VLQ decode error"));
5211 }
5212
5213 #[test]
5214 fn parse_error_display_scopes() {
5215 let err = ParseError::Scopes(srcmap_scopes::ScopesError::UnclosedScope);
5216 assert!(err.to_string().contains("scopes decode error"));
5217 }
5218
5219 #[test]
5220 fn indexed_map_with_names_in_sections() {
5221 let json = r#"{
5222 "version": 3,
5223 "sections": [
5224 {
5225 "offset": {"line": 0, "column": 0},
5226 "map": {
5227 "version": 3,
5228 "sources": ["a.js"],
5229 "names": ["foo"],
5230 "mappings": "AAAAA"
5231 }
5232 },
5233 {
5234 "offset": {"line": 1, "column": 0},
5235 "map": {
5236 "version": 3,
5237 "sources": ["a.js"],
5238 "names": ["foo"],
5239 "mappings": "AAAAA"
5240 }
5241 }
5242 ]
5243 }"#;
5244 let sm = SourceMap::from_json(json).unwrap();
5245 assert_eq!(sm.sources.len(), 1);
5247 assert_eq!(sm.names.len(), 1);
5248 }
5249
5250 #[test]
5251 fn indexed_map_with_ignore_list() {
5252 let json = r#"{
5253 "version": 3,
5254 "sections": [
5255 {
5256 "offset": {"line": 0, "column": 0},
5257 "map": {
5258 "version": 3,
5259 "sources": ["vendor.js"],
5260 "names": [],
5261 "mappings": "AAAA",
5262 "ignoreList": [0]
5263 }
5264 }
5265 ]
5266 }"#;
5267 let sm = SourceMap::from_json(json).unwrap();
5268 assert_eq!(sm.ignore_list, vec![0]);
5269 }
5270
5271 #[test]
5272 fn indexed_map_with_generated_only_segment() {
5273 let json = r#"{
5275 "version": 3,
5276 "sections": [
5277 {
5278 "offset": {"line": 0, "column": 0},
5279 "map": {
5280 "version": 3,
5281 "sources": ["a.js"],
5282 "names": [],
5283 "mappings": "A,AAAA"
5284 }
5285 }
5286 ]
5287 }"#;
5288 let sm = SourceMap::from_json(json).unwrap();
5289 assert!(sm.mapping_count() >= 1);
5290 }
5291
5292 #[test]
5293 fn indexed_map_empty_mappings() {
5294 let json = r#"{
5295 "version": 3,
5296 "sections": [
5297 {
5298 "offset": {"line": 0, "column": 0},
5299 "map": {
5300 "version": 3,
5301 "sources": [],
5302 "names": [],
5303 "mappings": ""
5304 }
5305 }
5306 ]
5307 }"#;
5308 let sm = SourceMap::from_json(json).unwrap();
5309 assert_eq!(sm.mapping_count(), 0);
5310 }
5311
5312 #[test]
5313 fn generated_position_glb_exact_match() {
5314 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAE,OAAO"}"#;
5315 let sm = SourceMap::from_json(json).unwrap();
5316
5317 let loc = sm.generated_position_for_with_bias("a.js", 0, 0, Bias::GreatestLowerBound);
5318 assert!(loc.is_some());
5319 assert_eq!(loc.unwrap().column, 0);
5320 }
5321
5322 #[test]
5323 fn generated_position_glb_no_exact_match() {
5324 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAE"}"#;
5325 let sm = SourceMap::from_json(json).unwrap();
5326
5327 let loc = sm.generated_position_for_with_bias("a.js", 0, 0, Bias::GreatestLowerBound);
5329 assert!(loc.is_some());
5330 }
5331
5332 #[test]
5333 fn generated_position_glb_wrong_source() {
5334 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":[],"mappings":"AAAA,KCCA"}"#;
5335 let sm = SourceMap::from_json(json).unwrap();
5336
5337 let loc = sm.generated_position_for_with_bias("b.js", 5, 0, Bias::GreatestLowerBound);
5339 if let Some(l) = loc {
5342 assert_eq!(l.line, 0);
5344 }
5345 }
5346
5347 #[test]
5348 fn generated_position_lub_wrong_source() {
5349 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
5350 let sm = SourceMap::from_json(json).unwrap();
5351
5352 let loc =
5354 sm.generated_position_for_with_bias("nonexistent.js", 0, 0, Bias::LeastUpperBound);
5355 assert!(loc.is_none());
5356 }
5357
5358 #[test]
5359 fn to_json_with_ignore_list() {
5360 let json = r#"{"version":3,"sources":["vendor.js"],"names":[],"mappings":"AAAA","ignoreList":[0]}"#;
5361 let sm = SourceMap::from_json(json).unwrap();
5362 let output = sm.to_json();
5363 assert!(output.contains("\"ignoreList\":[0]"));
5364 }
5365
5366 #[test]
5367 fn to_json_with_extensions() {
5368 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","x_custom":"test_value"}"#;
5369 let sm = SourceMap::from_json(json).unwrap();
5370 let output = sm.to_json();
5371 assert!(output.contains("x_custom"));
5372 assert!(output.contains("test_value"));
5373 }
5374
5375 #[test]
5376 fn from_parts_empty_mappings() {
5377 let sm = SourceMap::from_parts(
5378 None,
5379 None,
5380 vec!["a.js".to_string()],
5381 vec![Some("content".to_string())],
5382 vec![],
5383 vec![],
5384 vec![],
5385 None,
5386 None,
5387 );
5388 assert_eq!(sm.mapping_count(), 0);
5389 assert_eq!(sm.sources, vec!["a.js"]);
5390 }
5391
5392 #[test]
5393 fn from_vlq_basic() {
5394 let sm = SourceMap::from_vlq(
5395 "AAAA;AACA",
5396 vec!["a.js".to_string()],
5397 vec![],
5398 Some("out.js".to_string()),
5399 None,
5400 vec![Some("content".to_string())],
5401 vec![],
5402 None,
5403 )
5404 .unwrap();
5405
5406 assert_eq!(sm.file.as_deref(), Some("out.js"));
5407 assert_eq!(sm.sources, vec!["a.js"]);
5408 let loc = sm.original_position_for(0, 0).unwrap();
5409 assert_eq!(sm.source(loc.source), "a.js");
5410 assert_eq!(loc.line, 0);
5411 }
5412
5413 #[test]
5414 fn from_json_lines_basic_coverage() {
5415 let json =
5416 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA"}"#;
5417 let sm = SourceMap::from_json_lines(json, 1, 3).unwrap();
5418 assert!(sm.original_position_for(1, 0).is_some());
5420 assert!(sm.original_position_for(2, 0).is_some());
5421 }
5422
5423 #[test]
5424 fn from_json_lines_with_source_root() {
5425 let json = r#"{"version":3,"sourceRoot":"src/","sources":["a.js"],"names":[],"mappings":"AAAA;AACA"}"#;
5426 let sm = SourceMap::from_json_lines(json, 0, 2).unwrap();
5427 assert_eq!(sm.sources[0], "src/a.js");
5428 }
5429
5430 #[test]
5431 fn from_json_lines_with_null_source() {
5432 let json = r#"{"version":3,"sources":[null,"a.js"],"names":[],"mappings":"AAAA,KCCA"}"#;
5433 let sm = SourceMap::from_json_lines(json, 0, 1).unwrap();
5434 assert_eq!(sm.sources.len(), 2);
5435 }
5436
5437 #[test]
5438 fn json_escaping_special_chars_sourcemap() {
5439 let json = r#"{"version":3,"sources":["path/with\nnewline.js"],"sourcesContent":["line1\r\nline2\t\"quoted\"\\\u0001"],"names":[],"mappings":"AAAA"}"#;
5442 let sm = SourceMap::from_json(json).unwrap();
5443 let output = sm.to_json();
5445 let sm2 = SourceMap::from_json(&output).unwrap();
5446 assert_eq!(sm.sources[0], sm2.sources[0]);
5447 assert_eq!(sm.sources_content[0], sm2.sources_content[0]);
5448 }
5449
5450 #[test]
5451 fn to_json_exclude_content() {
5452 let json = r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":[],"mappings":"AAAA"}"#;
5453 let sm = SourceMap::from_json(json).unwrap();
5454 let output = sm.to_json_with_options(true);
5455 assert!(!output.contains("sourcesContent"));
5456 let output_with = sm.to_json_with_options(false);
5457 assert!(output_with.contains("sourcesContent"));
5458 }
5459
5460 #[test]
5461 fn encode_mappings_with_name() {
5462 let json = r#"{"version":3,"sources":["a.js"],"names":["foo"],"mappings":"AAAAA"}"#;
5464 let sm = SourceMap::from_json(json).unwrap();
5465 let encoded = sm.encode_mappings();
5466 assert_eq!(encoded, "AAAAA");
5467 }
5468
5469 #[test]
5470 fn encode_mappings_generated_only() {
5471 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A,AAAA"}"#;
5473 let sm = SourceMap::from_json(json).unwrap();
5474 let encoded = sm.encode_mappings();
5475 let roundtrip = SourceMap::from_json(&format!(
5476 r#"{{"version":3,"sources":["a.js"],"names":[],"mappings":"{}"}}"#,
5477 encoded
5478 ))
5479 .unwrap();
5480 assert_eq!(roundtrip.mapping_count(), sm.mapping_count());
5481 }
5482
5483 #[test]
5484 fn map_range_single_result() {
5485 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAC,OAAO"}"#;
5486 let sm = SourceMap::from_json(json).unwrap();
5487 let result = sm.map_range(0, 0, 0, 1);
5489 assert!(result.is_some());
5490 let range = result.unwrap();
5491 assert_eq!(range.source, 0);
5492 }
5493
5494 #[test]
5495 fn scopes_in_from_json() {
5496 let info = srcmap_scopes::ScopeInfo {
5498 scopes: vec![Some(srcmap_scopes::OriginalScope {
5499 start: srcmap_scopes::Position { line: 0, column: 0 },
5500 end: srcmap_scopes::Position { line: 5, column: 0 },
5501 name: None,
5502 kind: None,
5503 is_stack_frame: false,
5504 variables: vec![],
5505 children: vec![],
5506 })],
5507 ranges: vec![],
5508 };
5509 let mut names = vec![];
5510 let scopes_str = srcmap_scopes::encode_scopes(&info, &mut names);
5511
5512 let json = format!(
5513 r#"{{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","scopes":"{scopes_str}"}}"#
5514 );
5515
5516 let sm = SourceMap::from_json(&json).unwrap();
5517 assert!(sm.scopes.is_some());
5518 }
5519
5520 #[test]
5521 fn from_json_lines_with_scopes() {
5522 let info = srcmap_scopes::ScopeInfo {
5523 scopes: vec![Some(srcmap_scopes::OriginalScope {
5524 start: srcmap_scopes::Position { line: 0, column: 0 },
5525 end: srcmap_scopes::Position { line: 5, column: 0 },
5526 name: None,
5527 kind: None,
5528 is_stack_frame: false,
5529 variables: vec![],
5530 children: vec![],
5531 })],
5532 ranges: vec![],
5533 };
5534 let mut names = vec![];
5535 let scopes_str = srcmap_scopes::encode_scopes(&info, &mut names);
5536 let json = format!(
5537 r#"{{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;AACA","scopes":"{scopes_str}"}}"#
5538 );
5539 let sm = SourceMap::from_json_lines(&json, 0, 2).unwrap();
5540 assert!(sm.scopes.is_some());
5541 }
5542
5543 #[test]
5544 fn from_json_lines_with_extensions() {
5545 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","x_custom":"val","not_x":"skip"}"#;
5546 let sm = SourceMap::from_json_lines(json, 0, 1).unwrap();
5547 assert!(sm.extensions.contains_key("x_custom"));
5548 assert!(!sm.extensions.contains_key("not_x"));
5549 }
5550
5551 #[test]
5552 fn lazy_sourcemap_version_error() {
5553 let json = r#"{"version":2,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
5554 let err = LazySourceMap::from_json(json).unwrap_err();
5555 assert!(matches!(err, ParseError::InvalidVersion(2)));
5556 }
5557
5558 #[test]
5559 fn lazy_sourcemap_with_source_root() {
5560 let json =
5561 r#"{"version":3,"sourceRoot":"src/","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
5562 let sm = LazySourceMap::from_json(json).unwrap();
5563 assert_eq!(sm.sources[0], "src/a.js");
5564 }
5565
5566 #[test]
5567 fn lazy_sourcemap_with_ignore_list_and_extensions() {
5568 let json = r#"{"version":3,"sources":["v.js"],"names":[],"mappings":"AAAA","ignoreList":[0],"x_custom":"val","not_x":"skip"}"#;
5569 let sm = LazySourceMap::from_json(json).unwrap();
5570 assert_eq!(sm.ignore_list, vec![0]);
5571 assert!(sm.extensions.contains_key("x_custom"));
5572 assert!(!sm.extensions.contains_key("not_x"));
5573 }
5574
5575 #[test]
5576 fn lazy_sourcemap_with_scopes() {
5577 let info = srcmap_scopes::ScopeInfo {
5578 scopes: vec![Some(srcmap_scopes::OriginalScope {
5579 start: srcmap_scopes::Position { line: 0, column: 0 },
5580 end: srcmap_scopes::Position { line: 5, column: 0 },
5581 name: None,
5582 kind: None,
5583 is_stack_frame: false,
5584 variables: vec![],
5585 children: vec![],
5586 })],
5587 ranges: vec![],
5588 };
5589 let mut names = vec![];
5590 let scopes_str = srcmap_scopes::encode_scopes(&info, &mut names);
5591 let json = format!(
5592 r#"{{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","scopes":"{scopes_str}"}}"#
5593 );
5594 let sm = LazySourceMap::from_json(&json).unwrap();
5595 assert!(sm.scopes.is_some());
5596 }
5597
5598 #[test]
5599 fn lazy_sourcemap_null_source() {
5600 let json = r#"{"version":3,"sources":[null,"a.js"],"names":[],"mappings":"AAAA,KCCA"}"#;
5601 let sm = LazySourceMap::from_json(json).unwrap();
5602 assert_eq!(sm.sources.len(), 2);
5603 }
5604
5605 #[test]
5606 fn indexed_map_multi_line_section() {
5607 let json = r#"{
5609 "version": 3,
5610 "sections": [
5611 {
5612 "offset": {"line": 0, "column": 0},
5613 "map": {
5614 "version": 3,
5615 "sources": ["a.js"],
5616 "names": [],
5617 "mappings": "AAAA;AACA;AACA"
5618 }
5619 },
5620 {
5621 "offset": {"line": 5, "column": 0},
5622 "map": {
5623 "version": 3,
5624 "sources": ["b.js"],
5625 "names": [],
5626 "mappings": "AAAA;AACA"
5627 }
5628 }
5629 ]
5630 }"#;
5631 let sm = SourceMap::from_json(json).unwrap();
5632 assert!(sm.original_position_for(0, 0).is_some());
5633 assert!(sm.original_position_for(5, 0).is_some());
5634 }
5635
5636 #[test]
5637 fn source_mapping_url_extraction() {
5638 let input = "var x = 1;\n//# sourceMappingURL=bundle.js.map";
5640 let url = parse_source_mapping_url(input);
5641 assert!(matches!(url, Some(SourceMappingUrl::External(ref s)) if s == "bundle.js.map"));
5642
5643 let input = "body { }\n/*# sourceMappingURL=style.css.map */";
5645 let url = parse_source_mapping_url(input);
5646 assert!(matches!(url, Some(SourceMappingUrl::External(ref s)) if s == "style.css.map"));
5647
5648 let input = "var x;\n//@ sourceMappingURL=old-style.map";
5650 let url = parse_source_mapping_url(input);
5651 assert!(matches!(url, Some(SourceMappingUrl::External(ref s)) if s == "old-style.map"));
5652
5653 let input = "body{}\n/*@ sourceMappingURL=old-css.map */";
5655 let url = parse_source_mapping_url(input);
5656 assert!(matches!(url, Some(SourceMappingUrl::External(ref s)) if s == "old-css.map"));
5657
5658 let input = "var x = 1;";
5660 let url = parse_source_mapping_url(input);
5661 assert!(url.is_none());
5662
5663 let input = "//# sourceMappingURL=";
5665 let url = parse_source_mapping_url(input);
5666 assert!(url.is_none());
5667
5668 let map_json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
5670 let encoded = base64_encode_simple(map_json);
5671 let input = format!("var x;\n//# sourceMappingURL=data:application/json;base64,{encoded}");
5672 let url = parse_source_mapping_url(&input);
5673 assert!(matches!(url, Some(SourceMappingUrl::Inline(_))));
5674 }
5675
5676 #[test]
5677 fn validate_deep_unreferenced_coverage() {
5678 let sm = SourceMap::from_parts(
5680 None,
5681 None,
5682 vec!["used.js".to_string(), "unused.js".to_string()],
5683 vec![None, None],
5684 vec![],
5685 vec![Mapping {
5686 generated_line: 0,
5687 generated_column: 0,
5688 source: 0,
5689 original_line: 0,
5690 original_column: 0,
5691 name: NO_NAME,
5692 is_range_mapping: false,
5693 }],
5694 vec![],
5695 None,
5696 None,
5697 );
5698 let warnings = validate_deep(&sm);
5699 assert!(warnings.iter().any(|w| w.contains("unreferenced")));
5700 }
5701
5702 #[test]
5703 fn from_json_lines_generated_only_segment() {
5704 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A,AAAA;AACA"}"#;
5706 let sm = SourceMap::from_json_lines(json, 0, 2).unwrap();
5707 assert!(sm.mapping_count() >= 2);
5708 }
5709
5710 #[test]
5711 fn from_json_lines_with_names() {
5712 let json = r#"{"version":3,"sources":["a.js"],"names":["foo"],"mappings":"AAAAA;AACAA"}"#;
5713 let sm = SourceMap::from_json_lines(json, 0, 2).unwrap();
5714 let loc = sm.original_position_for(0, 0).unwrap();
5715 assert_eq!(loc.name, Some(0));
5716 }
5717
5718 #[test]
5719 fn from_parts_with_line_gap() {
5720 let sm = SourceMap::from_parts(
5722 None,
5723 None,
5724 vec!["a.js".to_string()],
5725 vec![None],
5726 vec![],
5727 vec![
5728 Mapping {
5729 generated_line: 0,
5730 generated_column: 0,
5731 source: 0,
5732 original_line: 0,
5733 original_column: 0,
5734 name: NO_NAME,
5735 is_range_mapping: false,
5736 },
5737 Mapping {
5738 generated_line: 5,
5739 generated_column: 0,
5740 source: 0,
5741 original_line: 5,
5742 original_column: 0,
5743 name: NO_NAME,
5744 is_range_mapping: false,
5745 },
5746 ],
5747 vec![],
5748 None,
5749 None,
5750 );
5751 assert!(sm.original_position_for(0, 0).is_some());
5752 assert!(sm.original_position_for(5, 0).is_some());
5753 assert!(sm.original_position_for(1, 0).is_none());
5755 }
5756
5757 #[test]
5758 fn lazy_decode_line_with_names_and_generated_only() {
5759 let json = r#"{"version":3,"sources":["a.js"],"names":["fn"],"mappings":"A,AAAAC"}"#;
5761 let sm = LazySourceMap::from_json(json).unwrap();
5762 let line = sm.decode_line(0).unwrap();
5763 assert!(line.len() >= 2);
5764 assert_eq!(line[0].source, NO_SOURCE);
5766 assert_ne!(line[1].name, NO_NAME);
5768 }
5769
5770 #[test]
5771 fn generated_position_glb_source_mismatch() {
5772 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":[],"mappings":"AAAA,KCCA"}"#;
5774 let sm = SourceMap::from_json(json).unwrap();
5775
5776 let loc = sm.generated_position_for_with_bias("a.js", 100, 0, Bias::LeastUpperBound);
5778 assert!(loc.is_none());
5779
5780 let loc = sm.generated_position_for_with_bias("b.js", 0, 0, Bias::GreatestLowerBound);
5784 assert!(loc.is_none());
5785
5786 let loc = sm.generated_position_for_with_bias("b.js", 1, 0, Bias::GreatestLowerBound);
5788 assert!(loc.is_some());
5789
5790 let loc = sm.generated_position_for_with_bias("b.js", 99, 0, Bias::LeastUpperBound);
5792 assert!(loc.is_none());
5793 }
5794
5795 #[test]
5798 fn from_json_invalid_scopes_error() {
5799 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","scopes":"!!invalid!!"}"#;
5801 let err = SourceMap::from_json(json).unwrap_err();
5802 assert!(matches!(err, ParseError::Scopes(_)));
5803 }
5804
5805 #[test]
5806 fn lazy_from_json_invalid_scopes_error() {
5807 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","scopes":"!!invalid!!"}"#;
5808 let err = LazySourceMap::from_json(json).unwrap_err();
5809 assert!(matches!(err, ParseError::Scopes(_)));
5810 }
5811
5812 #[test]
5813 fn from_json_lines_invalid_scopes_error() {
5814 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","scopes":"!!invalid!!"}"#;
5815 let err = SourceMap::from_json_lines(json, 0, 1).unwrap_err();
5816 assert!(matches!(err, ParseError::Scopes(_)));
5817 }
5818
5819 #[test]
5820 fn from_json_lines_invalid_version() {
5821 let json = r#"{"version":2,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
5822 let err = SourceMap::from_json_lines(json, 0, 1).unwrap_err();
5823 assert!(matches!(err, ParseError::InvalidVersion(2)));
5824 }
5825
5826 #[test]
5827 fn indexed_map_with_ignore_list_remapped() {
5828 let json = r#"{
5830 "version": 3,
5831 "sections": [{
5832 "offset": {"line": 0, "column": 0},
5833 "map": {
5834 "version": 3,
5835 "sources": ["a.js", "b.js"],
5836 "names": [],
5837 "mappings": "AAAA;ACAA",
5838 "ignoreList": [1]
5839 }
5840 }, {
5841 "offset": {"line": 5, "column": 0},
5842 "map": {
5843 "version": 3,
5844 "sources": ["b.js", "c.js"],
5845 "names": [],
5846 "mappings": "AAAA;ACAA",
5847 "ignoreList": [0]
5848 }
5849 }]
5850 }"#;
5851 let sm = SourceMap::from_json(json).unwrap();
5852 assert!(!sm.ignore_list.is_empty());
5854 }
5855
5856 #[test]
5857 fn to_json_with_debug_id() {
5858 let sm = SourceMap::from_parts(
5859 Some("out.js".to_string()),
5860 None,
5861 vec!["a.js".to_string()],
5862 vec![None],
5863 vec![],
5864 vec![Mapping {
5865 generated_line: 0,
5866 generated_column: 0,
5867 source: 0,
5868 original_line: 0,
5869 original_column: 0,
5870 name: NO_NAME,
5871 is_range_mapping: false,
5872 }],
5873 vec![],
5874 Some("abc-123".to_string()),
5875 None,
5876 );
5877 let json = sm.to_json();
5878 assert!(json.contains(r#""debugId":"abc-123""#));
5879 }
5880
5881 #[test]
5882 fn to_json_with_ignore_list_and_extensions() {
5883 let mut sm = SourceMap::from_parts(
5884 None,
5885 None,
5886 vec!["a.js".to_string(), "b.js".to_string()],
5887 vec![None, None],
5888 vec![],
5889 vec![Mapping {
5890 generated_line: 0,
5891 generated_column: 0,
5892 source: 0,
5893 original_line: 0,
5894 original_column: 0,
5895 name: NO_NAME,
5896 is_range_mapping: false,
5897 }],
5898 vec![1],
5899 None,
5900 None,
5901 );
5902 sm.extensions.insert("x_test".to_string(), serde_json::json!(42));
5903 let json = sm.to_json();
5904 assert!(json.contains("\"ignoreList\":[1]"));
5905 assert!(json.contains("\"x_test\":42"));
5906 }
5907
5908 #[test]
5909 fn from_vlq_with_all_options() {
5910 let sm = SourceMap::from_vlq(
5911 "AAAA;AACA",
5912 vec!["a.js".to_string()],
5913 vec![],
5914 Some("out.js".to_string()),
5915 Some("src/".to_string()),
5916 vec![Some("content".to_string())],
5917 vec![0],
5918 Some("debug-123".to_string()),
5919 )
5920 .unwrap();
5921 assert_eq!(sm.source(0), "a.js");
5922 assert!(sm.original_position_for(0, 0).is_some());
5923 assert!(sm.original_position_for(1, 0).is_some());
5924 }
5925
5926 #[test]
5927 fn lazy_into_sourcemap_roundtrip() {
5928 let json = r#"{"version":3,"sources":["a.js"],"names":["x"],"mappings":"AAAAA;AACAA"}"#;
5929 let lazy = LazySourceMap::from_json(json).unwrap();
5930 let sm = lazy.into_sourcemap().unwrap();
5931 assert!(sm.original_position_for(0, 0).is_some());
5932 assert!(sm.original_position_for(1, 0).is_some());
5933 assert_eq!(sm.name(0), "x");
5934 }
5935
5936 #[test]
5937 fn lazy_original_position_for_no_match() {
5938 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"KAAA"}"#;
5940 let sm = LazySourceMap::from_json(json).unwrap();
5941 assert!(sm.original_position_for(0, 0).is_none());
5943 }
5944
5945 #[test]
5946 fn lazy_original_position_for_empty_line() {
5947 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":";AAAA"}"#;
5948 let sm = LazySourceMap::from_json(json).unwrap();
5949 assert!(sm.original_position_for(0, 0).is_none());
5951 assert!(sm.original_position_for(1, 0).is_some());
5953 }
5954
5955 #[test]
5956 fn lazy_original_position_generated_only() {
5957 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A;AAAA"}"#;
5959 let sm = LazySourceMap::from_json(json).unwrap();
5960 assert!(sm.original_position_for(0, 0).is_none());
5962 assert!(sm.original_position_for(1, 0).is_some());
5964 }
5965
5966 #[test]
5967 fn from_json_lines_null_source() {
5968 let json = r#"{"version":3,"sources":[null,"a.js"],"names":[],"mappings":"ACAA"}"#;
5969 let sm = SourceMap::from_json_lines(json, 0, 1).unwrap();
5970 assert!(sm.mapping_count() >= 1);
5971 }
5972
5973 #[test]
5974 fn from_json_lines_with_source_root_prefix() {
5975 let json =
5976 r#"{"version":3,"sourceRoot":"lib/","sources":["b.js"],"names":[],"mappings":"AAAA"}"#;
5977 let sm = SourceMap::from_json_lines(json, 0, 1).unwrap();
5978 assert_eq!(sm.source(0), "lib/b.js");
5979 }
5980
5981 #[test]
5982 fn generated_position_for_glb_idx_zero() {
5983 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAKA"}"#;
5987 let sm = SourceMap::from_json(json).unwrap();
5988 let loc = sm.generated_position_for_with_bias("a.js", 0, 0, Bias::GreatestLowerBound);
5989 assert!(loc.is_none());
5990 }
5991
5992 #[test]
5993 fn from_json_lines_with_ignore_list() {
5994 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":[],"mappings":"AAAA;ACAA","ignoreList":[1]}"#;
5995 let sm = SourceMap::from_json_lines(json, 0, 2).unwrap();
5996 assert_eq!(sm.ignore_list, vec![1]);
5997 }
5998
5999 #[test]
6000 fn validate_deep_out_of_order_mappings() {
6001 let sm = SourceMap::from_parts(
6003 None,
6004 None,
6005 vec!["a.js".to_string()],
6006 vec![None],
6007 vec![],
6008 vec![
6009 Mapping {
6010 generated_line: 1,
6011 generated_column: 0,
6012 source: 0,
6013 original_line: 0,
6014 original_column: 0,
6015 name: NO_NAME,
6016 is_range_mapping: false,
6017 },
6018 Mapping {
6019 generated_line: 0,
6020 generated_column: 0,
6021 source: 0,
6022 original_line: 0,
6023 original_column: 0,
6024 name: NO_NAME,
6025 is_range_mapping: false,
6026 },
6027 ],
6028 vec![],
6029 None,
6030 None,
6031 );
6032 let warnings = validate_deep(&sm);
6033 assert!(warnings.iter().any(|w| w.contains("out of order")));
6034 }
6035
6036 #[test]
6037 fn validate_deep_out_of_bounds_source() {
6038 let sm = SourceMap::from_parts(
6039 None,
6040 None,
6041 vec!["a.js".to_string()],
6042 vec![None],
6043 vec![],
6044 vec![Mapping {
6045 generated_line: 0,
6046 generated_column: 0,
6047 source: 5,
6048 original_line: 0,
6049 original_column: 0,
6050 name: NO_NAME,
6051 is_range_mapping: false,
6052 }],
6053 vec![],
6054 None,
6055 None,
6056 );
6057 let warnings = validate_deep(&sm);
6058 assert!(warnings.iter().any(|w| w.contains("source index") && w.contains("out of bounds")));
6059 }
6060
6061 #[test]
6062 fn validate_deep_out_of_bounds_name() {
6063 let sm = SourceMap::from_parts(
6064 None,
6065 None,
6066 vec!["a.js".to_string()],
6067 vec![None],
6068 vec!["foo".to_string()],
6069 vec![Mapping {
6070 generated_line: 0,
6071 generated_column: 0,
6072 source: 0,
6073 original_line: 0,
6074 original_column: 0,
6075 name: 5,
6076 is_range_mapping: false,
6077 }],
6078 vec![],
6079 None,
6080 None,
6081 );
6082 let warnings = validate_deep(&sm);
6083 assert!(warnings.iter().any(|w| w.contains("name index") && w.contains("out of bounds")));
6084 }
6085
6086 #[test]
6087 fn validate_deep_out_of_bounds_ignore_list() {
6088 let sm = SourceMap::from_parts(
6089 None,
6090 None,
6091 vec!["a.js".to_string()],
6092 vec![None],
6093 vec![],
6094 vec![Mapping {
6095 generated_line: 0,
6096 generated_column: 0,
6097 source: 0,
6098 original_line: 0,
6099 original_column: 0,
6100 name: NO_NAME,
6101 is_range_mapping: false,
6102 }],
6103 vec![10],
6104 None,
6105 None,
6106 );
6107 let warnings = validate_deep(&sm);
6108 assert!(warnings.iter().any(|w| w.contains("ignoreList") && w.contains("out of bounds")));
6109 }
6110
6111 #[test]
6112 fn source_mapping_url_inline_decoded() {
6113 let map_json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
6115 let encoded = base64_encode_simple(map_json);
6116 let input = format!("var x;\n//# sourceMappingURL=data:application/json;base64,{encoded}");
6117 let url = parse_source_mapping_url(&input);
6118 match url {
6119 Some(SourceMappingUrl::Inline(json)) => {
6120 assert!(json.contains("version"));
6121 assert!(json.contains("AAAA"));
6122 }
6123 _ => panic!("expected inline source map"),
6124 }
6125 }
6126
6127 #[test]
6128 fn source_mapping_url_charset_variant() {
6129 let map_json = r#"{"version":3}"#;
6130 let encoded = base64_encode_simple(map_json);
6131 let input =
6132 format!("x\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,{encoded}");
6133 let url = parse_source_mapping_url(&input);
6134 assert!(matches!(url, Some(SourceMappingUrl::Inline(_))));
6135 }
6136
6137 #[test]
6138 fn source_mapping_url_invalid_base64_falls_through_to_external() {
6139 let input = "x\n//# sourceMappingURL=data:application/json;base64,!!!invalid!!!";
6141 let url = parse_source_mapping_url(input);
6142 assert!(matches!(url, Some(SourceMappingUrl::External(_))));
6144 }
6145
6146 #[test]
6147 fn from_json_lines_with_extensions_preserved() {
6148 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","x_custom":99}"#;
6149 let sm = SourceMap::from_json_lines(json, 0, 1).unwrap();
6150 assert!(sm.extensions.contains_key("x_custom"));
6151 }
6152
6153 fn base64_encode_simple(input: &str) -> String {
6155 const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
6156 let bytes = input.as_bytes();
6157 let mut result = String::new();
6158 for chunk in bytes.chunks(3) {
6159 let b0 = chunk[0] as u32;
6160 let b1 = if chunk.len() > 1 { chunk[1] as u32 } else { 0 };
6161 let b2 = if chunk.len() > 2 { chunk[2] as u32 } else { 0 };
6162 let n = (b0 << 16) | (b1 << 8) | b2;
6163 result.push(CHARS[((n >> 18) & 0x3F) as usize] as char);
6164 result.push(CHARS[((n >> 12) & 0x3F) as usize] as char);
6165 if chunk.len() > 1 {
6166 result.push(CHARS[((n >> 6) & 0x3F) as usize] as char);
6167 } else {
6168 result.push('=');
6169 }
6170 if chunk.len() > 2 {
6171 result.push(CHARS[(n & 0x3F) as usize] as char);
6172 } else {
6173 result.push('=');
6174 }
6175 }
6176 result
6177 }
6178
6179 #[test]
6182 fn mappings_iter_matches_decode() {
6183 let vlq = "AAAA;AACA,EAAA;AACA";
6184 let iter_mappings: Vec<Mapping> = MappingsIter::new(vlq).collect::<Result<_, _>>().unwrap();
6185 let (decoded, _) = decode_mappings(vlq).unwrap();
6186 assert_eq!(iter_mappings.len(), decoded.len());
6187 for (a, b) in iter_mappings.iter().zip(decoded.iter()) {
6188 assert_eq!(a.generated_line, b.generated_line);
6189 assert_eq!(a.generated_column, b.generated_column);
6190 assert_eq!(a.source, b.source);
6191 assert_eq!(a.original_line, b.original_line);
6192 assert_eq!(a.original_column, b.original_column);
6193 assert_eq!(a.name, b.name);
6194 }
6195 }
6196
6197 #[test]
6198 fn mappings_iter_empty() {
6199 let mappings: Vec<Mapping> = MappingsIter::new("").collect::<Result<_, _>>().unwrap();
6200 assert!(mappings.is_empty());
6201 }
6202
6203 #[test]
6204 fn mappings_iter_generated_only() {
6205 let vlq = "A,AAAA";
6206 let mappings: Vec<Mapping> = MappingsIter::new(vlq).collect::<Result<_, _>>().unwrap();
6207 assert_eq!(mappings.len(), 2);
6208 assert_eq!(mappings[0].source, u32::MAX);
6209 assert_eq!(mappings[1].source, 0);
6210 }
6211
6212 #[test]
6213 fn mappings_iter_with_names() {
6214 let vlq = "AAAAA";
6215 let mappings: Vec<Mapping> = MappingsIter::new(vlq).collect::<Result<_, _>>().unwrap();
6216 assert_eq!(mappings.len(), 1);
6217 assert_eq!(mappings[0].name, 0);
6218 }
6219
6220 #[test]
6221 fn mappings_iter_multiple_lines() {
6222 let vlq = "AAAA;AACA;AACA";
6223 let mappings: Vec<Mapping> = MappingsIter::new(vlq).collect::<Result<_, _>>().unwrap();
6224 assert_eq!(mappings.len(), 3);
6225 assert_eq!(mappings[0].generated_line, 0);
6226 assert_eq!(mappings[1].generated_line, 1);
6227 assert_eq!(mappings[2].generated_line, 2);
6228 }
6229 #[test]
6232 fn range_mappings_basic_decode() {
6233 let json = r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA,CAAC,GAAG","rangeMappings":"A,C"}"#;
6234 let sm = SourceMap::from_json(json).unwrap();
6235 assert!(sm.all_mappings()[0].is_range_mapping);
6236 assert!(!sm.all_mappings()[1].is_range_mapping);
6237 assert!(sm.all_mappings()[2].is_range_mapping);
6238 }
6239
6240 #[test]
6241 fn range_mapping_lookup_with_delta() {
6242 let json = r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA,GAAG","rangeMappings":"A"}"#;
6243 let sm = SourceMap::from_json(json).unwrap();
6244 assert_eq!(sm.original_position_for(0, 0).unwrap().column, 0);
6245 assert_eq!(sm.original_position_for(0, 1).unwrap().column, 1);
6246 assert_eq!(sm.original_position_for(0, 2).unwrap().column, 2);
6247 assert_eq!(sm.original_position_for(0, 3).unwrap().column, 3);
6248 }
6249
6250 #[test]
6251 fn range_mapping_cross_line() {
6252 let json = r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA","rangeMappings":"A"}"#;
6253 let sm = SourceMap::from_json(json).unwrap();
6254 assert_eq!(sm.original_position_for(1, 5).unwrap().line, 1);
6255 assert_eq!(sm.original_position_for(1, 5).unwrap().column, 0);
6256 assert_eq!(sm.original_position_for(2, 10).unwrap().line, 2);
6257 }
6258
6259 #[test]
6260 fn range_mapping_encode_roundtrip() {
6261 let json = r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA,CAAC,GAAG","rangeMappings":"A,C"}"#;
6262 assert_eq!(SourceMap::from_json(json).unwrap().encode_range_mappings().unwrap(), "A,C");
6263 }
6264
6265 #[test]
6266 fn no_range_mappings_test() {
6267 let sm = SourceMap::from_json(
6268 r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA"}"#,
6269 )
6270 .unwrap();
6271 assert!(!sm.has_range_mappings());
6272 assert!(sm.encode_range_mappings().is_none());
6273 }
6274
6275 #[test]
6276 fn range_mappings_multi_line_test() {
6277 let sm = SourceMap::from_json(r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA,CAAC;AAAA","rangeMappings":"A;A"}"#).unwrap();
6278 assert!(sm.all_mappings()[0].is_range_mapping);
6279 assert!(!sm.all_mappings()[1].is_range_mapping);
6280 assert!(sm.all_mappings()[2].is_range_mapping);
6281 }
6282
6283 #[test]
6284 fn range_mappings_json_roundtrip() {
6285 let sm = SourceMap::from_json(r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA,CAAC,GAAG","rangeMappings":"A,C"}"#).unwrap();
6286 let output = sm.to_json();
6287 assert!(output.contains("rangeMappings"));
6288 assert_eq!(SourceMap::from_json(&output).unwrap().range_mapping_count(), 2);
6289 }
6290
6291 #[test]
6292 fn range_mappings_absent_from_json_test() {
6293 assert!(
6294 !SourceMap::from_json(
6295 r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA"}"#
6296 )
6297 .unwrap()
6298 .to_json()
6299 .contains("rangeMappings")
6300 );
6301 }
6302
6303 #[test]
6304 fn range_mapping_fallback_test() {
6305 let sm = SourceMap::from_json(r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA;KACK","rangeMappings":"A"}"#).unwrap();
6306 let loc = sm.original_position_for(1, 2).unwrap();
6307 assert_eq!(loc.line, 1);
6308 assert_eq!(loc.column, 0);
6309 }
6310
6311 #[test]
6312 fn range_mapping_no_fallback_non_range() {
6313 assert!(
6314 SourceMap::from_json(
6315 r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA"}"#
6316 )
6317 .unwrap()
6318 .original_position_for(1, 5)
6319 .is_none()
6320 );
6321 }
6322
6323 #[test]
6324 fn range_mapping_from_vlq_test() {
6325 let sm = SourceMap::from_vlq_with_range_mappings(
6326 "AAAA,CAAC",
6327 vec!["input.js".into()],
6328 vec![],
6329 None,
6330 None,
6331 vec![],
6332 vec![],
6333 None,
6334 Some("A"),
6335 )
6336 .unwrap();
6337 assert!(sm.all_mappings()[0].is_range_mapping);
6338 assert!(!sm.all_mappings()[1].is_range_mapping);
6339 }
6340
6341 #[test]
6342 fn range_mapping_encode_multi_line_test() {
6343 let sm = SourceMap::from_json(r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA,CAAC;AAAA,CAAC","rangeMappings":"A;B"}"#).unwrap();
6344 assert!(sm.all_mappings()[0].is_range_mapping);
6345 assert!(!sm.all_mappings()[1].is_range_mapping);
6346 assert!(!sm.all_mappings()[2].is_range_mapping);
6347 assert!(sm.all_mappings()[3].is_range_mapping);
6348 assert_eq!(sm.encode_range_mappings().unwrap(), "A;B");
6349 }
6350
6351 #[test]
6352 fn range_mapping_from_parts_test() {
6353 let sm = SourceMap::from_parts(
6354 None,
6355 None,
6356 vec!["input.js".into()],
6357 vec![],
6358 vec![],
6359 vec![
6360 Mapping {
6361 generated_line: 0,
6362 generated_column: 0,
6363 source: 0,
6364 original_line: 0,
6365 original_column: 0,
6366 name: NO_NAME,
6367 is_range_mapping: true,
6368 },
6369 Mapping {
6370 generated_line: 0,
6371 generated_column: 5,
6372 source: 0,
6373 original_line: 0,
6374 original_column: 5,
6375 name: NO_NAME,
6376 is_range_mapping: false,
6377 },
6378 ],
6379 vec![],
6380 None,
6381 None,
6382 );
6383 assert_eq!(sm.original_position_for(0, 2).unwrap().column, 2);
6384 assert_eq!(sm.original_position_for(0, 6).unwrap().column, 5);
6385 }
6386
6387 #[test]
6388 fn range_mapping_indexed_test() {
6389 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();
6390 assert!(sm.has_range_mappings());
6391 assert_eq!(sm.original_position_for(1, 3).unwrap().line, 1);
6392 }
6393
6394 #[test]
6395 fn range_mapping_empty_string_test() {
6396 assert!(!SourceMap::from_json(r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"AAAA","rangeMappings":""}"#).unwrap().has_range_mappings());
6397 }
6398
6399 #[test]
6400 fn range_mapping_lub_no_underflow() {
6401 let json = r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"KAAK","rangeMappings":"A"}"#;
6404 let sm = SourceMap::from_json(json).unwrap();
6405
6406 let loc = sm.original_position_for_with_bias(0, 2, Bias::LeastUpperBound);
6407 assert!(loc.is_some());
6408 let loc = loc.unwrap();
6409 assert_eq!(loc.line, 0);
6411 assert_eq!(loc.column, 5);
6412 }
6413
6414 #[test]
6417 fn builder_basic() {
6418 let sm = SourceMap::builder()
6419 .file("output.js")
6420 .sources(["input.ts"])
6421 .sources_content([Some("let x = 1;")])
6422 .names(["x"])
6423 .mappings([Mapping {
6424 generated_line: 0,
6425 generated_column: 0,
6426 source: 0,
6427 original_line: 0,
6428 original_column: 4,
6429 name: 0,
6430 is_range_mapping: false,
6431 }])
6432 .build();
6433
6434 assert_eq!(sm.file.as_deref(), Some("output.js"));
6435 assert_eq!(sm.sources, vec!["input.ts"]);
6436 assert_eq!(sm.sources_content, vec![Some("let x = 1;".to_string())]);
6437 assert_eq!(sm.names, vec!["x"]);
6438 assert_eq!(sm.mapping_count(), 1);
6439
6440 let loc = sm.original_position_for(0, 0).unwrap();
6441 assert_eq!(sm.source(loc.source), "input.ts");
6442 assert_eq!(loc.column, 4);
6443 assert_eq!(sm.name(loc.name.unwrap()), "x");
6444 }
6445
6446 #[test]
6447 fn builder_empty() {
6448 let sm = SourceMap::builder().build();
6449 assert_eq!(sm.mapping_count(), 0);
6450 assert_eq!(sm.sources.len(), 0);
6451 assert_eq!(sm.names.len(), 0);
6452 assert!(sm.file.is_none());
6453 }
6454
6455 #[test]
6456 fn builder_multiple_sources() {
6457 let sm = SourceMap::builder()
6458 .sources(["a.ts", "b.ts", "c.ts"])
6459 .sources_content([Some("// a"), Some("// b"), None])
6460 .mappings([
6461 Mapping {
6462 generated_line: 0,
6463 generated_column: 0,
6464 source: 0,
6465 original_line: 0,
6466 original_column: 0,
6467 name: u32::MAX,
6468 is_range_mapping: false,
6469 },
6470 Mapping {
6471 generated_line: 1,
6472 generated_column: 0,
6473 source: 1,
6474 original_line: 0,
6475 original_column: 0,
6476 name: u32::MAX,
6477 is_range_mapping: false,
6478 },
6479 Mapping {
6480 generated_line: 2,
6481 generated_column: 0,
6482 source: 2,
6483 original_line: 0,
6484 original_column: 0,
6485 name: u32::MAX,
6486 is_range_mapping: false,
6487 },
6488 ])
6489 .build();
6490
6491 assert_eq!(sm.sources.len(), 3);
6492 assert_eq!(sm.mapping_count(), 3);
6493 assert_eq!(sm.line_count(), 3);
6494
6495 let loc0 = sm.original_position_for(0, 0).unwrap();
6496 assert_eq!(sm.source(loc0.source), "a.ts");
6497
6498 let loc1 = sm.original_position_for(1, 0).unwrap();
6499 assert_eq!(sm.source(loc1.source), "b.ts");
6500
6501 let loc2 = sm.original_position_for(2, 0).unwrap();
6502 assert_eq!(sm.source(loc2.source), "c.ts");
6503 }
6504
6505 #[test]
6506 fn builder_with_iterators() {
6507 let source_names: Vec<String> = (0..5).map(|i| format!("mod_{i}.ts")).collect();
6508 let mappings = (0..5u32).map(|i| Mapping {
6509 generated_line: i,
6510 generated_column: 0,
6511 source: i,
6512 original_line: i,
6513 original_column: 0,
6514 name: u32::MAX,
6515 is_range_mapping: false,
6516 });
6517
6518 let sm = SourceMap::builder()
6519 .sources(source_names.iter().map(|s| s.as_str()))
6520 .mappings(mappings)
6521 .build();
6522
6523 assert_eq!(sm.sources.len(), 5);
6524 assert_eq!(sm.mapping_count(), 5);
6525 for i in 0..5u32 {
6526 let loc = sm.original_position_for(i, 0).unwrap();
6527 assert_eq!(sm.source(loc.source), format!("mod_{i}.ts"));
6528 }
6529 }
6530
6531 #[test]
6532 fn builder_ignore_list_and_debug_id() {
6533 let sm = SourceMap::builder()
6534 .sources(["app.ts", "node_modules/lib.js"])
6535 .ignore_list([1])
6536 .debug_id("85314830-023f-4cf1-a267-535f4e37bb17")
6537 .build();
6538
6539 assert_eq!(sm.ignore_list, vec![1]);
6540 assert_eq!(sm.debug_id.as_deref(), Some("85314830-023f-4cf1-a267-535f4e37bb17"));
6541 }
6542
6543 #[test]
6544 fn builder_range_mappings() {
6545 let sm = SourceMap::builder()
6546 .sources(["input.ts"])
6547 .mappings([
6548 Mapping {
6549 generated_line: 0,
6550 generated_column: 0,
6551 source: 0,
6552 original_line: 0,
6553 original_column: 0,
6554 name: u32::MAX,
6555 is_range_mapping: true,
6556 },
6557 Mapping {
6558 generated_line: 0,
6559 generated_column: 10,
6560 source: 0,
6561 original_line: 5,
6562 original_column: 0,
6563 name: u32::MAX,
6564 is_range_mapping: false,
6565 },
6566 ])
6567 .build();
6568
6569 assert!(sm.has_range_mappings());
6570 assert_eq!(sm.mapping_count(), 2);
6571 }
6572
6573 #[test]
6574 fn builder_json_roundtrip() {
6575 let sm = SourceMap::builder()
6576 .file("out.js")
6577 .source_root("/src/")
6578 .sources(["a.ts", "b.ts"])
6579 .sources_content([Some("// a"), Some("// b")])
6580 .names(["foo", "bar"])
6581 .mappings([
6582 Mapping {
6583 generated_line: 0,
6584 generated_column: 0,
6585 source: 0,
6586 original_line: 0,
6587 original_column: 0,
6588 name: 0,
6589 is_range_mapping: false,
6590 },
6591 Mapping {
6592 generated_line: 1,
6593 generated_column: 5,
6594 source: 1,
6595 original_line: 3,
6596 original_column: 2,
6597 name: 1,
6598 is_range_mapping: false,
6599 },
6600 ])
6601 .build();
6602
6603 let json = sm.to_json();
6604 let sm2 = SourceMap::from_json(&json).unwrap();
6605
6606 assert_eq!(sm2.file, sm.file);
6607 assert_eq!(sm2.sources, vec!["/src/a.ts", "/src/b.ts"]);
6609 assert_eq!(sm2.names, sm.names);
6610 assert_eq!(sm2.mapping_count(), sm.mapping_count());
6611
6612 for m in sm.all_mappings() {
6613 let a = sm.original_position_for(m.generated_line, m.generated_column);
6614 let b = sm2.original_position_for(m.generated_line, m.generated_column);
6615 match (a, b) {
6616 (Some(a), Some(b)) => {
6617 assert_eq!(a.source, b.source);
6618 assert_eq!(a.line, b.line);
6619 assert_eq!(a.column, b.column);
6620 assert_eq!(a.name, b.name);
6621 }
6622 (None, None) => {}
6623 _ => panic!("lookup mismatch"),
6624 }
6625 }
6626 }
6627
6628 #[test]
6631 fn range_mapping_fallback_column_underflow() {
6632 let json = r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"KAAK","rangeMappings":"A"}"#;
6635 let sm = SourceMap::from_json(json).unwrap();
6636 let loc = sm.original_position_for(0, 2);
6639 assert!(loc.is_none());
6641 }
6642
6643 #[test]
6644 fn range_mapping_fallback_cross_line_column_zero() {
6645 let json = r#"{"version":3,"sources":["input.js"],"names":[],"mappings":"UAAU","rangeMappings":"A"}"#;
6649 let sm = SourceMap::from_json(json).unwrap();
6650 let loc = sm.original_position_for(1, 0).unwrap();
6651 assert_eq!(loc.line, 1);
6652 assert_eq!(loc.column, 10);
6653 }
6654
6655 #[test]
6656 fn vlq_overflow_at_shift_60() {
6657 let overflow_vlq = "ggggggggggggggA"; let json = format!(
6663 r#"{{"version":3,"sources":["a.js"],"names":[],"mappings":"{}"}}"#,
6664 overflow_vlq
6665 );
6666 let result = SourceMap::from_json(&json);
6667 assert!(result.is_err());
6668 assert!(matches!(result.unwrap_err(), ParseError::Vlq(_)));
6669 }
6670
6671 #[test]
6672 fn lazy_sourcemap_rejects_indexed_maps() {
6673 let json = r#"{"version":3,"sections":[{"offset":{"line":0,"column":0},"map":{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}}]}"#;
6674 let result = LazySourceMap::from_json_fast(json);
6675 assert!(result.is_err());
6676 assert!(matches!(result.unwrap_err(), ParseError::NestedIndexMap));
6677
6678 let result = LazySourceMap::from_json_no_content(json);
6679 assert!(result.is_err());
6680 assert!(matches!(result.unwrap_err(), ParseError::NestedIndexMap));
6681 }
6682
6683 #[test]
6684 fn lazy_sourcemap_regular_map_still_works() {
6685 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;AACA"}"#;
6686 let sm = LazySourceMap::from_json_fast(json).unwrap();
6687 let loc = sm.original_position_for(0, 0).unwrap();
6688 assert_eq!(sm.source(loc.source), "a.js");
6689 assert_eq!(loc.line, 0);
6690 }
6691
6692 #[test]
6693 fn lazy_sourcemap_get_source_name_bounds() {
6694 let json = r#"{"version":3,"sources":["a.js"],"names":["foo"],"mappings":"AAAAA"}"#;
6695 let sm = LazySourceMap::from_json_fast(json).unwrap();
6696 assert_eq!(sm.get_source(0), Some("a.js"));
6697 assert_eq!(sm.get_source(1), None);
6698 assert_eq!(sm.get_source(u32::MAX), None);
6699 assert_eq!(sm.get_name(0), Some("foo"));
6700 assert_eq!(sm.get_name(1), None);
6701 assert_eq!(sm.get_name(u32::MAX), None);
6702 }
6703
6704 #[test]
6705 fn lazy_sourcemap_backward_seek() {
6706 let json =
6708 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA"}"#;
6709 let sm = LazySourceMap::from_json_fast(json).unwrap();
6710
6711 let loc3 = sm.original_position_for(3, 0).unwrap();
6713 assert_eq!(loc3.line, 3);
6714
6715 let loc1 = sm.original_position_for(1, 0).unwrap();
6717 assert_eq!(loc1.line, 1);
6718
6719 let loc4 = sm.original_position_for(4, 0).unwrap();
6721 assert_eq!(loc4.line, 4);
6722
6723 let loc0 = sm.original_position_for(0, 0).unwrap();
6725 assert_eq!(loc0.line, 0);
6726 }
6727
6728 #[test]
6729 fn lazy_sourcemap_fast_scan_vs_prescan_consistency() {
6730 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":["x","y"],"mappings":"AAAAA,KACAC;ACAAD,KACAC"}"#;
6732 let fast = LazySourceMap::from_json_fast(json).unwrap();
6733 let prescan = LazySourceMap::from_json_no_content(json).unwrap();
6734
6735 for line in 0..2 {
6736 for col in [0, 5, 10] {
6737 let a = fast.original_position_for(line, col);
6738 let b = prescan.original_position_for(line, col);
6739 match (&a, &b) {
6740 (Some(a), Some(b)) => {
6741 assert_eq!(a.source, b.source, "line={line}, col={col}");
6742 assert_eq!(a.line, b.line, "line={line}, col={col}");
6743 assert_eq!(a.column, b.column, "line={line}, col={col}");
6744 assert_eq!(a.name, b.name, "line={line}, col={col}");
6745 }
6746 (None, None) => {}
6747 _ => panic!("mismatch at line={line}, col={col}: {a:?} vs {b:?}"),
6748 }
6749 }
6750 }
6751 }
6752
6753 #[test]
6754 fn mappings_iter_rejects_two_field_segment() {
6755 let result: Result<Vec<_>, _> = MappingsIter::new("AA").collect();
6757 assert!(result.is_err());
6758 assert!(matches!(result.unwrap_err(), DecodeError::InvalidSegmentLength { fields: 2, .. }));
6759 }
6760
6761 #[test]
6762 fn mappings_iter_rejects_three_field_segment() {
6763 let result: Result<Vec<_>, _> = MappingsIter::new("AAA").collect();
6765 assert!(result.is_err());
6766 assert!(matches!(result.unwrap_err(), DecodeError::InvalidSegmentLength { fields: 3, .. }));
6767 }
6768
6769 #[test]
6770 fn decode_mappings_range_caps_end_line() {
6771 let mappings = "AAAA;AACA";
6773 let (result, offsets) = decode_mappings_range(mappings, 0, 1_000_000).unwrap();
6774 assert_eq!(result.len(), 2);
6776 assert!(offsets.len() <= 3); }
6778
6779 #[test]
6780 fn decode_range_mappings_cross_line_bound_check() {
6781 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;AAAA","rangeMappings":"E"}"#;
6784 let sm = SourceMap::from_json(json).unwrap();
6785 assert!(!sm.all_mappings()[1].is_range_mapping);
6788 }
6789
6790 #[test]
6791 fn fast_scan_lines_empty() {
6792 let result = fast_scan_lines("");
6793 assert!(result.is_empty());
6794 }
6795
6796 #[test]
6797 fn fast_scan_lines_no_semicolons() {
6798 let result = fast_scan_lines("AAAA,CAAC");
6799 assert_eq!(result.len(), 1);
6800 assert_eq!(result[0].byte_offset, 0);
6801 assert_eq!(result[0].byte_end, 9);
6802 }
6803
6804 #[test]
6805 fn fast_scan_lines_only_semicolons() {
6806 let result = fast_scan_lines(";;;");
6807 assert_eq!(result.len(), 4);
6808 for info in &result {
6809 assert_eq!(info.byte_offset, info.byte_end); }
6811 }
6812
6813 #[test]
6816 fn from_data_url_base64() {
6817 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
6818 let encoded = base64_encode_simple(json);
6819 let url = format!("data:application/json;base64,{encoded}");
6820 let sm = SourceMap::from_data_url(&url).unwrap();
6821 assert_eq!(sm.sources, vec!["a.js"]);
6822 let loc = sm.original_position_for(0, 0).unwrap();
6823 assert_eq!(loc.line, 0);
6824 assert_eq!(loc.column, 0);
6825 }
6826
6827 #[test]
6828 fn from_data_url_base64_charset_utf8() {
6829 let json = r#"{"version":3,"sources":["b.js"],"names":[],"mappings":"AAAA"}"#;
6830 let encoded = base64_encode_simple(json);
6831 let url = format!("data:application/json;charset=utf-8;base64,{encoded}");
6832 let sm = SourceMap::from_data_url(&url).unwrap();
6833 assert_eq!(sm.sources, vec!["b.js"]);
6834 }
6835
6836 #[test]
6837 fn from_data_url_plain_json() {
6838 let json = r#"{"version":3,"sources":["c.js"],"names":[],"mappings":"AAAA"}"#;
6839 let url = format!("data:application/json,{json}");
6840 let sm = SourceMap::from_data_url(&url).unwrap();
6841 assert_eq!(sm.sources, vec!["c.js"]);
6842 }
6843
6844 #[test]
6845 fn from_data_url_percent_encoded() {
6846 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";
6847 let sm = SourceMap::from_data_url(url).unwrap();
6848 assert_eq!(sm.sources, vec!["d.js"]);
6849 }
6850
6851 #[test]
6852 fn from_data_url_invalid_prefix() {
6853 let result = SourceMap::from_data_url("data:text/plain;base64,abc");
6854 assert!(result.is_err());
6855 }
6856
6857 #[test]
6858 fn from_data_url_not_a_data_url() {
6859 let result = SourceMap::from_data_url("https://example.com/foo.map");
6860 assert!(result.is_err());
6861 }
6862
6863 #[test]
6864 fn from_data_url_invalid_base64() {
6865 let result = SourceMap::from_data_url("data:application/json;base64,!!!invalid!!!");
6866 assert!(result.is_err());
6867 }
6868
6869 #[test]
6870 fn from_data_url_roundtrip_with_to_data_url() {
6871 use crate::utils::to_data_url;
6872 let json = r#"{"version":3,"sources":["round.js"],"names":["x"],"mappings":"AACAA"}"#;
6873 let url = to_data_url(json);
6874 let sm = SourceMap::from_data_url(&url).unwrap();
6875 assert_eq!(sm.sources, vec!["round.js"]);
6876 assert_eq!(sm.names, vec!["x"]);
6877 }
6878
6879 #[test]
6882 fn to_writer_basic() {
6883 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
6884 let sm = SourceMap::from_json(json).unwrap();
6885 let mut buf = Vec::new();
6886 sm.to_writer(&mut buf).unwrap();
6887 let output = String::from_utf8(buf).unwrap();
6888 assert!(output.contains("\"version\":3"));
6889 assert!(output.contains("\"sources\":[\"a.js\"]"));
6890 let sm2 = SourceMap::from_json(&output).unwrap();
6892 assert_eq!(sm2.sources, sm.sources);
6893 }
6894
6895 #[test]
6896 fn to_writer_matches_to_json() {
6897 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":["foo"],"mappings":"AACAA,GCAA","sourcesContent":["var foo;","var bar;"]}"#;
6898 let sm = SourceMap::from_json(json).unwrap();
6899 let expected = sm.to_json();
6900 let mut buf = Vec::new();
6901 sm.to_writer(&mut buf).unwrap();
6902 let output = String::from_utf8(buf).unwrap();
6903 assert_eq!(output, expected);
6904 }
6905
6906 #[test]
6907 fn to_writer_with_options_excludes_content() {
6908 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","sourcesContent":["var x;"]}"#;
6909 let sm = SourceMap::from_json(json).unwrap();
6910 let mut buf = Vec::new();
6911 sm.to_writer_with_options(&mut buf, true).unwrap();
6912 let output = String::from_utf8(buf).unwrap();
6913 assert!(!output.contains("sourcesContent"));
6914 }
6915
6916 #[test]
6919 fn set_file() {
6920 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
6921 let mut sm = SourceMap::from_json(json).unwrap();
6922 assert_eq!(sm.file, None);
6923
6924 sm.set_file(Some("output.js".to_string()));
6925 assert_eq!(sm.file, Some("output.js".to_string()));
6926 assert!(sm.to_json().contains(r#""file":"output.js""#));
6927
6928 sm.set_file(None);
6929 assert_eq!(sm.file, None);
6930 assert!(!sm.to_json().contains("file"));
6931 }
6932
6933 #[test]
6934 fn set_source_root() {
6935 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
6936 let mut sm = SourceMap::from_json(json).unwrap();
6937 assert_eq!(sm.source_root, None);
6938
6939 sm.set_source_root(Some("src/".to_string()));
6940 assert_eq!(sm.source_root, Some("src/".to_string()));
6941 assert!(sm.to_json().contains(r#""sourceRoot":"src/""#));
6942
6943 sm.set_source_root(None);
6944 assert_eq!(sm.source_root, None);
6945 }
6946
6947 #[test]
6948 fn set_debug_id() {
6949 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
6950 let mut sm = SourceMap::from_json(json).unwrap();
6951 assert_eq!(sm.debug_id, None);
6952
6953 sm.set_debug_id(Some("abc-123".to_string()));
6954 assert_eq!(sm.debug_id, Some("abc-123".to_string()));
6955 assert!(sm.to_json().contains(r#""debugId":"abc-123""#));
6956
6957 sm.set_debug_id(None);
6958 assert_eq!(sm.debug_id, None);
6959 assert!(!sm.to_json().contains("debugId"));
6960 }
6961
6962 #[test]
6963 fn set_ignore_list() {
6964 let json = r#"{"version":3,"sources":["a.js","b.js"],"names":[],"mappings":"AAAA"}"#;
6965 let mut sm = SourceMap::from_json(json).unwrap();
6966 assert!(sm.ignore_list.is_empty());
6967
6968 sm.set_ignore_list(vec![0, 1]);
6969 assert_eq!(sm.ignore_list, vec![0, 1]);
6970 assert!(sm.to_json().contains("\"ignoreList\":[0,1]"));
6971
6972 sm.set_ignore_list(vec![]);
6973 assert!(sm.ignore_list.is_empty());
6974 assert!(!sm.to_json().contains("ignoreList"));
6975 }
6976
6977 #[test]
6978 fn set_sources() {
6979 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
6980 let mut sm = SourceMap::from_json(json).unwrap();
6981 assert_eq!(sm.sources, vec!["a.js"]);
6982
6983 sm.set_sources(vec![Some("x.js".to_string()), Some("y.js".to_string())]);
6984 assert_eq!(sm.sources, vec!["x.js", "y.js"]);
6985 assert_eq!(sm.source_index("x.js"), Some(0));
6986 assert_eq!(sm.source_index("y.js"), Some(1));
6987 assert_eq!(sm.source_index("a.js"), None);
6988 }
6989
6990 #[test]
6991 fn set_sources_with_source_root() {
6992 let json =
6993 r#"{"version":3,"sourceRoot":"src/","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
6994 let mut sm = SourceMap::from_json(json).unwrap();
6995 assert_eq!(sm.sources, vec!["src/a.js"]);
6996
6997 sm.set_sources(vec![Some("b.js".to_string())]);
6998 assert_eq!(sm.sources, vec!["src/b.js"]);
6999 }
7000
7001 #[test]
7002 fn to_data_url_roundtrip() {
7003 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
7004 let sm = SourceMap::from_json(json).unwrap();
7005 let url = sm.to_data_url();
7006 assert!(url.starts_with("data:application/json;base64,"));
7007 let sm2 = SourceMap::from_data_url(&url).unwrap();
7008 assert_eq!(sm.sources, sm2.sources);
7009 assert_eq!(sm.to_json(), sm2.to_json());
7010 }
7011}