1use srcmap_generator::{SourceMapGenerator, StreamingGenerator};
60use srcmap_sourcemap::SourceMap;
61use std::collections::HashSet;
62
63const NO_SOURCE: u32 = u32::MAX;
64const NO_NAME: u32 = u32::MAX;
65
66pub struct ConcatBuilder {
73 builder: SourceMapGenerator,
74}
75
76impl ConcatBuilder {
77 pub fn new(file: Option<String>) -> Self {
79 Self {
80 builder: SourceMapGenerator::new(file),
81 }
82 }
83
84 pub fn add_map(&mut self, sm: &SourceMap, line_offset: u32) {
89 let source_indices: Vec<u32> = sm
91 .sources
92 .iter()
93 .enumerate()
94 .map(|(i, s)| {
95 let idx = self.builder.add_source(s);
96 if let Some(Some(content)) = sm.sources_content.get(i) {
97 self.builder.set_source_content(idx, content.clone());
98 }
99 idx
100 })
101 .collect();
102
103 let name_indices: Vec<u32> = sm.names.iter().map(|n| self.builder.add_name(n)).collect();
105
106 for &ignored in &sm.ignore_list {
108 let global_idx = source_indices[ignored as usize];
109 self.builder.add_to_ignore_list(global_idx);
110 }
111
112 for m in sm.all_mappings() {
114 let gen_line = m.generated_line + line_offset;
115
116 if m.source == NO_SOURCE {
117 self.builder
118 .add_generated_mapping(gen_line, m.generated_column);
119 } else {
120 let src = source_indices[m.source as usize];
121 let has_name = m.name != NO_NAME;
122 match (has_name, m.is_range_mapping) {
123 (true, true) => self.builder.add_named_range_mapping(
124 gen_line,
125 m.generated_column,
126 src,
127 m.original_line,
128 m.original_column,
129 name_indices[m.name as usize],
130 ),
131 (true, false) => self.builder.add_named_mapping(
132 gen_line,
133 m.generated_column,
134 src,
135 m.original_line,
136 m.original_column,
137 name_indices[m.name as usize],
138 ),
139 (false, true) => self.builder.add_range_mapping(
140 gen_line,
141 m.generated_column,
142 src,
143 m.original_line,
144 m.original_column,
145 ),
146 (false, false) => self.builder.add_mapping(
147 gen_line,
148 m.generated_column,
149 src,
150 m.original_line,
151 m.original_column,
152 ),
153 }
154 }
155 }
156 }
157
158 pub fn to_json(&self) -> String {
160 self.builder.to_json()
161 }
162
163 pub fn build(&self) -> SourceMap {
165 self.builder.to_decoded_map()
166 }
167}
168
169struct UpstreamCache {
176 source_remap: Vec<Option<u32>>,
178 name_remap: Vec<Option<u32>>,
180}
181
182fn build_upstream_cache(upstream_sm: &SourceMap) -> UpstreamCache {
184 UpstreamCache {
185 source_remap: vec![None; upstream_sm.sources.len()],
186 name_remap: vec![None; upstream_sm.names.len()],
187 }
188}
189
190#[inline]
193fn resolve_upstream_source(
194 cache: &mut UpstreamCache,
195 upstream_sm: &SourceMap,
196 upstream_src: u32,
197 builder: &mut SourceMapGenerator,
198 ignored_sources: &mut HashSet<u32>,
199) -> u32 {
200 let si = upstream_src as usize;
201 if let Some(idx) = cache.source_remap[si] {
202 return idx;
203 }
204 let idx = builder.add_source(&upstream_sm.sources[si]);
205 if let Some(Some(content)) = upstream_sm.sources_content.get(si) {
206 builder.set_source_content(idx, content.clone());
207 }
208 if upstream_sm.ignore_list.contains(&upstream_src) && ignored_sources.insert(idx) {
209 builder.add_to_ignore_list(idx);
210 }
211 cache.source_remap[si] = Some(idx);
212 idx
213}
214
215#[inline]
218fn resolve_upstream_name(
219 cache: &mut UpstreamCache,
220 upstream_sm: &SourceMap,
221 upstream_name: u32,
222 builder: &mut SourceMapGenerator,
223) -> u32 {
224 let ni = upstream_name as usize;
225 if let Some(idx) = cache.name_remap[ni] {
226 return idx;
227 }
228 let idx = builder.add_name(&upstream_sm.names[ni]);
229 cache.name_remap[ni] = Some(idx);
230 idx
231}
232
233#[inline]
235fn resolve_upstream_source_streaming(
236 cache: &mut UpstreamCache,
237 upstream_sm: &SourceMap,
238 upstream_src: u32,
239 builder: &mut StreamingGenerator,
240 ignored_sources: &mut HashSet<u32>,
241) -> u32 {
242 let si = upstream_src as usize;
243 if let Some(idx) = cache.source_remap[si] {
244 return idx;
245 }
246 let idx = builder.add_source(&upstream_sm.sources[si]);
247 if let Some(Some(content)) = upstream_sm.sources_content.get(si) {
248 builder.set_source_content(idx, content.clone());
249 }
250 if upstream_sm.ignore_list.contains(&upstream_src) && ignored_sources.insert(idx) {
251 builder.add_to_ignore_list(idx);
252 }
253 cache.source_remap[si] = Some(idx);
254 idx
255}
256
257#[inline]
259fn resolve_upstream_name_streaming(
260 cache: &mut UpstreamCache,
261 upstream_sm: &SourceMap,
262 upstream_name: u32,
263 builder: &mut StreamingGenerator,
264) -> u32 {
265 let ni = upstream_name as usize;
266 if let Some(idx) = cache.name_remap[ni] {
267 return idx;
268 }
269 let idx = builder.add_name(&upstream_sm.names[ni]);
270 cache.name_remap[ni] = Some(idx);
271 idx
272}
273
274#[inline]
283fn lookup_upstream(upstream_sm: &SourceMap, line: u32, column: u32) -> Option<UpstreamLookup> {
284 let line_mappings = upstream_sm.mappings_for_line(line);
285 if line_mappings.is_empty() {
286 return fallback_to_full_lookup(upstream_sm, line, column);
287 }
288
289 let idx = match line_mappings.binary_search_by_key(&column, |m| m.generated_column) {
290 Ok(i) => i,
291 Err(0) => return fallback_to_full_lookup(upstream_sm, line, column),
292 Err(i) => i - 1,
293 };
294
295 let mapping = &line_mappings[idx];
296 if mapping.source == NO_SOURCE {
297 return None;
298 }
299
300 let original_column = if mapping.is_range_mapping && column >= mapping.generated_column {
301 mapping.original_column + (column - mapping.generated_column)
302 } else {
303 mapping.original_column
304 };
305
306 Some(UpstreamLookup {
307 source: mapping.source,
308 original_line: mapping.original_line,
309 original_column,
310 name: mapping.name,
311 })
312}
313
314struct UpstreamLookup {
318 source: u32,
319 original_line: u32,
320 original_column: u32,
321 name: u32,
322}
323
324fn fallback_to_full_lookup(
329 upstream_sm: &SourceMap,
330 line: u32,
331 column: u32,
332) -> Option<UpstreamLookup> {
333 let loc = upstream_sm.original_position_for(line, column)?;
334 Some(UpstreamLookup {
335 source: loc.source,
336 original_line: loc.line,
337 original_column: loc.column,
338 name: loc.name.unwrap_or(NO_NAME),
339 })
340}
341
342#[inline]
345#[allow(clippy::too_many_arguments)]
346fn emit_remapped_mapping(
347 builder: &mut SourceMapGenerator,
348 gen_line: u32,
349 gen_col: u32,
350 builder_src: u32,
351 orig_line: u32,
352 orig_col: u32,
353 builder_name: Option<u32>,
354 is_range: bool,
355) {
356 match (builder_name, is_range) {
357 (Some(n), true) => {
358 builder.add_named_range_mapping(gen_line, gen_col, builder_src, orig_line, orig_col, n);
359 }
360 (Some(n), false) => {
361 builder.add_named_mapping(gen_line, gen_col, builder_src, orig_line, orig_col, n);
362 }
363 (None, true) => {
364 builder.add_range_mapping(gen_line, gen_col, builder_src, orig_line, orig_col);
365 }
366 (None, false) => {
367 builder.add_mapping(gen_line, gen_col, builder_src, orig_line, orig_col);
368 }
369 }
370}
371
372#[inline]
374#[allow(clippy::too_many_arguments)]
375fn emit_remapped_mapping_streaming(
376 builder: &mut StreamingGenerator,
377 gen_line: u32,
378 gen_col: u32,
379 builder_src: u32,
380 orig_line: u32,
381 orig_col: u32,
382 builder_name: Option<u32>,
383 is_range: bool,
384) {
385 match (builder_name, is_range) {
386 (Some(n), true) => {
387 builder.add_named_range_mapping(gen_line, gen_col, builder_src, orig_line, orig_col, n);
388 }
389 (Some(n), false) => {
390 builder.add_named_mapping(gen_line, gen_col, builder_src, orig_line, orig_col, n);
391 }
392 (None, true) => {
393 builder.add_range_mapping(gen_line, gen_col, builder_src, orig_line, orig_col);
394 }
395 (None, false) => {
396 builder.add_mapping(gen_line, gen_col, builder_src, orig_line, orig_col);
397 }
398 }
399}
400
401enum SourceEntry {
404 Upstream {
406 map: Box<SourceMap>,
407 cache: UpstreamCache,
408 },
409 Passthrough { builder_src: u32 },
411 EmptySource,
414 Unloaded,
416}
417
418struct DedupeState {
421 last_gen_line: u32,
423 line_index: u32,
425 last_was_sourceless: bool,
427 last_source: Option<(u32, u32, u32, Option<u32>)>,
429}
430
431impl DedupeState {
432 fn new() -> Self {
433 Self {
434 last_gen_line: u32::MAX,
435 line_index: 0,
436 last_was_sourceless: false,
437 last_source: None,
438 }
439 }
440
441 fn skip_sourceless(&self, gen_line: u32) -> bool {
444 if gen_line != self.last_gen_line {
445 return true;
447 }
448 self.last_was_sourceless
450 }
451
452 fn skip_source(
455 &self,
456 gen_line: u32,
457 source: u32,
458 orig_line: u32,
459 orig_col: u32,
460 name: Option<u32>,
461 ) -> bool {
462 if gen_line != self.last_gen_line {
463 return false;
465 }
466 if self.last_was_sourceless {
467 return false;
469 }
470 self.last_source == Some((source, orig_line, orig_col, name))
472 }
473
474 fn record_sourceless(&mut self, gen_line: u32) {
476 if gen_line != self.last_gen_line {
477 self.last_gen_line = gen_line;
478 self.line_index = 0;
479 self.last_source = None;
480 }
481 self.line_index += 1;
482 self.last_was_sourceless = true;
483 }
484
485 fn record_source(
487 &mut self,
488 gen_line: u32,
489 source: u32,
490 orig_line: u32,
491 orig_col: u32,
492 name: Option<u32>,
493 ) {
494 if gen_line != self.last_gen_line {
495 self.last_gen_line = gen_line;
496 self.line_index = 0;
497 }
498 self.line_index += 1;
499 self.last_was_sourceless = false;
500 self.last_source = Some((source, orig_line, orig_col, name));
501 }
502}
503
504pub fn remap<F>(outer: &SourceMap, loader: F) -> SourceMap
521where
522 F: Fn(&str) -> Option<SourceMap>,
523{
524 let mapping_count = outer.mapping_count();
525 let source_count = outer.sources.len();
526 let mut builder = SourceMapGenerator::with_capacity(outer.file.clone(), mapping_count);
527 builder.set_assume_sorted(true);
529
530 let mut source_entries: Vec<SourceEntry> =
532 (0..source_count).map(|_| SourceEntry::Unloaded).collect();
533
534 let mut ignored_sources: HashSet<u32> = HashSet::new();
535
536 let mut outer_name_remap: Vec<Option<u32>> = vec![None; outer.names.len()];
538
539 let outer_ignore_set: HashSet<u32> = outer.ignore_list.iter().copied().collect();
541
542 let mut dedup = DedupeState::new();
543
544 for m in outer.all_mappings() {
545 if m.source == NO_SOURCE {
546 if !dedup.skip_sourceless(m.generated_line) {
547 builder.add_generated_mapping(m.generated_line, m.generated_column);
548 }
549 dedup.record_sourceless(m.generated_line);
550 continue;
551 }
552
553 let si = m.source as usize;
554
555 if matches!(source_entries[si], SourceEntry::Unloaded) {
557 let source_name = outer.source(m.source);
558 if source_name.is_empty() {
561 source_entries[si] = SourceEntry::EmptySource;
562 } else {
563 match loader(source_name) {
564 Some(upstream_sm) => {
565 let cache = build_upstream_cache(&upstream_sm);
566 source_entries[si] = SourceEntry::Upstream {
567 map: Box::new(upstream_sm),
568 cache,
569 };
570 }
571 None => {
572 let idx = builder.add_source(source_name);
573 if let Some(Some(content)) = outer.sources_content.get(si) {
574 builder.set_source_content(idx, content.clone());
575 }
576 if outer_ignore_set.contains(&m.source) && ignored_sources.insert(idx) {
577 builder.add_to_ignore_list(idx);
578 }
579 source_entries[si] = SourceEntry::Passthrough { builder_src: idx };
580 }
581 }
582 }
583 }
584
585 match &mut source_entries[si] {
586 SourceEntry::Upstream { map, cache } => {
587 match lookup_upstream(map, m.original_line, m.original_column) {
588 Some(upstream_m) => {
589 let builder_src = resolve_upstream_source(
590 cache,
591 map,
592 upstream_m.source,
593 &mut builder,
594 &mut ignored_sources,
595 );
596
597 let builder_name = if upstream_m.name != NO_NAME {
599 Some(resolve_upstream_name(
600 cache,
601 map,
602 upstream_m.name,
603 &mut builder,
604 ))
605 } else if m.name != NO_NAME {
606 let name_idx = *outer_name_remap[m.name as usize]
607 .get_or_insert_with(|| builder.add_name(outer.name(m.name)));
608 Some(name_idx)
609 } else {
610 None
611 };
612
613 if !dedup.skip_source(
614 m.generated_line,
615 builder_src,
616 upstream_m.original_line,
617 upstream_m.original_column,
618 builder_name,
619 ) {
620 emit_remapped_mapping(
621 &mut builder,
622 m.generated_line,
623 m.generated_column,
624 builder_src,
625 upstream_m.original_line,
626 upstream_m.original_column,
627 builder_name,
628 m.is_range_mapping,
629 );
630 }
631 dedup.record_source(
632 m.generated_line,
633 builder_src,
634 upstream_m.original_line,
635 upstream_m.original_column,
636 builder_name,
637 );
638 }
639 None => {
640 }
643 }
644 }
645 SourceEntry::Passthrough { builder_src } => {
646 let builder_src = *builder_src;
647
648 let builder_name = if m.name != NO_NAME {
649 Some(
650 *outer_name_remap[m.name as usize]
651 .get_or_insert_with(|| builder.add_name(outer.name(m.name))),
652 )
653 } else {
654 None
655 };
656
657 if !dedup.skip_source(
658 m.generated_line,
659 builder_src,
660 m.original_line,
661 m.original_column,
662 builder_name,
663 ) {
664 emit_remapped_mapping(
665 &mut builder,
666 m.generated_line,
667 m.generated_column,
668 builder_src,
669 m.original_line,
670 m.original_column,
671 builder_name,
672 m.is_range_mapping,
673 );
674 }
675 dedup.record_source(
676 m.generated_line,
677 builder_src,
678 m.original_line,
679 m.original_column,
680 builder_name,
681 );
682 }
683 SourceEntry::EmptySource => {
684 if !dedup.skip_sourceless(m.generated_line) {
686 builder.add_generated_mapping(m.generated_line, m.generated_column);
687 }
688 dedup.record_sourceless(m.generated_line);
689 }
690 SourceEntry::Unloaded => unreachable!(),
691 }
692 }
693
694 builder.to_decoded_map()
695}
696
697pub fn remap_chain(maps: &[&SourceMap]) -> Option<SourceMap> {
728 if maps.is_empty() {
729 return None;
730 }
731 if maps.len() == 1 {
732 return Some(maps[0].clone());
733 }
734
735 let mut current = compose_pair(maps[maps.len() - 2], maps[maps.len() - 1]);
747
748 for i in (0..maps.len() - 2).rev() {
750 current = compose_pair(maps[i], ¤t);
751 }
752
753 Some(current)
754}
755
756fn compose_pair(outer: &SourceMap, inner: &SourceMap) -> SourceMap {
759 remap(outer, |_source| Some(inner.clone()))
760}
761
762enum StreamingSourceEntry {
764 Upstream {
766 map: Box<SourceMap>,
767 cache: UpstreamCache,
768 },
769 Passthrough { builder_src: u32 },
771 EmptySource,
773 Unloaded,
775}
776
777pub fn remap_streaming<'a, F>(
790 mappings_iter: srcmap_sourcemap::MappingsIter<'a>,
791 sources: &[String],
792 names: &[String],
793 sources_content: &[Option<String>],
794 ignore_list: &[u32],
795 file: Option<String>,
796 loader: F,
797) -> SourceMap
798where
799 F: Fn(&str) -> Option<SourceMap>,
800{
801 let mut builder = StreamingGenerator::with_capacity(file, 4096);
802
803 let mut source_entries: Vec<StreamingSourceEntry> = (0..sources.len())
805 .map(|_| StreamingSourceEntry::Unloaded)
806 .collect();
807
808 let mut ignored_sources: HashSet<u32> = HashSet::new();
809
810 let mut outer_name_remap: Vec<Option<u32>> = vec![None; names.len()];
812
813 let outer_ignore_set: HashSet<u32> = ignore_list.iter().copied().collect();
815
816 let mut dedup = DedupeState::new();
817
818 for item in mappings_iter {
819 let m = match item {
820 Ok(m) => m,
821 Err(_) => continue,
822 };
823
824 if m.source == NO_SOURCE {
825 if !dedup.skip_sourceless(m.generated_line) {
826 builder.add_generated_mapping(m.generated_line, m.generated_column);
827 }
828 dedup.record_sourceless(m.generated_line);
829 continue;
830 }
831
832 let si = m.source as usize;
833 if si >= sources.len() {
834 continue;
835 }
836
837 if matches!(source_entries[si], StreamingSourceEntry::Unloaded) {
839 let source_name = &sources[si];
840 if source_name.is_empty() {
841 source_entries[si] = StreamingSourceEntry::EmptySource;
842 } else {
843 match loader(source_name) {
844 Some(upstream_sm) => {
845 let cache = build_upstream_cache(&upstream_sm);
846 source_entries[si] = StreamingSourceEntry::Upstream {
847 map: Box::new(upstream_sm),
848 cache,
849 };
850 }
851 None => {
852 let idx = builder.add_source(source_name);
853 if let Some(Some(content)) = sources_content.get(si) {
854 builder.set_source_content(idx, content.clone());
855 }
856 if outer_ignore_set.contains(&m.source) && ignored_sources.insert(idx) {
857 builder.add_to_ignore_list(idx);
858 }
859 source_entries[si] = StreamingSourceEntry::Passthrough { builder_src: idx };
860 }
861 }
862 }
863 }
864
865 match &mut source_entries[si] {
866 StreamingSourceEntry::Upstream { map, cache } => {
867 match lookup_upstream(map, m.original_line, m.original_column) {
868 Some(upstream_m) => {
869 let builder_src = resolve_upstream_source_streaming(
870 cache,
871 map,
872 upstream_m.source,
873 &mut builder,
874 &mut ignored_sources,
875 );
876
877 let builder_name = if upstream_m.name != NO_NAME {
878 Some(resolve_upstream_name_streaming(
879 cache,
880 map,
881 upstream_m.name,
882 &mut builder,
883 ))
884 } else {
885 resolve_outer_name(&mut outer_name_remap, m.name, names, &mut builder)
886 };
887
888 if !dedup.skip_source(
889 m.generated_line,
890 builder_src,
891 upstream_m.original_line,
892 upstream_m.original_column,
893 builder_name,
894 ) {
895 emit_remapped_mapping_streaming(
896 &mut builder,
897 m.generated_line,
898 m.generated_column,
899 builder_src,
900 upstream_m.original_line,
901 upstream_m.original_column,
902 builder_name,
903 m.is_range_mapping,
904 );
905 }
906 dedup.record_source(
907 m.generated_line,
908 builder_src,
909 upstream_m.original_line,
910 upstream_m.original_column,
911 builder_name,
912 );
913 }
914 None => {
915 }
918 }
919 }
920 StreamingSourceEntry::Passthrough { builder_src } => {
921 let builder_src = *builder_src;
922
923 let builder_name =
924 resolve_outer_name(&mut outer_name_remap, m.name, names, &mut builder);
925
926 if !dedup.skip_source(
927 m.generated_line,
928 builder_src,
929 m.original_line,
930 m.original_column,
931 builder_name,
932 ) {
933 emit_remapped_mapping_streaming(
934 &mut builder,
935 m.generated_line,
936 m.generated_column,
937 builder_src,
938 m.original_line,
939 m.original_column,
940 builder_name,
941 m.is_range_mapping,
942 );
943 }
944 dedup.record_source(
945 m.generated_line,
946 builder_src,
947 m.original_line,
948 m.original_column,
949 builder_name,
950 );
951 }
952 StreamingSourceEntry::EmptySource => {
953 if !dedup.skip_sourceless(m.generated_line) {
954 builder.add_generated_mapping(m.generated_line, m.generated_column);
955 }
956 dedup.record_sourceless(m.generated_line);
957 }
958 StreamingSourceEntry::Unloaded => unreachable!(),
959 }
960 }
961
962 builder
963 .to_decoded_map()
964 .expect("streaming VLQ should be valid")
965}
966
967#[inline]
969fn resolve_outer_name(
970 outer_name_remap: &mut [Option<u32>],
971 name_idx: u32,
972 names: &[String],
973 builder: &mut StreamingGenerator,
974) -> Option<u32> {
975 if name_idx == NO_NAME {
976 return None;
977 }
978 let slot = outer_name_remap.get_mut(name_idx as usize)?;
979 if let Some(idx) = *slot {
980 return Some(idx);
981 }
982 let outer_name = names.get(name_idx as usize)?;
983 let idx = builder.add_name(outer_name);
984 *slot = Some(idx);
985 Some(idx)
986}
987
988#[cfg(test)]
991mod tests {
992 use super::*;
993
994 #[test]
997 fn concat_two_simple_maps() {
998 let a = SourceMap::from_json(
999 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#,
1000 )
1001 .unwrap();
1002 let b = SourceMap::from_json(
1003 r#"{"version":3,"sources":["b.js"],"names":[],"mappings":"AAAA"}"#,
1004 )
1005 .unwrap();
1006
1007 let mut builder = ConcatBuilder::new(Some("bundle.js".to_string()));
1008 builder.add_map(&a, 0);
1009 builder.add_map(&b, 1);
1010
1011 let result = builder.build();
1012 assert_eq!(result.sources, vec!["a.js", "b.js"]);
1013 assert_eq!(result.mapping_count(), 2);
1014
1015 let loc0 = result.original_position_for(0, 0).unwrap();
1016 assert_eq!(result.source(loc0.source), "a.js");
1017
1018 let loc1 = result.original_position_for(1, 0).unwrap();
1019 assert_eq!(result.source(loc1.source), "b.js");
1020 }
1021
1022 #[test]
1023 fn concat_deduplicates_sources() {
1024 let a = SourceMap::from_json(
1025 r#"{"version":3,"sources":["shared.js"],"names":[],"mappings":"AAAA"}"#,
1026 )
1027 .unwrap();
1028 let b = SourceMap::from_json(
1029 r#"{"version":3,"sources":["shared.js"],"names":[],"mappings":"AAAA"}"#,
1030 )
1031 .unwrap();
1032
1033 let mut builder = ConcatBuilder::new(None);
1034 builder.add_map(&a, 0);
1035 builder.add_map(&b, 10);
1036
1037 let result = builder.build();
1038 assert_eq!(result.sources.len(), 1);
1039 assert_eq!(result.sources[0], "shared.js");
1040 }
1041
1042 #[test]
1043 fn concat_with_names() {
1044 let a = SourceMap::from_json(
1045 r#"{"version":3,"sources":["a.js"],"names":["foo"],"mappings":"AAAAA"}"#,
1046 )
1047 .unwrap();
1048 let b = SourceMap::from_json(
1049 r#"{"version":3,"sources":["b.js"],"names":["bar"],"mappings":"AAAAA"}"#,
1050 )
1051 .unwrap();
1052
1053 let mut builder = ConcatBuilder::new(None);
1054 builder.add_map(&a, 0);
1055 builder.add_map(&b, 1);
1056
1057 let result = builder.build();
1058 assert_eq!(result.names.len(), 2);
1059
1060 let loc0 = result.original_position_for(0, 0).unwrap();
1061 assert_eq!(loc0.name, Some(0));
1062 assert_eq!(result.name(0), "foo");
1063
1064 let loc1 = result.original_position_for(1, 0).unwrap();
1065 assert_eq!(loc1.name, Some(1));
1066 assert_eq!(result.name(1), "bar");
1067 }
1068
1069 #[test]
1070 fn concat_preserves_multi_line_maps() {
1071 let a = SourceMap::from_json(
1072 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;AACA;AACA"}"#,
1073 )
1074 .unwrap();
1075
1076 let mut builder = ConcatBuilder::new(None);
1077 builder.add_map(&a, 5); let result = builder.build();
1080 assert!(result.original_position_for(5, 0).is_some());
1081 assert!(result.original_position_for(6, 0).is_some());
1082 assert!(result.original_position_for(7, 0).is_some());
1083 assert!(result.original_position_for(4, 0).is_none());
1084 }
1085
1086 #[test]
1087 fn concat_with_sources_content() {
1088 let a = SourceMap::from_json(
1089 r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":[],"mappings":"AAAA"}"#,
1090 )
1091 .unwrap();
1092
1093 let mut builder = ConcatBuilder::new(None);
1094 builder.add_map(&a, 0);
1095
1096 let result = builder.build();
1097 assert_eq!(result.sources_content, vec![Some("var a;".to_string())]);
1098 }
1099
1100 #[test]
1101 fn concat_empty_builder() {
1102 let builder = ConcatBuilder::new(Some("empty.js".to_string()));
1103 let result = builder.build();
1104 assert_eq!(result.mapping_count(), 0);
1105 assert_eq!(result.sources.len(), 0);
1106 }
1107
1108 #[test]
1111 fn remap_single_level() {
1112 let outer = SourceMap::from_json(
1117 r#"{"version":3,"sources":["intermediate.js","other.js"],"names":[],"mappings":"AAAA,KCAA;ADCA"}"#,
1118 )
1119 .unwrap();
1120
1121 let inner = SourceMap::from_json(
1123 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AACA;AACA"}"#,
1124 )
1125 .unwrap();
1126
1127 let result = remap(&outer, |source| {
1128 if source == "intermediate.js" {
1129 Some(inner.clone())
1130 } else {
1131 None
1132 }
1133 });
1134
1135 assert!(result.sources.contains(&"original.js".to_string()));
1136 assert!(result.sources.contains(&"other.js".to_string()));
1138
1139 let loc = result.original_position_for(0, 0).unwrap();
1141 assert_eq!(result.source(loc.source), "original.js");
1142 assert_eq!(loc.line, 1);
1143 }
1144
1145 #[test]
1146 fn remap_no_upstream_passthrough() {
1147 let outer = SourceMap::from_json(
1148 r#"{"version":3,"sources":["already-original.js"],"names":[],"mappings":"AAAA"}"#,
1149 )
1150 .unwrap();
1151
1152 let result = remap(&outer, |_| None);
1154
1155 assert_eq!(result.sources, vec!["already-original.js"]);
1156 let loc = result.original_position_for(0, 0).unwrap();
1157 assert_eq!(result.source(loc.source), "already-original.js");
1158 assert_eq!(loc.line, 0);
1159 assert_eq!(loc.column, 0);
1160 }
1161
1162 #[test]
1163 fn remap_partial_sources() {
1164 let outer = SourceMap::from_json(
1166 r#"{"version":3,"sources":["compiled.js","passthrough.js"],"names":[],"mappings":"AAAA,KCCA"}"#,
1167 )
1168 .unwrap();
1169
1170 let inner = SourceMap::from_json(
1171 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":"AAAA"}"#,
1172 )
1173 .unwrap();
1174
1175 let result = remap(&outer, |source| {
1176 if source == "compiled.js" {
1177 Some(inner.clone())
1178 } else {
1179 None
1180 }
1181 });
1182
1183 assert!(result.sources.contains(&"original.ts".to_string()));
1185 assert!(result.sources.contains(&"passthrough.js".to_string()));
1186 }
1187
1188 #[test]
1189 fn remap_preserves_names() {
1190 let outer = SourceMap::from_json(
1191 r#"{"version":3,"sources":["compiled.js"],"names":["myFunc"],"mappings":"AAAAA"}"#,
1192 )
1193 .unwrap();
1194
1195 let inner = SourceMap::from_json(
1197 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":"AAAA"}"#,
1198 )
1199 .unwrap();
1200
1201 let result = remap(&outer, |_| Some(inner.clone()));
1202
1203 let loc = result.original_position_for(0, 0).unwrap();
1204 assert!(loc.name.is_some());
1205 assert_eq!(result.name(loc.name.unwrap()), "myFunc");
1206 }
1207
1208 #[test]
1209 fn remap_upstream_name_wins() {
1210 let outer = SourceMap::from_json(
1211 r#"{"version":3,"sources":["compiled.js"],"names":["outerName"],"mappings":"AAAAA"}"#,
1212 )
1213 .unwrap();
1214
1215 let inner = SourceMap::from_json(
1217 r#"{"version":3,"sources":["original.ts"],"names":["innerName"],"mappings":"AAAAA"}"#,
1218 )
1219 .unwrap();
1220
1221 let result = remap(&outer, |_| Some(inner.clone()));
1222
1223 let loc = result.original_position_for(0, 0).unwrap();
1224 assert!(loc.name.is_some());
1225 assert_eq!(result.name(loc.name.unwrap()), "innerName");
1226 }
1227
1228 #[test]
1229 fn remap_sources_content_from_upstream() {
1230 let outer = SourceMap::from_json(
1231 r#"{"version":3,"sources":["compiled.js"],"names":[],"mappings":"AAAA"}"#,
1232 )
1233 .unwrap();
1234
1235 let inner = SourceMap::from_json(
1236 r#"{"version":3,"sources":["original.ts"],"sourcesContent":["const x = 1;"],"names":[],"mappings":"AAAA"}"#,
1237 )
1238 .unwrap();
1239
1240 let result = remap(&outer, |_| Some(inner.clone()));
1241
1242 assert_eq!(
1243 result.sources_content,
1244 vec![Some("const x = 1;".to_string())]
1245 );
1246 }
1247
1248 #[test]
1251 fn concat_updates_source_content_on_duplicate() {
1252 let a = SourceMap::from_json(
1254 r#"{"version":3,"sources":["shared.js"],"names":[],"mappings":"AAAA"}"#,
1255 )
1256 .unwrap();
1257 let b = SourceMap::from_json(
1258 r#"{"version":3,"sources":["shared.js"],"sourcesContent":["var x = 1;"],"names":[],"mappings":"AAAA"}"#,
1259 )
1260 .unwrap();
1261
1262 let mut builder = ConcatBuilder::new(None);
1263 builder.add_map(&a, 0);
1264 builder.add_map(&b, 1);
1265
1266 let result = builder.build();
1267 assert_eq!(result.sources.len(), 1);
1268 assert_eq!(result.sources_content, vec![Some("var x = 1;".to_string())]);
1269 }
1270
1271 #[test]
1272 fn concat_deduplicates_names() {
1273 let a = SourceMap::from_json(
1274 r#"{"version":3,"sources":["a.js"],"names":["sharedName"],"mappings":"AAAAA"}"#,
1275 )
1276 .unwrap();
1277 let b = SourceMap::from_json(
1278 r#"{"version":3,"sources":["b.js"],"names":["sharedName"],"mappings":"AAAAA"}"#,
1279 )
1280 .unwrap();
1281
1282 let mut builder = ConcatBuilder::new(None);
1283 builder.add_map(&a, 0);
1284 builder.add_map(&b, 1);
1285
1286 let result = builder.build();
1287 assert_eq!(result.names.len(), 1);
1289 assert_eq!(result.names[0], "sharedName");
1290 }
1291
1292 #[test]
1293 fn concat_with_ignore_list() {
1294 let a = SourceMap::from_json(
1295 r#"{"version":3,"sources":["vendor.js"],"names":[],"mappings":"AAAA","ignoreList":[0]}"#,
1296 )
1297 .unwrap();
1298
1299 let mut builder = ConcatBuilder::new(None);
1300 builder.add_map(&a, 0);
1301
1302 let result = builder.build();
1303 assert_eq!(result.ignore_list, vec![0]);
1304 }
1305
1306 #[test]
1307 fn concat_with_generated_only_mappings() {
1308 let a = SourceMap::from_json(
1310 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A,AAAA"}"#,
1311 )
1312 .unwrap();
1313
1314 let mut builder = ConcatBuilder::new(None);
1315 builder.add_map(&a, 0);
1316
1317 let result = builder.build();
1318 assert!(result.mapping_count() >= 1);
1320 }
1321
1322 #[test]
1323 fn remap_generated_only_passthrough() {
1324 let outer = SourceMap::from_json(
1329 r#"{"version":3,"sources":["a.js","other.js"],"names":[],"mappings":"A,AAAA,KCAA"}"#,
1330 )
1331 .unwrap();
1332
1333 let inner = SourceMap::from_json(
1334 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AAAA"}"#,
1335 )
1336 .unwrap();
1337
1338 let result = remap(&outer, |source| {
1339 if source == "a.js" {
1340 Some(inner.clone())
1341 } else {
1342 None
1343 }
1344 });
1345
1346 assert!(result.mapping_count() >= 2);
1348 assert!(result.sources.contains(&"original.js".to_string()));
1349 assert!(result.sources.contains(&"other.js".to_string()));
1350 }
1351
1352 #[test]
1353 fn remap_no_upstream_mapping_with_name() {
1354 let outer = SourceMap::from_json(
1356 r#"{"version":3,"sources":["compiled.js"],"names":["myFunc"],"mappings":"AAAAA"}"#,
1357 )
1358 .unwrap();
1359
1360 let inner = SourceMap::from_json(
1362 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":";;;;AAAA"}"#,
1363 )
1364 .unwrap();
1365
1366 let result = remap(&outer, |_| Some(inner.clone()));
1367
1368 let loc = result.original_position_for(0, 0);
1372 assert!(loc.is_none());
1373 }
1374
1375 #[test]
1376 fn remap_no_upstream_with_sources_content_and_name() {
1377 let outer = SourceMap::from_json(
1378 r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":["fn1"],"mappings":"AAAAA"}"#,
1379 )
1380 .unwrap();
1381
1382 let result = remap(&outer, |_| None);
1384
1385 assert_eq!(result.sources, vec!["a.js"]);
1386 assert_eq!(result.sources_content, vec![Some("var a;".to_string())]);
1387 let loc = result.original_position_for(0, 0).unwrap();
1388 assert!(loc.name.is_some());
1389 assert_eq!(result.name(loc.name.unwrap()), "fn1");
1390 }
1391
1392 #[test]
1393 fn remap_no_upstream_no_name() {
1394 let outer = SourceMap::from_json(
1395 r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":[],"mappings":"AAAA"}"#,
1396 )
1397 .unwrap();
1398
1399 let result = remap(&outer, |_| None);
1400 let loc = result.original_position_for(0, 0).unwrap();
1401 assert!(loc.name.is_none());
1402 }
1403
1404 #[test]
1405 fn remap_no_upstream_mapping_no_name() {
1406 let outer = SourceMap::from_json(
1409 r#"{"version":3,"sources":["compiled.js"],"names":[],"mappings":"AAAA"}"#,
1410 )
1411 .unwrap();
1412
1413 let inner = SourceMap::from_json(
1416 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":";;;;AAAA"}"#,
1417 )
1418 .unwrap();
1419
1420 let result = remap(&outer, |_| Some(inner.clone()));
1421
1422 let loc = result.original_position_for(0, 0);
1424 assert!(loc.is_none());
1425 }
1426
1427 #[test]
1428 fn remap_upstream_found_no_name() {
1429 let outer = SourceMap::from_json(
1437 r#"{"version":3,"sources":["intermediate.js"],"names":[],"mappings":"AAAA"}"#,
1438 )
1439 .unwrap();
1440
1441 let inner = SourceMap::from_json(
1443 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AAAA"}"#,
1444 )
1445 .unwrap();
1446
1447 let result = remap(&outer, |_| Some(inner.clone()));
1448
1449 assert_eq!(result.sources, vec!["original.js"]);
1450 let loc = result.original_position_for(0, 0).unwrap();
1451 assert_eq!(result.source(loc.source), "original.js");
1452 assert_eq!(loc.line, 0);
1453 assert_eq!(loc.column, 0);
1454 assert!(loc.name.is_none());
1456 assert!(result.names.is_empty());
1457 }
1458
1459 #[test]
1462 fn concat_preserves_range_mappings() {
1463 let a = SourceMap::from_json(
1464 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,CAAC","rangeMappings":"A"}"#,
1465 )
1466 .unwrap();
1467
1468 let mut builder = ConcatBuilder::new(None);
1469 builder.add_map(&a, 0);
1470
1471 let result = builder.build();
1472 assert!(result.has_range_mappings());
1473 let mappings = result.all_mappings();
1474 assert!(mappings[0].is_range_mapping);
1475 assert!(!mappings[1].is_range_mapping);
1476 }
1477
1478 #[test]
1479 fn remap_preserves_range_mappings_passthrough() {
1480 let outer = SourceMap::from_json(
1481 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","rangeMappings":"A"}"#,
1482 )
1483 .unwrap();
1484
1485 let result = remap(&outer, |_| None);
1487 assert!(result.has_range_mappings());
1488 let mappings = result.all_mappings();
1489 assert!(mappings[0].is_range_mapping);
1490 }
1491
1492 #[test]
1493 fn remap_preserves_range_through_upstream() {
1494 let outer = SourceMap::from_json(
1495 r#"{"version":3,"sources":["intermediate.js"],"names":[],"mappings":"AAAA","rangeMappings":"A"}"#,
1496 )
1497 .unwrap();
1498
1499 let inner = SourceMap::from_json(
1500 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AACA"}"#,
1501 )
1502 .unwrap();
1503
1504 let result = remap(&outer, |_| Some(inner.clone()));
1505 assert!(result.has_range_mappings());
1506 }
1507
1508 #[test]
1509 fn remap_non_range_stays_non_range() {
1510 let outer = SourceMap::from_json(
1511 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#,
1512 )
1513 .unwrap();
1514
1515 let result = remap(&outer, |_| None);
1516 assert!(!result.has_range_mappings());
1517 }
1518
1519 fn streaming_from_sm<F>(sm: &SourceMap, loader: F) -> SourceMap
1524 where
1525 F: Fn(&str) -> Option<SourceMap>,
1526 {
1527 let vlq = sm.encode_mappings();
1528 let iter = srcmap_sourcemap::MappingsIter::new(&vlq);
1529 remap_streaming(
1530 iter,
1531 &sm.sources,
1532 &sm.names,
1533 &sm.sources_content,
1534 &sm.ignore_list,
1535 sm.file.clone(),
1536 loader,
1537 )
1538 }
1539
1540 #[test]
1541 fn streaming_single_level() {
1542 let outer = SourceMap::from_json(
1543 r#"{"version":3,"sources":["intermediate.js","other.js"],"names":[],"mappings":"AAAA,KCAA;ADCA"}"#,
1544 )
1545 .unwrap();
1546
1547 let inner = SourceMap::from_json(
1548 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AACA;AACA"}"#,
1549 )
1550 .unwrap();
1551
1552 let result = streaming_from_sm(&outer, |source| {
1553 if source == "intermediate.js" {
1554 Some(inner.clone())
1555 } else {
1556 None
1557 }
1558 });
1559
1560 assert!(result.sources.contains(&"original.js".to_string()));
1561 assert!(result.sources.contains(&"other.js".to_string()));
1562
1563 let loc = result.original_position_for(0, 0).unwrap();
1564 assert_eq!(result.source(loc.source), "original.js");
1565 assert_eq!(loc.line, 1);
1566 }
1567
1568 #[test]
1569 fn streaming_no_upstream_passthrough() {
1570 let outer = SourceMap::from_json(
1571 r#"{"version":3,"sources":["already-original.js"],"names":[],"mappings":"AAAA"}"#,
1572 )
1573 .unwrap();
1574
1575 let result = streaming_from_sm(&outer, |_| None);
1576
1577 assert_eq!(result.sources, vec!["already-original.js"]);
1578 let loc = result.original_position_for(0, 0).unwrap();
1579 assert_eq!(result.source(loc.source), "already-original.js");
1580 assert_eq!(loc.line, 0);
1581 assert_eq!(loc.column, 0);
1582 }
1583
1584 #[test]
1585 fn streaming_preserves_names() {
1586 let outer = SourceMap::from_json(
1587 r#"{"version":3,"sources":["compiled.js"],"names":["myFunc"],"mappings":"AAAAA"}"#,
1588 )
1589 .unwrap();
1590
1591 let inner = SourceMap::from_json(
1592 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":"AAAA"}"#,
1593 )
1594 .unwrap();
1595
1596 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
1597
1598 let loc = result.original_position_for(0, 0).unwrap();
1599 assert!(loc.name.is_some());
1600 assert_eq!(result.name(loc.name.unwrap()), "myFunc");
1601 }
1602
1603 #[test]
1604 fn streaming_upstream_name_wins() {
1605 let outer = SourceMap::from_json(
1606 r#"{"version":3,"sources":["compiled.js"],"names":["outerName"],"mappings":"AAAAA"}"#,
1607 )
1608 .unwrap();
1609
1610 let inner = SourceMap::from_json(
1611 r#"{"version":3,"sources":["original.ts"],"names":["innerName"],"mappings":"AAAAA"}"#,
1612 )
1613 .unwrap();
1614
1615 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
1616
1617 let loc = result.original_position_for(0, 0).unwrap();
1618 assert!(loc.name.is_some());
1619 assert_eq!(result.name(loc.name.unwrap()), "innerName");
1620 }
1621
1622 #[test]
1623 fn streaming_sources_content_from_upstream() {
1624 let outer = SourceMap::from_json(
1625 r#"{"version":3,"sources":["compiled.js"],"names":[],"mappings":"AAAA"}"#,
1626 )
1627 .unwrap();
1628
1629 let inner = SourceMap::from_json(
1630 r#"{"version":3,"sources":["original.ts"],"sourcesContent":["const x = 1;"],"names":[],"mappings":"AAAA"}"#,
1631 )
1632 .unwrap();
1633
1634 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
1635
1636 assert_eq!(
1637 result.sources_content,
1638 vec![Some("const x = 1;".to_string())]
1639 );
1640 }
1641
1642 #[test]
1643 fn streaming_no_upstream_with_sources_content() {
1644 let outer = SourceMap::from_json(
1645 r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":["fn1"],"mappings":"AAAAA"}"#,
1646 )
1647 .unwrap();
1648
1649 let result = streaming_from_sm(&outer, |_| None);
1650
1651 assert_eq!(result.sources, vec!["a.js"]);
1652 assert_eq!(result.sources_content, vec![Some("var a;".to_string())]);
1653 let loc = result.original_position_for(0, 0).unwrap();
1654 assert!(loc.name.is_some());
1655 assert_eq!(result.name(loc.name.unwrap()), "fn1");
1656 }
1657
1658 #[test]
1659 fn streaming_generated_only_passthrough() {
1660 let outer = SourceMap::from_json(
1661 r#"{"version":3,"sources":["a.js","other.js"],"names":[],"mappings":"A,AAAA,KCAA"}"#,
1662 )
1663 .unwrap();
1664
1665 let inner = SourceMap::from_json(
1666 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AAAA"}"#,
1667 )
1668 .unwrap();
1669
1670 let result = streaming_from_sm(&outer, |source| {
1671 if source == "a.js" {
1672 Some(inner.clone())
1673 } else {
1674 None
1675 }
1676 });
1677
1678 assert!(result.mapping_count() >= 2);
1679 assert!(result.sources.contains(&"original.js".to_string()));
1680 assert!(result.sources.contains(&"other.js".to_string()));
1681 }
1682
1683 #[test]
1684 fn streaming_matches_remap() {
1685 let outer = SourceMap::from_json(
1687 r#"{"version":3,"sources":["intermediate.js","other.js"],"names":["foo"],"mappings":"AAAAA,KCAA;ADCA"}"#,
1688 )
1689 .unwrap();
1690
1691 let inner = SourceMap::from_json(
1692 r#"{"version":3,"sources":["original.js"],"sourcesContent":["// src"],"names":["bar"],"mappings":"AAAAA;AACA"}"#,
1693 )
1694 .unwrap();
1695
1696 let loader = |source: &str| -> Option<SourceMap> {
1697 if source == "intermediate.js" {
1698 Some(inner.clone())
1699 } else {
1700 None
1701 }
1702 };
1703
1704 let result_normal = remap(&outer, loader);
1705 let result_stream = streaming_from_sm(&outer, loader);
1706
1707 assert_eq!(result_normal.sources, result_stream.sources);
1708 assert_eq!(result_normal.names, result_stream.names);
1709 assert_eq!(result_normal.sources_content, result_stream.sources_content);
1710 assert_eq!(result_normal.mapping_count(), result_stream.mapping_count());
1711
1712 for m in result_normal.all_mappings() {
1714 let loc_n = result_normal.original_position_for(m.generated_line, m.generated_column);
1715 let loc_s = result_stream.original_position_for(m.generated_line, m.generated_column);
1716 assert_eq!(loc_n.is_some(), loc_s.is_some());
1717 if let (Some(ln), Some(ls)) = (loc_n, loc_s) {
1718 assert_eq!(
1719 result_normal.source(ln.source),
1720 result_stream.source(ls.source)
1721 );
1722 assert_eq!(ln.line, ls.line);
1723 assert_eq!(ln.column, ls.column);
1724 }
1725 }
1726 }
1727
1728 #[test]
1729 fn streaming_no_upstream_mapping_fallback() {
1730 let outer = SourceMap::from_json(
1731 r#"{"version":3,"sources":["compiled.js"],"names":["myFunc"],"mappings":"AAAAA"}"#,
1732 )
1733 .unwrap();
1734
1735 let inner = SourceMap::from_json(
1737 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":";;;;AAAA"}"#,
1738 )
1739 .unwrap();
1740
1741 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
1742
1743 let loc = result.original_position_for(0, 0);
1745 assert!(loc.is_none());
1746 }
1747
1748 #[test]
1749 fn streaming_no_upstream_mapping_no_name() {
1750 let outer = SourceMap::from_json(
1751 r#"{"version":3,"sources":["compiled.js"],"names":[],"mappings":"AAAA"}"#,
1752 )
1753 .unwrap();
1754
1755 let inner = SourceMap::from_json(
1756 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":";;;;AAAA"}"#,
1757 )
1758 .unwrap();
1759
1760 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
1761
1762 let loc = result.original_position_for(0, 0);
1764 assert!(loc.is_none());
1765 }
1766
1767 #[test]
1770 fn remap_chain_empty() {
1771 assert!(remap_chain(&[]).is_none());
1772 }
1773
1774 #[test]
1775 fn remap_chain_single() {
1776 let sm = SourceMap::from_json(
1777 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#,
1778 )
1779 .unwrap();
1780 let result = remap_chain(&[&sm]).unwrap();
1781 assert_eq!(result.sources, vec!["a.js"]);
1782 assert_eq!(result.mapping_count(), 1);
1783 }
1784
1785 #[test]
1786 fn remap_chain_two_maps() {
1787 let step1 = SourceMap::from_json(
1789 r#"{"version":3,"file":"intermediate.js","sources":["original.js"],"names":[],"mappings":"AACA;AACA"}"#,
1790 )
1791 .unwrap();
1792 let step2 = SourceMap::from_json(
1794 r#"{"version":3,"file":"output.js","sources":["intermediate.js"],"names":[],"mappings":"AAAA;AACA"}"#,
1795 )
1796 .unwrap();
1797
1798 let result = remap_chain(&[&step2, &step1]).unwrap();
1800 assert_eq!(result.sources, vec!["original.js"]);
1801
1802 let loc = result.original_position_for(0, 0).unwrap();
1804 assert_eq!(result.source(loc.source), "original.js");
1805 assert_eq!(loc.line, 1);
1806 }
1807
1808 #[test]
1809 fn remap_chain_three_maps() {
1810 let a_to_b = SourceMap::from_json(
1812 r#"{"version":3,"file":"b.js","sources":["a.js"],"names":[],"mappings":"AACA"}"#,
1813 )
1814 .unwrap();
1815 let b_to_c = SourceMap::from_json(
1817 r#"{"version":3,"file":"c.js","sources":["b.js"],"names":[],"mappings":"AAAA"}"#,
1818 )
1819 .unwrap();
1820 let c_to_d = SourceMap::from_json(
1822 r#"{"version":3,"file":"d.js","sources":["c.js"],"names":[],"mappings":"AAAA"}"#,
1823 )
1824 .unwrap();
1825
1826 let result = remap_chain(&[&c_to_d, &b_to_c, &a_to_b]).unwrap();
1828 assert_eq!(result.sources, vec!["a.js"]);
1829
1830 let loc = result.original_position_for(0, 0).unwrap();
1831 assert_eq!(result.source(loc.source), "a.js");
1832 assert_eq!(loc.line, 1);
1833 }
1834
1835 #[test]
1838 fn remap_empty_string_source_filtered() {
1839 let outer =
1841 SourceMap::from_json(r#"{"version":3,"sources":[""],"names":[],"mappings":"AAAA"}"#)
1842 .unwrap();
1843
1844 let result = remap(&outer, |_| None);
1845
1846 assert!(
1848 !result.sources.iter().any(|s| s.is_empty()),
1849 "empty-string sources should be filtered out"
1850 );
1851 let loc = result.original_position_for(0, 0);
1853 assert!(loc.is_none());
1854 }
1855
1856 #[test]
1857 fn remap_null_source_filtered() {
1858 let outer =
1860 SourceMap::from_json(r#"{"version":3,"sources":[null],"names":[],"mappings":"AAAA"}"#)
1861 .unwrap();
1862
1863 let result = remap(&outer, |_| None);
1864
1865 assert!(
1866 !result.sources.iter().any(|s| s.is_empty()),
1867 "null sources should be filtered out"
1868 );
1869 }
1870
1871 #[test]
1872 fn streaming_empty_string_source_filtered() {
1873 let outer =
1874 SourceMap::from_json(r#"{"version":3,"sources":[""],"names":[],"mappings":"AAAA"}"#)
1875 .unwrap();
1876
1877 let result = streaming_from_sm(&outer, |_| None);
1878
1879 assert!(
1880 !result.sources.iter().any(|s| s.is_empty()),
1881 "streaming: empty-string sources should be filtered out"
1882 );
1883 }
1884
1885 #[test]
1888 fn remap_skips_redundant_sourced_segments() {
1889 let outer = SourceMap::from_json(
1893 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAA,EAAA"}"#,
1894 )
1895 .unwrap();
1896
1897 let result = remap(&outer, |_| None);
1898
1899 assert_eq!(result.mapping_count(), 1);
1901 }
1902
1903 #[test]
1904 fn remap_keeps_different_sourced_segments() {
1905 let outer = SourceMap::from_json(
1908 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAC"}"#,
1909 )
1910 .unwrap();
1911
1912 let result = remap(&outer, |_| None);
1913
1914 assert_eq!(result.mapping_count(), 2);
1916 }
1917
1918 #[test]
1919 fn remap_skips_sourceless_at_line_start() {
1920 let outer = SourceMap::from_json(r#"{"version":3,"sources":[],"names":[],"mappings":"A"}"#)
1923 .unwrap();
1924
1925 let result = remap(&outer, |_| None);
1926
1927 assert_eq!(result.mapping_count(), 0);
1929 }
1930
1931 #[test]
1932 fn streaming_skips_redundant_sourced_segments() {
1933 let outer = SourceMap::from_json(
1934 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAA,EAAA"}"#,
1935 )
1936 .unwrap();
1937
1938 let result = streaming_from_sm(&outer, |_| None);
1939
1940 assert_eq!(result.mapping_count(), 1);
1941 }
1942}