1use srcmap_generator::{SourceMapGenerator, StreamingGenerator};
60use srcmap_sourcemap::SourceMap;
61use std::collections::HashMap;
62
63pub struct ConcatBuilder {
70 builder: SourceMapGenerator,
71}
72
73impl ConcatBuilder {
74 pub fn new(file: Option<String>) -> Self {
76 Self {
77 builder: SourceMapGenerator::new(file),
78 }
79 }
80
81 pub fn add_map(&mut self, sm: &SourceMap, line_offset: u32) {
86 let source_indices: Vec<u32> = sm
88 .sources
89 .iter()
90 .enumerate()
91 .map(|(i, s)| {
92 let idx = self.builder.add_source(s);
93 if let Some(Some(content)) = sm.sources_content.get(i) {
94 self.builder.set_source_content(idx, content.clone());
95 }
96 idx
97 })
98 .collect();
99
100 let name_indices: Vec<u32> = sm.names.iter().map(|n| self.builder.add_name(n)).collect();
102
103 for &ignored in &sm.ignore_list {
105 let global_idx = source_indices[ignored as usize];
106 self.builder.add_to_ignore_list(global_idx);
107 }
108
109 for m in sm.all_mappings() {
111 let gen_line = m.generated_line + line_offset;
112
113 if m.source == u32::MAX {
114 self.builder
115 .add_generated_mapping(gen_line, m.generated_column);
116 } else {
117 let src = source_indices[m.source as usize];
118 if m.is_range_mapping {
119 if m.name != u32::MAX {
120 let name = name_indices[m.name as usize];
121 self.builder.add_named_range_mapping(
122 gen_line,
123 m.generated_column,
124 src,
125 m.original_line,
126 m.original_column,
127 name,
128 );
129 } else {
130 self.builder.add_range_mapping(
131 gen_line,
132 m.generated_column,
133 src,
134 m.original_line,
135 m.original_column,
136 );
137 }
138 } else if m.name != u32::MAX {
139 let name = name_indices[m.name as usize];
140 self.builder.add_named_mapping(
141 gen_line,
142 m.generated_column,
143 src,
144 m.original_line,
145 m.original_column,
146 name,
147 );
148 } else {
149 self.builder.add_mapping(
150 gen_line,
151 m.generated_column,
152 src,
153 m.original_line,
154 m.original_column,
155 );
156 }
157 }
158 }
159 }
160
161 pub fn to_json(&self) -> String {
163 self.builder.to_json()
164 }
165
166 pub fn build(&self) -> SourceMap {
168 self.builder.to_decoded_map()
169 }
170}
171
172struct MappingParams<'a> {
176 source: Option<&'a str>,
177 source_content: Option<&'a str>,
178 original_line: u32,
179 original_column: u32,
180 name: Option<&'a str>,
181}
182
183fn add_mapping_to_builder(
185 builder: &mut SourceMapGenerator,
186 gen_line: u32,
187 gen_col: u32,
188 params: &MappingParams<'_>,
189 is_range: bool,
190) {
191 let source = params.source.expect("source required for source mapping");
192 let src_idx = builder.add_source(source);
193
194 if let Some(content) = params.source_content {
195 builder.set_source_content(src_idx, content.to_string());
196 }
197
198 let name_idx = params.name.map(|n| builder.add_name(n));
199
200 match (name_idx, is_range) {
201 (Some(n), true) => builder.add_named_range_mapping(
202 gen_line,
203 gen_col,
204 src_idx,
205 params.original_line,
206 params.original_column,
207 n,
208 ),
209 (Some(n), false) => builder.add_named_mapping(
210 gen_line,
211 gen_col,
212 src_idx,
213 params.original_line,
214 params.original_column,
215 n,
216 ),
217 (None, true) => builder.add_range_mapping(
218 gen_line,
219 gen_col,
220 src_idx,
221 params.original_line,
222 params.original_column,
223 ),
224 (None, false) => builder.add_mapping(
225 gen_line,
226 gen_col,
227 src_idx,
228 params.original_line,
229 params.original_column,
230 ),
231 }
232}
233
234pub fn remap<F>(outer: &SourceMap, loader: F) -> SourceMap
246where
247 F: Fn(&str) -> Option<SourceMap>,
248{
249 let mut builder = SourceMapGenerator::new(outer.file.clone());
250
251 let mut upstream_maps: HashMap<u32, Option<SourceMap>> = HashMap::new();
253 let mut ignored_sources: std::collections::HashSet<u32> = std::collections::HashSet::new();
255
256 for m in outer.all_mappings() {
257 if m.source == u32::MAX {
258 builder.add_generated_mapping(m.generated_line, m.generated_column);
259 continue;
260 }
261
262 let source_name = outer.source(m.source);
263
264 let upstream = upstream_maps
266 .entry(m.source)
267 .or_insert_with(|| loader(source_name));
268
269 match upstream {
270 Some(upstream_sm) => {
271 match upstream_sm.original_position_for(m.original_line, m.original_column) {
273 Some(loc) => {
274 let orig_source = upstream_sm.source(loc.source);
275 let source_content = upstream_sm
276 .sources_content
277 .get(loc.source as usize)
278 .and_then(|c| c.as_deref());
279
280 let name = loc.name.map(|n| upstream_sm.name(n)).or_else(|| {
282 if m.name != u32::MAX {
283 Some(outer.name(m.name))
284 } else {
285 None
286 }
287 });
288
289 let params = MappingParams {
290 source: Some(orig_source),
291 source_content,
292 original_line: loc.line,
293 original_column: loc.column,
294 name,
295 };
296
297 add_mapping_to_builder(
298 &mut builder,
299 m.generated_line,
300 m.generated_column,
301 ¶ms,
302 m.is_range_mapping,
303 );
304
305 if upstream_sm.ignore_list.contains(&loc.source) {
307 let src_idx = builder.add_source(orig_source);
308 if ignored_sources.insert(src_idx) {
309 builder.add_to_ignore_list(src_idx);
310 }
311 }
312 }
313 None => {
314 let name = if m.name != u32::MAX {
316 Some(outer.name(m.name))
317 } else {
318 None
319 };
320
321 let params = MappingParams {
322 source: Some(source_name),
323 source_content: None,
324 original_line: m.original_line,
325 original_column: m.original_column,
326 name,
327 };
328
329 add_mapping_to_builder(
330 &mut builder,
331 m.generated_line,
332 m.generated_column,
333 ¶ms,
334 m.is_range_mapping,
335 );
336 }
337 }
338 }
339 None => {
340 let source_content = outer
342 .sources_content
343 .get(m.source as usize)
344 .and_then(|c| c.as_deref());
345
346 let name = if m.name != u32::MAX {
347 Some(outer.name(m.name))
348 } else {
349 None
350 };
351
352 let params = MappingParams {
353 source: Some(source_name),
354 source_content,
355 original_line: m.original_line,
356 original_column: m.original_column,
357 name,
358 };
359
360 add_mapping_to_builder(
361 &mut builder,
362 m.generated_line,
363 m.generated_column,
364 ¶ms,
365 m.is_range_mapping,
366 );
367
368 if outer.ignore_list.contains(&m.source) {
370 let src_idx = builder.add_source(source_name);
371 if ignored_sources.insert(src_idx) {
372 builder.add_to_ignore_list(src_idx);
373 }
374 }
375 }
376 }
377 }
378
379 builder.to_decoded_map()
380}
381
382fn add_mapping_to_streaming(
384 builder: &mut StreamingGenerator,
385 gen_line: u32,
386 gen_col: u32,
387 params: &MappingParams<'_>,
388 is_range: bool,
389) {
390 let source = params.source.expect("source required for source mapping");
391 let src_idx = builder.add_source(source);
392
393 if let Some(content) = params.source_content {
394 builder.set_source_content(src_idx, content.to_string());
395 }
396
397 let name_idx = params.name.map(|n| builder.add_name(n));
398
399 match (name_idx, is_range) {
400 (Some(n), true) => builder.add_named_range_mapping(
401 gen_line,
402 gen_col,
403 src_idx,
404 params.original_line,
405 params.original_column,
406 n,
407 ),
408 (Some(n), false) => builder.add_named_mapping(
409 gen_line,
410 gen_col,
411 src_idx,
412 params.original_line,
413 params.original_column,
414 n,
415 ),
416 (None, true) => builder.add_range_mapping(
417 gen_line,
418 gen_col,
419 src_idx,
420 params.original_line,
421 params.original_column,
422 ),
423 (None, false) => builder.add_mapping(
424 gen_line,
425 gen_col,
426 src_idx,
427 params.original_line,
428 params.original_column,
429 ),
430 }
431}
432
433pub fn remap_streaming<'a, F>(
446 mappings_iter: srcmap_sourcemap::MappingsIter<'a>,
447 sources: &[String],
448 names: &[String],
449 sources_content: &[Option<String>],
450 ignore_list: &[u32],
451 file: Option<String>,
452 loader: F,
453) -> SourceMap
454where
455 F: Fn(&str) -> Option<SourceMap>,
456{
457 let mut builder = StreamingGenerator::new(file);
458
459 let mut upstream_maps: HashMap<u32, Option<SourceMap>> = HashMap::new();
461 let mut ignored_sources: std::collections::HashSet<u32> = std::collections::HashSet::new();
463
464 for item in mappings_iter {
465 let m = match item {
466 Ok(m) => m,
467 Err(_) => continue, };
469
470 if m.source == u32::MAX {
471 builder.add_generated_mapping(m.generated_line, m.generated_column);
472 continue;
473 }
474
475 let Some(source_name) = sources.get(m.source as usize) else {
476 continue;
477 };
478
479 let upstream = upstream_maps
481 .entry(m.source)
482 .or_insert_with(|| loader(source_name));
483
484 match upstream {
485 Some(upstream_sm) => {
486 match upstream_sm.original_position_for(m.original_line, m.original_column) {
488 Some(loc) => {
489 let orig_source = upstream_sm.source(loc.source);
490 let source_content = upstream_sm
491 .sources_content
492 .get(loc.source as usize)
493 .and_then(|c| c.as_deref());
494
495 let name = loc.name.map(|n| upstream_sm.name(n)).or_else(|| {
497 if m.name != u32::MAX {
498 names.get(m.name as usize).map(|s| s.as_str())
499 } else {
500 None
501 }
502 });
503
504 let params = MappingParams {
505 source: Some(orig_source),
506 source_content,
507 original_line: loc.line,
508 original_column: loc.column,
509 name,
510 };
511
512 add_mapping_to_streaming(
513 &mut builder,
514 m.generated_line,
515 m.generated_column,
516 ¶ms,
517 m.is_range_mapping,
518 );
519
520 if upstream_sm.ignore_list.contains(&loc.source) {
522 let src_idx = builder.add_source(orig_source);
523 if ignored_sources.insert(src_idx) {
524 builder.add_to_ignore_list(src_idx);
525 }
526 }
527 }
528 None => {
529 let name = if m.name != u32::MAX {
531 names.get(m.name as usize).map(|s| s.as_str())
532 } else {
533 None
534 };
535
536 let params = MappingParams {
537 source: Some(source_name),
538 source_content: None,
539 original_line: m.original_line,
540 original_column: m.original_column,
541 name,
542 };
543
544 add_mapping_to_streaming(
545 &mut builder,
546 m.generated_line,
547 m.generated_column,
548 ¶ms,
549 m.is_range_mapping,
550 );
551 }
552 }
553 }
554 None => {
555 let source_content = sources_content
557 .get(m.source as usize)
558 .and_then(|c| c.as_deref());
559
560 let name = if m.name != u32::MAX {
561 names.get(m.name as usize).map(|s| s.as_str())
562 } else {
563 None
564 };
565
566 let params = MappingParams {
567 source: Some(source_name),
568 source_content,
569 original_line: m.original_line,
570 original_column: m.original_column,
571 name,
572 };
573
574 add_mapping_to_streaming(
575 &mut builder,
576 m.generated_line,
577 m.generated_column,
578 ¶ms,
579 m.is_range_mapping,
580 );
581
582 if ignore_list.contains(&m.source) {
584 let src_idx = builder.add_source(source_name);
585 if ignored_sources.insert(src_idx) {
586 builder.add_to_ignore_list(src_idx);
587 }
588 }
589 }
590 }
591 }
592
593 builder
594 .to_decoded_map()
595 .expect("streaming VLQ should be valid")
596}
597
598#[cfg(test)]
601mod tests {
602 use super::*;
603
604 #[test]
607 fn concat_two_simple_maps() {
608 let a = SourceMap::from_json(
609 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#,
610 )
611 .unwrap();
612 let b = SourceMap::from_json(
613 r#"{"version":3,"sources":["b.js"],"names":[],"mappings":"AAAA"}"#,
614 )
615 .unwrap();
616
617 let mut builder = ConcatBuilder::new(Some("bundle.js".to_string()));
618 builder.add_map(&a, 0);
619 builder.add_map(&b, 1);
620
621 let result = builder.build();
622 assert_eq!(result.sources, vec!["a.js", "b.js"]);
623 assert_eq!(result.mapping_count(), 2);
624
625 let loc0 = result.original_position_for(0, 0).unwrap();
626 assert_eq!(result.source(loc0.source), "a.js");
627
628 let loc1 = result.original_position_for(1, 0).unwrap();
629 assert_eq!(result.source(loc1.source), "b.js");
630 }
631
632 #[test]
633 fn concat_deduplicates_sources() {
634 let a = SourceMap::from_json(
635 r#"{"version":3,"sources":["shared.js"],"names":[],"mappings":"AAAA"}"#,
636 )
637 .unwrap();
638 let b = SourceMap::from_json(
639 r#"{"version":3,"sources":["shared.js"],"names":[],"mappings":"AAAA"}"#,
640 )
641 .unwrap();
642
643 let mut builder = ConcatBuilder::new(None);
644 builder.add_map(&a, 0);
645 builder.add_map(&b, 10);
646
647 let result = builder.build();
648 assert_eq!(result.sources.len(), 1);
649 assert_eq!(result.sources[0], "shared.js");
650 }
651
652 #[test]
653 fn concat_with_names() {
654 let a = SourceMap::from_json(
655 r#"{"version":3,"sources":["a.js"],"names":["foo"],"mappings":"AAAAA"}"#,
656 )
657 .unwrap();
658 let b = SourceMap::from_json(
659 r#"{"version":3,"sources":["b.js"],"names":["bar"],"mappings":"AAAAA"}"#,
660 )
661 .unwrap();
662
663 let mut builder = ConcatBuilder::new(None);
664 builder.add_map(&a, 0);
665 builder.add_map(&b, 1);
666
667 let result = builder.build();
668 assert_eq!(result.names.len(), 2);
669
670 let loc0 = result.original_position_for(0, 0).unwrap();
671 assert_eq!(loc0.name, Some(0));
672 assert_eq!(result.name(0), "foo");
673
674 let loc1 = result.original_position_for(1, 0).unwrap();
675 assert_eq!(loc1.name, Some(1));
676 assert_eq!(result.name(1), "bar");
677 }
678
679 #[test]
680 fn concat_preserves_multi_line_maps() {
681 let a = SourceMap::from_json(
682 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;AACA;AACA"}"#,
683 )
684 .unwrap();
685
686 let mut builder = ConcatBuilder::new(None);
687 builder.add_map(&a, 5); let result = builder.build();
690 assert!(result.original_position_for(5, 0).is_some());
691 assert!(result.original_position_for(6, 0).is_some());
692 assert!(result.original_position_for(7, 0).is_some());
693 assert!(result.original_position_for(4, 0).is_none());
694 }
695
696 #[test]
697 fn concat_with_sources_content() {
698 let a = SourceMap::from_json(
699 r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":[],"mappings":"AAAA"}"#,
700 )
701 .unwrap();
702
703 let mut builder = ConcatBuilder::new(None);
704 builder.add_map(&a, 0);
705
706 let result = builder.build();
707 assert_eq!(result.sources_content, vec![Some("var a;".to_string())]);
708 }
709
710 #[test]
711 fn concat_empty_builder() {
712 let builder = ConcatBuilder::new(Some("empty.js".to_string()));
713 let result = builder.build();
714 assert_eq!(result.mapping_count(), 0);
715 assert_eq!(result.sources.len(), 0);
716 }
717
718 #[test]
721 fn remap_single_level() {
722 let outer = SourceMap::from_json(
727 r#"{"version":3,"sources":["intermediate.js","other.js"],"names":[],"mappings":"AAAA,KCAA;ADCA"}"#,
728 )
729 .unwrap();
730
731 let inner = SourceMap::from_json(
733 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AACA;AACA"}"#,
734 )
735 .unwrap();
736
737 let result = remap(&outer, |source| {
738 if source == "intermediate.js" {
739 Some(inner.clone())
740 } else {
741 None
742 }
743 });
744
745 assert!(result.sources.contains(&"original.js".to_string()));
746 assert!(result.sources.contains(&"other.js".to_string()));
748
749 let loc = result.original_position_for(0, 0).unwrap();
751 assert_eq!(result.source(loc.source), "original.js");
752 assert_eq!(loc.line, 1);
753 }
754
755 #[test]
756 fn remap_no_upstream_passthrough() {
757 let outer = SourceMap::from_json(
758 r#"{"version":3,"sources":["already-original.js"],"names":[],"mappings":"AAAA"}"#,
759 )
760 .unwrap();
761
762 let result = remap(&outer, |_| None);
764
765 assert_eq!(result.sources, vec!["already-original.js"]);
766 let loc = result.original_position_for(0, 0).unwrap();
767 assert_eq!(result.source(loc.source), "already-original.js");
768 assert_eq!(loc.line, 0);
769 assert_eq!(loc.column, 0);
770 }
771
772 #[test]
773 fn remap_partial_sources() {
774 let outer = SourceMap::from_json(
776 r#"{"version":3,"sources":["compiled.js","passthrough.js"],"names":[],"mappings":"AAAA,KCCA"}"#,
777 )
778 .unwrap();
779
780 let inner = SourceMap::from_json(
781 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":"AAAA"}"#,
782 )
783 .unwrap();
784
785 let result = remap(&outer, |source| {
786 if source == "compiled.js" {
787 Some(inner.clone())
788 } else {
789 None
790 }
791 });
792
793 assert!(result.sources.contains(&"original.ts".to_string()));
795 assert!(result.sources.contains(&"passthrough.js".to_string()));
796 }
797
798 #[test]
799 fn remap_preserves_names() {
800 let outer = SourceMap::from_json(
801 r#"{"version":3,"sources":["compiled.js"],"names":["myFunc"],"mappings":"AAAAA"}"#,
802 )
803 .unwrap();
804
805 let inner = SourceMap::from_json(
807 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":"AAAA"}"#,
808 )
809 .unwrap();
810
811 let result = remap(&outer, |_| Some(inner.clone()));
812
813 let loc = result.original_position_for(0, 0).unwrap();
814 assert!(loc.name.is_some());
815 assert_eq!(result.name(loc.name.unwrap()), "myFunc");
816 }
817
818 #[test]
819 fn remap_upstream_name_wins() {
820 let outer = SourceMap::from_json(
821 r#"{"version":3,"sources":["compiled.js"],"names":["outerName"],"mappings":"AAAAA"}"#,
822 )
823 .unwrap();
824
825 let inner = SourceMap::from_json(
827 r#"{"version":3,"sources":["original.ts"],"names":["innerName"],"mappings":"AAAAA"}"#,
828 )
829 .unwrap();
830
831 let result = remap(&outer, |_| Some(inner.clone()));
832
833 let loc = result.original_position_for(0, 0).unwrap();
834 assert!(loc.name.is_some());
835 assert_eq!(result.name(loc.name.unwrap()), "innerName");
836 }
837
838 #[test]
839 fn remap_sources_content_from_upstream() {
840 let outer = SourceMap::from_json(
841 r#"{"version":3,"sources":["compiled.js"],"names":[],"mappings":"AAAA"}"#,
842 )
843 .unwrap();
844
845 let inner = SourceMap::from_json(
846 r#"{"version":3,"sources":["original.ts"],"sourcesContent":["const x = 1;"],"names":[],"mappings":"AAAA"}"#,
847 )
848 .unwrap();
849
850 let result = remap(&outer, |_| Some(inner.clone()));
851
852 assert_eq!(
853 result.sources_content,
854 vec![Some("const x = 1;".to_string())]
855 );
856 }
857
858 #[test]
861 fn concat_updates_source_content_on_duplicate() {
862 let a = SourceMap::from_json(
864 r#"{"version":3,"sources":["shared.js"],"names":[],"mappings":"AAAA"}"#,
865 )
866 .unwrap();
867 let b = SourceMap::from_json(
868 r#"{"version":3,"sources":["shared.js"],"sourcesContent":["var x = 1;"],"names":[],"mappings":"AAAA"}"#,
869 )
870 .unwrap();
871
872 let mut builder = ConcatBuilder::new(None);
873 builder.add_map(&a, 0);
874 builder.add_map(&b, 1);
875
876 let result = builder.build();
877 assert_eq!(result.sources.len(), 1);
878 assert_eq!(result.sources_content, vec![Some("var x = 1;".to_string())]);
879 }
880
881 #[test]
882 fn concat_deduplicates_names() {
883 let a = SourceMap::from_json(
884 r#"{"version":3,"sources":["a.js"],"names":["sharedName"],"mappings":"AAAAA"}"#,
885 )
886 .unwrap();
887 let b = SourceMap::from_json(
888 r#"{"version":3,"sources":["b.js"],"names":["sharedName"],"mappings":"AAAAA"}"#,
889 )
890 .unwrap();
891
892 let mut builder = ConcatBuilder::new(None);
893 builder.add_map(&a, 0);
894 builder.add_map(&b, 1);
895
896 let result = builder.build();
897 assert_eq!(result.names.len(), 1);
899 assert_eq!(result.names[0], "sharedName");
900 }
901
902 #[test]
903 fn concat_with_ignore_list() {
904 let a = SourceMap::from_json(
905 r#"{"version":3,"sources":["vendor.js"],"names":[],"mappings":"AAAA","ignoreList":[0]}"#,
906 )
907 .unwrap();
908
909 let mut builder = ConcatBuilder::new(None);
910 builder.add_map(&a, 0);
911
912 let result = builder.build();
913 assert_eq!(result.ignore_list, vec![0]);
914 }
915
916 #[test]
917 fn concat_with_generated_only_mappings() {
918 let a = SourceMap::from_json(
920 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A,AAAA"}"#,
921 )
922 .unwrap();
923
924 let mut builder = ConcatBuilder::new(None);
925 builder.add_map(&a, 0);
926
927 let result = builder.build();
928 assert!(result.mapping_count() >= 1);
930 }
931
932 #[test]
933 fn remap_generated_only_passthrough() {
934 let outer = SourceMap::from_json(
939 r#"{"version":3,"sources":["a.js","other.js"],"names":[],"mappings":"A,AAAA,KCAA"}"#,
940 )
941 .unwrap();
942
943 let inner = SourceMap::from_json(
944 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AAAA"}"#,
945 )
946 .unwrap();
947
948 let result = remap(&outer, |source| {
949 if source == "a.js" {
950 Some(inner.clone())
951 } else {
952 None
953 }
954 });
955
956 assert!(result.mapping_count() >= 2);
958 assert!(result.sources.contains(&"original.js".to_string()));
959 assert!(result.sources.contains(&"other.js".to_string()));
960 }
961
962 #[test]
963 fn remap_no_upstream_mapping_with_name() {
964 let outer = SourceMap::from_json(
966 r#"{"version":3,"sources":["compiled.js"],"names":["myFunc"],"mappings":"AAAAA"}"#,
967 )
968 .unwrap();
969
970 let inner = SourceMap::from_json(
972 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":";;;;AAAA"}"#,
973 )
974 .unwrap();
975
976 let result = remap(&outer, |_| Some(inner.clone()));
977
978 let loc = result.original_position_for(0, 0).unwrap();
982 assert!(loc.name.is_some());
983 assert_eq!(result.name(loc.name.unwrap()), "myFunc");
984 }
985
986 #[test]
987 fn remap_no_upstream_with_sources_content_and_name() {
988 let outer = SourceMap::from_json(
989 r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":["fn1"],"mappings":"AAAAA"}"#,
990 )
991 .unwrap();
992
993 let result = remap(&outer, |_| None);
995
996 assert_eq!(result.sources, vec!["a.js"]);
997 assert_eq!(result.sources_content, vec![Some("var a;".to_string())]);
998 let loc = result.original_position_for(0, 0).unwrap();
999 assert!(loc.name.is_some());
1000 assert_eq!(result.name(loc.name.unwrap()), "fn1");
1001 }
1002
1003 #[test]
1004 fn remap_no_upstream_no_name() {
1005 let outer = SourceMap::from_json(
1006 r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":[],"mappings":"AAAA"}"#,
1007 )
1008 .unwrap();
1009
1010 let result = remap(&outer, |_| None);
1011 let loc = result.original_position_for(0, 0).unwrap();
1012 assert!(loc.name.is_none());
1013 }
1014
1015 #[test]
1016 fn remap_no_upstream_mapping_no_name() {
1017 let outer = SourceMap::from_json(
1020 r#"{"version":3,"sources":["compiled.js"],"names":[],"mappings":"AAAA"}"#,
1021 )
1022 .unwrap();
1023
1024 let inner = SourceMap::from_json(
1028 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":";;;;AAAA"}"#,
1029 )
1030 .unwrap();
1031
1032 let result = remap(&outer, |_| Some(inner.clone()));
1033
1034 let loc = result.original_position_for(0, 0).unwrap();
1037 assert_eq!(result.source(loc.source), "compiled.js");
1038 assert_eq!(loc.line, 0);
1039 assert_eq!(loc.column, 0);
1040 assert!(loc.name.is_none());
1041 }
1042
1043 #[test]
1044 fn remap_upstream_found_no_name() {
1045 let outer = SourceMap::from_json(
1053 r#"{"version":3,"sources":["intermediate.js"],"names":[],"mappings":"AAAA"}"#,
1054 )
1055 .unwrap();
1056
1057 let inner = SourceMap::from_json(
1059 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AAAA"}"#,
1060 )
1061 .unwrap();
1062
1063 let result = remap(&outer, |_| Some(inner.clone()));
1064
1065 assert_eq!(result.sources, vec!["original.js"]);
1066 let loc = result.original_position_for(0, 0).unwrap();
1067 assert_eq!(result.source(loc.source), "original.js");
1068 assert_eq!(loc.line, 0);
1069 assert_eq!(loc.column, 0);
1070 assert!(loc.name.is_none());
1072 assert!(result.names.is_empty());
1073 }
1074
1075 #[test]
1078 fn concat_preserves_range_mappings() {
1079 let a = SourceMap::from_json(
1080 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,CAAC","rangeMappings":"A"}"#,
1081 )
1082 .unwrap();
1083
1084 let mut builder = ConcatBuilder::new(None);
1085 builder.add_map(&a, 0);
1086
1087 let result = builder.build();
1088 assert!(result.has_range_mappings());
1089 let mappings = result.all_mappings();
1090 assert!(mappings[0].is_range_mapping);
1091 assert!(!mappings[1].is_range_mapping);
1092 }
1093
1094 #[test]
1095 fn remap_preserves_range_mappings_passthrough() {
1096 let outer = SourceMap::from_json(
1097 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","rangeMappings":"A"}"#,
1098 )
1099 .unwrap();
1100
1101 let result = remap(&outer, |_| None);
1103 assert!(result.has_range_mappings());
1104 let mappings = result.all_mappings();
1105 assert!(mappings[0].is_range_mapping);
1106 }
1107
1108 #[test]
1109 fn remap_preserves_range_through_upstream() {
1110 let outer = SourceMap::from_json(
1111 r#"{"version":3,"sources":["intermediate.js"],"names":[],"mappings":"AAAA","rangeMappings":"A"}"#,
1112 )
1113 .unwrap();
1114
1115 let inner = SourceMap::from_json(
1116 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AACA"}"#,
1117 )
1118 .unwrap();
1119
1120 let result = remap(&outer, |_| Some(inner.clone()));
1121 assert!(result.has_range_mappings());
1122 }
1123
1124 #[test]
1125 fn remap_non_range_stays_non_range() {
1126 let outer = SourceMap::from_json(
1127 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#,
1128 )
1129 .unwrap();
1130
1131 let result = remap(&outer, |_| None);
1132 assert!(!result.has_range_mappings());
1133 }
1134
1135 fn streaming_from_sm<F>(sm: &SourceMap, loader: F) -> SourceMap
1140 where
1141 F: Fn(&str) -> Option<SourceMap>,
1142 {
1143 let vlq = sm.encode_mappings();
1144 let iter = srcmap_sourcemap::MappingsIter::new(&vlq);
1145 remap_streaming(
1146 iter,
1147 &sm.sources,
1148 &sm.names,
1149 &sm.sources_content,
1150 &sm.ignore_list,
1151 sm.file.clone(),
1152 loader,
1153 )
1154 }
1155
1156 #[test]
1157 fn streaming_single_level() {
1158 let outer = SourceMap::from_json(
1159 r#"{"version":3,"sources":["intermediate.js","other.js"],"names":[],"mappings":"AAAA,KCAA;ADCA"}"#,
1160 )
1161 .unwrap();
1162
1163 let inner = SourceMap::from_json(
1164 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AACA;AACA"}"#,
1165 )
1166 .unwrap();
1167
1168 let result = streaming_from_sm(&outer, |source| {
1169 if source == "intermediate.js" {
1170 Some(inner.clone())
1171 } else {
1172 None
1173 }
1174 });
1175
1176 assert!(result.sources.contains(&"original.js".to_string()));
1177 assert!(result.sources.contains(&"other.js".to_string()));
1178
1179 let loc = result.original_position_for(0, 0).unwrap();
1180 assert_eq!(result.source(loc.source), "original.js");
1181 assert_eq!(loc.line, 1);
1182 }
1183
1184 #[test]
1185 fn streaming_no_upstream_passthrough() {
1186 let outer = SourceMap::from_json(
1187 r#"{"version":3,"sources":["already-original.js"],"names":[],"mappings":"AAAA"}"#,
1188 )
1189 .unwrap();
1190
1191 let result = streaming_from_sm(&outer, |_| None);
1192
1193 assert_eq!(result.sources, vec!["already-original.js"]);
1194 let loc = result.original_position_for(0, 0).unwrap();
1195 assert_eq!(result.source(loc.source), "already-original.js");
1196 assert_eq!(loc.line, 0);
1197 assert_eq!(loc.column, 0);
1198 }
1199
1200 #[test]
1201 fn streaming_preserves_names() {
1202 let outer = SourceMap::from_json(
1203 r#"{"version":3,"sources":["compiled.js"],"names":["myFunc"],"mappings":"AAAAA"}"#,
1204 )
1205 .unwrap();
1206
1207 let inner = SourceMap::from_json(
1208 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":"AAAA"}"#,
1209 )
1210 .unwrap();
1211
1212 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
1213
1214 let loc = result.original_position_for(0, 0).unwrap();
1215 assert!(loc.name.is_some());
1216 assert_eq!(result.name(loc.name.unwrap()), "myFunc");
1217 }
1218
1219 #[test]
1220 fn streaming_upstream_name_wins() {
1221 let outer = SourceMap::from_json(
1222 r#"{"version":3,"sources":["compiled.js"],"names":["outerName"],"mappings":"AAAAA"}"#,
1223 )
1224 .unwrap();
1225
1226 let inner = SourceMap::from_json(
1227 r#"{"version":3,"sources":["original.ts"],"names":["innerName"],"mappings":"AAAAA"}"#,
1228 )
1229 .unwrap();
1230
1231 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
1232
1233 let loc = result.original_position_for(0, 0).unwrap();
1234 assert!(loc.name.is_some());
1235 assert_eq!(result.name(loc.name.unwrap()), "innerName");
1236 }
1237
1238 #[test]
1239 fn streaming_sources_content_from_upstream() {
1240 let outer = SourceMap::from_json(
1241 r#"{"version":3,"sources":["compiled.js"],"names":[],"mappings":"AAAA"}"#,
1242 )
1243 .unwrap();
1244
1245 let inner = SourceMap::from_json(
1246 r#"{"version":3,"sources":["original.ts"],"sourcesContent":["const x = 1;"],"names":[],"mappings":"AAAA"}"#,
1247 )
1248 .unwrap();
1249
1250 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
1251
1252 assert_eq!(
1253 result.sources_content,
1254 vec![Some("const x = 1;".to_string())]
1255 );
1256 }
1257
1258 #[test]
1259 fn streaming_no_upstream_with_sources_content() {
1260 let outer = SourceMap::from_json(
1261 r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":["fn1"],"mappings":"AAAAA"}"#,
1262 )
1263 .unwrap();
1264
1265 let result = streaming_from_sm(&outer, |_| None);
1266
1267 assert_eq!(result.sources, vec!["a.js"]);
1268 assert_eq!(result.sources_content, vec![Some("var a;".to_string())]);
1269 let loc = result.original_position_for(0, 0).unwrap();
1270 assert!(loc.name.is_some());
1271 assert_eq!(result.name(loc.name.unwrap()), "fn1");
1272 }
1273
1274 #[test]
1275 fn streaming_generated_only_passthrough() {
1276 let outer = SourceMap::from_json(
1277 r#"{"version":3,"sources":["a.js","other.js"],"names":[],"mappings":"A,AAAA,KCAA"}"#,
1278 )
1279 .unwrap();
1280
1281 let inner = SourceMap::from_json(
1282 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AAAA"}"#,
1283 )
1284 .unwrap();
1285
1286 let result = streaming_from_sm(&outer, |source| {
1287 if source == "a.js" {
1288 Some(inner.clone())
1289 } else {
1290 None
1291 }
1292 });
1293
1294 assert!(result.mapping_count() >= 2);
1295 assert!(result.sources.contains(&"original.js".to_string()));
1296 assert!(result.sources.contains(&"other.js".to_string()));
1297 }
1298
1299 #[test]
1300 fn streaming_matches_remap() {
1301 let outer = SourceMap::from_json(
1303 r#"{"version":3,"sources":["intermediate.js","other.js"],"names":["foo"],"mappings":"AAAAA,KCAA;ADCA"}"#,
1304 )
1305 .unwrap();
1306
1307 let inner = SourceMap::from_json(
1308 r#"{"version":3,"sources":["original.js"],"sourcesContent":["// src"],"names":["bar"],"mappings":"AAAAA;AACA"}"#,
1309 )
1310 .unwrap();
1311
1312 let loader = |source: &str| -> Option<SourceMap> {
1313 if source == "intermediate.js" {
1314 Some(inner.clone())
1315 } else {
1316 None
1317 }
1318 };
1319
1320 let result_normal = remap(&outer, loader);
1321 let result_stream = streaming_from_sm(&outer, loader);
1322
1323 assert_eq!(result_normal.sources, result_stream.sources);
1324 assert_eq!(result_normal.names, result_stream.names);
1325 assert_eq!(result_normal.sources_content, result_stream.sources_content);
1326 assert_eq!(result_normal.mapping_count(), result_stream.mapping_count());
1327
1328 for m in result_normal.all_mappings() {
1330 let loc_n = result_normal.original_position_for(m.generated_line, m.generated_column);
1331 let loc_s = result_stream.original_position_for(m.generated_line, m.generated_column);
1332 assert_eq!(loc_n.is_some(), loc_s.is_some());
1333 if let (Some(ln), Some(ls)) = (loc_n, loc_s) {
1334 assert_eq!(
1335 result_normal.source(ln.source),
1336 result_stream.source(ls.source)
1337 );
1338 assert_eq!(ln.line, ls.line);
1339 assert_eq!(ln.column, ls.column);
1340 }
1341 }
1342 }
1343
1344 #[test]
1345 fn streaming_no_upstream_mapping_fallback() {
1346 let outer = SourceMap::from_json(
1347 r#"{"version":3,"sources":["compiled.js"],"names":["myFunc"],"mappings":"AAAAA"}"#,
1348 )
1349 .unwrap();
1350
1351 let inner = SourceMap::from_json(
1353 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":";;;;AAAA"}"#,
1354 )
1355 .unwrap();
1356
1357 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
1358
1359 let loc = result.original_position_for(0, 0).unwrap();
1360 assert!(loc.name.is_some());
1361 assert_eq!(result.name(loc.name.unwrap()), "myFunc");
1362 }
1363
1364 #[test]
1365 fn streaming_no_upstream_mapping_no_name() {
1366 let outer = SourceMap::from_json(
1367 r#"{"version":3,"sources":["compiled.js"],"names":[],"mappings":"AAAA"}"#,
1368 )
1369 .unwrap();
1370
1371 let inner = SourceMap::from_json(
1372 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":";;;;AAAA"}"#,
1373 )
1374 .unwrap();
1375
1376 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
1377
1378 let loc = result.original_position_for(0, 0).unwrap();
1379 assert_eq!(result.source(loc.source), "compiled.js");
1380 assert!(loc.name.is_none());
1381 }
1382}