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