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 { builder: SourceMapGenerator::new(file) }
80 }
81
82 pub fn add_map(&mut self, sm: &SourceMap, line_offset: u32) {
87 let source_indices: Vec<u32> = sm
89 .sources
90 .iter()
91 .enumerate()
92 .map(|(i, s)| {
93 let idx = self.builder.add_source(s);
94 if let Some(Some(content)) = sm.sources_content.get(i) {
95 self.builder.set_source_content(idx, content.clone());
96 }
97 idx
98 })
99 .collect();
100
101 let name_indices: Vec<u32> = sm.names.iter().map(|n| self.builder.add_name(n)).collect();
103
104 for &ignored in &sm.ignore_list {
106 let global_idx = source_indices[ignored as usize];
107 self.builder.add_to_ignore_list(global_idx);
108 }
109
110 for m in sm.all_mappings() {
112 let gen_line = m.generated_line + line_offset;
113
114 if m.source == NO_SOURCE {
115 self.builder.add_generated_mapping(gen_line, m.generated_column);
116 } else {
117 let src = source_indices[m.source as usize];
118 let has_name = m.name != NO_NAME;
119 match (has_name, m.is_range_mapping) {
120 (true, true) => self.builder.add_named_range_mapping(
121 gen_line,
122 m.generated_column,
123 src,
124 m.original_line,
125 m.original_column,
126 name_indices[m.name as usize],
127 ),
128 (true, false) => self.builder.add_named_mapping(
129 gen_line,
130 m.generated_column,
131 src,
132 m.original_line,
133 m.original_column,
134 name_indices[m.name as usize],
135 ),
136 (false, true) => self.builder.add_range_mapping(
137 gen_line,
138 m.generated_column,
139 src,
140 m.original_line,
141 m.original_column,
142 ),
143 (false, false) => self.builder.add_mapping(
144 gen_line,
145 m.generated_column,
146 src,
147 m.original_line,
148 m.original_column,
149 ),
150 }
151 }
152 }
153 }
154
155 pub fn to_json(&self) -> String {
157 self.builder.to_json()
158 }
159
160 pub fn build(&self) -> SourceMap {
162 self.builder.to_decoded_map()
163 }
164}
165
166struct UpstreamCache {
173 source_remap: Vec<Option<u32>>,
175 name_remap: Vec<Option<u32>>,
177}
178
179fn build_upstream_cache(upstream_sm: &SourceMap) -> UpstreamCache {
181 UpstreamCache {
182 source_remap: vec![None; upstream_sm.sources.len()],
183 name_remap: vec![None; upstream_sm.names.len()],
184 }
185}
186
187#[inline]
190fn resolve_upstream_source(
191 cache: &mut UpstreamCache,
192 upstream_sm: &SourceMap,
193 upstream_src: u32,
194 builder: &mut SourceMapGenerator,
195 ignored_sources: &mut HashSet<u32>,
196) -> u32 {
197 let si = upstream_src as usize;
198 if let Some(idx) = cache.source_remap[si] {
199 return idx;
200 }
201 let idx = builder.add_source(&upstream_sm.sources[si]);
202 if let Some(Some(content)) = upstream_sm.sources_content.get(si) {
203 builder.set_source_content(idx, content.clone());
204 }
205 if upstream_sm.ignore_list.contains(&upstream_src) && ignored_sources.insert(idx) {
206 builder.add_to_ignore_list(idx);
207 }
208 cache.source_remap[si] = Some(idx);
209 idx
210}
211
212#[inline]
215fn resolve_upstream_name(
216 cache: &mut UpstreamCache,
217 upstream_sm: &SourceMap,
218 upstream_name: u32,
219 builder: &mut SourceMapGenerator,
220) -> u32 {
221 let ni = upstream_name as usize;
222 if let Some(idx) = cache.name_remap[ni] {
223 return idx;
224 }
225 let idx = builder.add_name(&upstream_sm.names[ni]);
226 cache.name_remap[ni] = Some(idx);
227 idx
228}
229
230#[inline]
232fn resolve_upstream_source_streaming(
233 cache: &mut UpstreamCache,
234 upstream_sm: &SourceMap,
235 upstream_src: u32,
236 builder: &mut StreamingGenerator,
237 ignored_sources: &mut HashSet<u32>,
238) -> u32 {
239 let si = upstream_src as usize;
240 if let Some(idx) = cache.source_remap[si] {
241 return idx;
242 }
243 let idx = builder.add_source(&upstream_sm.sources[si]);
244 if let Some(Some(content)) = upstream_sm.sources_content.get(si) {
245 builder.set_source_content(idx, content.clone());
246 }
247 if upstream_sm.ignore_list.contains(&upstream_src) && ignored_sources.insert(idx) {
248 builder.add_to_ignore_list(idx);
249 }
250 cache.source_remap[si] = Some(idx);
251 idx
252}
253
254#[inline]
256fn resolve_upstream_name_streaming(
257 cache: &mut UpstreamCache,
258 upstream_sm: &SourceMap,
259 upstream_name: u32,
260 builder: &mut StreamingGenerator,
261) -> u32 {
262 let ni = upstream_name as usize;
263 if let Some(idx) = cache.name_remap[ni] {
264 return idx;
265 }
266 let idx = builder.add_name(&upstream_sm.names[ni]);
267 cache.name_remap[ni] = Some(idx);
268 idx
269}
270
271#[inline]
280fn lookup_upstream(upstream_sm: &SourceMap, line: u32, column: u32) -> Option<UpstreamLookup> {
281 let line_mappings = upstream_sm.mappings_for_line(line);
282 if line_mappings.is_empty() {
283 return fallback_to_full_lookup(upstream_sm, line, column);
284 }
285
286 let idx = match line_mappings.binary_search_by_key(&column, |m| m.generated_column) {
287 Ok(i) => i,
288 Err(0) => return fallback_to_full_lookup(upstream_sm, line, column),
289 Err(i) => i - 1,
290 };
291
292 let mapping = &line_mappings[idx];
293 if mapping.source == NO_SOURCE {
294 return None;
295 }
296
297 let original_column = if mapping.is_range_mapping && column >= mapping.generated_column {
298 mapping.original_column + (column - mapping.generated_column)
299 } else {
300 mapping.original_column
301 };
302
303 Some(UpstreamLookup {
304 source: mapping.source,
305 original_line: mapping.original_line,
306 original_column,
307 name: mapping.name,
308 })
309}
310
311struct UpstreamLookup {
315 source: u32,
316 original_line: u32,
317 original_column: u32,
318 name: u32,
319}
320
321fn fallback_to_full_lookup(
326 upstream_sm: &SourceMap,
327 line: u32,
328 column: u32,
329) -> Option<UpstreamLookup> {
330 let loc = upstream_sm.original_position_for(line, column)?;
331 Some(UpstreamLookup {
332 source: loc.source,
333 original_line: loc.line,
334 original_column: loc.column,
335 name: loc.name.unwrap_or(NO_NAME),
336 })
337}
338
339#[inline]
342#[allow(
343 clippy::too_many_arguments,
344 reason = "passing remapped indices avoids per-mapping hashing in the hot path"
345)]
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, reason = "streaming path mirrors the indexed hot-path API")]
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 { map: Box<SourceMap>, cache: UpstreamCache },
406 Passthrough { builder_src: u32 },
408 EmptySource,
411 Unloaded,
413}
414
415struct DedupeState {
418 last_gen_line: u32,
420 line_index: u32,
422 last_was_sourceless: bool,
424 last_source: Option<(u32, u32, u32, Option<u32>)>,
426}
427
428impl DedupeState {
429 fn new() -> Self {
430 Self {
431 last_gen_line: u32::MAX,
432 line_index: 0,
433 last_was_sourceless: false,
434 last_source: None,
435 }
436 }
437
438 fn skip_sourceless(&self, gen_line: u32) -> bool {
441 if gen_line != self.last_gen_line {
442 return true;
444 }
445 self.last_was_sourceless
447 }
448
449 fn skip_source(
452 &self,
453 gen_line: u32,
454 source: u32,
455 orig_line: u32,
456 orig_col: u32,
457 name: Option<u32>,
458 ) -> bool {
459 if gen_line != self.last_gen_line {
460 return false;
462 }
463 if self.last_was_sourceless {
464 return false;
466 }
467 self.last_source == Some((source, orig_line, orig_col, name))
469 }
470
471 fn record_sourceless(&mut self, gen_line: u32) {
473 if gen_line != self.last_gen_line {
474 self.last_gen_line = gen_line;
475 self.line_index = 0;
476 self.last_source = None;
477 }
478 self.line_index += 1;
479 self.last_was_sourceless = true;
480 }
481
482 fn record_source(
484 &mut self,
485 gen_line: u32,
486 source: u32,
487 orig_line: u32,
488 orig_col: u32,
489 name: Option<u32>,
490 ) {
491 if gen_line != self.last_gen_line {
492 self.last_gen_line = gen_line;
493 self.line_index = 0;
494 }
495 self.line_index += 1;
496 self.last_was_sourceless = false;
497 self.last_source = Some((source, orig_line, orig_col, name));
498 }
499}
500
501pub fn remap<F>(outer: &SourceMap, loader: F) -> SourceMap
518where
519 F: Fn(&str) -> Option<SourceMap>,
520{
521 let mapping_count = outer.mapping_count();
522 let source_count = outer.sources.len();
523 let mut builder = SourceMapGenerator::with_capacity(outer.file.clone(), mapping_count);
524 builder.set_assume_sorted(true);
526
527 let mut source_entries: Vec<SourceEntry> =
529 std::iter::repeat_with(|| SourceEntry::Unloaded).take(source_count).collect();
530
531 let mut ignored_sources: HashSet<u32> = HashSet::new();
532
533 let mut outer_name_remap: Vec<Option<u32>> = vec![None; outer.names.len()];
535
536 let outer_ignore_set: HashSet<u32> = outer.ignore_list.iter().copied().collect();
538
539 let mut dedup = DedupeState::new();
540
541 for m in outer.all_mappings() {
542 if m.source == NO_SOURCE {
543 if !dedup.skip_sourceless(m.generated_line) {
544 builder.add_generated_mapping(m.generated_line, m.generated_column);
545 }
546 dedup.record_sourceless(m.generated_line);
547 continue;
548 }
549
550 let si = m.source as usize;
551
552 if matches!(source_entries[si], SourceEntry::Unloaded) {
554 let source_name = outer.source(m.source);
555 if source_name.is_empty() {
558 source_entries[si] = SourceEntry::EmptySource;
559 } else {
560 match loader(source_name) {
561 Some(upstream_sm) => {
562 let cache = build_upstream_cache(&upstream_sm);
563 source_entries[si] =
564 SourceEntry::Upstream { map: Box::new(upstream_sm), cache };
565 }
566 None => {
567 let idx = builder.add_source(source_name);
568 if let Some(Some(content)) = outer.sources_content.get(si) {
569 builder.set_source_content(idx, content.clone());
570 }
571 if outer_ignore_set.contains(&m.source) && ignored_sources.insert(idx) {
572 builder.add_to_ignore_list(idx);
573 }
574 source_entries[si] = SourceEntry::Passthrough { builder_src: idx };
575 }
576 }
577 }
578 }
579
580 match &mut source_entries[si] {
581 SourceEntry::Upstream { map, cache } => {
582 match lookup_upstream(map, m.original_line, m.original_column) {
583 Some(upstream_m) => {
584 let builder_src = resolve_upstream_source(
585 cache,
586 map,
587 upstream_m.source,
588 &mut builder,
589 &mut ignored_sources,
590 );
591
592 let builder_name = if upstream_m.name != NO_NAME {
594 Some(resolve_upstream_name(cache, map, upstream_m.name, &mut builder))
595 } else if m.name != NO_NAME {
596 let name_idx = *outer_name_remap[m.name as usize]
597 .get_or_insert_with(|| builder.add_name(outer.name(m.name)));
598 Some(name_idx)
599 } else {
600 None
601 };
602
603 if !dedup.skip_source(
604 m.generated_line,
605 builder_src,
606 upstream_m.original_line,
607 upstream_m.original_column,
608 builder_name,
609 ) {
610 emit_remapped_mapping(
611 &mut builder,
612 m.generated_line,
613 m.generated_column,
614 builder_src,
615 upstream_m.original_line,
616 upstream_m.original_column,
617 builder_name,
618 m.is_range_mapping,
619 );
620 }
621 dedup.record_source(
622 m.generated_line,
623 builder_src,
624 upstream_m.original_line,
625 upstream_m.original_column,
626 builder_name,
627 );
628 }
629 None => {
630 }
633 }
634 }
635 SourceEntry::Passthrough { builder_src } => {
636 let builder_src = *builder_src;
637
638 let builder_name = if m.name != NO_NAME {
639 Some(
640 *outer_name_remap[m.name as usize]
641 .get_or_insert_with(|| builder.add_name(outer.name(m.name))),
642 )
643 } else {
644 None
645 };
646
647 if !dedup.skip_source(
648 m.generated_line,
649 builder_src,
650 m.original_line,
651 m.original_column,
652 builder_name,
653 ) {
654 emit_remapped_mapping(
655 &mut builder,
656 m.generated_line,
657 m.generated_column,
658 builder_src,
659 m.original_line,
660 m.original_column,
661 builder_name,
662 m.is_range_mapping,
663 );
664 }
665 dedup.record_source(
666 m.generated_line,
667 builder_src,
668 m.original_line,
669 m.original_column,
670 builder_name,
671 );
672 }
673 SourceEntry::EmptySource => {
674 if !dedup.skip_sourceless(m.generated_line) {
676 builder.add_generated_mapping(m.generated_line, m.generated_column);
677 }
678 dedup.record_sourceless(m.generated_line);
679 }
680 SourceEntry::Unloaded => unreachable!(),
681 }
682 }
683
684 builder.to_decoded_map()
685}
686
687pub fn remap_chain(maps: &[&SourceMap]) -> Option<SourceMap> {
718 if maps.is_empty() {
719 return None;
720 }
721 if maps.len() == 1 {
722 return Some(maps[0].clone());
723 }
724
725 let mut current = compose_pair(maps[maps.len() - 2], maps[maps.len() - 1]);
737
738 for i in (0..maps.len() - 2).rev() {
740 current = compose_pair(maps[i], ¤t);
741 }
742
743 Some(current)
744}
745
746fn compose_pair(outer: &SourceMap, inner: &SourceMap) -> SourceMap {
749 remap(outer, |_source| Some(inner.clone()))
750}
751
752enum StreamingSourceEntry {
754 Upstream { map: Box<SourceMap>, cache: UpstreamCache },
756 Passthrough { builder_src: u32 },
758 EmptySource,
760 Unloaded,
762}
763
764pub fn remap_streaming<'a, F>(
777 mappings_iter: srcmap_sourcemap::MappingsIter<'a>,
778 sources: &[String],
779 names: &[String],
780 sources_content: &[Option<String>],
781 ignore_list: &[u32],
782 file: Option<String>,
783 loader: F,
784) -> SourceMap
785where
786 F: Fn(&str) -> Option<SourceMap>,
787{
788 let mut builder = StreamingGenerator::with_capacity(file, 4096);
789
790 let mut source_entries: Vec<StreamingSourceEntry> =
792 std::iter::repeat_with(|| StreamingSourceEntry::Unloaded).take(sources.len()).collect();
793
794 let mut ignored_sources: HashSet<u32> = HashSet::new();
795
796 let mut outer_name_remap: Vec<Option<u32>> = vec![None; names.len()];
798
799 let outer_ignore_set: HashSet<u32> = ignore_list.iter().copied().collect();
801
802 let mut dedup = DedupeState::new();
803
804 for item in mappings_iter {
805 let m = match item {
806 Ok(m) => m,
807 Err(_) => continue,
808 };
809
810 if m.source == NO_SOURCE {
811 if !dedup.skip_sourceless(m.generated_line) {
812 builder.add_generated_mapping(m.generated_line, m.generated_column);
813 }
814 dedup.record_sourceless(m.generated_line);
815 continue;
816 }
817
818 let si = m.source as usize;
819 if si >= sources.len() {
820 continue;
821 }
822
823 if matches!(source_entries[si], StreamingSourceEntry::Unloaded) {
825 let source_name = &sources[si];
826 if source_name.is_empty() {
827 source_entries[si] = StreamingSourceEntry::EmptySource;
828 } else {
829 match loader(source_name) {
830 Some(upstream_sm) => {
831 let cache = build_upstream_cache(&upstream_sm);
832 source_entries[si] =
833 StreamingSourceEntry::Upstream { map: Box::new(upstream_sm), cache };
834 }
835 None => {
836 let idx = builder.add_source(source_name);
837 if let Some(Some(content)) = sources_content.get(si) {
838 builder.set_source_content(idx, content.clone());
839 }
840 if outer_ignore_set.contains(&m.source) && ignored_sources.insert(idx) {
841 builder.add_to_ignore_list(idx);
842 }
843 source_entries[si] = StreamingSourceEntry::Passthrough { builder_src: idx };
844 }
845 }
846 }
847 }
848
849 match &mut source_entries[si] {
850 StreamingSourceEntry::Upstream { map, cache } => {
851 match lookup_upstream(map, m.original_line, m.original_column) {
852 Some(upstream_m) => {
853 let builder_src = resolve_upstream_source_streaming(
854 cache,
855 map,
856 upstream_m.source,
857 &mut builder,
858 &mut ignored_sources,
859 );
860
861 let builder_name = if upstream_m.name != NO_NAME {
862 Some(resolve_upstream_name_streaming(
863 cache,
864 map,
865 upstream_m.name,
866 &mut builder,
867 ))
868 } else {
869 resolve_outer_name(&mut outer_name_remap, m.name, names, &mut builder)
870 };
871
872 if !dedup.skip_source(
873 m.generated_line,
874 builder_src,
875 upstream_m.original_line,
876 upstream_m.original_column,
877 builder_name,
878 ) {
879 emit_remapped_mapping_streaming(
880 &mut builder,
881 m.generated_line,
882 m.generated_column,
883 builder_src,
884 upstream_m.original_line,
885 upstream_m.original_column,
886 builder_name,
887 m.is_range_mapping,
888 );
889 }
890 dedup.record_source(
891 m.generated_line,
892 builder_src,
893 upstream_m.original_line,
894 upstream_m.original_column,
895 builder_name,
896 );
897 }
898 None => {
899 }
902 }
903 }
904 StreamingSourceEntry::Passthrough { builder_src } => {
905 let builder_src = *builder_src;
906
907 let builder_name =
908 resolve_outer_name(&mut outer_name_remap, m.name, names, &mut builder);
909
910 if !dedup.skip_source(
911 m.generated_line,
912 builder_src,
913 m.original_line,
914 m.original_column,
915 builder_name,
916 ) {
917 emit_remapped_mapping_streaming(
918 &mut builder,
919 m.generated_line,
920 m.generated_column,
921 builder_src,
922 m.original_line,
923 m.original_column,
924 builder_name,
925 m.is_range_mapping,
926 );
927 }
928 dedup.record_source(
929 m.generated_line,
930 builder_src,
931 m.original_line,
932 m.original_column,
933 builder_name,
934 );
935 }
936 StreamingSourceEntry::EmptySource => {
937 if !dedup.skip_sourceless(m.generated_line) {
938 builder.add_generated_mapping(m.generated_line, m.generated_column);
939 }
940 dedup.record_sourceless(m.generated_line);
941 }
942 StreamingSourceEntry::Unloaded => unreachable!(),
943 }
944 }
945
946 builder.to_decoded_map().expect("streaming VLQ should be valid")
947}
948
949#[inline]
951fn resolve_outer_name(
952 outer_name_remap: &mut [Option<u32>],
953 name_idx: u32,
954 names: &[String],
955 builder: &mut StreamingGenerator,
956) -> Option<u32> {
957 if name_idx == NO_NAME {
958 return None;
959 }
960 let slot = outer_name_remap.get_mut(name_idx as usize)?;
961 if let Some(idx) = *slot {
962 return Some(idx);
963 }
964 let outer_name = names.get(name_idx as usize)?;
965 let idx = builder.add_name(outer_name);
966 *slot = Some(idx);
967 Some(idx)
968}
969
970#[cfg(test)]
973mod tests {
974 use super::*;
975
976 #[test]
979 fn concat_two_simple_maps() {
980 let a = SourceMap::from_json(
981 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#,
982 )
983 .unwrap();
984 let b = SourceMap::from_json(
985 r#"{"version":3,"sources":["b.js"],"names":[],"mappings":"AAAA"}"#,
986 )
987 .unwrap();
988
989 let mut builder = ConcatBuilder::new(Some("bundle.js".to_string()));
990 builder.add_map(&a, 0);
991 builder.add_map(&b, 1);
992
993 let result = builder.build();
994 assert_eq!(result.sources, vec!["a.js", "b.js"]);
995 assert_eq!(result.mapping_count(), 2);
996
997 let loc0 = result.original_position_for(0, 0).unwrap();
998 assert_eq!(result.source(loc0.source), "a.js");
999
1000 let loc1 = result.original_position_for(1, 0).unwrap();
1001 assert_eq!(result.source(loc1.source), "b.js");
1002 }
1003
1004 #[test]
1005 fn concat_deduplicates_sources() {
1006 let a = SourceMap::from_json(
1007 r#"{"version":3,"sources":["shared.js"],"names":[],"mappings":"AAAA"}"#,
1008 )
1009 .unwrap();
1010 let b = SourceMap::from_json(
1011 r#"{"version":3,"sources":["shared.js"],"names":[],"mappings":"AAAA"}"#,
1012 )
1013 .unwrap();
1014
1015 let mut builder = ConcatBuilder::new(None);
1016 builder.add_map(&a, 0);
1017 builder.add_map(&b, 10);
1018
1019 let result = builder.build();
1020 assert_eq!(result.sources.len(), 1);
1021 assert_eq!(result.sources[0], "shared.js");
1022 }
1023
1024 #[test]
1025 fn concat_with_names() {
1026 let a = SourceMap::from_json(
1027 r#"{"version":3,"sources":["a.js"],"names":["foo"],"mappings":"AAAAA"}"#,
1028 )
1029 .unwrap();
1030 let b = SourceMap::from_json(
1031 r#"{"version":3,"sources":["b.js"],"names":["bar"],"mappings":"AAAAA"}"#,
1032 )
1033 .unwrap();
1034
1035 let mut builder = ConcatBuilder::new(None);
1036 builder.add_map(&a, 0);
1037 builder.add_map(&b, 1);
1038
1039 let result = builder.build();
1040 assert_eq!(result.names.len(), 2);
1041
1042 let loc0 = result.original_position_for(0, 0).unwrap();
1043 assert_eq!(loc0.name, Some(0));
1044 assert_eq!(result.name(0), "foo");
1045
1046 let loc1 = result.original_position_for(1, 0).unwrap();
1047 assert_eq!(loc1.name, Some(1));
1048 assert_eq!(result.name(1), "bar");
1049 }
1050
1051 #[test]
1052 fn concat_preserves_multi_line_maps() {
1053 let a = SourceMap::from_json(
1054 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;AACA;AACA"}"#,
1055 )
1056 .unwrap();
1057
1058 let mut builder = ConcatBuilder::new(None);
1059 builder.add_map(&a, 5); let result = builder.build();
1062 assert!(result.original_position_for(5, 0).is_some());
1063 assert!(result.original_position_for(6, 0).is_some());
1064 assert!(result.original_position_for(7, 0).is_some());
1065 assert!(result.original_position_for(4, 0).is_none());
1066 }
1067
1068 #[test]
1069 fn concat_with_sources_content() {
1070 let a = SourceMap::from_json(
1071 r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":[],"mappings":"AAAA"}"#,
1072 )
1073 .unwrap();
1074
1075 let mut builder = ConcatBuilder::new(None);
1076 builder.add_map(&a, 0);
1077
1078 let result = builder.build();
1079 assert_eq!(result.sources_content, vec![Some("var a;".to_string())]);
1080 }
1081
1082 #[test]
1083 fn concat_empty_builder() {
1084 let builder = ConcatBuilder::new(Some("empty.js".to_string()));
1085 let result = builder.build();
1086 assert_eq!(result.mapping_count(), 0);
1087 assert_eq!(result.sources.len(), 0);
1088 }
1089
1090 #[test]
1093 fn remap_single_level() {
1094 let outer = SourceMap::from_json(
1099 r#"{"version":3,"sources":["intermediate.js","other.js"],"names":[],"mappings":"AAAA,KCAA;ADCA"}"#,
1100 )
1101 .unwrap();
1102
1103 let inner = SourceMap::from_json(
1105 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AACA;AACA"}"#,
1106 )
1107 .unwrap();
1108
1109 let result =
1110 remap(
1111 &outer,
1112 |source| {
1113 if source == "intermediate.js" { Some(inner.clone()) } else { None }
1114 },
1115 );
1116
1117 assert!(result.sources.contains(&"original.js".to_string()));
1118 assert!(result.sources.contains(&"other.js".to_string()));
1120
1121 let loc = result.original_position_for(0, 0).unwrap();
1123 assert_eq!(result.source(loc.source), "original.js");
1124 assert_eq!(loc.line, 1);
1125 }
1126
1127 #[test]
1128 fn remap_no_upstream_passthrough() {
1129 let outer = SourceMap::from_json(
1130 r#"{"version":3,"sources":["already-original.js"],"names":[],"mappings":"AAAA"}"#,
1131 )
1132 .unwrap();
1133
1134 let result = remap(&outer, |_| None);
1136
1137 assert_eq!(result.sources, vec!["already-original.js"]);
1138 let loc = result.original_position_for(0, 0).unwrap();
1139 assert_eq!(result.source(loc.source), "already-original.js");
1140 assert_eq!(loc.line, 0);
1141 assert_eq!(loc.column, 0);
1142 }
1143
1144 #[test]
1145 fn remap_partial_sources() {
1146 let outer = SourceMap::from_json(
1148 r#"{"version":3,"sources":["compiled.js","passthrough.js"],"names":[],"mappings":"AAAA,KCCA"}"#,
1149 )
1150 .unwrap();
1151
1152 let inner = SourceMap::from_json(
1153 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":"AAAA"}"#,
1154 )
1155 .unwrap();
1156
1157 let result =
1158 remap(
1159 &outer,
1160 |source| {
1161 if source == "compiled.js" { Some(inner.clone()) } else { None }
1162 },
1163 );
1164
1165 assert!(result.sources.contains(&"original.ts".to_string()));
1167 assert!(result.sources.contains(&"passthrough.js".to_string()));
1168 }
1169
1170 #[test]
1171 fn remap_preserves_names() {
1172 let outer = SourceMap::from_json(
1173 r#"{"version":3,"sources":["compiled.js"],"names":["myFunc"],"mappings":"AAAAA"}"#,
1174 )
1175 .unwrap();
1176
1177 let inner = SourceMap::from_json(
1179 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":"AAAA"}"#,
1180 )
1181 .unwrap();
1182
1183 let result = remap(&outer, |_| Some(inner.clone()));
1184
1185 let loc = result.original_position_for(0, 0).unwrap();
1186 assert!(loc.name.is_some());
1187 assert_eq!(result.name(loc.name.unwrap()), "myFunc");
1188 }
1189
1190 #[test]
1191 fn remap_upstream_name_wins() {
1192 let outer = SourceMap::from_json(
1193 r#"{"version":3,"sources":["compiled.js"],"names":["outerName"],"mappings":"AAAAA"}"#,
1194 )
1195 .unwrap();
1196
1197 let inner = SourceMap::from_json(
1199 r#"{"version":3,"sources":["original.ts"],"names":["innerName"],"mappings":"AAAAA"}"#,
1200 )
1201 .unwrap();
1202
1203 let result = remap(&outer, |_| Some(inner.clone()));
1204
1205 let loc = result.original_position_for(0, 0).unwrap();
1206 assert!(loc.name.is_some());
1207 assert_eq!(result.name(loc.name.unwrap()), "innerName");
1208 }
1209
1210 #[test]
1211 fn remap_sources_content_from_upstream() {
1212 let outer = SourceMap::from_json(
1213 r#"{"version":3,"sources":["compiled.js"],"names":[],"mappings":"AAAA"}"#,
1214 )
1215 .unwrap();
1216
1217 let inner = SourceMap::from_json(
1218 r#"{"version":3,"sources":["original.ts"],"sourcesContent":["const x = 1;"],"names":[],"mappings":"AAAA"}"#,
1219 )
1220 .unwrap();
1221
1222 let result = remap(&outer, |_| Some(inner.clone()));
1223
1224 assert_eq!(result.sources_content, vec![Some("const x = 1;".to_string())]);
1225 }
1226
1227 #[test]
1230 fn concat_updates_source_content_on_duplicate() {
1231 let a = SourceMap::from_json(
1233 r#"{"version":3,"sources":["shared.js"],"names":[],"mappings":"AAAA"}"#,
1234 )
1235 .unwrap();
1236 let b = SourceMap::from_json(
1237 r#"{"version":3,"sources":["shared.js"],"sourcesContent":["var x = 1;"],"names":[],"mappings":"AAAA"}"#,
1238 )
1239 .unwrap();
1240
1241 let mut builder = ConcatBuilder::new(None);
1242 builder.add_map(&a, 0);
1243 builder.add_map(&b, 1);
1244
1245 let result = builder.build();
1246 assert_eq!(result.sources.len(), 1);
1247 assert_eq!(result.sources_content, vec![Some("var x = 1;".to_string())]);
1248 }
1249
1250 #[test]
1251 fn concat_deduplicates_names() {
1252 let a = SourceMap::from_json(
1253 r#"{"version":3,"sources":["a.js"],"names":["sharedName"],"mappings":"AAAAA"}"#,
1254 )
1255 .unwrap();
1256 let b = SourceMap::from_json(
1257 r#"{"version":3,"sources":["b.js"],"names":["sharedName"],"mappings":"AAAAA"}"#,
1258 )
1259 .unwrap();
1260
1261 let mut builder = ConcatBuilder::new(None);
1262 builder.add_map(&a, 0);
1263 builder.add_map(&b, 1);
1264
1265 let result = builder.build();
1266 assert_eq!(result.names.len(), 1);
1268 assert_eq!(result.names[0], "sharedName");
1269 }
1270
1271 #[test]
1272 fn concat_with_ignore_list() {
1273 let a = SourceMap::from_json(
1274 r#"{"version":3,"sources":["vendor.js"],"names":[],"mappings":"AAAA","ignoreList":[0]}"#,
1275 )
1276 .unwrap();
1277
1278 let mut builder = ConcatBuilder::new(None);
1279 builder.add_map(&a, 0);
1280
1281 let result = builder.build();
1282 assert_eq!(result.ignore_list, vec![0]);
1283 }
1284
1285 #[test]
1286 fn concat_with_generated_only_mappings() {
1287 let a = SourceMap::from_json(
1289 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A,AAAA"}"#,
1290 )
1291 .unwrap();
1292
1293 let mut builder = ConcatBuilder::new(None);
1294 builder.add_map(&a, 0);
1295
1296 let result = builder.build();
1297 assert!(result.mapping_count() >= 1);
1299 }
1300
1301 #[test]
1302 fn remap_generated_only_passthrough() {
1303 let outer = SourceMap::from_json(
1308 r#"{"version":3,"sources":["a.js","other.js"],"names":[],"mappings":"A,AAAA,KCAA"}"#,
1309 )
1310 .unwrap();
1311
1312 let inner = SourceMap::from_json(
1313 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AAAA"}"#,
1314 )
1315 .unwrap();
1316
1317 let result =
1318 remap(&outer, |source| if source == "a.js" { Some(inner.clone()) } else { None });
1319
1320 assert!(result.mapping_count() >= 2);
1322 assert!(result.sources.contains(&"original.js".to_string()));
1323 assert!(result.sources.contains(&"other.js".to_string()));
1324 }
1325
1326 #[test]
1327 fn remap_no_upstream_mapping_with_name() {
1328 let outer = SourceMap::from_json(
1330 r#"{"version":3,"sources":["compiled.js"],"names":["myFunc"],"mappings":"AAAAA"}"#,
1331 )
1332 .unwrap();
1333
1334 let inner = SourceMap::from_json(
1336 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":";;;;AAAA"}"#,
1337 )
1338 .unwrap();
1339
1340 let result = remap(&outer, |_| Some(inner.clone()));
1341
1342 let loc = result.original_position_for(0, 0);
1346 assert!(loc.is_none());
1347 }
1348
1349 #[test]
1350 fn remap_no_upstream_with_sources_content_and_name() {
1351 let outer = SourceMap::from_json(
1352 r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":["fn1"],"mappings":"AAAAA"}"#,
1353 )
1354 .unwrap();
1355
1356 let result = remap(&outer, |_| None);
1358
1359 assert_eq!(result.sources, vec!["a.js"]);
1360 assert_eq!(result.sources_content, vec![Some("var a;".to_string())]);
1361 let loc = result.original_position_for(0, 0).unwrap();
1362 assert!(loc.name.is_some());
1363 assert_eq!(result.name(loc.name.unwrap()), "fn1");
1364 }
1365
1366 #[test]
1367 fn remap_no_upstream_no_name() {
1368 let outer = SourceMap::from_json(
1369 r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":[],"mappings":"AAAA"}"#,
1370 )
1371 .unwrap();
1372
1373 let result = remap(&outer, |_| None);
1374 let loc = result.original_position_for(0, 0).unwrap();
1375 assert!(loc.name.is_none());
1376 }
1377
1378 #[test]
1379 fn remap_no_upstream_mapping_no_name() {
1380 let outer = SourceMap::from_json(
1383 r#"{"version":3,"sources":["compiled.js"],"names":[],"mappings":"AAAA"}"#,
1384 )
1385 .unwrap();
1386
1387 let inner = SourceMap::from_json(
1390 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":";;;;AAAA"}"#,
1391 )
1392 .unwrap();
1393
1394 let result = remap(&outer, |_| Some(inner.clone()));
1395
1396 let loc = result.original_position_for(0, 0);
1398 assert!(loc.is_none());
1399 }
1400
1401 #[test]
1402 fn remap_upstream_found_no_name() {
1403 let outer = SourceMap::from_json(
1411 r#"{"version":3,"sources":["intermediate.js"],"names":[],"mappings":"AAAA"}"#,
1412 )
1413 .unwrap();
1414
1415 let inner = SourceMap::from_json(
1417 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AAAA"}"#,
1418 )
1419 .unwrap();
1420
1421 let result = remap(&outer, |_| Some(inner.clone()));
1422
1423 assert_eq!(result.sources, vec!["original.js"]);
1424 let loc = result.original_position_for(0, 0).unwrap();
1425 assert_eq!(result.source(loc.source), "original.js");
1426 assert_eq!(loc.line, 0);
1427 assert_eq!(loc.column, 0);
1428 assert!(loc.name.is_none());
1430 assert!(result.names.is_empty());
1431 }
1432
1433 #[test]
1436 fn concat_preserves_range_mappings() {
1437 let a = SourceMap::from_json(
1438 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,CAAC","rangeMappings":"A"}"#,
1439 )
1440 .unwrap();
1441
1442 let mut builder = ConcatBuilder::new(None);
1443 builder.add_map(&a, 0);
1444
1445 let result = builder.build();
1446 assert!(result.has_range_mappings());
1447 let mappings = result.all_mappings();
1448 assert!(mappings[0].is_range_mapping);
1449 assert!(!mappings[1].is_range_mapping);
1450 }
1451
1452 #[test]
1453 fn remap_preserves_range_mappings_passthrough() {
1454 let outer = SourceMap::from_json(
1455 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","rangeMappings":"A"}"#,
1456 )
1457 .unwrap();
1458
1459 let result = remap(&outer, |_| None);
1461 assert!(result.has_range_mappings());
1462 let mappings = result.all_mappings();
1463 assert!(mappings[0].is_range_mapping);
1464 }
1465
1466 #[test]
1467 fn remap_preserves_range_through_upstream() {
1468 let outer = SourceMap::from_json(
1469 r#"{"version":3,"sources":["intermediate.js"],"names":[],"mappings":"AAAA","rangeMappings":"A"}"#,
1470 )
1471 .unwrap();
1472
1473 let inner = SourceMap::from_json(
1474 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AACA"}"#,
1475 )
1476 .unwrap();
1477
1478 let result = remap(&outer, |_| Some(inner.clone()));
1479 assert!(result.has_range_mappings());
1480 }
1481
1482 #[test]
1483 fn remap_non_range_stays_non_range() {
1484 let outer = SourceMap::from_json(
1485 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#,
1486 )
1487 .unwrap();
1488
1489 let result = remap(&outer, |_| None);
1490 assert!(!result.has_range_mappings());
1491 }
1492
1493 fn streaming_from_sm<F>(sm: &SourceMap, loader: F) -> SourceMap
1498 where
1499 F: Fn(&str) -> Option<SourceMap>,
1500 {
1501 let vlq = sm.encode_mappings();
1502 let iter = srcmap_sourcemap::MappingsIter::new(&vlq);
1503 remap_streaming(
1504 iter,
1505 &sm.sources,
1506 &sm.names,
1507 &sm.sources_content,
1508 &sm.ignore_list,
1509 sm.file.clone(),
1510 loader,
1511 )
1512 }
1513
1514 #[test]
1515 fn streaming_single_level() {
1516 let outer = SourceMap::from_json(
1517 r#"{"version":3,"sources":["intermediate.js","other.js"],"names":[],"mappings":"AAAA,KCAA;ADCA"}"#,
1518 )
1519 .unwrap();
1520
1521 let inner = SourceMap::from_json(
1522 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AACA;AACA"}"#,
1523 )
1524 .unwrap();
1525
1526 let result = streaming_from_sm(&outer, |source| {
1527 if source == "intermediate.js" { Some(inner.clone()) } else { None }
1528 });
1529
1530 assert!(result.sources.contains(&"original.js".to_string()));
1531 assert!(result.sources.contains(&"other.js".to_string()));
1532
1533 let loc = result.original_position_for(0, 0).unwrap();
1534 assert_eq!(result.source(loc.source), "original.js");
1535 assert_eq!(loc.line, 1);
1536 }
1537
1538 #[test]
1539 fn streaming_no_upstream_passthrough() {
1540 let outer = SourceMap::from_json(
1541 r#"{"version":3,"sources":["already-original.js"],"names":[],"mappings":"AAAA"}"#,
1542 )
1543 .unwrap();
1544
1545 let result = streaming_from_sm(&outer, |_| None);
1546
1547 assert_eq!(result.sources, vec!["already-original.js"]);
1548 let loc = result.original_position_for(0, 0).unwrap();
1549 assert_eq!(result.source(loc.source), "already-original.js");
1550 assert_eq!(loc.line, 0);
1551 assert_eq!(loc.column, 0);
1552 }
1553
1554 #[test]
1555 fn streaming_preserves_names() {
1556 let outer = SourceMap::from_json(
1557 r#"{"version":3,"sources":["compiled.js"],"names":["myFunc"],"mappings":"AAAAA"}"#,
1558 )
1559 .unwrap();
1560
1561 let inner = SourceMap::from_json(
1562 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":"AAAA"}"#,
1563 )
1564 .unwrap();
1565
1566 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
1567
1568 let loc = result.original_position_for(0, 0).unwrap();
1569 assert!(loc.name.is_some());
1570 assert_eq!(result.name(loc.name.unwrap()), "myFunc");
1571 }
1572
1573 #[test]
1574 fn streaming_upstream_name_wins() {
1575 let outer = SourceMap::from_json(
1576 r#"{"version":3,"sources":["compiled.js"],"names":["outerName"],"mappings":"AAAAA"}"#,
1577 )
1578 .unwrap();
1579
1580 let inner = SourceMap::from_json(
1581 r#"{"version":3,"sources":["original.ts"],"names":["innerName"],"mappings":"AAAAA"}"#,
1582 )
1583 .unwrap();
1584
1585 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
1586
1587 let loc = result.original_position_for(0, 0).unwrap();
1588 assert!(loc.name.is_some());
1589 assert_eq!(result.name(loc.name.unwrap()), "innerName");
1590 }
1591
1592 #[test]
1593 fn streaming_sources_content_from_upstream() {
1594 let outer = SourceMap::from_json(
1595 r#"{"version":3,"sources":["compiled.js"],"names":[],"mappings":"AAAA"}"#,
1596 )
1597 .unwrap();
1598
1599 let inner = SourceMap::from_json(
1600 r#"{"version":3,"sources":["original.ts"],"sourcesContent":["const x = 1;"],"names":[],"mappings":"AAAA"}"#,
1601 )
1602 .unwrap();
1603
1604 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
1605
1606 assert_eq!(result.sources_content, vec![Some("const x = 1;".to_string())]);
1607 }
1608
1609 #[test]
1610 fn streaming_no_upstream_with_sources_content() {
1611 let outer = SourceMap::from_json(
1612 r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":["fn1"],"mappings":"AAAAA"}"#,
1613 )
1614 .unwrap();
1615
1616 let result = streaming_from_sm(&outer, |_| None);
1617
1618 assert_eq!(result.sources, vec!["a.js"]);
1619 assert_eq!(result.sources_content, vec![Some("var a;".to_string())]);
1620 let loc = result.original_position_for(0, 0).unwrap();
1621 assert!(loc.name.is_some());
1622 assert_eq!(result.name(loc.name.unwrap()), "fn1");
1623 }
1624
1625 #[test]
1626 fn streaming_generated_only_passthrough() {
1627 let outer = SourceMap::from_json(
1628 r#"{"version":3,"sources":["a.js","other.js"],"names":[],"mappings":"A,AAAA,KCAA"}"#,
1629 )
1630 .unwrap();
1631
1632 let inner = SourceMap::from_json(
1633 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AAAA"}"#,
1634 )
1635 .unwrap();
1636
1637 let result =
1638 streaming_from_sm(
1639 &outer,
1640 |source| {
1641 if source == "a.js" { Some(inner.clone()) } else { None }
1642 },
1643 );
1644
1645 assert!(result.mapping_count() >= 2);
1646 assert!(result.sources.contains(&"original.js".to_string()));
1647 assert!(result.sources.contains(&"other.js".to_string()));
1648 }
1649
1650 #[test]
1651 fn streaming_matches_remap() {
1652 let outer = SourceMap::from_json(
1654 r#"{"version":3,"sources":["intermediate.js","other.js"],"names":["foo"],"mappings":"AAAAA,KCAA;ADCA"}"#,
1655 )
1656 .unwrap();
1657
1658 let inner = SourceMap::from_json(
1659 r#"{"version":3,"sources":["original.js"],"sourcesContent":["// src"],"names":["bar"],"mappings":"AAAAA;AACA"}"#,
1660 )
1661 .unwrap();
1662
1663 let loader = |source: &str| -> Option<SourceMap> {
1664 if source == "intermediate.js" { Some(inner.clone()) } else { None }
1665 };
1666
1667 let result_normal = remap(&outer, loader);
1668 let result_stream = streaming_from_sm(&outer, loader);
1669
1670 assert_eq!(result_normal.sources, result_stream.sources);
1671 assert_eq!(result_normal.names, result_stream.names);
1672 assert_eq!(result_normal.sources_content, result_stream.sources_content);
1673 assert_eq!(result_normal.mapping_count(), result_stream.mapping_count());
1674
1675 for m in result_normal.all_mappings() {
1677 let loc_n = result_normal.original_position_for(m.generated_line, m.generated_column);
1678 let loc_s = result_stream.original_position_for(m.generated_line, m.generated_column);
1679 assert_eq!(loc_n.is_some(), loc_s.is_some());
1680 if let (Some(ln), Some(ls)) = (loc_n, loc_s) {
1681 assert_eq!(result_normal.source(ln.source), result_stream.source(ls.source));
1682 assert_eq!(ln.line, ls.line);
1683 assert_eq!(ln.column, ls.column);
1684 }
1685 }
1686 }
1687
1688 #[test]
1689 fn streaming_no_upstream_mapping_fallback() {
1690 let outer = SourceMap::from_json(
1691 r#"{"version":3,"sources":["compiled.js"],"names":["myFunc"],"mappings":"AAAAA"}"#,
1692 )
1693 .unwrap();
1694
1695 let inner = SourceMap::from_json(
1697 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":";;;;AAAA"}"#,
1698 )
1699 .unwrap();
1700
1701 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
1702
1703 let loc = result.original_position_for(0, 0);
1705 assert!(loc.is_none());
1706 }
1707
1708 #[test]
1709 fn streaming_no_upstream_mapping_no_name() {
1710 let outer = SourceMap::from_json(
1711 r#"{"version":3,"sources":["compiled.js"],"names":[],"mappings":"AAAA"}"#,
1712 )
1713 .unwrap();
1714
1715 let inner = SourceMap::from_json(
1716 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":";;;;AAAA"}"#,
1717 )
1718 .unwrap();
1719
1720 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
1721
1722 let loc = result.original_position_for(0, 0);
1724 assert!(loc.is_none());
1725 }
1726
1727 #[test]
1730 fn remap_chain_empty() {
1731 assert!(remap_chain(&[]).is_none());
1732 }
1733
1734 #[test]
1735 fn remap_chain_single() {
1736 let sm = SourceMap::from_json(
1737 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#,
1738 )
1739 .unwrap();
1740 let result = remap_chain(&[&sm]).unwrap();
1741 assert_eq!(result.sources, vec!["a.js"]);
1742 assert_eq!(result.mapping_count(), 1);
1743 }
1744
1745 #[test]
1746 fn remap_chain_two_maps() {
1747 let step1 = SourceMap::from_json(
1749 r#"{"version":3,"file":"intermediate.js","sources":["original.js"],"names":[],"mappings":"AACA;AACA"}"#,
1750 )
1751 .unwrap();
1752 let step2 = SourceMap::from_json(
1754 r#"{"version":3,"file":"output.js","sources":["intermediate.js"],"names":[],"mappings":"AAAA;AACA"}"#,
1755 )
1756 .unwrap();
1757
1758 let result = remap_chain(&[&step2, &step1]).unwrap();
1760 assert_eq!(result.sources, vec!["original.js"]);
1761
1762 let loc = result.original_position_for(0, 0).unwrap();
1764 assert_eq!(result.source(loc.source), "original.js");
1765 assert_eq!(loc.line, 1);
1766 }
1767
1768 #[test]
1769 fn remap_chain_three_maps() {
1770 let a_to_b = SourceMap::from_json(
1772 r#"{"version":3,"file":"b.js","sources":["a.js"],"names":[],"mappings":"AACA"}"#,
1773 )
1774 .unwrap();
1775 let b_to_c = SourceMap::from_json(
1777 r#"{"version":3,"file":"c.js","sources":["b.js"],"names":[],"mappings":"AAAA"}"#,
1778 )
1779 .unwrap();
1780 let c_to_d = SourceMap::from_json(
1782 r#"{"version":3,"file":"d.js","sources":["c.js"],"names":[],"mappings":"AAAA"}"#,
1783 )
1784 .unwrap();
1785
1786 let result = remap_chain(&[&c_to_d, &b_to_c, &a_to_b]).unwrap();
1788 assert_eq!(result.sources, vec!["a.js"]);
1789
1790 let loc = result.original_position_for(0, 0).unwrap();
1791 assert_eq!(result.source(loc.source), "a.js");
1792 assert_eq!(loc.line, 1);
1793 }
1794
1795 #[test]
1798 fn remap_empty_string_source_filtered() {
1799 let outer =
1801 SourceMap::from_json(r#"{"version":3,"sources":[""],"names":[],"mappings":"AAAA"}"#)
1802 .unwrap();
1803
1804 let result = remap(&outer, |_| None);
1805
1806 assert!(
1808 !result.sources.iter().any(|s| s.is_empty()),
1809 "empty-string sources should be filtered out"
1810 );
1811 let loc = result.original_position_for(0, 0);
1813 assert!(loc.is_none());
1814 }
1815
1816 #[test]
1817 fn remap_null_source_filtered() {
1818 let outer =
1820 SourceMap::from_json(r#"{"version":3,"sources":[null],"names":[],"mappings":"AAAA"}"#)
1821 .unwrap();
1822
1823 let result = remap(&outer, |_| None);
1824
1825 assert!(
1826 !result.sources.iter().any(|s| s.is_empty()),
1827 "null sources should be filtered out"
1828 );
1829 }
1830
1831 #[test]
1832 fn streaming_empty_string_source_filtered() {
1833 let outer =
1834 SourceMap::from_json(r#"{"version":3,"sources":[""],"names":[],"mappings":"AAAA"}"#)
1835 .unwrap();
1836
1837 let result = streaming_from_sm(&outer, |_| None);
1838
1839 assert!(
1840 !result.sources.iter().any(|s| s.is_empty()),
1841 "streaming: empty-string sources should be filtered out"
1842 );
1843 }
1844
1845 #[test]
1848 fn remap_skips_redundant_sourced_segments() {
1849 let outer = SourceMap::from_json(
1853 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAA,EAAA"}"#,
1854 )
1855 .unwrap();
1856
1857 let result = remap(&outer, |_| None);
1858
1859 assert_eq!(result.mapping_count(), 1);
1861 }
1862
1863 #[test]
1864 fn remap_keeps_different_sourced_segments() {
1865 let outer = SourceMap::from_json(
1868 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAC"}"#,
1869 )
1870 .unwrap();
1871
1872 let result = remap(&outer, |_| None);
1873
1874 assert_eq!(result.mapping_count(), 2);
1876 }
1877
1878 #[test]
1879 fn remap_skips_sourceless_at_line_start() {
1880 let outer = SourceMap::from_json(r#"{"version":3,"sources":[],"names":[],"mappings":"A"}"#)
1883 .unwrap();
1884
1885 let result = remap(&outer, |_| None);
1886
1887 assert_eq!(result.mapping_count(), 0);
1889 }
1890
1891 #[test]
1892 fn streaming_skips_redundant_sourced_segments() {
1893 let outer = SourceMap::from_json(
1894 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAA,EAAA"}"#,
1895 )
1896 .unwrap();
1897
1898 let result = streaming_from_sm(&outer, |_| None);
1899
1900 assert_eq!(result.mapping_count(), 1);
1901 }
1902}