1use rustc_hash::FxHashMap;
28use srcmap_codec::{vlq_encode_unchecked, vlq_encode_unsigned};
29
30use srcmap_scopes::ScopeInfo;
31
32use std::io;
33
34#[derive(Debug, Clone)]
43pub struct SourceMapParts {
44 pub file: Option<String>,
46 pub mappings: String,
48 pub sources: Vec<String>,
50 pub names: Vec<String>,
52 pub sources_content: Vec<Option<String>>,
54 pub ignore_list: Vec<u32>,
56 pub debug_id: Option<String>,
58 pub source_root: Option<String>,
60 pub range_mappings: Option<String>,
62}
63
64#[derive(Debug, Clone)]
69pub struct Mapping {
70 pub generated_line: u32,
72 pub generated_column: u32,
74 pub source: Option<u32>,
76 pub original_line: u32,
78 pub original_column: u32,
80 pub name: Option<u32>,
82 pub is_range_mapping: bool,
84}
85
86#[derive(Debug)]
102pub struct SourceMapGenerator {
103 file: Option<String>,
104 source_root: Option<String>,
105 sources: Vec<String>,
106 sources_content: Vec<Option<String>>,
107 names: Vec<String>,
108 mappings: Vec<Mapping>,
109 ignore_list: Vec<u32>,
110 debug_id: Option<String>,
111 scopes: Option<ScopeInfo>,
112 assume_sorted: bool,
113 mappings_in_order: bool,
117
118 source_map: FxHashMap<String, u32>,
120 name_map: FxHashMap<String, u32>,
121}
122
123impl SourceMapGenerator {
124 pub fn new(file: Option<String>) -> Self {
126 Self {
127 file,
128 source_root: None,
129 sources: Vec::new(),
130 sources_content: Vec::new(),
131 names: Vec::new(),
132 mappings: Vec::new(),
133 ignore_list: Vec::new(),
134 debug_id: None,
135 scopes: None,
136 assume_sorted: false,
137 mappings_in_order: true,
138 source_map: FxHashMap::default(),
139 name_map: FxHashMap::default(),
140 }
141 }
142
143 pub fn with_capacity(file: Option<String>, mapping_capacity: usize) -> Self {
148 Self {
149 file,
150 source_root: None,
151 sources: Vec::new(),
152 sources_content: Vec::new(),
153 names: Vec::new(),
154 mappings: Vec::with_capacity(mapping_capacity),
155 ignore_list: Vec::new(),
156 debug_id: None,
157 scopes: None,
158 assume_sorted: false,
159 mappings_in_order: true,
160 source_map: FxHashMap::default(),
161 name_map: FxHashMap::default(),
162 }
163 }
164
165 pub fn set_source_root(&mut self, root: impl Into<String>) {
167 self.source_root = Some(root.into());
168 }
169
170 pub fn set_debug_id(&mut self, id: impl Into<String>) {
172 self.debug_id = Some(id.into());
173 }
174
175 pub fn set_scopes(&mut self, scopes: ScopeInfo) {
177 self.scopes = Some(scopes);
178 }
179
180 pub fn set_assume_sorted(&mut self, sorted: bool) {
188 self.assume_sorted = sorted;
189 }
190
191 #[inline]
193 pub fn add_source(&mut self, source: &str) -> u32 {
194 if let Some(&idx) = self.source_map.get(source) {
195 return idx;
196 }
197 let idx = self.sources.len() as u32;
198 self.sources.push(source.to_string());
199 self.sources_content.push(None);
200 self.source_map.insert(source.to_string(), idx);
201 idx
202 }
203
204 pub fn set_source_content(&mut self, source_idx: u32, content: impl Into<String>) {
206 if (source_idx as usize) < self.sources_content.len() {
207 self.sources_content[source_idx as usize] = Some(content.into());
208 }
209 }
210
211 #[inline]
213 pub fn add_name(&mut self, name: &str) -> u32 {
214 if let Some(&idx) = self.name_map.get(name) {
215 return idx;
216 }
217 let idx = self.names.len() as u32;
218 self.names.push(name.to_string());
219 self.name_map.insert(name.to_string(), idx);
220 idx
221 }
222
223 pub fn add_to_ignore_list(&mut self, source_idx: u32) {
225 if !self.ignore_list.contains(&source_idx) {
226 self.ignore_list.push(source_idx);
227 }
228 }
229
230 #[inline]
238 fn push_mapping(&mut self, mapping: Mapping) {
239 if self.mappings_in_order
240 && let Some(last) = self.mappings.last()
241 && (mapping.generated_line, mapping.generated_column)
242 < (last.generated_line, last.generated_column)
243 {
244 self.mappings_in_order = false;
245 }
246 self.mappings.push(mapping);
247 }
248
249 pub fn add_generated_mapping(&mut self, generated_line: u32, generated_column: u32) {
250 self.push_mapping(Mapping {
251 generated_line,
252 generated_column,
253 source: None,
254 original_line: 0,
255 original_column: 0,
256 name: None,
257 is_range_mapping: false,
258 });
259 }
260
261 pub fn add_mapping(
263 &mut self,
264 generated_line: u32,
265 generated_column: u32,
266 source: u32,
267 original_line: u32,
268 original_column: u32,
269 ) {
270 self.push_mapping(Mapping {
271 generated_line,
272 generated_column,
273 source: Some(source),
274 original_line,
275 original_column,
276 name: None,
277 is_range_mapping: false,
278 });
279 }
280
281 pub fn add_named_mapping(
283 &mut self,
284 generated_line: u32,
285 generated_column: u32,
286 source: u32,
287 original_line: u32,
288 original_column: u32,
289 name: u32,
290 ) {
291 self.push_mapping(Mapping {
292 generated_line,
293 generated_column,
294 source: Some(source),
295 original_line,
296 original_column,
297 name: Some(name),
298 is_range_mapping: false,
299 });
300 }
301
302 pub fn add_range_mapping(
308 &mut self,
309 generated_line: u32,
310 generated_column: u32,
311 source: u32,
312 original_line: u32,
313 original_column: u32,
314 ) {
315 self.push_mapping(Mapping {
316 generated_line,
317 generated_column,
318 source: Some(source),
319 original_line,
320 original_column,
321 name: None,
322 is_range_mapping: true,
323 });
324 }
325
326 pub fn add_named_range_mapping(
328 &mut self,
329 generated_line: u32,
330 generated_column: u32,
331 source: u32,
332 original_line: u32,
333 original_column: u32,
334 name: u32,
335 ) {
336 self.push_mapping(Mapping {
337 generated_line,
338 generated_column,
339 source: Some(source),
340 original_line,
341 original_column,
342 name: Some(name),
343 is_range_mapping: true,
344 });
345 }
346
347 pub fn maybe_add_mapping(
353 &mut self,
354 generated_line: u32,
355 generated_column: u32,
356 source: u32,
357 original_line: u32,
358 original_column: u32,
359 ) -> bool {
360 if let Some(last) = self.mappings.last()
361 && last.generated_line == generated_line
362 && last.source == Some(source)
363 && last.original_line == original_line
364 && last.original_column == original_column
365 {
366 return false;
367 }
368 self.add_mapping(generated_line, generated_column, source, original_line, original_column);
369 true
370 }
371
372 fn encode_mappings_into(&self, out: &mut Vec<u8>) {
375 if self.mappings.is_empty() {
376 return;
377 }
378
379 if self.assume_sorted || self.mappings_in_order {
382 #[cfg(feature = "parallel")]
383 if self.mappings.len() >= 4096 {
384 let refs: Vec<&Mapping> = self.mappings.iter().collect();
385 let encoded = Self::encode_parallel_impl(&refs);
386 out.extend_from_slice(encoded.as_bytes());
387 return;
388 }
389 Self::encode_sequential_into(&self.mappings, out);
390 return;
391 }
392
393 let mut sorted: Vec<&Mapping> = self.mappings.iter().collect();
395 sorted.sort_unstable_by(|a, b| {
396 a.generated_line
397 .cmp(&b.generated_line)
398 .then(a.generated_column.cmp(&b.generated_column))
399 });
400
401 #[cfg(feature = "parallel")]
402 if sorted.len() >= 4096 {
403 let encoded = Self::encode_parallel_impl(&sorted);
404 out.extend_from_slice(encoded.as_bytes());
405 return;
406 }
407
408 Self::encode_sequential_into(&sorted, out);
409 }
410
411 #[allow(dead_code, reason = "used by tests and the parallel encoding path")]
413 fn encode_mappings(&self) -> String {
414 if self.mappings.is_empty() {
415 return String::new();
416 }
417
418 if self.assume_sorted || self.mappings_in_order {
421 #[cfg(feature = "parallel")]
422 if self.mappings.len() >= 4096 {
423 let refs: Vec<&Mapping> = self.mappings.iter().collect();
424 return Self::encode_parallel_impl(&refs);
425 }
426 return Self::encode_sequential_impl(&self.mappings);
427 }
428
429 let mut sorted: Vec<&Mapping> = self.mappings.iter().collect();
431 sorted.sort_unstable_by(|a, b| {
432 a.generated_line
433 .cmp(&b.generated_line)
434 .then(a.generated_column.cmp(&b.generated_column))
435 });
436
437 #[cfg(feature = "parallel")]
438 if sorted.len() >= 4096 {
439 return Self::encode_parallel_impl(&sorted);
440 }
441
442 Self::encode_sequential_impl(&sorted)
443 }
444
445 #[inline]
450 fn encode_sequential_into<T: std::borrow::Borrow<Mapping>>(sorted: &[T], out: &mut Vec<u8>) {
451 let max_line = sorted.last().map_or(0, |m| m.borrow().generated_line as usize);
453 out.reserve(sorted.len() * 36 + max_line + 1);
454
455 let mut prev_gen_col: i64 = 0;
456 let mut prev_source: i64 = 0;
457 let mut prev_orig_line: i64 = 0;
458 let mut prev_orig_col: i64 = 0;
459 let mut prev_name: i64 = 0;
460 let mut prev_gen_line: u32 = 0;
461 let mut first_in_line = true;
462
463 for item in sorted {
464 let m = item.borrow();
465 while prev_gen_line < m.generated_line {
466 out.push(b';');
467 prev_gen_line += 1;
468 prev_gen_col = 0;
469 first_in_line = true;
470 }
471
472 if !first_in_line {
473 out.push(b',');
474 }
475 first_in_line = false;
476
477 unsafe {
482 vlq_encode_unchecked(out, m.generated_column as i64 - prev_gen_col);
483 prev_gen_col = m.generated_column as i64;
484
485 if let Some(source) = m.source {
486 vlq_encode_unchecked(out, source as i64 - prev_source);
487 prev_source = source as i64;
488
489 vlq_encode_unchecked(out, m.original_line as i64 - prev_orig_line);
490 prev_orig_line = m.original_line as i64;
491
492 vlq_encode_unchecked(out, m.original_column as i64 - prev_orig_col);
493 prev_orig_col = m.original_column as i64;
494
495 if let Some(name) = m.name {
496 vlq_encode_unchecked(out, name as i64 - prev_name);
497 prev_name = name as i64;
498 }
499 }
500 }
501 }
502 }
503
504 #[inline]
505 #[allow(dead_code, reason = "used by tests and the parallel encoding path")]
506 fn encode_sequential_impl<T: std::borrow::Borrow<Mapping>>(sorted: &[T]) -> String {
507 let max_line = sorted.last().map_or(0, |m| m.borrow().generated_line as usize);
508 let mut out: Vec<u8> = Vec::with_capacity(sorted.len() * 36 + max_line + 1);
509 Self::encode_sequential_into(sorted, &mut out);
510
511 debug_assert!(out.is_ascii());
512 unsafe { String::from_utf8_unchecked(out) }
515 }
516
517 #[cfg(feature = "parallel")]
518 fn encode_parallel_impl(sorted: &[&Mapping]) -> String {
519 use rayon::prelude::*;
520
521 let max_line = sorted
522 .last()
523 .expect("encode_parallel_impl requires non-empty sorted slice")
524 .generated_line as usize;
525
526 let mut line_ranges: Vec<(usize, usize)> = vec![(0, 0); max_line + 1];
528 let mut i = 0;
529 while i < sorted.len() {
530 let line = sorted[i].generated_line as usize;
531 let start = i;
532 while i < sorted.len() && sorted[i].generated_line as usize == line {
533 i += 1;
534 }
535 line_ranges[line] = (start, i);
536 }
537
538 let mut states: Vec<(i64, i64, i64, i64)> = Vec::with_capacity(max_line + 1);
540 let mut prev_source: i64 = 0;
541 let mut prev_orig_line: i64 = 0;
542 let mut prev_orig_col: i64 = 0;
543 let mut prev_name: i64 = 0;
544
545 for &(start, end) in &line_ranges {
546 states.push((prev_source, prev_orig_line, prev_orig_col, prev_name));
547 for m in &sorted[start..end] {
548 if let Some(source) = m.source {
549 prev_source = source as i64;
550 prev_orig_line = m.original_line as i64;
551 prev_orig_col = m.original_column as i64;
552 if let Some(name) = m.name {
553 prev_name = name as i64;
554 }
555 }
556 }
557 }
558
559 let encoded_lines: Vec<Vec<u8>> = line_ranges
561 .par_iter()
562 .zip(states.par_iter())
563 .map(|(&(start, end), &(s, ol, oc, n))| {
564 if start == end {
565 return Vec::new();
566 }
567 encode_mapping_slice(&sorted[start..end], s, ol, oc, n)
568 })
569 .collect();
570
571 let total_len = encoded_lines.iter().map(|l| l.len()).sum::<usize>() + max_line;
573 let mut out: Vec<u8> = Vec::with_capacity(total_len);
574 for (i, bytes) in encoded_lines.iter().enumerate() {
575 if i > 0 {
576 out.push(b';');
577 }
578 out.extend_from_slice(bytes);
579 }
580
581 debug_assert!(out.is_ascii());
584 unsafe { String::from_utf8_unchecked(out) }
585 }
586
587 fn encode_range_mappings(&self) -> Option<String> {
590 if !self.mappings.iter().any(|m| m.is_range_mapping) {
591 return None;
592 }
593
594 let ordered: Vec<&Mapping> = if self.assume_sorted || self.mappings_in_order
595 {
596 self.mappings.iter().collect()
597 } else {
598 let mut sorted: Vec<&Mapping> = self.mappings.iter().collect();
599 sorted.sort_unstable_by(|a, b| {
600 a.generated_line
601 .cmp(&b.generated_line)
602 .then(a.generated_column.cmp(&b.generated_column))
603 });
604 sorted
605 };
606
607 let max_line = ordered.last().map_or(0, |m| m.generated_line);
608 let mut out: Vec<u8> = Vec::new();
609 let mut ordered_idx = 0;
610
611 for line in 0..=max_line {
612 if line > 0 {
613 out.push(b';');
614 }
615 let mut prev_offset: u64 = 0;
616 let mut first_on_line = true;
617 let mut line_local_idx: u64 = 0;
618
619 while ordered_idx < ordered.len() && ordered[ordered_idx].generated_line == line {
620 if ordered[ordered_idx].is_range_mapping {
621 if !first_on_line {
622 out.push(b',');
623 }
624 first_on_line = false;
625 let delta = line_local_idx - prev_offset;
626 vlq_encode_unsigned(&mut out, delta);
627 prev_offset = line_local_idx;
628 }
629 line_local_idx += 1;
630 ordered_idx += 1;
631 }
632 }
633
634 while out.last() == Some(&b';') {
635 out.pop();
636 }
637
638 if out.is_empty() {
639 return None;
640 }
641
642 debug_assert!(out.is_ascii());
643 Some(unsafe { String::from_utf8_unchecked(out) })
646 }
647
648 pub fn to_json(&self) -> String {
650 use std::io::Write;
651
652 let (scopes_str, names_for_json) = if let Some(ref scopes_info) = self.scopes {
654 let mut names = self.names.clone();
655 let s = srcmap_scopes::encode_scopes(scopes_info, &mut names);
656 (Some(s), std::borrow::Cow::Owned(names))
657 } else {
658 (None, std::borrow::Cow::Borrowed(&self.names))
659 };
660
661 let sources_size: usize = self.sources.iter().map(|s| s.len() + 4).sum();
663 let names_size: usize = names_for_json.iter().map(|n| n.len() + 4).sum();
664 let content_size: usize =
665 self.sources_content.iter().map(|c| c.as_ref().map_or(5, |s| s.len() + 4)).sum();
666 let mappings_estimate = self.mappings.len() * 6;
668 let capacity = 100 + sources_size + names_size + mappings_estimate + content_size;
669
670 let mut json: Vec<u8> = Vec::with_capacity(capacity);
671 json.extend_from_slice(br#"{"version":3"#);
672
673 if let Some(ref file) = self.file {
674 json.extend_from_slice(br#","file":"#);
675 json_quote_into(&mut json, file);
676 }
677
678 if let Some(ref root) = self.source_root {
679 json.extend_from_slice(br#","sourceRoot":"#);
680 json_quote_into(&mut json, root);
681 }
682
683 json.extend_from_slice(br#","sources":["#);
685 for (i, s) in self.sources.iter().enumerate() {
686 if i > 0 {
687 json.push(b',');
688 }
689 json_quote_into(&mut json, s);
690 }
691 json.push(b']');
692
693 if self.sources_content.iter().any(|c| c.is_some()) {
695 json.extend_from_slice(br#","sourcesContent":["#);
696
697 #[cfg(feature = "parallel")]
698 {
699 use rayon::prelude::*;
700
701 let total_content: usize =
702 self.sources_content.iter().map(|c| c.as_ref().map_or(0, |s| s.len())).sum();
703
704 if self.sources_content.len() >= 8 && total_content >= 8192 {
705 let quoted: Vec<String> = self
706 .sources_content
707 .par_iter()
708 .map(|c| match c {
709 Some(content) => json_quote(content),
710 None => "null".to_string(),
711 })
712 .collect();
713 for (i, q) in quoted.iter().enumerate() {
714 if i > 0 {
715 json.push(b',');
716 }
717 json.extend_from_slice(q.as_bytes());
718 }
719 } else {
720 for (i, c) in self.sources_content.iter().enumerate() {
721 if i > 0 {
722 json.push(b',');
723 }
724 match c {
725 Some(content) => json_quote_into(&mut json, content),
726 None => json.extend_from_slice(b"null"),
727 }
728 }
729 }
730 }
731
732 #[cfg(not(feature = "parallel"))]
733 for (i, c) in self.sources_content.iter().enumerate() {
734 if i > 0 {
735 json.push(b',');
736 }
737 match c {
738 Some(content) => json_quote_into(&mut json, content),
739 None => json.extend_from_slice(b"null"),
740 }
741 }
742
743 json.push(b']');
744 }
745
746 json.extend_from_slice(br#","names":["#);
748 for (i, n) in names_for_json.iter().enumerate() {
749 if i > 0 {
750 json.push(b',');
751 }
752 json_quote_into(&mut json, n);
753 }
754 json.push(b']');
755
756 json.extend_from_slice(br#","mappings":""#);
759 self.encode_mappings_into(&mut json);
760 json.push(b'"');
761
762 if !self.ignore_list.is_empty() {
764 json.extend_from_slice(br#","ignoreList":["#);
765 for (i, &idx) in self.ignore_list.iter().enumerate() {
766 if i > 0 {
767 json.push(b',');
768 }
769 let _ = write!(json, "{idx}");
770 }
771 json.push(b']');
772 }
773
774 if let Some(ref range_mappings) = self.encode_range_mappings() {
776 json.extend_from_slice(br#","rangeMappings":""#);
777 json.extend_from_slice(range_mappings.as_bytes());
778 json.push(b'"');
779 }
780
781 if let Some(ref id) = self.debug_id {
783 json.extend_from_slice(br#","debugId":"#);
784 json_quote_into(&mut json, id);
785 }
786
787 if let Some(ref s) = scopes_str {
789 json.extend_from_slice(br#","scopes":"#);
790 json_quote_into(&mut json, s);
791 }
792
793 json.push(b'}');
794
795 unsafe { String::from_utf8_unchecked(json) }
798 }
799
800 pub fn mapping_count(&self) -> usize {
802 self.mappings.len()
803 }
804
805 pub fn to_decoded_map(&self) -> srcmap_sourcemap::SourceMap {
810 let convert_mapping = |m: &Mapping| srcmap_sourcemap::Mapping {
811 generated_line: m.generated_line,
812 generated_column: m.generated_column,
813 source: m.source.unwrap_or(u32::MAX),
814 original_line: m.original_line,
815 original_column: m.original_column,
816 name: m.name.unwrap_or(u32::MAX),
817 is_range_mapping: m.is_range_mapping,
818 };
819
820 let sm_mappings: Vec<srcmap_sourcemap::Mapping> =
821 if self.assume_sorted || self.mappings_in_order {
822 self.mappings.iter().map(convert_mapping).collect()
824 } else {
825 let mut sorted: Vec<&Mapping> = self.mappings.iter().collect();
827 sorted.sort_unstable_by(|a, b| {
828 a.generated_line
829 .cmp(&b.generated_line)
830 .then(a.generated_column.cmp(&b.generated_column))
831 });
832 sorted.iter().map(|m| convert_mapping(m)).collect()
833 };
834
835 let sources_content: Vec<Option<String>> = self.sources_content.clone();
837
838 let sources: Vec<String> = match &self.source_root {
840 Some(root) if !root.is_empty() => {
841 self.sources.iter().map(|s| format!("{root}{s}")).collect()
842 }
843 _ => self.sources.clone(),
844 };
845
846 srcmap_sourcemap::SourceMap::from_parts(
847 self.file.clone(),
848 self.source_root.clone(),
849 sources,
850 sources_content,
851 self.names.clone(),
852 sm_mappings,
853 self.ignore_list.clone(),
854 self.debug_id.clone(),
855 None, )
857 }
858
859 pub fn into_parts(self) -> SourceMapParts {
865 let mappings = self.encode_mappings();
866 let range_mappings = self.encode_range_mappings();
867
868 SourceMapParts {
869 file: self.file,
870 mappings,
871 sources: self.sources,
872 names: self.names,
873 sources_content: self.sources_content,
874 ignore_list: self.ignore_list,
875 debug_id: self.debug_id,
876 source_root: self.source_root,
877 range_mappings,
878 }
879 }
880
881 pub fn to_writer(&self, writer: &mut impl io::Write) -> io::Result<()> {
886 let (scopes_str, names_for_json) = if let Some(ref scopes_info) = self.scopes {
888 let mut names = self.names.clone();
889 let s = srcmap_scopes::encode_scopes(scopes_info, &mut names);
890 (Some(s), std::borrow::Cow::Owned(names))
891 } else {
892 (None, std::borrow::Cow::Borrowed(&self.names))
893 };
894
895 writer.write_all(br#"{"version":3"#)?;
896
897 if let Some(ref file) = self.file {
898 writer.write_all(br#","file":"#)?;
899 write_json_quoted(writer, file)?;
900 }
901
902 if let Some(ref root) = self.source_root {
903 writer.write_all(br#","sourceRoot":"#)?;
904 write_json_quoted(writer, root)?;
905 }
906
907 writer.write_all(br#","sources":["#)?;
909 for (i, s) in self.sources.iter().enumerate() {
910 if i > 0 {
911 writer.write_all(b",")?;
912 }
913 write_json_quoted(writer, s)?;
914 }
915 writer.write_all(b"]")?;
916
917 if self.sources_content.iter().any(|c| c.is_some()) {
919 writer.write_all(br#","sourcesContent":["#)?;
920 for (i, c) in self.sources_content.iter().enumerate() {
921 if i > 0 {
922 writer.write_all(b",")?;
923 }
924 match c {
925 Some(content) => write_json_quoted(writer, content)?,
926 None => writer.write_all(b"null")?,
927 }
928 }
929 writer.write_all(b"]")?;
930 }
931
932 writer.write_all(br#","names":["#)?;
934 for (i, n) in names_for_json.iter().enumerate() {
935 if i > 0 {
936 writer.write_all(b",")?;
937 }
938 write_json_quoted(writer, n)?;
939 }
940 writer.write_all(b"]")?;
941
942 writer.write_all(br#","mappings":""#)?;
944 let mut vlq_buf: Vec<u8> = Vec::new();
945 self.encode_mappings_into(&mut vlq_buf);
946 writer.write_all(&vlq_buf)?;
947 writer.write_all(b"\"")?;
948
949 if !self.ignore_list.is_empty() {
951 writer.write_all(br#","ignoreList":["#)?;
952 for (i, &idx) in self.ignore_list.iter().enumerate() {
953 if i > 0 {
954 writer.write_all(b",")?;
955 }
956 write!(writer, "{idx}")?;
957 }
958 writer.write_all(b"]")?;
959 }
960
961 if let Some(ref range_mappings) = self.encode_range_mappings() {
963 writer.write_all(br#","rangeMappings":""#)?;
964 writer.write_all(range_mappings.as_bytes())?;
965 writer.write_all(b"\"")?;
966 }
967
968 if let Some(ref id) = self.debug_id {
970 writer.write_all(br#","debugId":"#)?;
971 write_json_quoted(writer, id)?;
972 }
973
974 if let Some(ref s) = scopes_str {
976 writer.write_all(br#","scopes":"#)?;
977 write_json_quoted(writer, s)?;
978 }
979
980 writer.write_all(b"}")?;
981 Ok(())
982 }
983}
984
985#[derive(Debug)]
1011pub struct StreamingGenerator {
1012 file: Option<String>,
1013 source_root: Option<String>,
1014 sources: Vec<String>,
1015 sources_content: Vec<Option<String>>,
1016 names: Vec<String>,
1017 ignore_list: Vec<u32>,
1018 debug_id: Option<String>,
1019
1020 source_map: FxHashMap<String, u32>,
1022 name_map: FxHashMap<String, u32>,
1023
1024 vlq_out: Vec<u8>,
1026 prev_gen_line: u32,
1027 prev_gen_col: i64,
1028 prev_source: i64,
1029 prev_orig_line: i64,
1030 prev_orig_col: i64,
1031 prev_name: i64,
1032 first_in_line: bool,
1033 mapping_count: usize,
1034
1035 line_local_index: u32,
1037 range_entries: Vec<(u32, u32)>,
1038}
1039
1040impl StreamingGenerator {
1041 pub fn new(file: Option<String>) -> Self {
1043 Self {
1044 file,
1045 source_root: None,
1046 sources: Vec::new(),
1047 sources_content: Vec::new(),
1048 names: Vec::new(),
1049 ignore_list: Vec::new(),
1050 debug_id: None,
1051 source_map: FxHashMap::default(),
1052 name_map: FxHashMap::default(),
1053 vlq_out: Vec::with_capacity(1024),
1054 prev_gen_line: 0,
1055 prev_gen_col: 0,
1056 prev_source: 0,
1057 prev_orig_line: 0,
1058 prev_orig_col: 0,
1059 prev_name: 0,
1060 first_in_line: true,
1061 mapping_count: 0,
1062 line_local_index: 0,
1063 range_entries: Vec::new(),
1064 }
1065 }
1066
1067 pub fn with_capacity(file: Option<String>, vlq_capacity: usize) -> Self {
1069 Self {
1070 file,
1071 source_root: None,
1072 sources: Vec::new(),
1073 sources_content: Vec::new(),
1074 names: Vec::new(),
1075 ignore_list: Vec::new(),
1076 debug_id: None,
1077 source_map: FxHashMap::default(),
1078 name_map: FxHashMap::default(),
1079 vlq_out: Vec::with_capacity(vlq_capacity),
1080 prev_gen_line: 0,
1081 prev_gen_col: 0,
1082 prev_source: 0,
1083 prev_orig_line: 0,
1084 prev_orig_col: 0,
1085 prev_name: 0,
1086 first_in_line: true,
1087 mapping_count: 0,
1088 line_local_index: 0,
1089 range_entries: Vec::new(),
1090 }
1091 }
1092
1093 pub fn set_source_root(&mut self, root: impl Into<String>) {
1095 self.source_root = Some(root.into());
1096 }
1097
1098 pub fn set_debug_id(&mut self, id: impl Into<String>) {
1100 self.debug_id = Some(id.into());
1101 }
1102
1103 #[inline]
1105 pub fn add_source(&mut self, source: &str) -> u32 {
1106 if let Some(&idx) = self.source_map.get(source) {
1107 return idx;
1108 }
1109 let idx = self.sources.len() as u32;
1110 self.sources.push(source.to_string());
1111 self.sources_content.push(None);
1112 self.source_map.insert(source.to_string(), idx);
1113 idx
1114 }
1115
1116 pub fn set_source_content(&mut self, source_idx: u32, content: impl Into<String>) {
1118 if (source_idx as usize) < self.sources_content.len() {
1119 self.sources_content[source_idx as usize] = Some(content.into());
1120 }
1121 }
1122
1123 #[inline]
1125 pub fn add_name(&mut self, name: &str) -> u32 {
1126 if let Some(&idx) = self.name_map.get(name) {
1127 return idx;
1128 }
1129 let idx = self.names.len() as u32;
1130 self.names.push(name.to_string());
1131 self.name_map.insert(name.to_string(), idx);
1132 idx
1133 }
1134
1135 pub fn add_to_ignore_list(&mut self, source_idx: u32) {
1137 if !self.ignore_list.contains(&source_idx) {
1138 self.ignore_list.push(source_idx);
1139 }
1140 }
1141
1142 #[inline]
1146 pub fn add_generated_mapping(&mut self, generated_line: u32, generated_column: u32) {
1147 self.advance_to_line(generated_line);
1148
1149 self.vlq_out.reserve(8);
1151
1152 if !self.first_in_line {
1153 self.vlq_out.push(b',');
1154 }
1155 self.first_in_line = false;
1156
1157 unsafe {
1159 vlq_encode_unchecked(&mut self.vlq_out, generated_column as i64 - self.prev_gen_col);
1160 }
1161 self.prev_gen_col = generated_column as i64;
1162 self.line_local_index += 1;
1163 self.mapping_count += 1;
1164 }
1165
1166 #[inline]
1170 pub fn add_mapping(
1171 &mut self,
1172 generated_line: u32,
1173 generated_column: u32,
1174 source: u32,
1175 original_line: u32,
1176 original_column: u32,
1177 ) {
1178 self.advance_to_line(generated_line);
1179
1180 self.vlq_out.reserve(29);
1182
1183 if !self.first_in_line {
1184 self.vlq_out.push(b',');
1185 }
1186 self.first_in_line = false;
1187
1188 unsafe {
1190 vlq_encode_unchecked(&mut self.vlq_out, generated_column as i64 - self.prev_gen_col);
1191 self.prev_gen_col = generated_column as i64;
1192
1193 vlq_encode_unchecked(&mut self.vlq_out, source as i64 - self.prev_source);
1194 self.prev_source = source as i64;
1195
1196 vlq_encode_unchecked(&mut self.vlq_out, original_line as i64 - self.prev_orig_line);
1197 self.prev_orig_line = original_line as i64;
1198
1199 vlq_encode_unchecked(&mut self.vlq_out, original_column as i64 - self.prev_orig_col);
1200 self.prev_orig_col = original_column as i64;
1201 }
1202
1203 self.line_local_index += 1;
1204 self.mapping_count += 1;
1205 }
1206
1207 #[inline]
1212 pub fn add_range_mapping(
1213 &mut self,
1214 generated_line: u32,
1215 generated_column: u32,
1216 source: u32,
1217 original_line: u32,
1218 original_column: u32,
1219 ) {
1220 self.advance_to_line(generated_line);
1221 self.range_entries.push((self.prev_gen_line, self.line_local_index));
1222
1223 self.vlq_out.reserve(29);
1225
1226 if !self.first_in_line {
1227 self.vlq_out.push(b',');
1228 }
1229 self.first_in_line = false;
1230
1231 unsafe {
1233 vlq_encode_unchecked(&mut self.vlq_out, generated_column as i64 - self.prev_gen_col);
1234 self.prev_gen_col = generated_column as i64;
1235
1236 vlq_encode_unchecked(&mut self.vlq_out, source as i64 - self.prev_source);
1237 self.prev_source = source as i64;
1238
1239 vlq_encode_unchecked(&mut self.vlq_out, original_line as i64 - self.prev_orig_line);
1240 self.prev_orig_line = original_line as i64;
1241
1242 vlq_encode_unchecked(&mut self.vlq_out, original_column as i64 - self.prev_orig_col);
1243 self.prev_orig_col = original_column as i64;
1244 }
1245
1246 self.line_local_index += 1;
1247 self.mapping_count += 1;
1248 }
1249
1250 #[inline]
1254 pub fn add_named_mapping(
1255 &mut self,
1256 generated_line: u32,
1257 generated_column: u32,
1258 source: u32,
1259 original_line: u32,
1260 original_column: u32,
1261 name: u32,
1262 ) {
1263 self.advance_to_line(generated_line);
1264
1265 self.vlq_out.reserve(36);
1267
1268 if !self.first_in_line {
1269 self.vlq_out.push(b',');
1270 }
1271 self.first_in_line = false;
1272
1273 unsafe {
1275 vlq_encode_unchecked(&mut self.vlq_out, generated_column as i64 - self.prev_gen_col);
1276 self.prev_gen_col = generated_column as i64;
1277
1278 vlq_encode_unchecked(&mut self.vlq_out, source as i64 - self.prev_source);
1279 self.prev_source = source as i64;
1280
1281 vlq_encode_unchecked(&mut self.vlq_out, original_line as i64 - self.prev_orig_line);
1282 self.prev_orig_line = original_line as i64;
1283
1284 vlq_encode_unchecked(&mut self.vlq_out, original_column as i64 - self.prev_orig_col);
1285 self.prev_orig_col = original_column as i64;
1286
1287 vlq_encode_unchecked(&mut self.vlq_out, name as i64 - self.prev_name);
1288 self.prev_name = name as i64;
1289 }
1290
1291 self.line_local_index += 1;
1292 self.mapping_count += 1;
1293 }
1294
1295 #[inline]
1301 pub fn add_named_range_mapping(
1302 &mut self,
1303 generated_line: u32,
1304 generated_column: u32,
1305 source: u32,
1306 original_line: u32,
1307 original_column: u32,
1308 name: u32,
1309 ) {
1310 self.advance_to_line(generated_line);
1311 self.range_entries.push((self.prev_gen_line, self.line_local_index));
1312
1313 self.vlq_out.reserve(36);
1315
1316 if !self.first_in_line {
1317 self.vlq_out.push(b',');
1318 }
1319 self.first_in_line = false;
1320
1321 unsafe {
1323 vlq_encode_unchecked(&mut self.vlq_out, generated_column as i64 - self.prev_gen_col);
1324 self.prev_gen_col = generated_column as i64;
1325
1326 vlq_encode_unchecked(&mut self.vlq_out, source as i64 - self.prev_source);
1327 self.prev_source = source as i64;
1328
1329 vlq_encode_unchecked(&mut self.vlq_out, original_line as i64 - self.prev_orig_line);
1330 self.prev_orig_line = original_line as i64;
1331
1332 vlq_encode_unchecked(&mut self.vlq_out, original_column as i64 - self.prev_orig_col);
1333 self.prev_orig_col = original_column as i64;
1334
1335 vlq_encode_unchecked(&mut self.vlq_out, name as i64 - self.prev_name);
1336 self.prev_name = name as i64;
1337 }
1338
1339 self.line_local_index += 1;
1340 self.mapping_count += 1;
1341 }
1342
1343 pub fn mapping_count(&self) -> usize {
1345 self.mapping_count
1346 }
1347
1348 #[inline]
1350 fn advance_to_line(&mut self, generated_line: u32) {
1351 while self.prev_gen_line < generated_line {
1352 self.vlq_out.push(b';');
1353 self.prev_gen_line += 1;
1354 self.prev_gen_col = 0;
1355 self.first_in_line = true;
1356 self.line_local_index = 0;
1357 }
1358 }
1359
1360 pub fn to_json(&self) -> String {
1362 use std::io::Write;
1363
1364 let vlq = self.vlq_str();
1365
1366 let sources_size: usize = self.sources.iter().map(|s| s.len() + 4).sum();
1368 let names_size: usize = self.names.iter().map(|n| n.len() + 4).sum();
1369 let content_size: usize =
1370 self.sources_content.iter().map(|c| c.as_ref().map_or(5, |s| s.len() + 4)).sum();
1371 let capacity = 100 + sources_size + names_size + vlq.len() + content_size;
1372
1373 let mut json: Vec<u8> = Vec::with_capacity(capacity);
1374 json.extend_from_slice(br#"{"version":3"#);
1375
1376 if let Some(ref file) = self.file {
1377 json.extend_from_slice(br#","file":"#);
1378 json_quote_into(&mut json, file);
1379 }
1380
1381 if let Some(ref root) = self.source_root {
1382 json.extend_from_slice(br#","sourceRoot":"#);
1383 json_quote_into(&mut json, root);
1384 }
1385
1386 json.extend_from_slice(br#","sources":["#);
1387 for (i, s) in self.sources.iter().enumerate() {
1388 if i > 0 {
1389 json.push(b',');
1390 }
1391 json_quote_into(&mut json, s);
1392 }
1393 json.push(b']');
1394
1395 if self.sources_content.iter().any(|c| c.is_some()) {
1396 json.extend_from_slice(br#","sourcesContent":["#);
1397 for (i, c) in self.sources_content.iter().enumerate() {
1398 if i > 0 {
1399 json.push(b',');
1400 }
1401 match c {
1402 Some(content) => json_quote_into(&mut json, content),
1403 None => json.extend_from_slice(b"null"),
1404 }
1405 }
1406 json.push(b']');
1407 }
1408
1409 json.extend_from_slice(br#","names":["#);
1410 for (i, n) in self.names.iter().enumerate() {
1411 if i > 0 {
1412 json.push(b',');
1413 }
1414 json_quote_into(&mut json, n);
1415 }
1416 json.push(b']');
1417
1418 json.extend_from_slice(br#","mappings":""#);
1420 json.extend_from_slice(vlq.as_bytes());
1421 json.push(b'"');
1422
1423 if !self.ignore_list.is_empty() {
1424 json.extend_from_slice(br#","ignoreList":["#);
1425 for (i, &idx) in self.ignore_list.iter().enumerate() {
1426 if i > 0 {
1427 json.push(b',');
1428 }
1429 let _ = write!(json, "{idx}");
1430 }
1431 json.push(b']');
1432 }
1433
1434 if let Some(ref range_mappings) = self.encode_range_mappings() {
1435 json.extend_from_slice(br#","rangeMappings":""#);
1436 json.extend_from_slice(range_mappings.as_bytes());
1437 json.push(b'"');
1438 }
1439
1440 if let Some(ref id) = self.debug_id {
1441 json.extend_from_slice(br#","debugId":"#);
1442 json_quote_into(&mut json, id);
1443 }
1444
1445 json.push(b'}');
1446
1447 unsafe { String::from_utf8_unchecked(json) }
1450 }
1451
1452 pub fn to_decoded_map(
1464 &self,
1465 ) -> Result<srcmap_sourcemap::SourceMap, srcmap_sourcemap::ParseError> {
1466 let vlq = self.vlq_str();
1467 let range_mappings = self.encode_range_mappings();
1468
1469 let sources: Vec<String> = match &self.source_root {
1470 Some(root) if !root.is_empty() => {
1471 self.sources.iter().map(|s| format!("{root}{s}")).collect()
1472 }
1473 _ => self.sources.clone(),
1474 };
1475
1476 srcmap_sourcemap::SourceMap::from_vlq_with_range_mappings(
1477 vlq,
1478 sources,
1479 self.names.clone(),
1480 self.file.clone(),
1481 self.source_root.clone(),
1482 self.sources_content.clone(),
1483 self.ignore_list.clone(),
1484 self.debug_id.clone(),
1485 range_mappings.as_deref(),
1486 )
1487 }
1488
1489 fn encode_range_mappings(&self) -> Option<String> {
1492 if self.range_entries.is_empty() {
1493 return None;
1494 }
1495
1496 let max_line = self.range_entries.last().map_or(0, |&(line, _)| line);
1497 let mut out: Vec<u8> = Vec::new();
1498 let mut entry_idx = 0;
1499
1500 for line in 0..=max_line {
1501 if line > 0 {
1502 out.push(b';');
1503 }
1504 let mut prev_offset: u64 = 0;
1505 let mut first_on_line = true;
1506
1507 while entry_idx < self.range_entries.len() && self.range_entries[entry_idx].0 == line {
1508 if !first_on_line {
1509 out.push(b',');
1510 }
1511 first_on_line = false;
1512 let local_idx = self.range_entries[entry_idx].1 as u64;
1513 let delta = local_idx - prev_offset;
1514 vlq_encode_unsigned(&mut out, delta);
1515 prev_offset = local_idx;
1516 entry_idx += 1;
1517 }
1518 }
1519
1520 while out.last() == Some(&b';') {
1521 out.pop();
1522 }
1523
1524 if out.is_empty() {
1525 return None;
1526 }
1527
1528 Some(unsafe { String::from_utf8_unchecked(out) })
1530 }
1531
1532 fn vlq_str(&self) -> &str {
1534 let end = self.vlq_out.iter().rposition(|&b| b != b';').map_or(0, |i| i + 1);
1535 unsafe { std::str::from_utf8_unchecked(&self.vlq_out[..end]) }
1537 }
1538
1539 pub fn into_parts(self) -> SourceMapParts {
1545 let range_mappings = self.encode_range_mappings();
1546 let end = self.vlq_out.iter().rposition(|&b| b != b';').map_or(0, |i| i + 1);
1548 let mappings = unsafe { String::from_utf8_unchecked(self.vlq_out[..end].to_vec()) };
1550
1551 SourceMapParts {
1552 file: self.file,
1553 mappings,
1554 sources: self.sources,
1555 names: self.names,
1556 sources_content: self.sources_content,
1557 ignore_list: self.ignore_list,
1558 debug_id: self.debug_id,
1559 source_root: self.source_root,
1560 range_mappings,
1561 }
1562 }
1563
1564 pub fn to_writer(&self, writer: &mut impl io::Write) -> io::Result<()> {
1569 let vlq = self.vlq_str();
1570
1571 writer.write_all(br#"{"version":3"#)?;
1572
1573 if let Some(ref file) = self.file {
1574 writer.write_all(br#","file":"#)?;
1575 write_json_quoted(writer, file)?;
1576 }
1577
1578 if let Some(ref root) = self.source_root {
1579 writer.write_all(br#","sourceRoot":"#)?;
1580 write_json_quoted(writer, root)?;
1581 }
1582
1583 writer.write_all(br#","sources":["#)?;
1585 for (i, s) in self.sources.iter().enumerate() {
1586 if i > 0 {
1587 writer.write_all(b",")?;
1588 }
1589 write_json_quoted(writer, s)?;
1590 }
1591 writer.write_all(b"]")?;
1592
1593 if self.sources_content.iter().any(|c| c.is_some()) {
1595 writer.write_all(br#","sourcesContent":["#)?;
1596 for (i, c) in self.sources_content.iter().enumerate() {
1597 if i > 0 {
1598 writer.write_all(b",")?;
1599 }
1600 match c {
1601 Some(content) => write_json_quoted(writer, content)?,
1602 None => writer.write_all(b"null")?,
1603 }
1604 }
1605 writer.write_all(b"]")?;
1606 }
1607
1608 writer.write_all(br#","names":["#)?;
1610 for (i, n) in self.names.iter().enumerate() {
1611 if i > 0 {
1612 writer.write_all(b",")?;
1613 }
1614 write_json_quoted(writer, n)?;
1615 }
1616 writer.write_all(b"]")?;
1617
1618 writer.write_all(br#","mappings":""#)?;
1620 writer.write_all(vlq.as_bytes())?;
1621 writer.write_all(b"\"")?;
1622
1623 if !self.ignore_list.is_empty() {
1625 writer.write_all(br#","ignoreList":["#)?;
1626 for (i, &idx) in self.ignore_list.iter().enumerate() {
1627 if i > 0 {
1628 writer.write_all(b",")?;
1629 }
1630 write!(writer, "{idx}")?;
1631 }
1632 writer.write_all(b"]")?;
1633 }
1634
1635 if let Some(ref range_mappings) = self.encode_range_mappings() {
1637 writer.write_all(br#","rangeMappings":""#)?;
1638 writer.write_all(range_mappings.as_bytes())?;
1639 writer.write_all(b"\"")?;
1640 }
1641
1642 if let Some(ref id) = self.debug_id {
1644 writer.write_all(br#","debugId":"#)?;
1645 write_json_quoted(writer, id)?;
1646 }
1647
1648 writer.write_all(b"}")?;
1649 Ok(())
1650 }
1651}
1652
1653#[cfg(feature = "parallel")]
1658fn encode_mapping_slice(
1659 mappings: &[&Mapping],
1660 init_source: i64,
1661 init_orig_line: i64,
1662 init_orig_col: i64,
1663 init_name: i64,
1664) -> Vec<u8> {
1665 let mut buf = Vec::with_capacity(mappings.len() * 36);
1667 let mut prev_gen_col: i64 = 0;
1668 let mut prev_source = init_source;
1669 let mut prev_orig_line = init_orig_line;
1670 let mut prev_orig_col = init_orig_col;
1671 let mut prev_name = init_name;
1672 let mut first = true;
1673
1674 for m in mappings {
1675 if !first {
1676 buf.push(b',');
1677 }
1678 first = false;
1679
1680 unsafe {
1683 vlq_encode_unchecked(&mut buf, m.generated_column as i64 - prev_gen_col);
1684 prev_gen_col = m.generated_column as i64;
1685
1686 if let Some(source) = m.source {
1687 vlq_encode_unchecked(&mut buf, source as i64 - prev_source);
1688 prev_source = source as i64;
1689
1690 vlq_encode_unchecked(&mut buf, m.original_line as i64 - prev_orig_line);
1691 prev_orig_line = m.original_line as i64;
1692
1693 vlq_encode_unchecked(&mut buf, m.original_column as i64 - prev_orig_col);
1694 prev_orig_col = m.original_column as i64;
1695
1696 if let Some(name) = m.name {
1697 vlq_encode_unchecked(&mut buf, name as i64 - prev_name);
1698 prev_name = name as i64;
1699 }
1700 }
1701 }
1702 }
1703
1704 buf
1705}
1706
1707fn json_quote_into(out: &mut Vec<u8>, s: &str) {
1709 let bytes = s.as_bytes();
1710 out.push(b'"');
1711
1712 let mut start = 0;
1713 for (i, &b) in bytes.iter().enumerate() {
1714 let escape: &[u8] = match b {
1715 b'"' => b"\\\"",
1716 b'\\' => b"\\\\",
1717 b'\n' => b"\\n",
1718 b'\r' => b"\\r",
1719 b'\t' => b"\\t",
1720 0x00..=0x1f => {
1721 out.extend_from_slice(&bytes[start..i]);
1722 let hex = b"0123456789abcdef";
1723 out.extend_from_slice(&[
1724 b'\\',
1725 b'u',
1726 b'0',
1727 b'0',
1728 hex[(b >> 4) as usize],
1729 hex[(b & 0xf) as usize],
1730 ]);
1731 start = i + 1;
1732 continue;
1733 }
1734 _ => continue,
1735 };
1736 out.extend_from_slice(&bytes[start..i]);
1737 out.extend_from_slice(escape);
1738 start = i + 1;
1739 }
1740
1741 out.extend_from_slice(&bytes[start..]);
1742 out.push(b'"');
1743}
1744
1745#[cfg(feature = "parallel")]
1747fn json_quote(s: &str) -> String {
1748 let mut out = Vec::with_capacity(s.len() + 2);
1749 json_quote_into(&mut out, s);
1750 unsafe { String::from_utf8_unchecked(out) }
1752}
1753
1754fn write_json_quoted(writer: &mut impl io::Write, s: &str) -> io::Result<()> {
1756 let mut buf = Vec::with_capacity(s.len() + 2);
1757 json_quote_into(&mut buf, s);
1758 writer.write_all(&buf)
1759}
1760
1761#[cfg(test)]
1764mod tests {
1765 use super::*;
1766
1767 #[test]
1768 fn empty_generator() {
1769 let builder = SourceMapGenerator::new(None);
1770 let json = builder.to_json();
1771 assert!(json.contains(r#""version":3"#));
1772 assert!(json.contains(r#""mappings":"""#));
1773 }
1774
1775 #[test]
1776 fn simple_mapping() {
1777 let mut builder = SourceMapGenerator::new(Some("output.js".to_string()));
1778 let src = builder.add_source("input.js");
1779 builder.add_mapping(0, 0, src, 0, 0);
1780
1781 let json = builder.to_json();
1782 assert!(json.contains(r#""file":"output.js""#));
1783 assert!(json.contains(r#""sources":["input.js"]"#));
1784
1785 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
1787 let loc = sm.original_position_for(0, 0).unwrap();
1788 assert_eq!(sm.source(loc.source), "input.js");
1789 assert_eq!(loc.line, 0);
1790 assert_eq!(loc.column, 0);
1791 }
1792
1793 #[test]
1794 fn mapping_with_name() {
1795 let mut builder = SourceMapGenerator::new(None);
1796 let src = builder.add_source("input.js");
1797 let name = builder.add_name("myFunction");
1798 builder.add_named_mapping(0, 0, src, 0, 0, name);
1799
1800 let json = builder.to_json();
1801 assert!(json.contains(r#""names":["myFunction"]"#));
1802
1803 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
1804 let loc = sm.original_position_for(0, 0).unwrap();
1805 assert_eq!(loc.name, Some(0));
1806 assert_eq!(sm.name(0), "myFunction");
1807 }
1808
1809 #[test]
1810 fn multiple_lines() {
1811 let mut builder = SourceMapGenerator::new(None);
1812 let src = builder.add_source("input.js");
1813 builder.add_mapping(0, 0, src, 0, 0);
1814 builder.add_mapping(1, 4, src, 1, 2);
1815 builder.add_mapping(2, 0, src, 2, 0);
1816
1817 let json = builder.to_json();
1818 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
1819 assert_eq!(sm.line_count(), 3);
1820
1821 let loc = sm.original_position_for(1, 4).unwrap();
1822 assert_eq!(loc.line, 1);
1823 assert_eq!(loc.column, 2);
1824 }
1825
1826 #[test]
1827 fn multiple_sources() {
1828 let mut builder = SourceMapGenerator::new(None);
1829 let a = builder.add_source("a.js");
1830 let b = builder.add_source("b.js");
1831 builder.add_mapping(0, 0, a, 0, 0);
1832 builder.add_mapping(1, 0, b, 0, 0);
1833
1834 let json = builder.to_json();
1835 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
1836
1837 let loc0 = sm.original_position_for(0, 0).unwrap();
1838 let loc1 = sm.original_position_for(1, 0).unwrap();
1839 assert_eq!(sm.source(loc0.source), "a.js");
1840 assert_eq!(sm.source(loc1.source), "b.js");
1841 }
1842
1843 #[test]
1844 fn source_content() {
1845 let mut builder = SourceMapGenerator::new(None);
1846 let src = builder.add_source("input.js");
1847 builder.set_source_content(src, "var x = 1;".to_string());
1848 builder.add_mapping(0, 0, src, 0, 0);
1849
1850 let json = builder.to_json();
1851 assert!(json.contains(r#""sourcesContent":["var x = 1;"]"#));
1852
1853 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
1854 assert_eq!(sm.sources_content[0], Some("var x = 1;".to_string()));
1855 }
1856
1857 #[test]
1858 fn source_root() {
1859 let mut builder = SourceMapGenerator::new(None);
1860 builder.set_source_root("src/".to_string());
1861 let src = builder.add_source("input.js");
1862 builder.add_mapping(0, 0, src, 0, 0);
1863
1864 let json = builder.to_json();
1865 assert!(json.contains(r#""sourceRoot":"src/""#));
1866 }
1867
1868 #[test]
1869 fn ignore_list() {
1870 let mut builder = SourceMapGenerator::new(None);
1871 let _app = builder.add_source("app.js");
1872 let lib = builder.add_source("node_modules/lib.js");
1873 builder.add_to_ignore_list(lib);
1874 builder.add_mapping(0, 0, lib, 0, 0);
1875
1876 let json = builder.to_json();
1877 assert!(json.contains(r#""ignoreList":[1]"#));
1878 }
1879
1880 #[test]
1881 fn generated_only_mapping() {
1882 let mut builder = SourceMapGenerator::new(None);
1883 builder.add_generated_mapping(0, 0);
1884
1885 let json = builder.to_json();
1886 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
1887 assert!(sm.original_position_for(0, 0).is_none());
1889 }
1890
1891 #[test]
1892 fn dedup_sources_and_names() {
1893 let mut builder = SourceMapGenerator::new(None);
1894 let s1 = builder.add_source("input.js");
1895 let s2 = builder.add_source("input.js"); assert_eq!(s1, s2);
1897
1898 let n1 = builder.add_name("foo");
1899 let n2 = builder.add_name("foo"); assert_eq!(n1, n2);
1901
1902 assert_eq!(builder.sources.len(), 1);
1903 assert_eq!(builder.names.len(), 1);
1904 }
1905
1906 #[test]
1907 fn large_roundtrip() {
1908 let mut builder = SourceMapGenerator::new(Some("bundle.js".to_string()));
1909
1910 for i in 0..5 {
1911 builder.add_source(&format!("src/file{i}.js"));
1912 }
1913 for i in 0..10 {
1914 builder.add_name(&format!("var{i}"));
1915 }
1916
1917 for line in 0..100u32 {
1919 for col in 0..10u32 {
1920 let src = (line * 10 + col) % 5;
1921 let name = if col % 3 == 0 { Some(col % 10) } else { None };
1922
1923 match name {
1924 Some(n) => builder.add_named_mapping(line, col * 10, src, line, col * 5, n),
1925 None => builder.add_mapping(line, col * 10, src, line, col * 5),
1926 }
1927 }
1928 }
1929
1930 let json = builder.to_json();
1931 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
1932
1933 assert_eq!(sm.mapping_count(), 1000);
1934 assert_eq!(sm.line_count(), 100);
1935
1936 let loc = sm.original_position_for(50, 30).unwrap();
1938 assert_eq!(loc.line, 50);
1939 assert_eq!(loc.column, 15);
1940 }
1941
1942 #[test]
1943 fn json_escaping() {
1944 let mut builder = SourceMapGenerator::new(None);
1945 let src = builder.add_source("path/with\"quotes.js");
1946 builder.set_source_content(src, "line1\nline2\ttab".to_string());
1947 builder.add_mapping(0, 0, src, 0, 0);
1948
1949 let json = builder.to_json();
1950 let _: serde_json::Value = serde_json::from_str(&json).unwrap();
1952 }
1953
1954 #[test]
1955 fn maybe_add_mapping_skips_redundant() {
1956 let mut builder = SourceMapGenerator::new(None);
1957 let src = builder.add_source("input.js");
1958
1959 assert!(builder.maybe_add_mapping(0, 0, src, 10, 0));
1961 assert!(!builder.maybe_add_mapping(0, 5, src, 10, 0));
1963 assert!(builder.maybe_add_mapping(0, 10, src, 11, 0));
1965 assert!(builder.maybe_add_mapping(1, 0, src, 11, 0));
1967
1968 assert_eq!(builder.mapping_count(), 3);
1969
1970 let json = builder.to_json();
1971 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
1972 assert_eq!(sm.mapping_count(), 3);
1973 }
1974
1975 #[test]
1976 fn maybe_add_mapping_different_source() {
1977 let mut builder = SourceMapGenerator::new(None);
1978 let a = builder.add_source("a.js");
1979 let b = builder.add_source("b.js");
1980
1981 assert!(builder.maybe_add_mapping(0, 0, a, 0, 0));
1982 assert!(builder.maybe_add_mapping(0, 5, b, 0, 0));
1984
1985 assert_eq!(builder.mapping_count(), 2);
1986 }
1987
1988 #[test]
1989 fn to_decoded_map_basic() {
1990 let mut builder = SourceMapGenerator::new(Some("output.js".to_string()));
1991 let src = builder.add_source("input.js");
1992 builder.add_mapping(0, 0, src, 0, 0);
1993 builder.add_mapping(1, 4, src, 1, 2);
1994
1995 let sm = builder.to_decoded_map();
1996 assert_eq!(sm.mapping_count(), 2);
1997 assert_eq!(sm.line_count(), 2);
1998
1999 let loc = sm.original_position_for(0, 0).unwrap();
2000 assert_eq!(sm.source(loc.source), "input.js");
2001 assert_eq!(loc.line, 0);
2002 assert_eq!(loc.column, 0);
2003
2004 let loc = sm.original_position_for(1, 4).unwrap();
2005 assert_eq!(loc.line, 1);
2006 assert_eq!(loc.column, 2);
2007 }
2008
2009 #[test]
2010 fn to_decoded_map_with_names() {
2011 let mut builder = SourceMapGenerator::new(None);
2012 let src = builder.add_source("input.js");
2013 let name = builder.add_name("myFunction");
2014 builder.add_named_mapping(0, 0, src, 0, 0, name);
2015
2016 let sm = builder.to_decoded_map();
2017 let loc = sm.original_position_for(0, 0).unwrap();
2018 assert_eq!(loc.name, Some(0));
2019 assert_eq!(sm.name(0), "myFunction");
2020 }
2021
2022 #[test]
2023 fn to_decoded_map_matches_json_roundtrip() {
2024 let mut builder = SourceMapGenerator::new(Some("bundle.js".to_string()));
2025 for i in 0..5 {
2026 builder.add_source(&format!("src/file{i}.js"));
2027 }
2028 for i in 0..10 {
2029 builder.add_name(&format!("var{i}"));
2030 }
2031
2032 for line in 0..50u32 {
2033 for col in 0..10u32 {
2034 let src = (line * 10 + col) % 5;
2035 let name = if col % 3 == 0 { Some(col % 10) } else { None };
2036 match name {
2037 Some(n) => builder.add_named_mapping(line, col * 10, src, line, col * 5, n),
2038 None => builder.add_mapping(line, col * 10, src, line, col * 5),
2039 }
2040 }
2041 }
2042
2043 let sm_decoded = builder.to_decoded_map();
2045 let json = builder.to_json();
2046 let sm_json = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
2047
2048 assert_eq!(sm_decoded.mapping_count(), sm_json.mapping_count());
2049 assert_eq!(sm_decoded.line_count(), sm_json.line_count());
2050
2051 for m in sm_json.all_mappings() {
2053 let a = sm_json.original_position_for(m.generated_line, m.generated_column);
2054 let b = sm_decoded.original_position_for(m.generated_line, m.generated_column);
2055 match (a, b) {
2056 (Some(a), Some(b)) => {
2057 assert_eq!(
2058 a.source, b.source,
2059 "source mismatch at ({}, {})",
2060 m.generated_line, m.generated_column
2061 );
2062 assert_eq!(
2063 a.line, b.line,
2064 "line mismatch at ({}, {})",
2065 m.generated_line, m.generated_column
2066 );
2067 assert_eq!(
2068 a.column, b.column,
2069 "column mismatch at ({}, {})",
2070 m.generated_line, m.generated_column
2071 );
2072 assert_eq!(
2073 a.name, b.name,
2074 "name mismatch at ({}, {})",
2075 m.generated_line, m.generated_column
2076 );
2077 }
2078 (None, None) => {}
2079 _ => panic!("lookup mismatch at ({}, {})", m.generated_line, m.generated_column),
2080 }
2081 }
2082 }
2083
2084 #[test]
2085 fn to_decoded_map_empty() {
2086 let builder = SourceMapGenerator::new(None);
2087 let sm = builder.to_decoded_map();
2088 assert_eq!(sm.mapping_count(), 0);
2089 assert_eq!(sm.line_count(), 0);
2090 }
2091
2092 #[test]
2093 fn to_decoded_map_generated_only() {
2094 let mut builder = SourceMapGenerator::new(None);
2095 builder.add_generated_mapping(0, 0);
2096
2097 let sm = builder.to_decoded_map();
2098 assert_eq!(sm.mapping_count(), 1);
2099 assert!(sm.original_position_for(0, 0).is_none());
2101 }
2102
2103 #[test]
2104 fn to_decoded_map_multiple_sources() {
2105 let mut builder = SourceMapGenerator::new(None);
2106 let a = builder.add_source("a.js");
2107 let b = builder.add_source("b.js");
2108 builder.add_mapping(0, 0, a, 0, 0);
2109 builder.add_mapping(1, 0, b, 0, 0);
2110
2111 let sm = builder.to_decoded_map();
2112 let loc0 = sm.original_position_for(0, 0).unwrap();
2113 let loc1 = sm.original_position_for(1, 0).unwrap();
2114 assert_eq!(sm.source(loc0.source), "a.js");
2115 assert_eq!(sm.source(loc1.source), "b.js");
2116 }
2117
2118 #[test]
2119 fn to_decoded_map_with_source_content() {
2120 let mut builder = SourceMapGenerator::new(None);
2121 let src = builder.add_source("input.js");
2122 builder.set_source_content(src, "var x = 1;".to_string());
2123 builder.add_mapping(0, 0, src, 0, 0);
2124
2125 let sm = builder.to_decoded_map();
2126 assert_eq!(sm.sources_content[0], Some("var x = 1;".to_string()));
2127 }
2128
2129 #[test]
2130 fn to_decoded_map_reverse_lookup() {
2131 let mut builder = SourceMapGenerator::new(None);
2132 let src = builder.add_source("input.js");
2133 builder.add_mapping(0, 0, src, 10, 5);
2134
2135 let sm = builder.to_decoded_map();
2136 let loc = sm.generated_position_for("input.js", 10, 5).unwrap();
2137 assert_eq!(loc.line, 0);
2138 assert_eq!(loc.column, 0);
2139 }
2140
2141 #[test]
2142 fn out_of_order_insertion_is_sorted_on_encode() {
2143 let mut unsorted = SourceMapGenerator::new(None);
2147 let src = unsorted.add_source("input.js");
2148 unsorted.add_mapping(2, 0, src, 2, 0);
2149 unsorted.add_mapping(0, 5, src, 0, 5);
2150 unsorted.add_mapping(0, 0, src, 0, 0);
2151 unsorted.add_mapping(1, 0, src, 1, 0);
2152
2153 let mut sorted = SourceMapGenerator::new(None);
2154 let src = sorted.add_source("input.js");
2155 sorted.add_mapping(0, 0, src, 0, 0);
2156 sorted.add_mapping(0, 5, src, 0, 5);
2157 sorted.add_mapping(1, 0, src, 1, 0);
2158 sorted.add_mapping(2, 0, src, 2, 0);
2159
2160 assert_eq!(unsorted.encode_mappings(), sorted.encode_mappings());
2161
2162 let sm = unsorted.to_decoded_map();
2164 let loc = sm.original_position_for(0, 5).unwrap();
2165 assert_eq!((loc.line, loc.column), (0, 5));
2166 let loc = sm.original_position_for(2, 0).unwrap();
2167 assert_eq!((loc.line, loc.column), (2, 0));
2168 }
2169
2170 #[test]
2171 fn to_decoded_map_sparse_lines() {
2172 let mut builder = SourceMapGenerator::new(None);
2173 let src = builder.add_source("input.js");
2174 builder.add_mapping(0, 0, src, 0, 0);
2175 builder.add_mapping(5, 0, src, 5, 0);
2176
2177 let sm = builder.to_decoded_map();
2178 assert_eq!(sm.line_count(), 6);
2179 assert!(sm.original_position_for(0, 0).is_some());
2180 assert!(sm.original_position_for(2, 0).is_none());
2181 assert!(sm.original_position_for(5, 0).is_some());
2182 }
2183
2184 #[test]
2185 fn empty_lines_between_mappings() {
2186 let mut builder = SourceMapGenerator::new(None);
2187 let src = builder.add_source("input.js");
2188 builder.add_mapping(0, 0, src, 0, 0);
2189 builder.add_mapping(5, 0, src, 5, 0);
2191
2192 let json = builder.to_json();
2193 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
2194
2195 assert!(sm.original_position_for(0, 0).is_some());
2197 assert!(sm.original_position_for(2, 0).is_none());
2199 assert!(sm.original_position_for(5, 0).is_some());
2201 }
2202
2203 #[test]
2204 fn debug_id() {
2205 let mut builder = SourceMapGenerator::new(None);
2206 builder.set_debug_id("85314830-023f-4cf1-a267-535f4e37bb17".to_string());
2207 let src = builder.add_source("input.js");
2208 builder.add_mapping(0, 0, src, 0, 0);
2209
2210 let json = builder.to_json();
2211 assert!(json.contains(r#""debugId":"85314830-023f-4cf1-a267-535f4e37bb17""#));
2212
2213 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
2214 assert_eq!(sm.debug_id.as_deref(), Some("85314830-023f-4cf1-a267-535f4e37bb17"));
2215 }
2216
2217 #[test]
2218 fn scopes_roundtrip() {
2219 use srcmap_scopes::{Binding, GeneratedRange, OriginalScope, Position, ScopeInfo};
2220
2221 let mut builder = SourceMapGenerator::new(Some("bundle.js".to_string()));
2222 let src = builder.add_source("input.js");
2223 builder.set_source_content(
2224 src,
2225 "function hello(name) {\n return name;\n}\nhello('world');".to_string(),
2226 );
2227 let name_hello = builder.add_name("hello");
2228 builder.add_named_mapping(0, 0, src, 0, 0, name_hello);
2229 builder.add_mapping(1, 0, src, 1, 0);
2230
2231 builder.set_scopes(ScopeInfo {
2233 scopes: vec![Some(OriginalScope {
2234 start: Position { line: 0, column: 0 },
2235 end: Position { line: 3, column: 14 },
2236 name: None,
2237 kind: Some("global".to_string()),
2238 is_stack_frame: false,
2239 variables: vec!["hello".to_string()],
2240 children: vec![OriginalScope {
2241 start: Position { line: 0, column: 9 },
2242 end: Position { line: 2, column: 1 },
2243 name: Some("hello".to_string()),
2244 kind: Some("function".to_string()),
2245 is_stack_frame: true,
2246 variables: vec!["name".to_string()],
2247 children: vec![],
2248 }],
2249 })],
2250 ranges: vec![GeneratedRange {
2251 start: Position { line: 0, column: 0 },
2252 end: Position { line: 3, column: 14 },
2253 is_stack_frame: false,
2254 is_hidden: false,
2255 definition: Some(0),
2256 call_site: None,
2257 bindings: vec![Binding::Expression("hello".to_string())],
2258 children: vec![GeneratedRange {
2259 start: Position { line: 0, column: 9 },
2260 end: Position { line: 2, column: 1 },
2261 is_stack_frame: true,
2262 is_hidden: false,
2263 definition: Some(1),
2264 call_site: None,
2265 bindings: vec![Binding::Expression("name".to_string())],
2266 children: vec![],
2267 }],
2268 }],
2269 });
2270
2271 let json = builder.to_json();
2272
2273 assert!(json.contains(r#""scopes":"#));
2275
2276 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
2278 assert!(sm.scopes.is_some());
2279
2280 let scopes_info = sm.scopes.unwrap();
2281
2282 assert_eq!(scopes_info.scopes.len(), 1);
2284 let root_scope = scopes_info.scopes[0].as_ref().unwrap();
2285 assert_eq!(root_scope.kind.as_deref(), Some("global"));
2286 assert_eq!(root_scope.variables, vec!["hello"]);
2287 assert_eq!(root_scope.children.len(), 1);
2288
2289 let fn_scope = &root_scope.children[0];
2290 assert_eq!(fn_scope.name.as_deref(), Some("hello"));
2291 assert_eq!(fn_scope.kind.as_deref(), Some("function"));
2292 assert!(fn_scope.is_stack_frame);
2293 assert_eq!(fn_scope.variables, vec!["name"]);
2294
2295 assert_eq!(scopes_info.ranges.len(), 1);
2297 let outer = &scopes_info.ranges[0];
2298 assert_eq!(outer.definition, Some(0));
2299 assert_eq!(outer.bindings, vec![Binding::Expression("hello".to_string())]);
2300 assert_eq!(outer.children.len(), 1);
2301
2302 let inner = &outer.children[0];
2303 assert_eq!(inner.definition, Some(1));
2304 assert!(inner.is_stack_frame);
2305 assert_eq!(inner.bindings, vec![Binding::Expression("name".to_string())]);
2306 }
2307
2308 #[test]
2309 fn scopes_with_inlining_roundtrip() {
2310 use srcmap_scopes::{
2311 Binding, CallSite, GeneratedRange, OriginalScope, Position, ScopeInfo,
2312 };
2313
2314 let mut builder = SourceMapGenerator::new(None);
2315 let src = builder.add_source("input.js");
2316 builder.add_mapping(0, 0, src, 0, 0);
2317
2318 builder.set_scopes(ScopeInfo {
2319 scopes: vec![Some(OriginalScope {
2320 start: Position { line: 0, column: 0 },
2321 end: Position { line: 10, column: 0 },
2322 name: None,
2323 kind: None,
2324 is_stack_frame: false,
2325 variables: vec!["x".to_string()],
2326 children: vec![OriginalScope {
2327 start: Position { line: 1, column: 0 },
2328 end: Position { line: 4, column: 1 },
2329 name: Some("greet".to_string()),
2330 kind: Some("function".to_string()),
2331 is_stack_frame: true,
2332 variables: vec!["msg".to_string()],
2333 children: vec![],
2334 }],
2335 })],
2336 ranges: vec![GeneratedRange {
2337 start: Position { line: 0, column: 0 },
2338 end: Position { line: 10, column: 0 },
2339 is_stack_frame: false,
2340 is_hidden: false,
2341 definition: Some(0),
2342 call_site: None,
2343 bindings: vec![Binding::Expression("_x".to_string())],
2344 children: vec![GeneratedRange {
2345 start: Position { line: 6, column: 0 },
2346 end: Position { line: 8, column: 0 },
2347 is_stack_frame: true,
2348 is_hidden: false,
2349 definition: Some(1),
2350 call_site: Some(CallSite { source_index: 0, line: 8, column: 0 }),
2351 bindings: vec![Binding::Expression("\"Hello\"".to_string())],
2352 children: vec![],
2353 }],
2354 }],
2355 });
2356
2357 let json = builder.to_json();
2358 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
2359 let info = sm.scopes.unwrap();
2360
2361 let inlined = &info.ranges[0].children[0];
2363 assert_eq!(inlined.call_site, Some(CallSite { source_index: 0, line: 8, column: 0 }));
2364 assert_eq!(inlined.bindings, vec![Binding::Expression("\"Hello\"".to_string())]);
2365 }
2366
2367 #[test]
2368 fn set_source_content_out_of_bounds() {
2369 let mut builder = SourceMapGenerator::new(None);
2370 builder.set_source_content(0, "content".to_string());
2372 let json = builder.to_json();
2374 assert!(!json.contains("content"));
2375 }
2376
2377 #[test]
2378 fn add_to_ignore_list_dedup() {
2379 let mut builder = SourceMapGenerator::new(None);
2380 let idx = builder.add_source("vendor.js");
2381 builder.add_to_ignore_list(idx);
2382 builder.add_to_ignore_list(idx); let json = builder.to_json();
2384 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
2386 assert_eq!(sm.ignore_list, vec![0]);
2387 }
2388
2389 #[test]
2390 fn to_decoded_map_with_source_root() {
2391 let mut builder = SourceMapGenerator::new(None);
2392 builder.set_source_root("src/".to_string());
2393 let src = builder.add_source("app.ts");
2394 builder.add_mapping(0, 0, src, 0, 0);
2395 let sm = builder.to_decoded_map();
2396 assert_eq!(sm.sources, vec!["src/app.ts"]);
2398 }
2399
2400 #[test]
2401 fn json_escaping_special_chars() {
2402 let mut builder = SourceMapGenerator::new(None);
2403 let src = builder.add_source("a.js");
2404 builder.set_source_content(src, "line1\nline2\r\ttab\\\"\x01end".to_string());
2406 builder.add_mapping(0, 0, src, 0, 0);
2407 let json = builder.to_json();
2408 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
2410 assert_eq!(sm.sources_content, vec![Some("line1\nline2\r\ttab\\\"\x01end".to_string())]);
2411 }
2412
2413 #[test]
2414 fn json_escaping_in_names() {
2415 let mut builder = SourceMapGenerator::new(None);
2416 let src = builder.add_source("a.js");
2417 let name = builder.add_name("func\"with\\special");
2418 builder.add_named_mapping(0, 0, src, 0, 0, name);
2419 let json = builder.to_json();
2420 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
2421 assert_eq!(sm.names[0], "func\"with\\special");
2422 }
2423
2424 #[test]
2425 fn json_escaping_in_sources() {
2426 let mut builder = SourceMapGenerator::new(None);
2427 let src = builder.add_source("path/with\"quotes.js");
2428 builder.add_mapping(0, 0, src, 0, 0);
2429 let json = builder.to_json();
2430 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
2431 assert_eq!(sm.sources[0], "path/with\"quotes.js");
2432 }
2433
2434 #[cfg(feature = "parallel")]
2435 mod parallel_tests {
2436 use super::*;
2437
2438 fn build_large_generator(lines: u32, cols_per_line: u32) -> SourceMapGenerator {
2439 let mut builder = SourceMapGenerator::new(Some("bundle.js".to_string()));
2440 for i in 0..10 {
2441 let src = builder.add_source(&format!("src/file{i}.js"));
2442 builder.set_source_content(
2443 src,
2444 format!("// source file {i}\n{}", "x = 1;\n".repeat(100)),
2445 );
2446 }
2447 for i in 0..20 {
2448 builder.add_name(&format!("var{i}"));
2449 }
2450
2451 for line in 0..lines {
2452 for col in 0..cols_per_line {
2453 let src = (line * cols_per_line + col) % 10;
2454 let name = if col % 3 == 0 { Some(col % 20) } else { None };
2455 match name {
2456 Some(n) => builder.add_named_mapping(line, col * 10, src, line, col * 5, n),
2457 None => builder.add_mapping(line, col * 10, src, line, col * 5),
2458 }
2459 }
2460 }
2461 builder
2462 }
2463
2464 #[test]
2465 fn parallel_large_roundtrip() {
2466 let builder = build_large_generator(500, 20);
2467 let json = builder.to_json();
2468 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
2469 assert_eq!(sm.mapping_count(), 10000);
2470 assert_eq!(sm.line_count(), 500);
2471
2472 let loc = sm.original_position_for(250, 50).unwrap();
2474 assert_eq!(loc.line, 250);
2475 assert_eq!(loc.column, 25);
2476 }
2477
2478 #[test]
2479 fn parallel_matches_sequential() {
2480 let builder = build_large_generator(500, 20);
2481
2482 let mut sorted: Vec<&Mapping> = builder.mappings.iter().collect();
2484 sorted.sort_unstable_by(|a, b| {
2485 a.generated_line
2486 .cmp(&b.generated_line)
2487 .then(a.generated_column.cmp(&b.generated_column))
2488 });
2489
2490 let sequential = SourceMapGenerator::encode_sequential_impl(&sorted);
2491 let parallel = SourceMapGenerator::encode_parallel_impl(&sorted);
2492 assert_eq!(sequential, parallel);
2493 }
2494
2495 #[test]
2496 fn parallel_with_sparse_lines() {
2497 let mut builder = SourceMapGenerator::new(None);
2498 let src = builder.add_source("input.js");
2499
2500 for i in 0..50 {
2502 let line = i * 100;
2503 for col in 0..100u32 {
2504 builder.add_mapping(line, col * 10, src, line, col * 5);
2505 }
2506 }
2507
2508 let json = builder.to_json();
2509 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
2510 assert_eq!(sm.mapping_count(), 5000);
2511
2512 assert!(sm.original_position_for(50, 0).is_none());
2514 let loc = sm.original_position_for(200, 50).unwrap();
2516 assert_eq!(loc.line, 200);
2517 assert_eq!(loc.column, 25);
2518 }
2519 }
2520
2521 #[test]
2524 fn streaming_basic() {
2525 let mut sg = StreamingGenerator::new(Some("out.js".to_string()));
2526 let src = sg.add_source("input.js");
2527 sg.add_mapping(0, 0, src, 0, 0);
2528 sg.add_mapping(1, 0, src, 1, 0);
2529
2530 let json = sg.to_json();
2531 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
2532 assert_eq!(sm.sources, vec!["input.js"]);
2533 assert_eq!(sm.mapping_count(), 2);
2534
2535 let loc0 = sm.original_position_for(0, 0).unwrap();
2536 assert_eq!(sm.source(loc0.source), "input.js");
2537 assert_eq!(loc0.line, 0);
2538
2539 let loc1 = sm.original_position_for(1, 0).unwrap();
2540 assert_eq!(loc1.line, 1);
2541 }
2542
2543 #[test]
2544 fn streaming_with_names() {
2545 let mut sg = StreamingGenerator::new(None);
2546 let src = sg.add_source("a.js");
2547 let name = sg.add_name("foo");
2548 sg.add_named_mapping(0, 0, src, 0, 0, name);
2549
2550 let sm = srcmap_sourcemap::SourceMap::from_json(&sg.to_json()).unwrap();
2551 let loc = sm.original_position_for(0, 0).unwrap();
2552 assert_eq!(loc.name, Some(0));
2553 assert_eq!(sm.name(0), "foo");
2554 }
2555
2556 #[test]
2557 fn streaming_generated_only() {
2558 let mut sg = StreamingGenerator::new(None);
2559 let src = sg.add_source("a.js");
2560 sg.add_generated_mapping(0, 0);
2561 sg.add_mapping(0, 5, src, 0, 0);
2562
2563 let sm = srcmap_sourcemap::SourceMap::from_json(&sg.to_json()).unwrap();
2564 assert_eq!(sm.mapping_count(), 2);
2565 assert!(sm.original_position_for(0, 0).is_none());
2566 assert!(sm.original_position_for(0, 5).is_some());
2567 }
2568
2569 #[test]
2570 fn streaming_matches_regular_generator() {
2571 let mut regular = SourceMapGenerator::new(Some("out.js".to_string()));
2572 let mut streaming = StreamingGenerator::new(Some("out.js".to_string()));
2573
2574 let src_r = regular.add_source("a.js");
2575 let src_s = streaming.add_source("a.js");
2576
2577 let name_r = regular.add_name("hello");
2578 let name_s = streaming.add_name("hello");
2579
2580 regular.set_source_content(src_r, "var hello;".to_string());
2581 streaming.set_source_content(src_s, "var hello;".to_string());
2582
2583 regular.add_named_mapping(0, 0, src_r, 0, 0, name_r);
2584 streaming.add_named_mapping(0, 0, src_s, 0, 0, name_s);
2585
2586 regular.add_mapping(0, 10, src_r, 0, 4);
2587 streaming.add_mapping(0, 10, src_s, 0, 4);
2588
2589 regular.add_mapping(1, 0, src_r, 1, 0);
2590 streaming.add_mapping(1, 0, src_s, 1, 0);
2591
2592 let sm_r = srcmap_sourcemap::SourceMap::from_json(®ular.to_json()).unwrap();
2593 let sm_s = srcmap_sourcemap::SourceMap::from_json(&streaming.to_json()).unwrap();
2594
2595 assert_eq!(sm_r.mapping_count(), sm_s.mapping_count());
2596 assert_eq!(sm_r.sources, sm_s.sources);
2597 assert_eq!(sm_r.names, sm_s.names);
2598 assert_eq!(sm_r.sources_content, sm_s.sources_content);
2599
2600 for (a, b) in sm_r.all_mappings().iter().zip(sm_s.all_mappings().iter()) {
2601 assert_eq!(a.generated_line, b.generated_line);
2602 assert_eq!(a.generated_column, b.generated_column);
2603 assert_eq!(a.source, b.source);
2604 assert_eq!(a.original_line, b.original_line);
2605 assert_eq!(a.original_column, b.original_column);
2606 assert_eq!(a.name, b.name);
2607 }
2608 }
2609
2610 #[test]
2611 fn streaming_to_decoded_map() {
2612 let mut sg = StreamingGenerator::new(None);
2613 let src = sg.add_source("test.js");
2614 sg.add_mapping(0, 0, src, 0, 0);
2615 sg.add_mapping(2, 5, src, 1, 3);
2616
2617 let sm = sg.to_decoded_map().unwrap();
2618 assert_eq!(sm.mapping_count(), 2);
2619 assert_eq!(sm.sources, vec!["test.js"]);
2620
2621 let loc = sm.original_position_for(2, 5).unwrap();
2622 assert_eq!(loc.line, 1);
2623 assert_eq!(loc.column, 3);
2624 }
2625
2626 #[test]
2627 fn streaming_source_dedup() {
2628 let mut sg = StreamingGenerator::new(None);
2629 let src1 = sg.add_source("a.js");
2630 let src2 = sg.add_source("a.js");
2631 assert_eq!(src1, src2);
2632 assert_eq!(sg.sources.len(), 1);
2633 }
2634
2635 #[test]
2636 fn streaming_ignore_list() {
2637 let mut sg = StreamingGenerator::new(None);
2638 let src = sg.add_source("vendor.js");
2639 sg.add_to_ignore_list(src);
2640 sg.add_mapping(0, 0, src, 0, 0);
2641
2642 let sm = srcmap_sourcemap::SourceMap::from_json(&sg.to_json()).unwrap();
2643 assert_eq!(sm.ignore_list, vec![0]);
2644 }
2645
2646 #[test]
2647 fn streaming_empty() {
2648 let sg = StreamingGenerator::new(None);
2649 let json = sg.to_json();
2650 let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
2651 assert_eq!(sm.mapping_count(), 0);
2652 }
2653
2654 #[test]
2655 fn streaming_sparse_lines() {
2656 let mut sg = StreamingGenerator::new(None);
2657 let src = sg.add_source("a.js");
2658 sg.add_mapping(0, 0, src, 0, 0);
2659 sg.add_mapping(5, 0, src, 5, 0);
2660
2661 let sm = srcmap_sourcemap::SourceMap::from_json(&sg.to_json()).unwrap();
2662 assert_eq!(sm.mapping_count(), 2);
2663 assert!(sm.original_position_for(0, 0).is_some());
2664 assert!(sm.original_position_for(5, 0).is_some());
2665 }
2666
2667 #[test]
2670 fn range_mapping_basic() {
2671 let mut builder = SourceMapGenerator::new(None);
2672 let src = builder.add_source("input.js");
2673 builder.add_range_mapping(0, 0, src, 0, 0);
2674 builder.add_mapping(0, 5, src, 0, 10);
2675
2676 let json = builder.to_json();
2677 assert!(json.contains(r#""rangeMappings":"A""#));
2678 }
2679
2680 #[test]
2681 fn range_mapping_multiple_on_line() {
2682 let mut builder = SourceMapGenerator::new(None);
2683 let src = builder.add_source("input.js");
2684 builder.add_range_mapping(0, 0, src, 0, 0);
2685 builder.add_mapping(0, 5, src, 0, 10);
2686 builder.add_range_mapping(0, 10, src, 0, 20);
2687
2688 let json = builder.to_json();
2689 assert!(json.contains(r#""rangeMappings":"A,C""#));
2690 }
2691
2692 #[test]
2693 fn range_mapping_multi_line() {
2694 let mut builder = SourceMapGenerator::new(None);
2695 let src = builder.add_source("input.js");
2696 builder.add_range_mapping(0, 0, src, 0, 0);
2697 builder.add_range_mapping(1, 0, src, 1, 0);
2698
2699 let json = builder.to_json();
2700 assert!(json.contains(r#""rangeMappings":"A;A""#));
2701 }
2702
2703 #[test]
2704 fn no_range_mappings_omits_field() {
2705 let mut builder = SourceMapGenerator::new(None);
2706 let src = builder.add_source("input.js");
2707 builder.add_mapping(0, 0, src, 0, 0);
2708
2709 let json = builder.to_json();
2710 assert!(!json.contains("rangeMappings"));
2711 }
2712
2713 #[test]
2714 fn named_range_mapping() {
2715 let mut builder = SourceMapGenerator::new(None);
2716 let src = builder.add_source("input.js");
2717 let name = builder.add_name("foo");
2718 builder.add_named_range_mapping(0, 0, src, 0, 0, name);
2719
2720 let json = builder.to_json();
2721 assert!(json.contains(r#""rangeMappings":"A""#));
2722 }
2723
2724 #[test]
2725 fn to_decoded_map_preserves_range_mappings() {
2726 let mut builder = SourceMapGenerator::new(None);
2727 let src = builder.add_source("input.js");
2728 builder.add_range_mapping(0, 0, src, 0, 0);
2729 builder.add_mapping(0, 5, src, 0, 10);
2730
2731 let sm = builder.to_decoded_map();
2732 assert!(sm.has_range_mappings());
2733 let mappings = sm.all_mappings();
2734 assert!(mappings[0].is_range_mapping);
2735 assert!(!mappings[1].is_range_mapping);
2736 }
2737
2738 #[test]
2741 fn streaming_range_mapping_basic() {
2742 let mut sg = StreamingGenerator::new(None);
2743 let src = sg.add_source("input.js");
2744 sg.add_range_mapping(0, 0, src, 0, 0);
2745 sg.add_mapping(0, 5, src, 0, 10);
2746
2747 let json = sg.to_json();
2748 assert!(json.contains(r#""rangeMappings":"A""#));
2749 }
2750
2751 #[test]
2752 fn streaming_range_mapping_roundtrip() {
2753 let mut sg = StreamingGenerator::new(None);
2754 let src = sg.add_source("input.js");
2755 sg.add_range_mapping(0, 0, src, 0, 0);
2756 sg.add_mapping(0, 5, src, 0, 10);
2757
2758 let sm = sg.to_decoded_map().unwrap();
2759 assert!(sm.has_range_mappings());
2760 let mappings = sm.all_mappings();
2761 assert!(mappings[0].is_range_mapping);
2762 assert!(!mappings[1].is_range_mapping);
2763 }
2764
2765 #[test]
2766 fn streaming_range_and_named_range() {
2767 let mut sg = StreamingGenerator::new(None);
2768 let src = sg.add_source("input.js");
2769 let name = sg.add_name("foo");
2770 sg.add_range_mapping(0, 0, src, 0, 0);
2771 sg.add_named_range_mapping(0, 10, src, 0, 5, name);
2772
2773 let json = sg.to_json();
2774 assert!(json.contains(r#""rangeMappings":"A,B""#));
2775
2776 let sm = sg.to_decoded_map().unwrap();
2777 assert!(sm.has_range_mappings());
2778 let mappings = sm.all_mappings();
2779 assert!(mappings[0].is_range_mapping);
2780 assert!(mappings[1].is_range_mapping);
2781 }
2782
2783 #[test]
2784 fn streaming_range_mapping_matches_regular() {
2785 let mut regular = SourceMapGenerator::new(None);
2786 let mut streaming = StreamingGenerator::new(None);
2787
2788 let src_r = regular.add_source("input.js");
2789 let src_s = streaming.add_source("input.js");
2790
2791 regular.add_range_mapping(0, 0, src_r, 0, 0);
2792 streaming.add_range_mapping(0, 0, src_s, 0, 0);
2793
2794 regular.add_mapping(0, 5, src_r, 0, 10);
2795 streaming.add_mapping(0, 5, src_s, 0, 10);
2796
2797 regular.add_range_mapping(0, 10, src_r, 0, 20);
2798 streaming.add_range_mapping(0, 10, src_s, 0, 20);
2799
2800 regular.add_range_mapping(1, 0, src_r, 1, 0);
2801 streaming.add_range_mapping(1, 0, src_s, 1, 0);
2802
2803 let json_r = regular.to_json();
2804 let json_s = streaming.to_json();
2805
2806 let sm_r = srcmap_sourcemap::SourceMap::from_json(&json_r).unwrap();
2807 let sm_s = srcmap_sourcemap::SourceMap::from_json(&json_s).unwrap();
2808
2809 assert_eq!(sm_r.mapping_count(), sm_s.mapping_count());
2810
2811 for (a, b) in sm_r.all_mappings().iter().zip(sm_s.all_mappings().iter()) {
2812 assert_eq!(a.generated_line, b.generated_line);
2813 assert_eq!(a.generated_column, b.generated_column);
2814 assert_eq!(a.source, b.source);
2815 assert_eq!(a.original_line, b.original_line);
2816 assert_eq!(a.original_column, b.original_column);
2817 assert_eq!(a.name, b.name);
2818 assert_eq!(a.is_range_mapping, b.is_range_mapping);
2819 }
2820 }
2821
2822 #[test]
2825 fn into_parts_basic() {
2826 let mut builder = SourceMapGenerator::new(Some("output.js".to_string()));
2827 let src = builder.add_source("input.js");
2828 builder.set_source_content(src, "var x = 1;".to_string());
2829 let name = builder.add_name("x");
2830 builder.add_named_mapping(0, 0, src, 0, 4, name);
2831 builder.add_mapping(1, 0, src, 1, 0);
2832 builder.set_debug_id("test-id");
2833
2834 let json = builder.to_json();
2835 let sm_json = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
2836
2837 let mut builder2 = SourceMapGenerator::new(Some("output.js".to_string()));
2839 let src2 = builder2.add_source("input.js");
2840 builder2.set_source_content(src2, "var x = 1;".to_string());
2841 let name2 = builder2.add_name("x");
2842 builder2.add_named_mapping(0, 0, src2, 0, 4, name2);
2843 builder2.add_mapping(1, 0, src2, 1, 0);
2844 builder2.set_debug_id("test-id");
2845
2846 let parts = builder2.into_parts();
2847 assert_eq!(parts.file, Some("output.js".to_string()));
2848 assert_eq!(parts.sources, vec!["input.js"]);
2849 assert_eq!(parts.names, vec!["x"]);
2850 assert_eq!(parts.sources_content, vec![Some("var x = 1;".to_string())]);
2851 assert_eq!(parts.debug_id, Some("test-id".to_string()));
2852 assert!(!parts.mappings.is_empty());
2853
2854 let sm_parts = srcmap_sourcemap::SourceMap::from_vlq(
2856 &parts.mappings,
2857 parts.sources,
2858 parts.names,
2859 parts.file,
2860 parts.source_root,
2861 parts.sources_content,
2862 parts.ignore_list,
2863 parts.debug_id,
2864 )
2865 .unwrap();
2866 assert_eq!(sm_parts.mapping_count(), sm_json.mapping_count());
2867 }
2868
2869 #[test]
2870 fn into_parts_empty() {
2871 let builder = SourceMapGenerator::new(None);
2872 let parts = builder.into_parts();
2873 assert_eq!(parts.file, None);
2874 assert!(parts.mappings.is_empty());
2875 assert!(parts.sources.is_empty());
2876 assert!(parts.names.is_empty());
2877 }
2878
2879 #[test]
2880 fn into_parts_with_ignore_list() {
2881 let mut builder = SourceMapGenerator::new(None);
2882 let src = builder.add_source("vendor.js");
2883 builder.add_to_ignore_list(src);
2884 builder.add_mapping(0, 0, src, 0, 0);
2885
2886 let parts = builder.into_parts();
2887 assert_eq!(parts.ignore_list, vec![0]);
2888 }
2889
2890 #[test]
2891 fn into_parts_with_range_mappings() {
2892 let mut builder = SourceMapGenerator::new(None);
2893 let src = builder.add_source("input.js");
2894 builder.add_range_mapping(0, 0, src, 0, 0);
2895 builder.add_mapping(0, 5, src, 0, 10);
2896
2897 let parts = builder.into_parts();
2898 assert!(parts.range_mappings.is_some());
2899 }
2900
2901 #[test]
2902 fn streaming_into_parts() {
2903 let mut sg = StreamingGenerator::new(Some("out.js".to_string()));
2904 let src = sg.add_source("input.js");
2905 sg.set_source_content(src, "var x = 1;".to_string());
2906 let name = sg.add_name("x");
2907 sg.add_named_mapping(0, 0, src, 0, 4, name);
2908 sg.add_mapping(1, 0, src, 1, 0);
2909
2910 let parts = sg.into_parts();
2911 assert_eq!(parts.file, Some("out.js".to_string()));
2912 assert_eq!(parts.sources, vec!["input.js"]);
2913 assert_eq!(parts.names, vec!["x"]);
2914 assert!(!parts.mappings.is_empty());
2915 }
2916
2917 #[test]
2920 fn to_writer_matches_to_json() {
2921 let mut builder = SourceMapGenerator::new(Some("output.js".to_string()));
2922 let src = builder.add_source("input.js");
2923 builder.set_source_content(src, "var x = 1;".to_string());
2924 let name = builder.add_name("x");
2925 builder.add_named_mapping(0, 0, src, 0, 4, name);
2926 builder.add_mapping(1, 0, src, 1, 0);
2927
2928 let json = builder.to_json();
2929 let mut buf = Vec::new();
2930 builder.to_writer(&mut buf).unwrap();
2931 let writer_output = String::from_utf8(buf).unwrap();
2932
2933 assert_eq!(json, writer_output);
2934 }
2935
2936 #[test]
2937 fn to_writer_empty() {
2938 let builder = SourceMapGenerator::new(None);
2939 let mut buf = Vec::new();
2940 builder.to_writer(&mut buf).unwrap();
2941 let output = String::from_utf8(buf).unwrap();
2942 assert!(output.contains(r#""version":3"#));
2943 assert!(output.contains(r#""mappings":"""#));
2944 }
2945
2946 #[test]
2947 fn to_writer_with_all_fields() {
2948 let mut builder = SourceMapGenerator::new(Some("bundle.js".to_string()));
2949 builder.set_source_root("src/");
2950 builder.set_debug_id("test-uuid");
2951 let src = builder.add_source("app.ts");
2952 builder.set_source_content(src, "const x = 1;".to_string());
2953 builder.add_to_ignore_list(src);
2954 let name = builder.add_name("x");
2955 builder.add_named_mapping(0, 0, src, 0, 6, name);
2956
2957 let json = builder.to_json();
2958 let mut buf = Vec::new();
2959 builder.to_writer(&mut buf).unwrap();
2960 let writer_output = String::from_utf8(buf).unwrap();
2961
2962 assert_eq!(json, writer_output);
2963
2964 let sm = srcmap_sourcemap::SourceMap::from_json(&writer_output).unwrap();
2966 assert_eq!(sm.source(0), "src/app.ts");
2967 assert_eq!(sm.name(0), "x");
2968 }
2969
2970 #[test]
2971 fn streaming_to_writer_matches_to_json() {
2972 let mut sg = StreamingGenerator::new(Some("out.js".to_string()));
2973 let src = sg.add_source("input.js");
2974 sg.set_source_content(src, "var x = 1;".to_string());
2975 let name = sg.add_name("x");
2976 sg.add_named_mapping(0, 0, src, 0, 4, name);
2977 sg.add_mapping(1, 0, src, 1, 0);
2978
2979 let json = sg.to_json();
2980 let mut buf = Vec::new();
2981 sg.to_writer(&mut buf).unwrap();
2982 let writer_output = String::from_utf8(buf).unwrap();
2983
2984 assert_eq!(json, writer_output);
2985 }
2986}