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