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