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