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