1use std::cell::OnceCell;
27use std::collections::HashMap;
28use std::fmt;
29
30use serde::Deserialize;
31use srcmap_codec::DecodeError;
32use srcmap_scopes::ScopeInfo;
33
34const NO_SOURCE: u32 = u32::MAX;
37const NO_NAME: u32 = u32::MAX;
38
39#[derive(Debug, Clone, Copy)]
43pub struct Mapping {
44 pub generated_line: u32,
45 pub generated_column: u32,
46 pub source: u32,
48 pub original_line: u32,
49 pub original_column: u32,
50 pub name: u32,
52}
53
54#[derive(Debug, Clone)]
56pub struct OriginalLocation {
57 pub source: u32,
58 pub line: u32,
59 pub column: u32,
60 pub name: Option<u32>,
61}
62
63#[derive(Debug, Clone)]
65pub struct GeneratedLocation {
66 pub line: u32,
67 pub column: u32,
68}
69
70#[derive(Debug)]
72pub enum ParseError {
73 Json(serde_json::Error),
74 Vlq(DecodeError),
75 InvalidVersion(u32),
76 Scopes(String),
77}
78
79impl fmt::Display for ParseError {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 match self {
82 Self::Json(e) => write!(f, "JSON parse error: {e}"),
83 Self::Vlq(e) => write!(f, "VLQ decode error: {e}"),
84 Self::InvalidVersion(v) => write!(f, "unsupported source map version: {v}"),
85 Self::Scopes(e) => write!(f, "scopes decode error: {e}"),
86 }
87 }
88}
89
90impl std::error::Error for ParseError {}
91
92impl From<serde_json::Error> for ParseError {
93 fn from(e: serde_json::Error) -> Self {
94 Self::Json(e)
95 }
96}
97
98impl From<DecodeError> for ParseError {
99 fn from(e: DecodeError) -> Self {
100 Self::Vlq(e)
101 }
102}
103
104#[derive(Deserialize)]
107struct RawSourceMap<'a> {
108 version: u32,
109 #[serde(default)]
110 file: Option<String>,
111 #[serde(default, rename = "sourceRoot")]
112 source_root: Option<String>,
113 #[serde(default)]
114 sources: Vec<Option<String>>,
115 #[serde(default, rename = "sourcesContent")]
116 sources_content: Option<Vec<Option<String>>>,
117 #[serde(default)]
118 names: Vec<String>,
119 #[serde(default, borrow)]
120 mappings: &'a str,
121 #[serde(default, rename = "ignoreList")]
122 ignore_list: Vec<u32>,
123 #[serde(default, rename = "debugId", alias = "debug_id")]
126 debug_id: Option<String>,
127 #[serde(default, borrow)]
129 scopes: Option<&'a str>,
130 #[serde(default)]
132 sections: Option<Vec<RawSection>>,
133}
134
135#[derive(Deserialize)]
137struct RawSection {
138 offset: RawOffset,
139 map: serde_json::Value,
140}
141
142#[derive(Deserialize)]
143struct RawOffset {
144 line: u32,
145 column: u32,
146}
147
148#[derive(Debug, Clone)]
152pub struct SourceMap {
153 pub file: Option<String>,
154 pub source_root: Option<String>,
155 pub sources: Vec<String>,
156 pub sources_content: Vec<Option<String>>,
157 pub names: Vec<String>,
158 pub ignore_list: Vec<u32>,
159 pub debug_id: Option<String>,
161 pub scopes: Option<ScopeInfo>,
163
164 mappings: Vec<Mapping>,
166
167 line_offsets: Vec<u32>,
170
171 reverse_index: OnceCell<Vec<u32>>,
174
175 source_map: HashMap<String, u32>,
177}
178
179impl SourceMap {
180 pub fn from_json(json: &str) -> Result<Self, ParseError> {
183 let raw: RawSourceMap<'_> = serde_json::from_str(json)?;
184
185 if raw.version != 3 {
186 return Err(ParseError::InvalidVersion(raw.version));
187 }
188
189 if let Some(sections) = raw.sections {
191 return Self::from_sections(raw.file, sections);
192 }
193
194 Self::from_regular(raw)
195 }
196
197 fn from_regular(raw: RawSourceMap<'_>) -> Result<Self, ParseError> {
199 let source_root = raw.source_root.as_deref().unwrap_or("");
201 let sources: Vec<String> = raw
202 .sources
203 .iter()
204 .map(|s| match s {
205 Some(s) if !source_root.is_empty() => format!("{source_root}{s}"),
206 Some(s) => s.clone(),
207 None => String::new(),
208 })
209 .collect();
210
211 let sources_content = raw.sources_content.unwrap_or_default();
212
213 let source_map: HashMap<String, u32> = sources
215 .iter()
216 .enumerate()
217 .map(|(i, s)| (s.clone(), i as u32))
218 .collect();
219
220 let (mappings, line_offsets) = decode_mappings(raw.mappings)?;
222
223 let num_sources = sources.len();
225 let scopes = match raw.scopes {
226 Some(scopes_str) if !scopes_str.is_empty() => Some(
227 srcmap_scopes::decode_scopes(scopes_str, &raw.names, num_sources)
228 .map_err(|e| ParseError::Scopes(e.to_string()))?,
229 ),
230 _ => None,
231 };
232
233 Ok(Self {
234 file: raw.file,
235 source_root: raw.source_root,
236 sources,
237 sources_content,
238 names: raw.names,
239 ignore_list: raw.ignore_list,
240 debug_id: raw.debug_id,
241 scopes,
242 mappings,
243 line_offsets,
244 reverse_index: OnceCell::new(),
245 source_map,
246 })
247 }
248
249 fn from_sections(file: Option<String>, sections: Vec<RawSection>) -> Result<Self, ParseError> {
251 let mut all_sources: Vec<String> = Vec::new();
252 let mut all_sources_content: Vec<Option<String>> = Vec::new();
253 let mut all_names: Vec<String> = Vec::new();
254 let mut all_mappings: Vec<Mapping> = Vec::new();
255 let mut all_ignore_list: Vec<u32> = Vec::new();
256 let mut max_line: u32 = 0;
257
258 let mut source_index_map: HashMap<String, u32> = HashMap::new();
260 let mut name_index_map: HashMap<String, u32> = HashMap::new();
261
262 for section in §ions {
263 let section_json = serde_json::to_string(§ion.map).map_err(ParseError::Json)?;
264 let sub = Self::from_json(§ion_json)?;
265
266 let line_offset = section.offset.line;
267 let col_offset = section.offset.column;
268
269 let source_remap: Vec<u32> = sub
271 .sources
272 .iter()
273 .enumerate()
274 .map(|(i, s)| {
275 if let Some(&existing) = source_index_map.get(s) {
276 existing
277 } else {
278 let idx = all_sources.len() as u32;
279 all_sources.push(s.clone());
280 let content = sub.sources_content.get(i).cloned().unwrap_or(None);
282 all_sources_content.push(content);
283 source_index_map.insert(s.clone(), idx);
284 idx
285 }
286 })
287 .collect();
288
289 let name_remap: Vec<u32> = sub
291 .names
292 .iter()
293 .map(|n| {
294 if let Some(&existing) = name_index_map.get(n) {
295 existing
296 } else {
297 let idx = all_names.len() as u32;
298 all_names.push(n.clone());
299 name_index_map.insert(n.clone(), idx);
300 idx
301 }
302 })
303 .collect();
304
305 for &idx in &sub.ignore_list {
307 let global_idx = source_remap[idx as usize];
308 if !all_ignore_list.contains(&global_idx) {
309 all_ignore_list.push(global_idx);
310 }
311 }
312
313 for m in &sub.mappings {
315 let gen_line = m.generated_line + line_offset;
316 let gen_col = if m.generated_line == 0 {
317 m.generated_column + col_offset
318 } else {
319 m.generated_column
320 };
321
322 all_mappings.push(Mapping {
323 generated_line: gen_line,
324 generated_column: gen_col,
325 source: if m.source == NO_SOURCE {
326 NO_SOURCE
327 } else {
328 source_remap[m.source as usize]
329 },
330 original_line: m.original_line,
331 original_column: m.original_column,
332 name: if m.name == NO_NAME {
333 NO_NAME
334 } else {
335 name_remap[m.name as usize]
336 },
337 });
338
339 if gen_line > max_line {
340 max_line = gen_line;
341 }
342 }
343 }
344
345 all_mappings.sort_unstable_by(|a, b| {
347 a.generated_line
348 .cmp(&b.generated_line)
349 .then(a.generated_column.cmp(&b.generated_column))
350 });
351
352 let line_count = if all_mappings.is_empty() {
354 0
355 } else {
356 max_line as usize + 1
357 };
358 let mut line_offsets: Vec<u32> = vec![0; line_count + 1];
359 let mut current_line: usize = 0;
360 for (i, m) in all_mappings.iter().enumerate() {
361 while current_line < m.generated_line as usize {
362 current_line += 1;
363 if current_line < line_offsets.len() {
364 line_offsets[current_line] = i as u32;
365 }
366 }
367 }
368 if !line_offsets.is_empty() {
370 let last = all_mappings.len() as u32;
371 for offset in line_offsets.iter_mut().skip(current_line + 1) {
372 *offset = last;
373 }
374 }
375
376 Ok(Self {
377 file,
378 source_root: None,
379 sources: all_sources.clone(),
380 sources_content: all_sources_content,
381 names: all_names,
382 ignore_list: all_ignore_list,
383 debug_id: None,
384 scopes: None, mappings: all_mappings,
386 line_offsets,
387 reverse_index: OnceCell::new(),
388 source_map: all_sources
389 .into_iter()
390 .enumerate()
391 .map(|(i, s)| (s, i as u32))
392 .collect(),
393 })
394 }
395
396 pub fn original_position_for(&self, line: u32, column: u32) -> Option<OriginalLocation> {
401 let line_idx = line as usize;
402 if line_idx + 1 >= self.line_offsets.len() {
403 return None;
404 }
405
406 let start = self.line_offsets[line_idx] as usize;
407 let end = self.line_offsets[line_idx + 1] as usize;
408
409 if start == end {
410 return None;
411 }
412
413 let line_mappings = &self.mappings[start..end];
414
415 let idx = match line_mappings.binary_search_by_key(&column, |m| m.generated_column) {
417 Ok(i) => i,
418 Err(0) => return None,
419 Err(i) => i - 1,
420 };
421
422 let mapping = &line_mappings[idx];
423
424 if mapping.source == NO_SOURCE {
425 return None;
426 }
427
428 Some(OriginalLocation {
429 source: mapping.source,
430 line: mapping.original_line,
431 column: mapping.original_column,
432 name: if mapping.name == NO_NAME {
433 None
434 } else {
435 Some(mapping.name)
436 },
437 })
438 }
439
440 pub fn generated_position_for(
444 &self,
445 source: &str,
446 line: u32,
447 column: u32,
448 ) -> Option<GeneratedLocation> {
449 let &source_idx = self.source_map.get(source)?;
450
451 let reverse_index = self
452 .reverse_index
453 .get_or_init(|| build_reverse_index(&self.mappings));
454
455 let idx = reverse_index.partition_point(|&i| {
457 let m = &self.mappings[i as usize];
458 (m.source, m.original_line, m.original_column) < (source_idx, line, column)
459 });
460
461 if idx >= reverse_index.len() {
462 return None;
463 }
464
465 let mapping = &self.mappings[reverse_index[idx] as usize];
466
467 if mapping.source != source_idx {
468 return None;
469 }
470
471 Some(GeneratedLocation {
472 line: mapping.generated_line,
473 column: mapping.generated_column,
474 })
475 }
476
477 pub fn source(&self, index: u32) -> &str {
479 &self.sources[index as usize]
480 }
481
482 pub fn name(&self, index: u32) -> &str {
484 &self.names[index as usize]
485 }
486
487 pub fn source_index(&self, name: &str) -> Option<u32> {
489 self.source_map.get(name).copied()
490 }
491
492 pub fn mapping_count(&self) -> usize {
494 self.mappings.len()
495 }
496
497 pub fn line_count(&self) -> usize {
499 self.line_offsets.len().saturating_sub(1)
500 }
501
502 pub fn mappings_for_line(&self, line: u32) -> &[Mapping] {
504 let line_idx = line as usize;
505 if line_idx + 1 >= self.line_offsets.len() {
506 return &[];
507 }
508 let start = self.line_offsets[line_idx] as usize;
509 let end = self.line_offsets[line_idx + 1] as usize;
510 &self.mappings[start..end]
511 }
512
513 pub fn all_mappings(&self) -> &[Mapping] {
515 &self.mappings
516 }
517
518 pub fn to_json(&self) -> String {
523 let mappings = self.encode_mappings();
524
525 let mut json = String::with_capacity(256 + mappings.len());
526 json.push_str(r#"{"version":3"#);
527
528 if let Some(ref file) = self.file {
529 json.push_str(r#","file":"#);
530 json_quote_into(&mut json, file);
531 }
532
533 if let Some(ref root) = self.source_root {
534 json.push_str(r#","sourceRoot":"#);
535 json_quote_into(&mut json, root);
536 }
537
538 json.push_str(r#","sources":["#);
539 for (i, s) in self.sources.iter().enumerate() {
540 if i > 0 {
541 json.push(',');
542 }
543 json_quote_into(&mut json, s);
544 }
545 json.push(']');
546
547 if !self.sources_content.is_empty() && self.sources_content.iter().any(|c| c.is_some()) {
548 json.push_str(r#","sourcesContent":["#);
549 for (i, c) in self.sources_content.iter().enumerate() {
550 if i > 0 {
551 json.push(',');
552 }
553 match c {
554 Some(content) => json_quote_into(&mut json, content),
555 None => json.push_str("null"),
556 }
557 }
558 json.push(']');
559 }
560
561 json.push_str(r#","names":["#);
562 for (i, n) in self.names.iter().enumerate() {
563 if i > 0 {
564 json.push(',');
565 }
566 json_quote_into(&mut json, n);
567 }
568 json.push(']');
569
570 json.push_str(r#","mappings":"#);
571 json_quote_into(&mut json, &mappings);
572
573 if !self.ignore_list.is_empty() {
574 json.push_str(r#","ignoreList":["#);
575 for (i, &idx) in self.ignore_list.iter().enumerate() {
576 if i > 0 {
577 json.push(',');
578 }
579 json.push_str(&idx.to_string());
580 }
581 json.push(']');
582 }
583
584 if let Some(ref id) = self.debug_id {
585 json.push_str(r#","debugId":"#);
586 json_quote_into(&mut json, id);
587 }
588
589 json.push('}');
590 json
591 }
592
593 fn encode_mappings(&self) -> String {
595 if self.mappings.is_empty() {
596 return String::new();
597 }
598
599 let mut out: Vec<u8> = Vec::with_capacity(self.mappings.len() * 6);
600
601 let mut prev_gen_col: i64 = 0;
602 let mut prev_source: i64 = 0;
603 let mut prev_orig_line: i64 = 0;
604 let mut prev_orig_col: i64 = 0;
605 let mut prev_name: i64 = 0;
606 let mut prev_gen_line: u32 = 0;
607 let mut first_in_line = true;
608
609 for m in &self.mappings {
610 while prev_gen_line < m.generated_line {
611 out.push(b';');
612 prev_gen_line += 1;
613 prev_gen_col = 0;
614 first_in_line = true;
615 }
616
617 if !first_in_line {
618 out.push(b',');
619 }
620 first_in_line = false;
621
622 srcmap_codec::vlq_encode(&mut out, m.generated_column as i64 - prev_gen_col);
623 prev_gen_col = m.generated_column as i64;
624
625 if m.source != NO_SOURCE {
626 srcmap_codec::vlq_encode(&mut out, m.source as i64 - prev_source);
627 prev_source = m.source as i64;
628
629 srcmap_codec::vlq_encode(&mut out, m.original_line as i64 - prev_orig_line);
630 prev_orig_line = m.original_line as i64;
631
632 srcmap_codec::vlq_encode(&mut out, m.original_column as i64 - prev_orig_col);
633 prev_orig_col = m.original_column as i64;
634
635 if m.name != NO_NAME {
636 srcmap_codec::vlq_encode(&mut out, m.name as i64 - prev_name);
637 prev_name = m.name as i64;
638 }
639 }
640 }
641
642 unsafe { String::from_utf8_unchecked(out) }
644 }
645}
646
647fn json_quote_into(out: &mut String, s: &str) {
649 out.push('"');
650 for c in s.chars() {
651 match c {
652 '"' => out.push_str("\\\""),
653 '\\' => out.push_str("\\\\"),
654 '\n' => out.push_str("\\n"),
655 '\r' => out.push_str("\\r"),
656 '\t' => out.push_str("\\t"),
657 c if c < '\x20' => {
658 out.push_str(&format!("\\u{:04x}", c as u32));
659 }
660 c => out.push(c),
661 }
662 }
663 out.push('"');
664}
665
666const B64: [u8; 128] = {
670 let mut table = [0xFFu8; 128];
671 let chars = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
672 let mut i = 0u8;
673 while i < 64 {
674 table[chars[i as usize] as usize] = i;
675 i += 1;
676 }
677 table
678};
679
680#[inline(always)]
683fn vlq_fast(bytes: &[u8], pos: &mut usize) -> Result<i64, DecodeError> {
684 let p = *pos;
685 if p >= bytes.len() {
686 return Err(DecodeError::UnexpectedEof { offset: p });
687 }
688
689 let b0 = bytes[p];
690 if b0 >= 128 {
691 return Err(DecodeError::InvalidBase64 {
692 byte: b0,
693 offset: p,
694 });
695 }
696 let d0 = B64[b0 as usize];
697 if d0 == 0xFF {
698 return Err(DecodeError::InvalidBase64 {
699 byte: b0,
700 offset: p,
701 });
702 }
703
704 if (d0 & 0x20) == 0 {
706 *pos = p + 1;
707 let val = (d0 >> 1) as i64;
708 return Ok(if (d0 & 1) != 0 { -val } else { val });
709 }
710
711 let mut result: u64 = (d0 & 0x1F) as u64;
713 let mut shift: u32 = 5;
714 let mut i = p + 1;
715
716 loop {
717 if i >= bytes.len() {
718 return Err(DecodeError::UnexpectedEof { offset: i });
719 }
720 let b = bytes[i];
721 if b >= 128 {
722 return Err(DecodeError::InvalidBase64 { byte: b, offset: i });
723 }
724 let d = B64[b as usize];
725 if d == 0xFF {
726 return Err(DecodeError::InvalidBase64 { byte: b, offset: i });
727 }
728 i += 1;
729
730 if shift >= 64 {
731 return Err(DecodeError::VlqOverflow { offset: p });
732 }
733
734 result += ((d & 0x1F) as u64) << shift;
735 shift += 5;
736
737 if (d & 0x20) == 0 {
738 break;
739 }
740 }
741
742 *pos = i;
743 let value = if (result & 1) == 1 {
744 -((result >> 1) as i64)
745 } else {
746 (result >> 1) as i64
747 };
748 Ok(value)
749}
750
751fn decode_mappings(input: &str) -> Result<(Vec<Mapping>, Vec<u32>), DecodeError> {
752 if input.is_empty() {
753 return Ok((Vec::new(), vec![0]));
754 }
755
756 let bytes = input.as_bytes();
757 let len = bytes.len();
758
759 let line_count = bytes.iter().filter(|&&b| b == b';').count() + 1;
761 let approx_segments = bytes.iter().filter(|&&b| b == b',').count() + line_count;
762
763 let mut mappings: Vec<Mapping> = Vec::with_capacity(approx_segments);
764 let mut line_offsets: Vec<u32> = Vec::with_capacity(line_count + 1);
765
766 let mut source_index: i64 = 0;
767 let mut original_line: i64 = 0;
768 let mut original_column: i64 = 0;
769 let mut name_index: i64 = 0;
770 let mut generated_line: u32 = 0;
771 let mut pos: usize = 0;
772
773 loop {
774 line_offsets.push(mappings.len() as u32);
775 let mut generated_column: i64 = 0;
776 let mut saw_semicolon = false;
777
778 while pos < len {
779 let byte = bytes[pos];
780
781 if byte == b';' {
782 pos += 1;
783 saw_semicolon = true;
784 break;
785 }
786
787 if byte == b',' {
788 pos += 1;
789 continue;
790 }
791
792 generated_column += vlq_fast(bytes, &mut pos)?;
794
795 if pos < len && bytes[pos] != b',' && bytes[pos] != b';' {
796 source_index += vlq_fast(bytes, &mut pos)?;
798 original_line += vlq_fast(bytes, &mut pos)?;
799 original_column += vlq_fast(bytes, &mut pos)?;
800
801 let name = if pos < len && bytes[pos] != b',' && bytes[pos] != b';' {
803 name_index += vlq_fast(bytes, &mut pos)?;
804 name_index as u32
805 } else {
806 NO_NAME
807 };
808
809 mappings.push(Mapping {
810 generated_line,
811 generated_column: generated_column as u32,
812 source: source_index as u32,
813 original_line: original_line as u32,
814 original_column: original_column as u32,
815 name,
816 });
817 } else {
818 mappings.push(Mapping {
820 generated_line,
821 generated_column: generated_column as u32,
822 source: NO_SOURCE,
823 original_line: 0,
824 original_column: 0,
825 name: NO_NAME,
826 });
827 }
828 }
829
830 if !saw_semicolon {
831 break;
832 }
833 generated_line += 1;
834 }
835
836 line_offsets.push(mappings.len() as u32);
838
839 Ok((mappings, line_offsets))
840}
841
842fn build_reverse_index(mappings: &[Mapping]) -> Vec<u32> {
844 let mut indices: Vec<u32> = (0..mappings.len() as u32)
845 .filter(|&i| mappings[i as usize].source != NO_SOURCE)
846 .collect();
847
848 indices.sort_unstable_by(|&a, &b| {
849 let ma = &mappings[a as usize];
850 let mb = &mappings[b as usize];
851 ma.source
852 .cmp(&mb.source)
853 .then(ma.original_line.cmp(&mb.original_line))
854 .then(ma.original_column.cmp(&mb.original_column))
855 });
856
857 indices
858}
859
860#[cfg(test)]
863mod tests {
864 use super::*;
865
866 fn simple_map() -> &'static str {
867 r#"{"version":3,"sources":["input.js"],"names":["hello"],"mappings":"AAAA;AACA,EAAA;AACA"}"#
868 }
869
870 #[test]
871 fn parse_basic() {
872 let sm = SourceMap::from_json(simple_map()).unwrap();
873 assert_eq!(sm.sources, vec!["input.js"]);
874 assert_eq!(sm.names, vec!["hello"]);
875 assert_eq!(sm.line_count(), 3);
876 assert!(sm.mapping_count() > 0);
877 }
878
879 #[test]
880 fn to_json_roundtrip() {
881 let json = simple_map();
882 let sm = SourceMap::from_json(json).unwrap();
883 let output = sm.to_json();
884
885 let sm2 = SourceMap::from_json(&output).unwrap();
887 assert_eq!(sm2.sources, sm.sources);
888 assert_eq!(sm2.names, sm.names);
889 assert_eq!(sm2.mapping_count(), sm.mapping_count());
890 assert_eq!(sm2.line_count(), sm.line_count());
891
892 for m in sm.all_mappings() {
894 let loc1 = sm.original_position_for(m.generated_line, m.generated_column);
895 let loc2 = sm2.original_position_for(m.generated_line, m.generated_column);
896 match (loc1, loc2) {
897 (Some(a), Some(b)) => {
898 assert_eq!(a.source, b.source);
899 assert_eq!(a.line, b.line);
900 assert_eq!(a.column, b.column);
901 assert_eq!(a.name, b.name);
902 }
903 (None, None) => {}
904 _ => panic!(
905 "lookup mismatch at ({}, {})",
906 m.generated_line, m.generated_column
907 ),
908 }
909 }
910 }
911
912 #[test]
913 fn to_json_roundtrip_large() {
914 let json = generate_test_sourcemap(50, 10, 3);
915 let sm = SourceMap::from_json(&json).unwrap();
916 let output = sm.to_json();
917 let sm2 = SourceMap::from_json(&output).unwrap();
918
919 assert_eq!(sm2.mapping_count(), sm.mapping_count());
920
921 for line in (0..sm.line_count() as u32).step_by(5) {
923 for col in [0u32, 10, 20, 50] {
924 let a = sm.original_position_for(line, col);
925 let b = sm2.original_position_for(line, col);
926 match (a, b) {
927 (Some(a), Some(b)) => {
928 assert_eq!(a.source, b.source);
929 assert_eq!(a.line, b.line);
930 assert_eq!(a.column, b.column);
931 }
932 (None, None) => {}
933 _ => panic!("mismatch at ({line}, {col})"),
934 }
935 }
936 }
937 }
938
939 #[test]
940 fn to_json_preserves_fields() {
941 let json = r#"{"version":3,"file":"out.js","sourceRoot":"src/","sources":["app.ts"],"sourcesContent":["const x = 1;"],"names":["x"],"mappings":"AAAAA","ignoreList":[0]}"#;
942 let sm = SourceMap::from_json(json).unwrap();
943 let output = sm.to_json();
944
945 assert!(output.contains(r#""file":"out.js""#));
946 assert!(output.contains(r#""sourceRoot":"src/""#));
947 assert!(output.contains(r#""sourcesContent":["const x = 1;"]"#));
948 assert!(output.contains(r#""ignoreList":[0]"#));
949
950 let sm2 = SourceMap::from_json(&output).unwrap();
952 assert_eq!(sm2.file.as_deref(), Some("out.js"));
953 assert_eq!(sm2.ignore_list, vec![0]);
954 }
955
956 #[test]
957 fn original_position_for_exact_match() {
958 let sm = SourceMap::from_json(simple_map()).unwrap();
959 let loc = sm.original_position_for(0, 0).unwrap();
960 assert_eq!(loc.source, 0);
961 assert_eq!(loc.line, 0);
962 assert_eq!(loc.column, 0);
963 }
964
965 #[test]
966 fn original_position_for_column_within_segment() {
967 let sm = SourceMap::from_json(simple_map()).unwrap();
968 let loc = sm.original_position_for(1, 5);
970 assert!(loc.is_some());
971 }
972
973 #[test]
974 fn original_position_for_nonexistent_line() {
975 let sm = SourceMap::from_json(simple_map()).unwrap();
976 assert!(sm.original_position_for(999, 0).is_none());
977 }
978
979 #[test]
980 fn original_position_for_before_first_mapping() {
981 let sm = SourceMap::from_json(simple_map()).unwrap();
983 let loc = sm.original_position_for(1, 0);
984 let _ = loc;
987 }
988
989 #[test]
990 fn generated_position_for_basic() {
991 let sm = SourceMap::from_json(simple_map()).unwrap();
992 let loc = sm.generated_position_for("input.js", 0, 0).unwrap();
993 assert_eq!(loc.line, 0);
994 assert_eq!(loc.column, 0);
995 }
996
997 #[test]
998 fn generated_position_for_unknown_source() {
999 let sm = SourceMap::from_json(simple_map()).unwrap();
1000 assert!(sm.generated_position_for("nonexistent.js", 0, 0).is_none());
1001 }
1002
1003 #[test]
1004 fn parse_invalid_version() {
1005 let json = r#"{"version":2,"sources":[],"names":[],"mappings":""}"#;
1006 let err = SourceMap::from_json(json).unwrap_err();
1007 assert!(matches!(err, ParseError::InvalidVersion(2)));
1008 }
1009
1010 #[test]
1011 fn parse_empty_mappings() {
1012 let json = r#"{"version":3,"sources":[],"names":[],"mappings":""}"#;
1013 let sm = SourceMap::from_json(json).unwrap();
1014 assert_eq!(sm.mapping_count(), 0);
1015 assert!(sm.original_position_for(0, 0).is_none());
1016 }
1017
1018 #[test]
1019 fn parse_with_source_root() {
1020 let json = r#"{"version":3,"sourceRoot":"src/","sources":["foo.js"],"names":[],"mappings":"AAAA"}"#;
1021 let sm = SourceMap::from_json(json).unwrap();
1022 assert_eq!(sm.sources, vec!["src/foo.js"]);
1023 }
1024
1025 #[test]
1026 fn parse_with_sources_content() {
1027 let json = r#"{"version":3,"sources":["a.js"],"sourcesContent":["var x = 1;"],"names":[],"mappings":"AAAA"}"#;
1028 let sm = SourceMap::from_json(json).unwrap();
1029 assert_eq!(sm.sources_content, vec![Some("var x = 1;".to_string())]);
1030 }
1031
1032 #[test]
1033 fn mappings_for_line() {
1034 let sm = SourceMap::from_json(simple_map()).unwrap();
1035 let line0 = sm.mappings_for_line(0);
1036 assert!(!line0.is_empty());
1037 let empty = sm.mappings_for_line(999);
1038 assert!(empty.is_empty());
1039 }
1040
1041 #[test]
1042 fn large_sourcemap_lookup() {
1043 let json = generate_test_sourcemap(500, 20, 5);
1045 let sm = SourceMap::from_json(&json).unwrap();
1046
1047 for line in [0, 10, 100, 250, 499] {
1049 let mappings = sm.mappings_for_line(line);
1050 if let Some(m) = mappings.first() {
1051 let loc = sm.original_position_for(line, m.generated_column);
1052 assert!(loc.is_some(), "lookup failed for line {line}");
1053 }
1054 }
1055 }
1056
1057 #[test]
1058 fn reverse_lookup_roundtrip() {
1059 let json = generate_test_sourcemap(100, 10, 3);
1060 let sm = SourceMap::from_json(&json).unwrap();
1061
1062 let mapping = &sm.mappings[50];
1064 if mapping.source != NO_SOURCE {
1065 let source_name = sm.source(mapping.source);
1066 let result = sm.generated_position_for(
1067 source_name,
1068 mapping.original_line,
1069 mapping.original_column,
1070 );
1071 assert!(result.is_some(), "reverse lookup failed");
1072 }
1073 }
1074
1075 #[test]
1076 fn indexed_source_map() {
1077 let json = r#"{
1078 "version": 3,
1079 "file": "bundle.js",
1080 "sections": [
1081 {
1082 "offset": {"line": 0, "column": 0},
1083 "map": {
1084 "version": 3,
1085 "sources": ["a.js"],
1086 "names": ["foo"],
1087 "mappings": "AAAAA"
1088 }
1089 },
1090 {
1091 "offset": {"line": 10, "column": 0},
1092 "map": {
1093 "version": 3,
1094 "sources": ["b.js"],
1095 "names": ["bar"],
1096 "mappings": "AAAAA"
1097 }
1098 }
1099 ]
1100 }"#;
1101
1102 let sm = SourceMap::from_json(json).unwrap();
1103
1104 assert_eq!(sm.sources.len(), 2);
1106 assert!(sm.sources.contains(&"a.js".to_string()));
1107 assert!(sm.sources.contains(&"b.js".to_string()));
1108
1109 assert_eq!(sm.names.len(), 2);
1111 assert!(sm.names.contains(&"foo".to_string()));
1112 assert!(sm.names.contains(&"bar".to_string()));
1113
1114 let loc = sm.original_position_for(0, 0).unwrap();
1116 assert_eq!(sm.source(loc.source), "a.js");
1117 assert_eq!(loc.line, 0);
1118 assert_eq!(loc.column, 0);
1119
1120 let loc = sm.original_position_for(10, 0).unwrap();
1122 assert_eq!(sm.source(loc.source), "b.js");
1123 assert_eq!(loc.line, 0);
1124 assert_eq!(loc.column, 0);
1125 }
1126
1127 #[test]
1128 fn indexed_source_map_shared_sources() {
1129 let json = r#"{
1131 "version": 3,
1132 "sections": [
1133 {
1134 "offset": {"line": 0, "column": 0},
1135 "map": {
1136 "version": 3,
1137 "sources": ["shared.js"],
1138 "names": [],
1139 "mappings": "AAAA"
1140 }
1141 },
1142 {
1143 "offset": {"line": 5, "column": 0},
1144 "map": {
1145 "version": 3,
1146 "sources": ["shared.js"],
1147 "names": [],
1148 "mappings": "AACA"
1149 }
1150 }
1151 ]
1152 }"#;
1153
1154 let sm = SourceMap::from_json(json).unwrap();
1155
1156 assert_eq!(sm.sources.len(), 1);
1158 assert_eq!(sm.sources[0], "shared.js");
1159
1160 let loc0 = sm.original_position_for(0, 0).unwrap();
1162 let loc5 = sm.original_position_for(5, 0).unwrap();
1163 assert_eq!(loc0.source, loc5.source);
1164 }
1165
1166 #[test]
1167 fn parse_ignore_list() {
1168 let json = r#"{"version":3,"sources":["app.js","node_modules/lib.js"],"names":[],"mappings":"AAAA;ACAA","ignoreList":[1]}"#;
1169 let sm = SourceMap::from_json(json).unwrap();
1170 assert_eq!(sm.ignore_list, vec![1]);
1171 }
1172
1173 fn build_sourcemap_json(
1175 sources: &[&str],
1176 names: &[&str],
1177 mappings_data: &[Vec<Vec<i64>>],
1178 ) -> String {
1179 let mappings_vec: Vec<Vec<Vec<i64>>> = mappings_data.to_vec();
1180 let encoded = srcmap_codec::encode(&mappings_vec);
1181 format!(
1182 r#"{{"version":3,"sources":[{}],"names":[{}],"mappings":"{}"}}"#,
1183 sources
1184 .iter()
1185 .map(|s| format!("\"{s}\""))
1186 .collect::<Vec<_>>()
1187 .join(","),
1188 names
1189 .iter()
1190 .map(|n| format!("\"{n}\""))
1191 .collect::<Vec<_>>()
1192 .join(","),
1193 encoded,
1194 )
1195 }
1196
1197 #[test]
1200 fn decode_multiple_consecutive_semicolons() {
1201 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;;;AACA"}"#;
1202 let sm = SourceMap::from_json(json).unwrap();
1203 assert_eq!(sm.line_count(), 4);
1204 assert!(sm.mappings_for_line(1).is_empty());
1205 assert!(sm.mappings_for_line(2).is_empty());
1206 assert!(!sm.mappings_for_line(0).is_empty());
1207 assert!(!sm.mappings_for_line(3).is_empty());
1208 }
1209
1210 #[test]
1211 fn decode_trailing_semicolons() {
1212 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;;"}"#;
1213 let sm = SourceMap::from_json(json).unwrap();
1214 assert_eq!(sm.line_count(), 3);
1215 assert!(!sm.mappings_for_line(0).is_empty());
1216 assert!(sm.mappings_for_line(1).is_empty());
1217 assert!(sm.mappings_for_line(2).is_empty());
1218 }
1219
1220 #[test]
1221 fn decode_leading_comma() {
1222 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":",AAAA"}"#;
1223 let sm = SourceMap::from_json(json).unwrap();
1224 assert_eq!(sm.mapping_count(), 1);
1225 let m = &sm.all_mappings()[0];
1226 assert_eq!(m.generated_line, 0);
1227 assert_eq!(m.generated_column, 0);
1228 }
1229
1230 #[test]
1231 fn decode_single_field_segments() {
1232 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A,C"}"#;
1233 let sm = SourceMap::from_json(json).unwrap();
1234 assert_eq!(sm.mapping_count(), 2);
1235 for m in sm.all_mappings() {
1236 assert_eq!(m.source, NO_SOURCE);
1237 }
1238 assert_eq!(sm.all_mappings()[0].generated_column, 0);
1239 assert_eq!(sm.all_mappings()[1].generated_column, 1);
1240 assert!(sm.original_position_for(0, 0).is_none());
1241 assert!(sm.original_position_for(0, 1).is_none());
1242 }
1243
1244 #[test]
1245 fn decode_five_field_segments_with_names() {
1246 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0, 0], vec![10, 0, 0, 5, 1]]];
1247 let json = build_sourcemap_json(&["app.js"], &["foo", "bar"], &mappings_data);
1248 let sm = SourceMap::from_json(&json).unwrap();
1249 assert_eq!(sm.mapping_count(), 2);
1250 assert_eq!(sm.all_mappings()[0].name, 0);
1251 assert_eq!(sm.all_mappings()[1].name, 1);
1252
1253 let loc = sm.original_position_for(0, 0).unwrap();
1254 assert_eq!(loc.name, Some(0));
1255 assert_eq!(sm.name(0), "foo");
1256
1257 let loc = sm.original_position_for(0, 10).unwrap();
1258 assert_eq!(loc.name, Some(1));
1259 assert_eq!(sm.name(1), "bar");
1260 }
1261
1262 #[test]
1263 fn decode_large_vlq_values() {
1264 let mappings_data = vec![vec![vec![500_i64, 0, 1000, 2000]]];
1265 let json = build_sourcemap_json(&["big.js"], &[], &mappings_data);
1266 let sm = SourceMap::from_json(&json).unwrap();
1267 assert_eq!(sm.mapping_count(), 1);
1268 let m = &sm.all_mappings()[0];
1269 assert_eq!(m.generated_column, 500);
1270 assert_eq!(m.original_line, 1000);
1271 assert_eq!(m.original_column, 2000);
1272
1273 let loc = sm.original_position_for(0, 500).unwrap();
1274 assert_eq!(loc.line, 1000);
1275 assert_eq!(loc.column, 2000);
1276 }
1277
1278 #[test]
1279 fn decode_only_semicolons() {
1280 let json = r#"{"version":3,"sources":[],"names":[],"mappings":";;;"}"#;
1281 let sm = SourceMap::from_json(json).unwrap();
1282 assert_eq!(sm.line_count(), 4);
1283 assert_eq!(sm.mapping_count(), 0);
1284 for line in 0..4 {
1285 assert!(sm.mappings_for_line(line).is_empty());
1286 }
1287 }
1288
1289 #[test]
1290 fn decode_mixed_single_and_four_field_segments() {
1291 let mappings_data = vec![vec![vec![5_i64, 0, 0, 0]]];
1292 let four_field_encoded = srcmap_codec::encode(&mappings_data);
1293 let combined_mappings = format!("A,{four_field_encoded}");
1294 let json = format!(
1295 r#"{{"version":3,"sources":["x.js"],"names":[],"mappings":"{combined_mappings}"}}"#,
1296 );
1297 let sm = SourceMap::from_json(&json).unwrap();
1298 assert_eq!(sm.mapping_count(), 2);
1299 assert_eq!(sm.all_mappings()[0].source, NO_SOURCE);
1300 assert_eq!(sm.all_mappings()[1].source, 0);
1301 }
1302
1303 #[test]
1306 fn parse_missing_optional_fields() {
1307 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
1308 let sm = SourceMap::from_json(json).unwrap();
1309 assert!(sm.file.is_none());
1310 assert!(sm.source_root.is_none());
1311 assert!(sm.sources_content.is_empty());
1312 assert!(sm.ignore_list.is_empty());
1313 }
1314
1315 #[test]
1316 fn parse_with_file_field() {
1317 let json =
1318 r#"{"version":3,"file":"output.js","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
1319 let sm = SourceMap::from_json(json).unwrap();
1320 assert_eq!(sm.file.as_deref(), Some("output.js"));
1321 }
1322
1323 #[test]
1324 fn parse_null_entries_in_sources() {
1325 let json = r#"{"version":3,"sources":["a.js",null,"c.js"],"names":[],"mappings":"AAAA"}"#;
1326 let sm = SourceMap::from_json(json).unwrap();
1327 assert_eq!(sm.sources.len(), 3);
1328 assert_eq!(sm.sources[0], "a.js");
1329 assert_eq!(sm.sources[1], "");
1330 assert_eq!(sm.sources[2], "c.js");
1331 }
1332
1333 #[test]
1334 fn parse_null_entries_in_sources_with_source_root() {
1335 let json = r#"{"version":3,"sourceRoot":"lib/","sources":["a.js",null],"names":[],"mappings":"AAAA"}"#;
1336 let sm = SourceMap::from_json(json).unwrap();
1337 assert_eq!(sm.sources[0], "lib/a.js");
1338 assert_eq!(sm.sources[1], "");
1339 }
1340
1341 #[test]
1342 fn parse_empty_names_array() {
1343 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
1344 let sm = SourceMap::from_json(json).unwrap();
1345 assert!(sm.names.is_empty());
1346 }
1347
1348 #[test]
1349 fn parse_invalid_json() {
1350 let result = SourceMap::from_json("not valid json");
1351 assert!(result.is_err());
1352 assert!(matches!(result.unwrap_err(), ParseError::Json(_)));
1353 }
1354
1355 #[test]
1356 fn parse_json_missing_version() {
1357 let result = SourceMap::from_json(r#"{"sources":[],"names":[],"mappings":""}"#);
1358 assert!(result.is_err());
1359 }
1360
1361 #[test]
1362 fn parse_multiple_sources_overlapping_original_positions() {
1363 let mappings_data = vec![vec![vec![0_i64, 0, 5, 10], vec![10, 1, 5, 10]]];
1364 let json = build_sourcemap_json(&["a.js", "b.js"], &[], &mappings_data);
1365 let sm = SourceMap::from_json(&json).unwrap();
1366
1367 let loc0 = sm.original_position_for(0, 0).unwrap();
1368 assert_eq!(loc0.source, 0);
1369 assert_eq!(sm.source(loc0.source), "a.js");
1370
1371 let loc1 = sm.original_position_for(0, 10).unwrap();
1372 assert_eq!(loc1.source, 1);
1373 assert_eq!(sm.source(loc1.source), "b.js");
1374
1375 assert_eq!(loc0.line, loc1.line);
1376 assert_eq!(loc0.column, loc1.column);
1377 }
1378
1379 #[test]
1380 fn parse_sources_content_with_null_entries() {
1381 let json = r#"{"version":3,"sources":["a.js","b.js"],"sourcesContent":["content a",null],"names":[],"mappings":"AAAA"}"#;
1382 let sm = SourceMap::from_json(json).unwrap();
1383 assert_eq!(sm.sources_content.len(), 2);
1384 assert_eq!(sm.sources_content[0], Some("content a".to_string()));
1385 assert_eq!(sm.sources_content[1], None);
1386 }
1387
1388 #[test]
1389 fn parse_empty_sources_and_names() {
1390 let json = r#"{"version":3,"sources":[],"names":[],"mappings":""}"#;
1391 let sm = SourceMap::from_json(json).unwrap();
1392 assert!(sm.sources.is_empty());
1393 assert!(sm.names.is_empty());
1394 assert_eq!(sm.mapping_count(), 0);
1395 }
1396
1397 #[test]
1400 fn lookup_exact_match() {
1401 let mappings_data = vec![vec![
1402 vec![0_i64, 0, 10, 20],
1403 vec![5, 0, 10, 25],
1404 vec![15, 0, 11, 0],
1405 ]];
1406 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
1407 let sm = SourceMap::from_json(&json).unwrap();
1408
1409 let loc = sm.original_position_for(0, 5).unwrap();
1410 assert_eq!(loc.line, 10);
1411 assert_eq!(loc.column, 25);
1412 }
1413
1414 #[test]
1415 fn lookup_before_first_segment() {
1416 let mappings_data = vec![vec![vec![5_i64, 0, 0, 0]]];
1417 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
1418 let sm = SourceMap::from_json(&json).unwrap();
1419
1420 assert!(sm.original_position_for(0, 0).is_none());
1421 assert!(sm.original_position_for(0, 4).is_none());
1422 }
1423
1424 #[test]
1425 fn lookup_between_segments() {
1426 let mappings_data = vec![vec![
1427 vec![0_i64, 0, 1, 0],
1428 vec![10, 0, 2, 0],
1429 vec![20, 0, 3, 0],
1430 ]];
1431 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
1432 let sm = SourceMap::from_json(&json).unwrap();
1433
1434 let loc = sm.original_position_for(0, 7).unwrap();
1435 assert_eq!(loc.line, 1);
1436 assert_eq!(loc.column, 0);
1437
1438 let loc = sm.original_position_for(0, 15).unwrap();
1439 assert_eq!(loc.line, 2);
1440 assert_eq!(loc.column, 0);
1441 }
1442
1443 #[test]
1444 fn lookup_after_last_segment() {
1445 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0], vec![10, 0, 1, 5]]];
1446 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
1447 let sm = SourceMap::from_json(&json).unwrap();
1448
1449 let loc = sm.original_position_for(0, 100).unwrap();
1450 assert_eq!(loc.line, 1);
1451 assert_eq!(loc.column, 5);
1452 }
1453
1454 #[test]
1455 fn lookup_empty_lines_no_mappings() {
1456 let mappings_data = vec![
1457 vec![vec![0_i64, 0, 0, 0]],
1458 vec![],
1459 vec![vec![0_i64, 0, 2, 0]],
1460 ];
1461 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
1462 let sm = SourceMap::from_json(&json).unwrap();
1463
1464 assert!(sm.original_position_for(1, 0).is_none());
1465 assert!(sm.original_position_for(1, 10).is_none());
1466 assert!(sm.original_position_for(0, 0).is_some());
1467 assert!(sm.original_position_for(2, 0).is_some());
1468 }
1469
1470 #[test]
1471 fn lookup_line_with_single_mapping() {
1472 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0]]];
1473 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
1474 let sm = SourceMap::from_json(&json).unwrap();
1475
1476 let loc = sm.original_position_for(0, 0).unwrap();
1477 assert_eq!(loc.line, 0);
1478 assert_eq!(loc.column, 0);
1479
1480 let loc = sm.original_position_for(0, 50).unwrap();
1481 assert_eq!(loc.line, 0);
1482 assert_eq!(loc.column, 0);
1483 }
1484
1485 #[test]
1486 fn lookup_column_0_vs_column_nonzero() {
1487 let mappings_data = vec![vec![vec![0_i64, 0, 10, 0], vec![8, 0, 20, 5]]];
1488 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
1489 let sm = SourceMap::from_json(&json).unwrap();
1490
1491 let loc0 = sm.original_position_for(0, 0).unwrap();
1492 assert_eq!(loc0.line, 10);
1493 assert_eq!(loc0.column, 0);
1494
1495 let loc8 = sm.original_position_for(0, 8).unwrap();
1496 assert_eq!(loc8.line, 20);
1497 assert_eq!(loc8.column, 5);
1498
1499 let loc4 = sm.original_position_for(0, 4).unwrap();
1500 assert_eq!(loc4.line, 10);
1501 }
1502
1503 #[test]
1504 fn lookup_beyond_last_line() {
1505 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0]]];
1506 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
1507 let sm = SourceMap::from_json(&json).unwrap();
1508
1509 assert!(sm.original_position_for(1, 0).is_none());
1510 assert!(sm.original_position_for(100, 0).is_none());
1511 }
1512
1513 #[test]
1514 fn lookup_single_field_returns_none() {
1515 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A"}"#;
1516 let sm = SourceMap::from_json(json).unwrap();
1517 assert_eq!(sm.mapping_count(), 1);
1518 assert!(sm.original_position_for(0, 0).is_none());
1519 }
1520
1521 #[test]
1524 fn reverse_lookup_exact_match() {
1525 let mappings_data = vec![
1526 vec![vec![0_i64, 0, 0, 0]],
1527 vec![vec![4, 0, 1, 0], vec![10, 0, 1, 8]],
1528 vec![vec![0, 0, 2, 0]],
1529 ];
1530 let json = build_sourcemap_json(&["main.js"], &[], &mappings_data);
1531 let sm = SourceMap::from_json(&json).unwrap();
1532
1533 let loc = sm.generated_position_for("main.js", 1, 8).unwrap();
1534 assert_eq!(loc.line, 1);
1535 assert_eq!(loc.column, 10);
1536 }
1537
1538 #[test]
1539 fn reverse_lookup_no_match() {
1540 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0], vec![10, 0, 0, 10]]];
1541 let json = build_sourcemap_json(&["main.js"], &[], &mappings_data);
1542 let sm = SourceMap::from_json(&json).unwrap();
1543
1544 assert!(sm.generated_position_for("main.js", 99, 0).is_none());
1545 }
1546
1547 #[test]
1548 fn reverse_lookup_unknown_source() {
1549 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0]]];
1550 let json = build_sourcemap_json(&["main.js"], &[], &mappings_data);
1551 let sm = SourceMap::from_json(&json).unwrap();
1552
1553 assert!(sm.generated_position_for("unknown.js", 0, 0).is_none());
1554 }
1555
1556 #[test]
1557 fn reverse_lookup_multiple_mappings_same_original() {
1558 let mappings_data = vec![vec![vec![0_i64, 0, 5, 10]], vec![vec![20, 0, 5, 10]]];
1559 let json = build_sourcemap_json(&["src.js"], &[], &mappings_data);
1560 let sm = SourceMap::from_json(&json).unwrap();
1561
1562 let loc = sm.generated_position_for("src.js", 5, 10);
1563 assert!(loc.is_some());
1564 let loc = loc.unwrap();
1565 assert!(
1566 (loc.line == 0 && loc.column == 0) || (loc.line == 1 && loc.column == 20),
1567 "Expected (0,0) or (1,20), got ({},{})",
1568 loc.line,
1569 loc.column
1570 );
1571 }
1572
1573 #[test]
1574 fn reverse_lookup_with_multiple_sources() {
1575 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0], vec![10, 1, 0, 0]]];
1576 let json = build_sourcemap_json(&["a.js", "b.js"], &[], &mappings_data);
1577 let sm = SourceMap::from_json(&json).unwrap();
1578
1579 let loc_a = sm.generated_position_for("a.js", 0, 0).unwrap();
1580 assert_eq!(loc_a.line, 0);
1581 assert_eq!(loc_a.column, 0);
1582
1583 let loc_b = sm.generated_position_for("b.js", 0, 0).unwrap();
1584 assert_eq!(loc_b.line, 0);
1585 assert_eq!(loc_b.column, 10);
1586 }
1587
1588 #[test]
1589 fn reverse_lookup_skips_single_field_segments() {
1590 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A,KAAAA"}"#;
1591 let sm = SourceMap::from_json(json).unwrap();
1592
1593 let loc = sm.generated_position_for("a.js", 0, 0).unwrap();
1594 assert_eq!(loc.line, 0);
1595 assert_eq!(loc.column, 5);
1596 }
1597
1598 #[test]
1599 fn reverse_lookup_finds_each_original_line() {
1600 let mappings_data = vec![
1601 vec![vec![0_i64, 0, 0, 0]],
1602 vec![vec![0, 0, 1, 0]],
1603 vec![vec![0, 0, 2, 0]],
1604 vec![vec![0, 0, 3, 0]],
1605 ];
1606 let json = build_sourcemap_json(&["x.js"], &[], &mappings_data);
1607 let sm = SourceMap::from_json(&json).unwrap();
1608
1609 for orig_line in 0..4 {
1610 let loc = sm.generated_position_for("x.js", orig_line, 0).unwrap();
1611 assert_eq!(
1612 loc.line, orig_line,
1613 "reverse lookup for orig line {orig_line}"
1614 );
1615 assert_eq!(loc.column, 0);
1616 }
1617 }
1618
1619 #[test]
1622 fn parse_with_ignore_list_multiple() {
1623 let json = r#"{"version":3,"sources":["app.js","node_modules/lib.js","vendor.js"],"names":[],"mappings":"AAAA","ignoreList":[1,2]}"#;
1624 let sm = SourceMap::from_json(json).unwrap();
1625 assert_eq!(sm.ignore_list, vec![1, 2]);
1626 }
1627
1628 #[test]
1629 fn parse_with_empty_ignore_list() {
1630 let json =
1631 r#"{"version":3,"sources":["app.js"],"names":[],"mappings":"AAAA","ignoreList":[]}"#;
1632 let sm = SourceMap::from_json(json).unwrap();
1633 assert!(sm.ignore_list.is_empty());
1634 }
1635
1636 #[test]
1637 fn parse_without_ignore_list_field() {
1638 let json = r#"{"version":3,"sources":["app.js"],"names":[],"mappings":"AAAA"}"#;
1639 let sm = SourceMap::from_json(json).unwrap();
1640 assert!(sm.ignore_list.is_empty());
1641 }
1642
1643 #[test]
1646 fn source_index_lookup() {
1647 let json = r#"{"version":3,"sources":["a.js","b.js","c.js"],"names":[],"mappings":"AAAA"}"#;
1648 let sm = SourceMap::from_json(json).unwrap();
1649 assert_eq!(sm.source_index("a.js"), Some(0));
1650 assert_eq!(sm.source_index("b.js"), Some(1));
1651 assert_eq!(sm.source_index("c.js"), Some(2));
1652 assert_eq!(sm.source_index("d.js"), None);
1653 }
1654
1655 #[test]
1656 fn all_mappings_returns_complete_list() {
1657 let mappings_data = vec![
1658 vec![vec![0_i64, 0, 0, 0], vec![5, 0, 0, 5]],
1659 vec![vec![0, 0, 1, 0]],
1660 ];
1661 let json = build_sourcemap_json(&["x.js"], &[], &mappings_data);
1662 let sm = SourceMap::from_json(&json).unwrap();
1663 assert_eq!(sm.all_mappings().len(), 3);
1664 assert_eq!(sm.mapping_count(), 3);
1665 }
1666
1667 #[test]
1668 fn line_count_matches_decoded_lines() {
1669 let mappings_data = vec![
1670 vec![vec![0_i64, 0, 0, 0]],
1671 vec![],
1672 vec![vec![0_i64, 0, 2, 0]],
1673 vec![],
1674 vec![],
1675 ];
1676 let json = build_sourcemap_json(&["x.js"], &[], &mappings_data);
1677 let sm = SourceMap::from_json(&json).unwrap();
1678 assert_eq!(sm.line_count(), 5);
1679 }
1680
1681 #[test]
1682 fn parse_error_display() {
1683 let err = ParseError::InvalidVersion(5);
1684 assert_eq!(format!("{err}"), "unsupported source map version: 5");
1685
1686 let json_err = SourceMap::from_json("{}").unwrap_err();
1687 let display = format!("{json_err}");
1688 assert!(display.contains("JSON parse error") || display.contains("missing field"));
1689 }
1690
1691 #[test]
1692 fn original_position_name_none_for_four_field() {
1693 let mappings_data = vec![vec![vec![0_i64, 0, 5, 10]]];
1694 let json = build_sourcemap_json(&["a.js"], &["unused_name"], &mappings_data);
1695 let sm = SourceMap::from_json(&json).unwrap();
1696
1697 let loc = sm.original_position_for(0, 0).unwrap();
1698 assert!(loc.name.is_none());
1699 }
1700
1701 #[test]
1702 fn forward_and_reverse_roundtrip_comprehensive() {
1703 let mappings_data = vec![
1704 vec![vec![0_i64, 0, 0, 0], vec![10, 0, 0, 10], vec![20, 1, 5, 0]],
1705 vec![vec![0, 0, 1, 0], vec![5, 1, 6, 3]],
1706 vec![vec![0, 0, 2, 0]],
1707 ];
1708 let json = build_sourcemap_json(&["a.js", "b.js"], &[], &mappings_data);
1709 let sm = SourceMap::from_json(&json).unwrap();
1710
1711 for m in sm.all_mappings() {
1712 if m.source == NO_SOURCE {
1713 continue;
1714 }
1715 let source_name = sm.source(m.source);
1716
1717 let orig = sm
1718 .original_position_for(m.generated_line, m.generated_column)
1719 .unwrap();
1720 assert_eq!(orig.source, m.source);
1721 assert_eq!(orig.line, m.original_line);
1722 assert_eq!(orig.column, m.original_column);
1723
1724 let gen_loc = sm
1725 .generated_position_for(source_name, m.original_line, m.original_column)
1726 .unwrap();
1727 assert_eq!(gen_loc.line, m.generated_line);
1728 assert_eq!(gen_loc.column, m.generated_column);
1729 }
1730 }
1731
1732 #[test]
1737 fn source_root_with_multiple_sources() {
1738 let json = r#"{"version":3,"sourceRoot":"lib/","sources":["a.js","b.js","c.js"],"names":[],"mappings":"AAAA,KACA,KACA"}"#;
1739 let sm = SourceMap::from_json(json).unwrap();
1740 assert_eq!(sm.sources, vec!["lib/a.js", "lib/b.js", "lib/c.js"]);
1741 }
1742
1743 #[test]
1744 fn source_root_empty_string() {
1745 let json =
1746 r#"{"version":3,"sourceRoot":"","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
1747 let sm = SourceMap::from_json(json).unwrap();
1748 assert_eq!(sm.sources, vec!["a.js"]);
1749 }
1750
1751 #[test]
1752 fn source_root_preserved_in_to_json() {
1753 let json =
1754 r#"{"version":3,"sourceRoot":"src/","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
1755 let sm = SourceMap::from_json(json).unwrap();
1756 let output = sm.to_json();
1757 assert!(output.contains(r#""sourceRoot":"src/""#));
1758 }
1759
1760 #[test]
1761 fn source_root_reverse_lookup_uses_prefixed_name() {
1762 let json =
1763 r#"{"version":3,"sourceRoot":"src/","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
1764 let sm = SourceMap::from_json(json).unwrap();
1765 assert!(sm.generated_position_for("src/a.js", 0, 0).is_some());
1767 assert!(sm.generated_position_for("a.js", 0, 0).is_none());
1768 }
1769
1770 #[test]
1771 fn source_root_with_trailing_slash() {
1772 let json =
1773 r#"{"version":3,"sourceRoot":"src/","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
1774 let sm = SourceMap::from_json(json).unwrap();
1775 assert_eq!(sm.sources[0], "src/a.js");
1776 }
1777
1778 #[test]
1779 fn source_root_without_trailing_slash() {
1780 let json =
1781 r#"{"version":3,"sourceRoot":"src","sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
1782 let sm = SourceMap::from_json(json).unwrap();
1783 assert_eq!(sm.sources[0], "srca.js"); }
1785
1786 #[test]
1789 fn parse_empty_json_object() {
1790 let result = SourceMap::from_json("{}");
1792 assert!(result.is_err());
1793 }
1794
1795 #[test]
1796 fn parse_version_0() {
1797 let json = r#"{"version":0,"sources":[],"names":[],"mappings":""}"#;
1798 assert!(matches!(
1799 SourceMap::from_json(json).unwrap_err(),
1800 ParseError::InvalidVersion(0)
1801 ));
1802 }
1803
1804 #[test]
1805 fn parse_version_4() {
1806 let json = r#"{"version":4,"sources":[],"names":[],"mappings":""}"#;
1807 assert!(matches!(
1808 SourceMap::from_json(json).unwrap_err(),
1809 ParseError::InvalidVersion(4)
1810 ));
1811 }
1812
1813 #[test]
1814 fn parse_extra_unknown_fields_ignored() {
1815 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","x_custom_field":true,"x_debug":{"foo":"bar"}}"#;
1816 let sm = SourceMap::from_json(json).unwrap();
1817 assert_eq!(sm.mapping_count(), 1);
1818 }
1819
1820 #[test]
1821 fn parse_vlq_error_propagated() {
1822 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AA!A"}"#;
1824 let result = SourceMap::from_json(json);
1825 assert!(result.is_err());
1826 assert!(matches!(result.unwrap_err(), ParseError::Vlq(_)));
1827 }
1828
1829 #[test]
1830 fn parse_truncated_vlq_error() {
1831 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"g"}"#;
1833 let result = SourceMap::from_json(json);
1834 assert!(result.is_err());
1835 }
1836
1837 #[test]
1840 fn to_json_produces_valid_json() {
1841 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]}"#;
1842 let sm = SourceMap::from_json(json).unwrap();
1843 let output = sm.to_json();
1844 let _: serde_json::Value = serde_json::from_str(&output).unwrap();
1846 }
1847
1848 #[test]
1849 fn to_json_escapes_special_chars() {
1850 let json = r#"{"version":3,"sources":["path/with\"quotes.js"],"sourcesContent":["line1\nline2\ttab\\backslash"],"names":[],"mappings":"AAAA"}"#;
1851 let sm = SourceMap::from_json(json).unwrap();
1852 let output = sm.to_json();
1853 let _: serde_json::Value = serde_json::from_str(&output).unwrap();
1854 let sm2 = SourceMap::from_json(&output).unwrap();
1855 assert_eq!(
1856 sm2.sources_content[0].as_deref(),
1857 Some("line1\nline2\ttab\\backslash")
1858 );
1859 }
1860
1861 #[test]
1862 fn to_json_empty_map() {
1863 let json = r#"{"version":3,"sources":[],"names":[],"mappings":""}"#;
1864 let sm = SourceMap::from_json(json).unwrap();
1865 let output = sm.to_json();
1866 let sm2 = SourceMap::from_json(&output).unwrap();
1867 assert_eq!(sm2.mapping_count(), 0);
1868 assert!(sm2.sources.is_empty());
1869 }
1870
1871 #[test]
1872 fn to_json_roundtrip_with_names() {
1873 let mappings_data = vec![vec![
1874 vec![0_i64, 0, 0, 0, 0],
1875 vec![10, 0, 0, 10, 1],
1876 vec![20, 0, 1, 0, 2],
1877 ]];
1878 let json = build_sourcemap_json(&["src.js"], &["foo", "bar", "baz"], &mappings_data);
1879 let sm = SourceMap::from_json(&json).unwrap();
1880 let output = sm.to_json();
1881 let sm2 = SourceMap::from_json(&output).unwrap();
1882
1883 for m in sm2.all_mappings() {
1884 if m.source != NO_SOURCE && m.name != NO_NAME {
1885 let loc = sm2
1886 .original_position_for(m.generated_line, m.generated_column)
1887 .unwrap();
1888 assert!(loc.name.is_some());
1889 }
1890 }
1891 }
1892
1893 #[test]
1896 fn indexed_source_map_column_offset() {
1897 let json = r#"{
1898 "version": 3,
1899 "sections": [
1900 {
1901 "offset": {"line": 0, "column": 10},
1902 "map": {
1903 "version": 3,
1904 "sources": ["a.js"],
1905 "names": [],
1906 "mappings": "AAAA"
1907 }
1908 }
1909 ]
1910 }"#;
1911 let sm = SourceMap::from_json(json).unwrap();
1912 let loc = sm.original_position_for(0, 10).unwrap();
1914 assert_eq!(loc.line, 0);
1915 assert_eq!(loc.column, 0);
1916 assert!(sm.original_position_for(0, 0).is_none());
1918 }
1919
1920 #[test]
1921 fn indexed_source_map_column_offset_only_first_line() {
1922 let json = r#"{
1924 "version": 3,
1925 "sections": [
1926 {
1927 "offset": {"line": 0, "column": 20},
1928 "map": {
1929 "version": 3,
1930 "sources": ["a.js"],
1931 "names": [],
1932 "mappings": "AAAA;AAAA"
1933 }
1934 }
1935 ]
1936 }"#;
1937 let sm = SourceMap::from_json(json).unwrap();
1938 let loc = sm.original_position_for(0, 20).unwrap();
1940 assert_eq!(loc.column, 0);
1941 let loc = sm.original_position_for(1, 0).unwrap();
1943 assert_eq!(loc.column, 0);
1944 }
1945
1946 #[test]
1947 fn indexed_source_map_empty_section() {
1948 let json = r#"{
1949 "version": 3,
1950 "sections": [
1951 {
1952 "offset": {"line": 0, "column": 0},
1953 "map": {
1954 "version": 3,
1955 "sources": [],
1956 "names": [],
1957 "mappings": ""
1958 }
1959 },
1960 {
1961 "offset": {"line": 5, "column": 0},
1962 "map": {
1963 "version": 3,
1964 "sources": ["b.js"],
1965 "names": [],
1966 "mappings": "AAAA"
1967 }
1968 }
1969 ]
1970 }"#;
1971 let sm = SourceMap::from_json(json).unwrap();
1972 assert_eq!(sm.sources.len(), 1);
1973 let loc = sm.original_position_for(5, 0).unwrap();
1974 assert_eq!(sm.source(loc.source), "b.js");
1975 }
1976
1977 #[test]
1978 fn indexed_source_map_with_sources_content() {
1979 let json = r#"{
1980 "version": 3,
1981 "sections": [
1982 {
1983 "offset": {"line": 0, "column": 0},
1984 "map": {
1985 "version": 3,
1986 "sources": ["a.js"],
1987 "sourcesContent": ["var a = 1;"],
1988 "names": [],
1989 "mappings": "AAAA"
1990 }
1991 },
1992 {
1993 "offset": {"line": 5, "column": 0},
1994 "map": {
1995 "version": 3,
1996 "sources": ["b.js"],
1997 "sourcesContent": ["var b = 2;"],
1998 "names": [],
1999 "mappings": "AAAA"
2000 }
2001 }
2002 ]
2003 }"#;
2004 let sm = SourceMap::from_json(json).unwrap();
2005 assert_eq!(sm.sources_content.len(), 2);
2006 assert_eq!(sm.sources_content[0], Some("var a = 1;".to_string()));
2007 assert_eq!(sm.sources_content[1], Some("var b = 2;".to_string()));
2008 }
2009
2010 #[test]
2011 fn indexed_source_map_with_ignore_list() {
2012 let json = r#"{
2013 "version": 3,
2014 "sections": [
2015 {
2016 "offset": {"line": 0, "column": 0},
2017 "map": {
2018 "version": 3,
2019 "sources": ["app.js", "vendor.js"],
2020 "names": [],
2021 "mappings": "AAAA",
2022 "ignoreList": [1]
2023 }
2024 }
2025 ]
2026 }"#;
2027 let sm = SourceMap::from_json(json).unwrap();
2028 assert!(!sm.ignore_list.is_empty());
2029 }
2030
2031 #[test]
2034 fn lookup_max_column_on_line() {
2035 let mappings_data = vec![vec![vec![0_i64, 0, 0, 0]]];
2036 let json = build_sourcemap_json(&["a.js"], &[], &mappings_data);
2037 let sm = SourceMap::from_json(&json).unwrap();
2038 let loc = sm.original_position_for(0, u32::MAX - 1).unwrap();
2040 assert_eq!(loc.line, 0);
2041 assert_eq!(loc.column, 0);
2042 }
2043
2044 #[test]
2045 fn mappings_for_line_beyond_end() {
2046 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
2047 let sm = SourceMap::from_json(json).unwrap();
2048 assert!(sm.mappings_for_line(u32::MAX).is_empty());
2049 }
2050
2051 #[test]
2052 fn source_with_unicode_path() {
2053 let json =
2054 r#"{"version":3,"sources":["src/日本語.ts"],"names":["変数"],"mappings":"AAAAA"}"#;
2055 let sm = SourceMap::from_json(json).unwrap();
2056 assert_eq!(sm.sources[0], "src/日本語.ts");
2057 assert_eq!(sm.names[0], "変数");
2058 let loc = sm.original_position_for(0, 0).unwrap();
2059 assert_eq!(sm.source(loc.source), "src/日本語.ts");
2060 assert_eq!(sm.name(loc.name.unwrap()), "変数");
2061 }
2062
2063 #[test]
2064 fn to_json_roundtrip_unicode_sources() {
2065 let json = r#"{"version":3,"sources":["src/日本語.ts"],"sourcesContent":["const 変数 = 1;"],"names":["変数"],"mappings":"AAAAA"}"#;
2066 let sm = SourceMap::from_json(json).unwrap();
2067 let output = sm.to_json();
2068 let _: serde_json::Value = serde_json::from_str(&output).unwrap();
2069 let sm2 = SourceMap::from_json(&output).unwrap();
2070 assert_eq!(sm2.sources[0], "src/日本語.ts");
2071 assert_eq!(sm2.sources_content[0], Some("const 変数 = 1;".to_string()));
2072 }
2073
2074 #[test]
2075 fn many_sources_lookup() {
2076 let sources: Vec<String> = (0..100).map(|i| format!("src/file{i}.js")).collect();
2078 let source_strs: Vec<&str> = sources.iter().map(|s| s.as_str()).collect();
2079 let mappings_data = vec![
2080 sources
2081 .iter()
2082 .enumerate()
2083 .map(|(i, _)| vec![(i * 10) as i64, i as i64, 0, 0])
2084 .collect::<Vec<_>>(),
2085 ];
2086 let json = build_sourcemap_json(&source_strs, &[], &mappings_data);
2087 let sm = SourceMap::from_json(&json).unwrap();
2088
2089 for (i, src) in sources.iter().enumerate() {
2090 assert_eq!(sm.source_index(src), Some(i as u32));
2091 }
2092 }
2093
2094 #[test]
2095 fn clone_sourcemap() {
2096 let json = r#"{"version":3,"sources":["a.js"],"names":["x"],"mappings":"AAAAA"}"#;
2097 let sm = SourceMap::from_json(json).unwrap();
2098 let sm2 = sm.clone();
2099 assert_eq!(sm2.sources, sm.sources);
2100 assert_eq!(sm2.mapping_count(), sm.mapping_count());
2101 let loc = sm2.original_position_for(0, 0).unwrap();
2102 assert_eq!(sm2.source(loc.source), "a.js");
2103 }
2104
2105 #[test]
2106 fn parse_debug_id() {
2107 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","debugId":"85314830-023f-4cf1-a267-535f4e37bb17"}"#;
2108 let sm = SourceMap::from_json(json).unwrap();
2109 assert_eq!(
2110 sm.debug_id.as_deref(),
2111 Some("85314830-023f-4cf1-a267-535f4e37bb17")
2112 );
2113 }
2114
2115 #[test]
2116 fn parse_debug_id_snake_case() {
2117 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","debug_id":"85314830-023f-4cf1-a267-535f4e37bb17"}"#;
2118 let sm = SourceMap::from_json(json).unwrap();
2119 assert_eq!(
2120 sm.debug_id.as_deref(),
2121 Some("85314830-023f-4cf1-a267-535f4e37bb17")
2122 );
2123 }
2124
2125 #[test]
2126 fn parse_no_debug_id() {
2127 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
2128 let sm = SourceMap::from_json(json).unwrap();
2129 assert_eq!(sm.debug_id, None);
2130 }
2131
2132 #[test]
2133 fn debug_id_roundtrip() {
2134 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","debugId":"85314830-023f-4cf1-a267-535f4e37bb17"}"#;
2135 let sm = SourceMap::from_json(json).unwrap();
2136 let output = sm.to_json();
2137 assert!(output.contains(r#""debugId":"85314830-023f-4cf1-a267-535f4e37bb17""#));
2138 let sm2 = SourceMap::from_json(&output).unwrap();
2139 assert_eq!(sm.debug_id, sm2.debug_id);
2140 }
2141
2142 #[test]
2143 fn debug_id_not_in_json_when_absent() {
2144 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#;
2145 let sm = SourceMap::from_json(json).unwrap();
2146 let output = sm.to_json();
2147 assert!(!output.contains("debugId"));
2148 }
2149
2150 fn generate_test_sourcemap(lines: usize, segs_per_line: usize, num_sources: usize) -> String {
2152 let sources: Vec<String> = (0..num_sources)
2153 .map(|i| format!("src/file{i}.js"))
2154 .collect();
2155 let names: Vec<String> = (0..20).map(|i| format!("var{i}")).collect();
2156
2157 let mut mappings_parts = Vec::with_capacity(lines);
2158 let mut gen_col;
2159 let mut src: i64 = 0;
2160 let mut src_line: i64 = 0;
2161 let mut src_col: i64;
2162 let mut name: i64 = 0;
2163
2164 for _ in 0..lines {
2165 gen_col = 0i64;
2166 let mut line_parts = Vec::with_capacity(segs_per_line);
2167
2168 for s in 0..segs_per_line {
2169 let gc_delta = 2 + (s as i64 * 3) % 20;
2170 gen_col += gc_delta;
2171
2172 let src_delta = if s % 7 == 0 { 1 } else { 0 };
2173 src = (src + src_delta) % num_sources as i64;
2174
2175 src_line += 1;
2176 src_col = (s as i64 * 5 + 1) % 30;
2177
2178 let has_name = s % 4 == 0;
2179 if has_name {
2180 name = (name + 1) % names.len() as i64;
2181 }
2182
2183 let segment = if has_name {
2185 vec![gen_col, src, src_line, src_col, name]
2186 } else {
2187 vec![gen_col, src, src_line, src_col]
2188 };
2189
2190 line_parts.push(segment);
2191 }
2192
2193 mappings_parts.push(line_parts);
2194 }
2195
2196 let encoded = srcmap_codec::encode(&mappings_parts);
2197
2198 format!(
2199 r#"{{"version":3,"sources":[{}],"names":[{}],"mappings":"{}"}}"#,
2200 sources
2201 .iter()
2202 .map(|s| format!("\"{s}\""))
2203 .collect::<Vec<_>>()
2204 .join(","),
2205 names
2206 .iter()
2207 .map(|n| format!("\"{n}\""))
2208 .collect::<Vec<_>>()
2209 .join(","),
2210 encoded,
2211 )
2212 }
2213}