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 = add_concat_sources(&mut self.builder, sm);
88 let name_indices: Vec<u32> = sm.names.iter().map(|n| self.builder.add_name(n)).collect();
90
91 for &ignored in &sm.ignore_list {
93 let global_idx = source_indices[ignored as usize];
94 self.builder.add_to_ignore_list(global_idx);
95 }
96
97 for m in sm.all_mappings() {
99 let gen_line = m.generated_line + line_offset;
100
101 if m.source == NO_SOURCE {
102 self.builder.add_generated_mapping(gen_line, m.generated_column);
103 } else {
104 let src = source_indices[m.source as usize];
105 let has_name = m.name != NO_NAME;
106 match (has_name, m.is_range_mapping) {
107 (true, true) => self.builder.add_named_range_mapping(
108 gen_line,
109 m.generated_column,
110 src,
111 m.original_line,
112 m.original_column,
113 name_indices[m.name as usize],
114 ),
115 (true, false) => self.builder.add_named_mapping(
116 gen_line,
117 m.generated_column,
118 src,
119 m.original_line,
120 m.original_column,
121 name_indices[m.name as usize],
122 ),
123 (false, true) => self.builder.add_range_mapping(
124 gen_line,
125 m.generated_column,
126 src,
127 m.original_line,
128 m.original_column,
129 ),
130 (false, false) => self.builder.add_mapping(
131 gen_line,
132 m.generated_column,
133 src,
134 m.original_line,
135 m.original_column,
136 ),
137 }
138 }
139 }
140 }
141
142 pub fn to_json(&self) -> String {
144 self.builder.to_json()
145 }
146
147 pub fn build(&self) -> SourceMap {
149 self.builder.to_decoded_map()
150 }
151}
152
153fn add_concat_sources(builder: &mut SourceMapGenerator, sm: &SourceMap) -> Vec<u32> {
154 sm.sources
155 .iter()
156 .enumerate()
157 .map(|(i, s)| {
158 let idx = builder.add_source(s);
159 if let Some(Some(content)) = sm.sources_content.get(i) {
160 builder.set_source_content(idx, content.clone());
161 }
162 idx
163 })
164 .collect()
165}
166
167struct UpstreamCache {
174 source_remap: Vec<Option<u32>>,
176 name_remap: Vec<Option<u32>>,
178}
179
180fn build_upstream_cache(upstream_sm: &SourceMap) -> UpstreamCache {
182 UpstreamCache {
183 source_remap: vec![None; upstream_sm.sources.len()],
184 name_remap: vec![None; upstream_sm.names.len()],
185 }
186}
187
188trait RemapBuilder {
190 fn add_source(&mut self, source: &str) -> u32;
191 fn set_source_content(&mut self, idx: u32, content: String);
192 fn add_name(&mut self, name: &str) -> u32;
193 fn add_to_ignore_list(&mut self, idx: u32);
194 fn add_generated_mapping(&mut self, gen_line: u32, gen_col: u32);
195 fn add_mapping(&mut self, gen_line: u32, gen_col: u32, src: u32, orig_line: u32, orig_col: u32);
196 fn add_named_mapping(
197 &mut self,
198 gen_line: u32,
199 gen_col: u32,
200 src: u32,
201 orig_line: u32,
202 orig_col: u32,
203 name: u32,
204 );
205 fn add_range_mapping(
206 &mut self,
207 gen_line: u32,
208 gen_col: u32,
209 src: u32,
210 orig_line: u32,
211 orig_col: u32,
212 );
213 fn add_named_range_mapping(
214 &mut self,
215 gen_line: u32,
216 gen_col: u32,
217 src: u32,
218 orig_line: u32,
219 orig_col: u32,
220 name: u32,
221 );
222}
223
224impl RemapBuilder for SourceMapGenerator {
225 fn add_source(&mut self, source: &str) -> u32 {
226 SourceMapGenerator::add_source(self, source)
227 }
228
229 fn set_source_content(&mut self, idx: u32, content: String) {
230 SourceMapGenerator::set_source_content(self, idx, content)
231 }
232
233 fn add_name(&mut self, name: &str) -> u32 {
234 SourceMapGenerator::add_name(self, name)
235 }
236
237 fn add_to_ignore_list(&mut self, idx: u32) {
238 SourceMapGenerator::add_to_ignore_list(self, idx)
239 }
240
241 fn add_generated_mapping(&mut self, gen_line: u32, gen_col: u32) {
242 SourceMapGenerator::add_generated_mapping(self, gen_line, gen_col)
243 }
244
245 fn add_mapping(
246 &mut self,
247 gen_line: u32,
248 gen_col: u32,
249 src: u32,
250 orig_line: u32,
251 orig_col: u32,
252 ) {
253 SourceMapGenerator::add_mapping(self, gen_line, gen_col, src, orig_line, orig_col)
254 }
255
256 fn add_named_mapping(
257 &mut self,
258 gen_line: u32,
259 gen_col: u32,
260 src: u32,
261 orig_line: u32,
262 orig_col: u32,
263 name: u32,
264 ) {
265 SourceMapGenerator::add_named_mapping(
266 self, gen_line, gen_col, src, orig_line, orig_col, name,
267 )
268 }
269
270 fn add_range_mapping(
271 &mut self,
272 gen_line: u32,
273 gen_col: u32,
274 src: u32,
275 orig_line: u32,
276 orig_col: u32,
277 ) {
278 SourceMapGenerator::add_range_mapping(self, gen_line, gen_col, src, orig_line, orig_col)
279 }
280
281 fn add_named_range_mapping(
282 &mut self,
283 gen_line: u32,
284 gen_col: u32,
285 src: u32,
286 orig_line: u32,
287 orig_col: u32,
288 name: u32,
289 ) {
290 SourceMapGenerator::add_named_range_mapping(
291 self, gen_line, gen_col, src, orig_line, orig_col, name,
292 )
293 }
294}
295
296impl RemapBuilder for StreamingGenerator {
297 fn add_source(&mut self, source: &str) -> u32 {
298 StreamingGenerator::add_source(self, source)
299 }
300
301 fn set_source_content(&mut self, idx: u32, content: String) {
302 StreamingGenerator::set_source_content(self, idx, content)
303 }
304
305 fn add_name(&mut self, name: &str) -> u32 {
306 StreamingGenerator::add_name(self, name)
307 }
308
309 fn add_to_ignore_list(&mut self, idx: u32) {
310 StreamingGenerator::add_to_ignore_list(self, idx)
311 }
312
313 fn add_generated_mapping(&mut self, gen_line: u32, gen_col: u32) {
314 StreamingGenerator::add_generated_mapping(self, gen_line, gen_col)
315 }
316
317 fn add_mapping(
318 &mut self,
319 gen_line: u32,
320 gen_col: u32,
321 src: u32,
322 orig_line: u32,
323 orig_col: u32,
324 ) {
325 StreamingGenerator::add_mapping(self, gen_line, gen_col, src, orig_line, orig_col)
326 }
327
328 fn add_named_mapping(
329 &mut self,
330 gen_line: u32,
331 gen_col: u32,
332 src: u32,
333 orig_line: u32,
334 orig_col: u32,
335 name: u32,
336 ) {
337 StreamingGenerator::add_named_mapping(
338 self, gen_line, gen_col, src, orig_line, orig_col, name,
339 )
340 }
341
342 fn add_range_mapping(
343 &mut self,
344 gen_line: u32,
345 gen_col: u32,
346 src: u32,
347 orig_line: u32,
348 orig_col: u32,
349 ) {
350 StreamingGenerator::add_range_mapping(self, gen_line, gen_col, src, orig_line, orig_col)
351 }
352
353 fn add_named_range_mapping(
354 &mut self,
355 gen_line: u32,
356 gen_col: u32,
357 src: u32,
358 orig_line: u32,
359 orig_col: u32,
360 name: u32,
361 ) {
362 StreamingGenerator::add_named_range_mapping(
363 self, gen_line, gen_col, src, orig_line, orig_col, name,
364 )
365 }
366}
367
368#[inline]
371fn resolve_upstream_source<B: RemapBuilder>(
372 cache: &mut UpstreamCache,
373 upstream_sm: &SourceMap,
374 upstream_src: u32,
375 builder: &mut B,
376 ignored_sources: &mut HashSet<u32>,
377) -> u32 {
378 let si = upstream_src as usize;
379 if let Some(idx) = cache.source_remap[si] {
380 return idx;
381 }
382 let idx = builder.add_source(&upstream_sm.sources[si]);
383 if let Some(Some(content)) = upstream_sm.sources_content.get(si) {
384 builder.set_source_content(idx, content.clone());
385 }
386 if upstream_sm.ignore_list.contains(&upstream_src) && ignored_sources.insert(idx) {
387 builder.add_to_ignore_list(idx);
388 }
389 cache.source_remap[si] = Some(idx);
390 idx
391}
392
393#[inline]
396fn resolve_upstream_name<B: RemapBuilder>(
397 cache: &mut UpstreamCache,
398 upstream_sm: &SourceMap,
399 upstream_name: u32,
400 builder: &mut B,
401) -> u32 {
402 let ni = upstream_name as usize;
403 if let Some(idx) = cache.name_remap[ni] {
404 return idx;
405 }
406 let idx = builder.add_name(&upstream_sm.names[ni]);
407 cache.name_remap[ni] = Some(idx);
408 idx
409}
410
411#[inline]
420fn lookup_upstream(upstream_sm: &SourceMap, line: u32, column: u32) -> Option<UpstreamLookup> {
421 let line_mappings = upstream_sm.mappings_for_line(line);
422 if line_mappings.is_empty() {
423 return fallback_to_full_lookup(upstream_sm, line, column);
424 }
425
426 let idx = match line_mappings.binary_search_by_key(&column, |m| m.generated_column) {
427 Ok(i) => i,
428 Err(0) => return fallback_to_full_lookup(upstream_sm, line, column),
429 Err(i) => i - 1,
430 };
431
432 let mapping = &line_mappings[idx];
433 if mapping.source == NO_SOURCE {
434 return None;
435 }
436
437 let original_column = if mapping.is_range_mapping && column >= mapping.generated_column {
438 mapping.original_column + (column - mapping.generated_column)
439 } else {
440 mapping.original_column
441 };
442
443 Some(UpstreamLookup {
444 source: mapping.source,
445 original_line: mapping.original_line,
446 original_column,
447 name: mapping.name,
448 })
449}
450
451struct UpstreamLookup {
455 source: u32,
456 original_line: u32,
457 original_column: u32,
458 name: u32,
459}
460
461fn fallback_to_full_lookup(
466 upstream_sm: &SourceMap,
467 line: u32,
468 column: u32,
469) -> Option<UpstreamLookup> {
470 let loc = upstream_sm.original_position_for(line, column)?;
471 Some(UpstreamLookup {
472 source: loc.source,
473 original_line: loc.line,
474 original_column: loc.column,
475 name: loc.name.unwrap_or(NO_NAME),
476 })
477}
478
479#[inline]
481fn resolve_outer_name_cached<B: RemapBuilder>(
482 outer_name_remap: &mut [Option<u32>],
483 name_idx: u32,
484 names: &[String],
485 builder: &mut B,
486) -> Option<u32> {
487 if name_idx == NO_NAME {
488 return None;
489 }
490 let slot = outer_name_remap.get_mut(name_idx as usize)?;
491 if let Some(idx) = *slot {
492 return Some(idx);
493 }
494 let outer_name = names.get(name_idx as usize)?;
495 let idx = builder.add_name(outer_name);
496 *slot = Some(idx);
497 Some(idx)
498}
499
500#[inline]
503#[allow(
504 clippy::too_many_arguments,
505 reason = "passing remapped indices avoids per-mapping hashing in the hot path"
506)]
507fn emit_remapped_mapping<B: RemapBuilder>(
508 builder: &mut B,
509 gen_line: u32,
510 gen_col: u32,
511 builder_src: u32,
512 orig_line: u32,
513 orig_col: u32,
514 builder_name: Option<u32>,
515 is_range: bool,
516) {
517 match (builder_name, is_range) {
518 (Some(n), true) => {
519 builder.add_named_range_mapping(gen_line, gen_col, builder_src, orig_line, orig_col, n);
520 }
521 (Some(n), false) => {
522 builder.add_named_mapping(gen_line, gen_col, builder_src, orig_line, orig_col, n);
523 }
524 (None, true) => {
525 builder.add_range_mapping(gen_line, gen_col, builder_src, orig_line, orig_col);
526 }
527 (None, false) => {
528 builder.add_mapping(gen_line, gen_col, builder_src, orig_line, orig_col);
529 }
530 }
531}
532
533enum SourceEntry {
536 Upstream { map: Box<SourceMap>, cache: UpstreamCache },
538 Passthrough { builder_src: u32 },
540 EmptySource,
543 Unloaded,
545}
546
547struct DedupeState {
550 last_gen_line: u32,
552 line_index: u32,
554 last_was_sourceless: bool,
556 last_source: Option<(u32, u32, u32, Option<u32>)>,
558}
559
560impl DedupeState {
561 fn new() -> Self {
562 Self {
563 last_gen_line: u32::MAX,
564 line_index: 0,
565 last_was_sourceless: false,
566 last_source: None,
567 }
568 }
569
570 fn skip_sourceless(&self, gen_line: u32) -> bool {
573 if gen_line != self.last_gen_line {
574 return true;
576 }
577 self.last_was_sourceless
579 }
580
581 fn skip_source(
584 &self,
585 gen_line: u32,
586 source: u32,
587 orig_line: u32,
588 orig_col: u32,
589 name: Option<u32>,
590 ) -> bool {
591 if gen_line != self.last_gen_line {
592 return false;
594 }
595 if self.last_was_sourceless {
596 return false;
598 }
599 self.last_source == Some((source, orig_line, orig_col, name))
601 }
602
603 fn record_sourceless(&mut self, gen_line: u32) {
605 if gen_line != self.last_gen_line {
606 self.last_gen_line = gen_line;
607 self.line_index = 0;
608 self.last_source = None;
609 }
610 self.line_index += 1;
611 self.last_was_sourceless = true;
612 }
613
614 fn record_source(
616 &mut self,
617 gen_line: u32,
618 source: u32,
619 orig_line: u32,
620 orig_col: u32,
621 name: Option<u32>,
622 ) {
623 if gen_line != self.last_gen_line {
624 self.last_gen_line = gen_line;
625 self.line_index = 0;
626 }
627 self.line_index += 1;
628 self.last_was_sourceless = false;
629 self.last_source = Some((source, orig_line, orig_col, name));
630 }
631}
632
633pub fn remap<F>(outer: &SourceMap, loader: F) -> SourceMap
650where
651 F: Fn(&str) -> Option<SourceMap>,
652{
653 let mapping_count = outer.mapping_count();
654 let source_count = outer.sources.len();
655 let mut builder = SourceMapGenerator::with_capacity(outer.file.clone(), mapping_count);
656 builder.set_assume_sorted(true);
658
659 let mut source_entries: Vec<SourceEntry> =
661 std::iter::repeat_with(|| SourceEntry::Unloaded).take(source_count).collect();
662
663 let mut ignored_sources: HashSet<u32> = HashSet::new();
664
665 let mut outer_name_remap: Vec<Option<u32>> = vec![None; outer.names.len()];
667
668 let outer_ignore_set: HashSet<u32> = outer.ignore_list.iter().copied().collect();
670
671 let mut dedup = DedupeState::new();
672
673 for m in outer.all_mappings() {
674 if m.source == NO_SOURCE {
675 trace_and_emit_sourceless(
676 &mut builder,
677 &mut dedup,
678 m.generated_line,
679 m.generated_column,
680 );
681 continue;
682 }
683
684 let si = m.source as usize;
685
686 load_source_entry(
688 &mut source_entries,
689 si,
690 outer,
691 m.source,
692 SourceEntryLoadContext {
693 builder: &mut builder,
694 outer_ignore_set: &outer_ignore_set,
695 ignored_sources: &mut ignored_sources,
696 },
697 &loader,
698 );
699
700 trace_source_entry(
701 &mut source_entries[si],
702 m,
703 RemapTraceContext {
704 builder: &mut builder,
705 dedup: &mut dedup,
706 outer_name_remap: &mut outer_name_remap,
707 names: &outer.names,
708 ignored_sources: &mut ignored_sources,
709 },
710 );
711 }
712
713 builder.to_decoded_map()
714}
715
716struct RemapTraceContext<'a> {
717 builder: &'a mut SourceMapGenerator,
718 dedup: &'a mut DedupeState,
719 outer_name_remap: &'a mut [Option<u32>],
720 names: &'a [String],
721 ignored_sources: &'a mut HashSet<u32>,
722}
723
724fn trace_source_entry(
725 entry: &mut SourceEntry,
726 m: &srcmap_sourcemap::Mapping,
727 ctx: RemapTraceContext<'_>,
728) {
729 match entry {
730 SourceEntry::Upstream { map, cache } => {
731 if let Some(upstream_m) = lookup_upstream(map, m.original_line, m.original_column) {
732 trace_and_emit_upstream(
733 ctx.builder,
734 ctx.dedup,
735 UpstreamEmitContext {
736 gen_line: m.generated_line,
737 gen_col: m.generated_column,
738 upstream_m,
739 cache,
740 upstream_map: map,
741 outer_name_remap: ctx.outer_name_remap,
742 outer_name_idx: m.name,
743 names: ctx.names,
744 ignored_sources: ctx.ignored_sources,
745 is_range: m.is_range_mapping,
746 },
747 );
748 }
749 }
750 SourceEntry::Passthrough { builder_src } => {
751 trace_and_emit_passthrough(
752 ctx.builder,
753 ctx.dedup,
754 PassthroughEmitContext {
755 gen_line: m.generated_line,
756 gen_col: m.generated_column,
757 orig_line: m.original_line,
758 orig_col: m.original_column,
759 builder_src: *builder_src,
760 outer_name_remap: ctx.outer_name_remap,
761 outer_name_idx: m.name,
762 names: ctx.names,
763 is_range: m.is_range_mapping,
764 },
765 );
766 }
767 SourceEntry::EmptySource => {
768 trace_and_emit_sourceless(ctx.builder, ctx.dedup, m.generated_line, m.generated_column);
769 }
770 SourceEntry::Unloaded => unreachable!(),
771 }
772}
773
774struct SourceEntryLoadContext<'a, B> {
775 builder: &'a mut B,
776 outer_ignore_set: &'a HashSet<u32>,
777 ignored_sources: &'a mut HashSet<u32>,
778}
779
780fn load_source_entry<F>(
781 source_entries: &mut [SourceEntry],
782 si: usize,
783 outer: &SourceMap,
784 outer_source_idx: u32,
785 ctx: SourceEntryLoadContext<'_, SourceMapGenerator>,
786 loader: &F,
787) where
788 F: Fn(&str) -> Option<SourceMap>,
789{
790 if !matches!(source_entries[si], SourceEntry::Unloaded) {
791 return;
792 }
793
794 let source_name = outer.source(outer_source_idx);
795 if source_name.is_empty() {
798 source_entries[si] = SourceEntry::EmptySource;
799 return;
800 }
801
802 match loader(source_name) {
803 Some(upstream_sm) => {
804 let cache = build_upstream_cache(&upstream_sm);
805 source_entries[si] = SourceEntry::Upstream { map: Box::new(upstream_sm), cache };
806 }
807 None => {
808 let idx = ctx.builder.add_source(source_name);
809 if let Some(Some(content)) = outer.sources_content.get(si) {
810 ctx.builder.set_source_content(idx, content.clone());
811 }
812 if ctx.outer_ignore_set.contains(&outer_source_idx) && ctx.ignored_sources.insert(idx) {
813 ctx.builder.add_to_ignore_list(idx);
814 }
815 source_entries[si] = SourceEntry::Passthrough { builder_src: idx };
816 }
817 }
818}
819
820pub fn remap_chain(maps: &[&SourceMap]) -> Option<SourceMap> {
851 if maps.is_empty() {
852 return None;
853 }
854 if maps.len() == 1 {
855 return Some(maps[0].clone());
856 }
857
858 let mut current = compose_pair(maps[maps.len() - 2], maps[maps.len() - 1]);
870
871 for i in (0..maps.len() - 2).rev() {
873 current = compose_pair(maps[i], ¤t);
874 }
875
876 Some(current)
877}
878
879fn compose_pair(outer: &SourceMap, inner: &SourceMap) -> SourceMap {
884 let fallback_source = if inner.file.is_none() {
885 let mut sources = outer.sources.iter().filter(|source| !source.is_empty());
886 match (sources.next(), sources.next()) {
887 (Some(source), None) => Some(source.clone()),
888 _ => None,
889 }
890 } else {
891 None
892 };
893
894 remap(outer, |source| {
895 if inner.file.as_deref() == Some(source) || fallback_source.as_deref() == Some(source) {
896 Some(inner.clone())
897 } else {
898 None
899 }
900 })
901}
902
903enum StreamingSourceEntry {
905 Upstream { map: Box<SourceMap>, cache: UpstreamCache },
907 Passthrough { builder_src: u32 },
909 EmptySource,
911 Unloaded,
913}
914
915pub fn remap_streaming<'a, F>(
928 mappings_iter: srcmap_sourcemap::MappingsIter<'a>,
929 sources: &[String],
930 names: &[String],
931 sources_content: &[Option<String>],
932 ignore_list: &[u32],
933 file: Option<String>,
934 loader: F,
935) -> SourceMap
936where
937 F: Fn(&str) -> Option<SourceMap>,
938{
939 let mut builder = StreamingGenerator::with_capacity(file, 4096);
940
941 let mut source_entries: Vec<StreamingSourceEntry> =
943 std::iter::repeat_with(|| StreamingSourceEntry::Unloaded).take(sources.len()).collect();
944
945 let mut ignored_sources: HashSet<u32> = HashSet::new();
946
947 let mut outer_name_remap: Vec<Option<u32>> = vec![None; names.len()];
949
950 let outer_ignore_set: HashSet<u32> = ignore_list.iter().copied().collect();
952
953 let mut dedup = DedupeState::new();
954
955 for item in mappings_iter {
956 let m = match item {
957 Ok(m) => m,
958 Err(_) => continue,
959 };
960
961 if m.source == NO_SOURCE {
962 trace_and_emit_sourceless(
963 &mut builder,
964 &mut dedup,
965 m.generated_line,
966 m.generated_column,
967 );
968 continue;
969 }
970
971 let si = m.source as usize;
972 if si >= sources.len() {
973 continue;
974 }
975
976 load_streaming_source_entry(
977 &mut source_entries,
978 si,
979 sources,
980 sources_content,
981 m.source,
982 SourceEntryLoadContext {
983 builder: &mut builder,
984 outer_ignore_set: &outer_ignore_set,
985 ignored_sources: &mut ignored_sources,
986 },
987 &loader,
988 );
989
990 trace_streaming_source_entry(
991 &mut source_entries[si],
992 &m,
993 StreamingTraceContext {
994 builder: &mut builder,
995 dedup: &mut dedup,
996 outer_name_remap: &mut outer_name_remap,
997 names,
998 ignored_sources: &mut ignored_sources,
999 },
1000 );
1001 }
1002
1003 builder.to_decoded_map().expect("streaming VLQ should be valid")
1004}
1005
1006struct StreamingTraceContext<'a> {
1007 builder: &'a mut StreamingGenerator,
1008 dedup: &'a mut DedupeState,
1009 outer_name_remap: &'a mut [Option<u32>],
1010 names: &'a [String],
1011 ignored_sources: &'a mut HashSet<u32>,
1012}
1013
1014fn trace_streaming_source_entry(
1015 entry: &mut StreamingSourceEntry,
1016 m: &srcmap_sourcemap::Mapping,
1017 ctx: StreamingTraceContext<'_>,
1018) {
1019 match entry {
1020 StreamingSourceEntry::Upstream { map, cache } => {
1021 if let Some(upstream_m) = lookup_upstream(map, m.original_line, m.original_column) {
1022 trace_and_emit_upstream(
1023 ctx.builder,
1024 ctx.dedup,
1025 UpstreamEmitContext {
1026 gen_line: m.generated_line,
1027 gen_col: m.generated_column,
1028 upstream_m,
1029 cache,
1030 upstream_map: map,
1031 outer_name_remap: ctx.outer_name_remap,
1032 outer_name_idx: m.name,
1033 names: ctx.names,
1034 ignored_sources: ctx.ignored_sources,
1035 is_range: m.is_range_mapping,
1036 },
1037 );
1038 }
1039 }
1040 StreamingSourceEntry::Passthrough { builder_src } => {
1041 trace_and_emit_passthrough(
1042 ctx.builder,
1043 ctx.dedup,
1044 PassthroughEmitContext {
1045 gen_line: m.generated_line,
1046 gen_col: m.generated_column,
1047 orig_line: m.original_line,
1048 orig_col: m.original_column,
1049 builder_src: *builder_src,
1050 outer_name_remap: ctx.outer_name_remap,
1051 outer_name_idx: m.name,
1052 names: ctx.names,
1053 is_range: m.is_range_mapping,
1054 },
1055 );
1056 }
1057 StreamingSourceEntry::EmptySource => {
1058 trace_and_emit_sourceless(ctx.builder, ctx.dedup, m.generated_line, m.generated_column);
1059 }
1060 StreamingSourceEntry::Unloaded => unreachable!(),
1061 }
1062}
1063
1064fn load_streaming_source_entry<F>(
1065 source_entries: &mut [StreamingSourceEntry],
1066 si: usize,
1067 sources: &[String],
1068 sources_content: &[Option<String>],
1069 outer_source_idx: u32,
1070 ctx: SourceEntryLoadContext<'_, StreamingGenerator>,
1071 loader: &F,
1072) where
1073 F: Fn(&str) -> Option<SourceMap>,
1074{
1075 if !matches!(source_entries[si], StreamingSourceEntry::Unloaded) {
1076 return;
1077 }
1078
1079 let source_name = &sources[si];
1080 if source_name.is_empty() {
1081 source_entries[si] = StreamingSourceEntry::EmptySource;
1082 return;
1083 }
1084
1085 match loader(source_name) {
1086 Some(upstream_sm) => {
1087 let cache = build_upstream_cache(&upstream_sm);
1088 source_entries[si] =
1089 StreamingSourceEntry::Upstream { map: Box::new(upstream_sm), cache };
1090 }
1091 None => {
1092 let idx = ctx.builder.add_source(source_name);
1093 if let Some(Some(content)) = sources_content.get(si) {
1094 ctx.builder.set_source_content(idx, content.clone());
1095 }
1096 if ctx.outer_ignore_set.contains(&outer_source_idx) && ctx.ignored_sources.insert(idx) {
1097 ctx.builder.add_to_ignore_list(idx);
1098 }
1099 source_entries[si] = StreamingSourceEntry::Passthrough { builder_src: idx };
1100 }
1101 }
1102}
1103
1104#[inline]
1105fn emit_generated_mapping<B: RemapBuilder>(builder: &mut B, gen_line: u32, gen_col: u32) {
1106 builder.add_generated_mapping(gen_line, gen_col);
1107}
1108
1109struct UpstreamEmitContext<'a> {
1110 gen_line: u32,
1111 gen_col: u32,
1112 upstream_m: UpstreamLookup,
1113 cache: &'a mut UpstreamCache,
1114 upstream_map: &'a SourceMap,
1115 outer_name_remap: &'a mut [Option<u32>],
1116 outer_name_idx: u32,
1117 names: &'a [String],
1118 ignored_sources: &'a mut HashSet<u32>,
1119 is_range: bool,
1120}
1121
1122#[inline]
1123fn trace_and_emit_upstream<B: RemapBuilder>(
1124 builder: &mut B,
1125 dedup: &mut DedupeState,
1126 ctx: UpstreamEmitContext<'_>,
1127) {
1128 let UpstreamEmitContext {
1129 gen_line,
1130 gen_col,
1131 upstream_m,
1132 cache,
1133 upstream_map,
1134 outer_name_remap,
1135 outer_name_idx,
1136 names,
1137 ignored_sources,
1138 is_range,
1139 } = ctx;
1140 let builder_src =
1141 resolve_upstream_source(cache, upstream_map, upstream_m.source, builder, ignored_sources);
1142
1143 let builder_name = if upstream_m.name != NO_NAME {
1144 Some(resolve_upstream_name(cache, upstream_map, upstream_m.name, builder))
1145 } else {
1146 resolve_outer_name_cached(outer_name_remap, outer_name_idx, names, builder)
1147 };
1148
1149 if !dedup.skip_source(
1150 gen_line,
1151 builder_src,
1152 upstream_m.original_line,
1153 upstream_m.original_column,
1154 builder_name,
1155 ) {
1156 emit_remapped_mapping(
1157 builder,
1158 gen_line,
1159 gen_col,
1160 builder_src,
1161 upstream_m.original_line,
1162 upstream_m.original_column,
1163 builder_name,
1164 is_range,
1165 );
1166 }
1167 dedup.record_source(
1168 gen_line,
1169 builder_src,
1170 upstream_m.original_line,
1171 upstream_m.original_column,
1172 builder_name,
1173 );
1174}
1175
1176struct PassthroughEmitContext<'a> {
1177 gen_line: u32,
1178 gen_col: u32,
1179 orig_line: u32,
1180 orig_col: u32,
1181 builder_src: u32,
1182 outer_name_remap: &'a mut [Option<u32>],
1183 outer_name_idx: u32,
1184 names: &'a [String],
1185 is_range: bool,
1186}
1187
1188#[inline]
1189fn trace_and_emit_passthrough<B: RemapBuilder>(
1190 builder: &mut B,
1191 dedup: &mut DedupeState,
1192 ctx: PassthroughEmitContext<'_>,
1193) {
1194 let PassthroughEmitContext {
1195 gen_line,
1196 gen_col,
1197 orig_line,
1198 orig_col,
1199 builder_src,
1200 outer_name_remap,
1201 outer_name_idx,
1202 names,
1203 is_range,
1204 } = ctx;
1205 let builder_name = resolve_outer_name_cached(outer_name_remap, outer_name_idx, names, builder);
1206
1207 if !dedup.skip_source(gen_line, builder_src, orig_line, orig_col, builder_name) {
1208 emit_remapped_mapping(
1209 builder,
1210 gen_line,
1211 gen_col,
1212 builder_src,
1213 orig_line,
1214 orig_col,
1215 builder_name,
1216 is_range,
1217 );
1218 }
1219 dedup.record_source(gen_line, builder_src, orig_line, orig_col, builder_name);
1220}
1221
1222#[inline]
1223fn trace_and_emit_sourceless<B: RemapBuilder>(
1224 builder: &mut B,
1225 dedup: &mut DedupeState,
1226 gen_line: u32,
1227 gen_col: u32,
1228) {
1229 if !dedup.skip_sourceless(gen_line) {
1230 emit_generated_mapping(builder, gen_line, gen_col);
1231 }
1232 dedup.record_sourceless(gen_line);
1233}
1234
1235#[cfg(test)]
1238mod tests {
1239 use super::*;
1240
1241 #[test]
1244 fn concat_two_simple_maps() {
1245 let a = SourceMap::from_json(
1246 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#,
1247 )
1248 .unwrap();
1249 let b = SourceMap::from_json(
1250 r#"{"version":3,"sources":["b.js"],"names":[],"mappings":"AAAA"}"#,
1251 )
1252 .unwrap();
1253
1254 let mut builder = ConcatBuilder::new(Some("bundle.js".to_string()));
1255 builder.add_map(&a, 0);
1256 builder.add_map(&b, 1);
1257
1258 let result = builder.build();
1259 assert_eq!(result.sources, vec!["a.js", "b.js"]);
1260 assert_eq!(result.mapping_count(), 2);
1261
1262 let loc0 = result.original_position_for(0, 0).unwrap();
1263 assert_eq!(result.source(loc0.source), "a.js");
1264
1265 let loc1 = result.original_position_for(1, 0).unwrap();
1266 assert_eq!(result.source(loc1.source), "b.js");
1267 }
1268
1269 #[test]
1270 fn concat_deduplicates_sources() {
1271 let a = SourceMap::from_json(
1272 r#"{"version":3,"sources":["shared.js"],"names":[],"mappings":"AAAA"}"#,
1273 )
1274 .unwrap();
1275 let b = SourceMap::from_json(
1276 r#"{"version":3,"sources":["shared.js"],"names":[],"mappings":"AAAA"}"#,
1277 )
1278 .unwrap();
1279
1280 let mut builder = ConcatBuilder::new(None);
1281 builder.add_map(&a, 0);
1282 builder.add_map(&b, 10);
1283
1284 let result = builder.build();
1285 assert_eq!(result.sources.len(), 1);
1286 assert_eq!(result.sources[0], "shared.js");
1287 }
1288
1289 #[test]
1290 fn concat_with_names() {
1291 let a = SourceMap::from_json(
1292 r#"{"version":3,"sources":["a.js"],"names":["foo"],"mappings":"AAAAA"}"#,
1293 )
1294 .unwrap();
1295 let b = SourceMap::from_json(
1296 r#"{"version":3,"sources":["b.js"],"names":["bar"],"mappings":"AAAAA"}"#,
1297 )
1298 .unwrap();
1299
1300 let mut builder = ConcatBuilder::new(None);
1301 builder.add_map(&a, 0);
1302 builder.add_map(&b, 1);
1303
1304 let result = builder.build();
1305 assert_eq!(result.names.len(), 2);
1306
1307 let loc0 = result.original_position_for(0, 0).unwrap();
1308 assert_eq!(loc0.name, Some(0));
1309 assert_eq!(result.name(0), "foo");
1310
1311 let loc1 = result.original_position_for(1, 0).unwrap();
1312 assert_eq!(loc1.name, Some(1));
1313 assert_eq!(result.name(1), "bar");
1314 }
1315
1316 #[test]
1317 fn concat_preserves_multi_line_maps() {
1318 let a = SourceMap::from_json(
1319 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;AACA;AACA"}"#,
1320 )
1321 .unwrap();
1322
1323 let mut builder = ConcatBuilder::new(None);
1324 builder.add_map(&a, 5); let result = builder.build();
1327 assert!(result.original_position_for(5, 0).is_some());
1328 assert!(result.original_position_for(6, 0).is_some());
1329 assert!(result.original_position_for(7, 0).is_some());
1330 assert!(result.original_position_for(4, 0).is_none());
1331 }
1332
1333 #[test]
1334 fn concat_with_sources_content() {
1335 let a = SourceMap::from_json(
1336 r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":[],"mappings":"AAAA"}"#,
1337 )
1338 .unwrap();
1339
1340 let mut builder = ConcatBuilder::new(None);
1341 builder.add_map(&a, 0);
1342
1343 let result = builder.build();
1344 assert_eq!(result.sources_content, vec![Some("var a;".to_string())]);
1345 }
1346
1347 #[test]
1348 fn concat_empty_builder() {
1349 let builder = ConcatBuilder::new(Some("empty.js".to_string()));
1350 let result = builder.build();
1351 assert_eq!(result.mapping_count(), 0);
1352 assert_eq!(result.sources.len(), 0);
1353 }
1354
1355 #[test]
1358 fn remap_single_level() {
1359 let outer = SourceMap::from_json(
1364 r#"{"version":3,"sources":["intermediate.js","other.js"],"names":[],"mappings":"AAAA,KCAA;ADCA"}"#,
1365 )
1366 .unwrap();
1367
1368 let inner = SourceMap::from_json(
1370 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AACA;AACA"}"#,
1371 )
1372 .unwrap();
1373
1374 let result =
1375 remap(
1376 &outer,
1377 |source| {
1378 if source == "intermediate.js" { Some(inner.clone()) } else { None }
1379 },
1380 );
1381
1382 assert!(result.sources.contains(&"original.js".to_string()));
1383 assert!(result.sources.contains(&"other.js".to_string()));
1385
1386 let loc = result.original_position_for(0, 0).unwrap();
1388 assert_eq!(result.source(loc.source), "original.js");
1389 assert_eq!(loc.line, 1);
1390 }
1391
1392 #[test]
1393 fn remap_accepts_source_map_built_from_parts() {
1394 let outer = SourceMap::from_parts(
1395 Some("output.js".to_string()),
1396 None,
1397 vec!["intermediate.js".to_string()],
1398 vec![],
1399 vec![],
1400 vec![srcmap_sourcemap::Mapping {
1401 generated_line: 0,
1402 generated_column: 0,
1403 source: 0,
1404 original_line: 0,
1405 original_column: 0,
1406 name: u32::MAX,
1407 is_range_mapping: false,
1408 }],
1409 vec![],
1410 None,
1411 None,
1412 );
1413 let inner = SourceMap::builder()
1414 .file("intermediate.js")
1415 .sources(["original.ts"])
1416 .sources_content([Some("export const value = 1;")])
1417 .mappings([srcmap_sourcemap::Mapping {
1418 generated_line: 0,
1419 generated_column: 0,
1420 source: 0,
1421 original_line: 3,
1422 original_column: 12,
1423 name: u32::MAX,
1424 is_range_mapping: false,
1425 }])
1426 .build();
1427
1428 let result =
1429 remap(
1430 &outer,
1431 |source| {
1432 if source == "intermediate.js" { Some(inner.clone()) } else { None }
1433 },
1434 );
1435
1436 let loc = result.original_position_for(0, 0).unwrap();
1437 assert_eq!(result.source(loc.source), "original.ts");
1438 assert_eq!(loc.line, 3);
1439 assert_eq!(loc.column, 12);
1440 assert_eq!(result.sources_content, vec![Some("export const value = 1;".to_string())]);
1441 }
1442
1443 #[test]
1444 fn remap_no_upstream_passthrough() {
1445 let outer = SourceMap::from_json(
1446 r#"{"version":3,"sources":["already-original.js"],"names":[],"mappings":"AAAA"}"#,
1447 )
1448 .unwrap();
1449
1450 let result = remap(&outer, |_| None);
1452
1453 assert_eq!(result.sources, vec!["already-original.js"]);
1454 let loc = result.original_position_for(0, 0).unwrap();
1455 assert_eq!(result.source(loc.source), "already-original.js");
1456 assert_eq!(loc.line, 0);
1457 assert_eq!(loc.column, 0);
1458 }
1459
1460 #[test]
1461 fn remap_partial_sources() {
1462 let outer = SourceMap::from_json(
1464 r#"{"version":3,"sources":["compiled.js","passthrough.js"],"names":[],"mappings":"AAAA,KCCA"}"#,
1465 )
1466 .unwrap();
1467
1468 let inner = SourceMap::from_json(
1469 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":"AAAA"}"#,
1470 )
1471 .unwrap();
1472
1473 let result =
1474 remap(
1475 &outer,
1476 |source| {
1477 if source == "compiled.js" { Some(inner.clone()) } else { None }
1478 },
1479 );
1480
1481 assert!(result.sources.contains(&"original.ts".to_string()));
1483 assert!(result.sources.contains(&"passthrough.js".to_string()));
1484 }
1485
1486 #[test]
1487 fn remap_preserves_names() {
1488 let outer = SourceMap::from_json(
1489 r#"{"version":3,"sources":["compiled.js"],"names":["myFunc"],"mappings":"AAAAA"}"#,
1490 )
1491 .unwrap();
1492
1493 let inner = SourceMap::from_json(
1495 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":"AAAA"}"#,
1496 )
1497 .unwrap();
1498
1499 let result = remap(&outer, |_| Some(inner.clone()));
1500
1501 let loc = result.original_position_for(0, 0).unwrap();
1502 assert!(loc.name.is_some());
1503 assert_eq!(result.name(loc.name.unwrap()), "myFunc");
1504 }
1505
1506 #[test]
1507 fn remap_upstream_name_wins() {
1508 let outer = SourceMap::from_json(
1509 r#"{"version":3,"sources":["compiled.js"],"names":["outerName"],"mappings":"AAAAA"}"#,
1510 )
1511 .unwrap();
1512
1513 let inner = SourceMap::from_json(
1515 r#"{"version":3,"sources":["original.ts"],"names":["innerName"],"mappings":"AAAAA"}"#,
1516 )
1517 .unwrap();
1518
1519 let result = remap(&outer, |_| Some(inner.clone()));
1520
1521 let loc = result.original_position_for(0, 0).unwrap();
1522 assert!(loc.name.is_some());
1523 assert_eq!(result.name(loc.name.unwrap()), "innerName");
1524 }
1525
1526 #[test]
1527 fn remap_sources_content_from_upstream() {
1528 let outer = SourceMap::from_json(
1529 r#"{"version":3,"sources":["compiled.js"],"names":[],"mappings":"AAAA"}"#,
1530 )
1531 .unwrap();
1532
1533 let inner = SourceMap::from_json(
1534 r#"{"version":3,"sources":["original.ts"],"sourcesContent":["const x = 1;"],"names":[],"mappings":"AAAA"}"#,
1535 )
1536 .unwrap();
1537
1538 let result = remap(&outer, |_| Some(inner.clone()));
1539
1540 assert_eq!(result.sources_content, vec![Some("const x = 1;".to_string())]);
1541 }
1542
1543 #[test]
1546 fn concat_updates_source_content_on_duplicate() {
1547 let a = SourceMap::from_json(
1549 r#"{"version":3,"sources":["shared.js"],"names":[],"mappings":"AAAA"}"#,
1550 )
1551 .unwrap();
1552 let b = SourceMap::from_json(
1553 r#"{"version":3,"sources":["shared.js"],"sourcesContent":["var x = 1;"],"names":[],"mappings":"AAAA"}"#,
1554 )
1555 .unwrap();
1556
1557 let mut builder = ConcatBuilder::new(None);
1558 builder.add_map(&a, 0);
1559 builder.add_map(&b, 1);
1560
1561 let result = builder.build();
1562 assert_eq!(result.sources.len(), 1);
1563 assert_eq!(result.sources_content, vec![Some("var x = 1;".to_string())]);
1564 }
1565
1566 #[test]
1567 fn concat_deduplicates_names() {
1568 let a = SourceMap::from_json(
1569 r#"{"version":3,"sources":["a.js"],"names":["sharedName"],"mappings":"AAAAA"}"#,
1570 )
1571 .unwrap();
1572 let b = SourceMap::from_json(
1573 r#"{"version":3,"sources":["b.js"],"names":["sharedName"],"mappings":"AAAAA"}"#,
1574 )
1575 .unwrap();
1576
1577 let mut builder = ConcatBuilder::new(None);
1578 builder.add_map(&a, 0);
1579 builder.add_map(&b, 1);
1580
1581 let result = builder.build();
1582 assert_eq!(result.names.len(), 1);
1584 assert_eq!(result.names[0], "sharedName");
1585 }
1586
1587 #[test]
1588 fn concat_with_ignore_list() {
1589 let a = SourceMap::from_json(
1590 r#"{"version":3,"sources":["vendor.js"],"names":[],"mappings":"AAAA","ignoreList":[0]}"#,
1591 )
1592 .unwrap();
1593
1594 let mut builder = ConcatBuilder::new(None);
1595 builder.add_map(&a, 0);
1596
1597 let result = builder.build();
1598 assert_eq!(result.ignore_list, vec![0]);
1599 }
1600
1601 #[test]
1602 fn concat_with_generated_only_mappings() {
1603 let a = SourceMap::from_json(
1605 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A,AAAA"}"#,
1606 )
1607 .unwrap();
1608
1609 let mut builder = ConcatBuilder::new(None);
1610 builder.add_map(&a, 0);
1611
1612 let result = builder.build();
1613 assert!(result.mapping_count() >= 1);
1615 }
1616
1617 #[test]
1618 fn remap_generated_only_passthrough() {
1619 let outer = SourceMap::from_json(
1624 r#"{"version":3,"sources":["a.js","other.js"],"names":[],"mappings":"A,AAAA,KCAA"}"#,
1625 )
1626 .unwrap();
1627
1628 let inner = SourceMap::from_json(
1629 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AAAA"}"#,
1630 )
1631 .unwrap();
1632
1633 let result =
1634 remap(&outer, |source| if source == "a.js" { Some(inner.clone()) } else { None });
1635
1636 assert!(result.mapping_count() >= 2);
1638 assert!(result.sources.contains(&"original.js".to_string()));
1639 assert!(result.sources.contains(&"other.js".to_string()));
1640 }
1641
1642 #[test]
1643 fn remap_no_upstream_mapping_with_name() {
1644 let outer = SourceMap::from_json(
1646 r#"{"version":3,"sources":["compiled.js"],"names":["myFunc"],"mappings":"AAAAA"}"#,
1647 )
1648 .unwrap();
1649
1650 let inner = SourceMap::from_json(
1652 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":";;;;AAAA"}"#,
1653 )
1654 .unwrap();
1655
1656 let result = remap(&outer, |_| Some(inner.clone()));
1657
1658 let loc = result.original_position_for(0, 0);
1662 assert!(loc.is_none());
1663 }
1664
1665 #[test]
1666 fn remap_no_upstream_with_sources_content_and_name() {
1667 let outer = SourceMap::from_json(
1668 r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":["fn1"],"mappings":"AAAAA"}"#,
1669 )
1670 .unwrap();
1671
1672 let result = remap(&outer, |_| None);
1674
1675 assert_eq!(result.sources, vec!["a.js"]);
1676 assert_eq!(result.sources_content, vec![Some("var a;".to_string())]);
1677 let loc = result.original_position_for(0, 0).unwrap();
1678 assert!(loc.name.is_some());
1679 assert_eq!(result.name(loc.name.unwrap()), "fn1");
1680 }
1681
1682 #[test]
1683 fn remap_no_upstream_no_name() {
1684 let outer = SourceMap::from_json(
1685 r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":[],"mappings":"AAAA"}"#,
1686 )
1687 .unwrap();
1688
1689 let result = remap(&outer, |_| None);
1690 let loc = result.original_position_for(0, 0).unwrap();
1691 assert!(loc.name.is_none());
1692 }
1693
1694 #[test]
1695 fn remap_no_upstream_mapping_no_name() {
1696 let outer = SourceMap::from_json(
1699 r#"{"version":3,"sources":["compiled.js"],"names":[],"mappings":"AAAA"}"#,
1700 )
1701 .unwrap();
1702
1703 let inner = SourceMap::from_json(
1706 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":";;;;AAAA"}"#,
1707 )
1708 .unwrap();
1709
1710 let result = remap(&outer, |_| Some(inner.clone()));
1711
1712 let loc = result.original_position_for(0, 0);
1714 assert!(loc.is_none());
1715 }
1716
1717 #[test]
1718 fn remap_upstream_found_no_name() {
1719 let outer = SourceMap::from_json(
1727 r#"{"version":3,"sources":["intermediate.js"],"names":[],"mappings":"AAAA"}"#,
1728 )
1729 .unwrap();
1730
1731 let inner = SourceMap::from_json(
1733 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AAAA"}"#,
1734 )
1735 .unwrap();
1736
1737 let result = remap(&outer, |_| Some(inner.clone()));
1738
1739 assert_eq!(result.sources, vec!["original.js"]);
1740 let loc = result.original_position_for(0, 0).unwrap();
1741 assert_eq!(result.source(loc.source), "original.js");
1742 assert_eq!(loc.line, 0);
1743 assert_eq!(loc.column, 0);
1744 assert!(loc.name.is_none());
1746 assert!(result.names.is_empty());
1747 }
1748
1749 #[test]
1752 fn concat_preserves_range_mappings() {
1753 let a = SourceMap::from_json(
1754 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,CAAC","rangeMappings":"A"}"#,
1755 )
1756 .unwrap();
1757
1758 let mut builder = ConcatBuilder::new(None);
1759 builder.add_map(&a, 0);
1760
1761 let result = builder.build();
1762 assert!(result.has_range_mappings());
1763 let mappings = result.all_mappings();
1764 assert!(mappings[0].is_range_mapping);
1765 assert!(!mappings[1].is_range_mapping);
1766 }
1767
1768 #[test]
1769 fn remap_preserves_range_mappings_passthrough() {
1770 let outer = SourceMap::from_json(
1771 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","rangeMappings":"A"}"#,
1772 )
1773 .unwrap();
1774
1775 let result = remap(&outer, |_| None);
1777 assert!(result.has_range_mappings());
1778 let mappings = result.all_mappings();
1779 assert!(mappings[0].is_range_mapping);
1780 }
1781
1782 #[test]
1783 fn remap_preserves_range_through_upstream() {
1784 let outer = SourceMap::from_json(
1785 r#"{"version":3,"sources":["intermediate.js"],"names":[],"mappings":"AAAA","rangeMappings":"A"}"#,
1786 )
1787 .unwrap();
1788
1789 let inner = SourceMap::from_json(
1790 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AACA"}"#,
1791 )
1792 .unwrap();
1793
1794 let result = remap(&outer, |_| Some(inner.clone()));
1795 assert!(result.has_range_mappings());
1796 }
1797
1798 #[test]
1799 fn remap_non_range_stays_non_range() {
1800 let outer = SourceMap::from_json(
1801 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#,
1802 )
1803 .unwrap();
1804
1805 let result = remap(&outer, |_| None);
1806 assert!(!result.has_range_mappings());
1807 }
1808
1809 fn streaming_from_sm<F>(sm: &SourceMap, loader: F) -> SourceMap
1814 where
1815 F: Fn(&str) -> Option<SourceMap>,
1816 {
1817 let vlq = sm.encode_mappings();
1818 let iter = srcmap_sourcemap::MappingsIter::new(&vlq);
1819 remap_streaming(
1820 iter,
1821 &sm.sources,
1822 &sm.names,
1823 &sm.sources_content,
1824 &sm.ignore_list,
1825 sm.file.clone(),
1826 loader,
1827 )
1828 }
1829
1830 #[test]
1831 fn streaming_single_level() {
1832 let outer = SourceMap::from_json(
1833 r#"{"version":3,"sources":["intermediate.js","other.js"],"names":[],"mappings":"AAAA,KCAA;ADCA"}"#,
1834 )
1835 .unwrap();
1836
1837 let inner = SourceMap::from_json(
1838 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AACA;AACA"}"#,
1839 )
1840 .unwrap();
1841
1842 let result = streaming_from_sm(&outer, |source| {
1843 if source == "intermediate.js" { Some(inner.clone()) } else { None }
1844 });
1845
1846 assert!(result.sources.contains(&"original.js".to_string()));
1847 assert!(result.sources.contains(&"other.js".to_string()));
1848
1849 let loc = result.original_position_for(0, 0).unwrap();
1850 assert_eq!(result.source(loc.source), "original.js");
1851 assert_eq!(loc.line, 1);
1852 }
1853
1854 #[test]
1855 fn streaming_no_upstream_passthrough() {
1856 let outer = SourceMap::from_json(
1857 r#"{"version":3,"sources":["already-original.js"],"names":[],"mappings":"AAAA"}"#,
1858 )
1859 .unwrap();
1860
1861 let result = streaming_from_sm(&outer, |_| None);
1862
1863 assert_eq!(result.sources, vec!["already-original.js"]);
1864 let loc = result.original_position_for(0, 0).unwrap();
1865 assert_eq!(result.source(loc.source), "already-original.js");
1866 assert_eq!(loc.line, 0);
1867 assert_eq!(loc.column, 0);
1868 }
1869
1870 #[test]
1871 fn streaming_preserves_names() {
1872 let outer = SourceMap::from_json(
1873 r#"{"version":3,"sources":["compiled.js"],"names":["myFunc"],"mappings":"AAAAA"}"#,
1874 )
1875 .unwrap();
1876
1877 let inner = SourceMap::from_json(
1878 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":"AAAA"}"#,
1879 )
1880 .unwrap();
1881
1882 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
1883
1884 let loc = result.original_position_for(0, 0).unwrap();
1885 assert!(loc.name.is_some());
1886 assert_eq!(result.name(loc.name.unwrap()), "myFunc");
1887 }
1888
1889 #[test]
1890 fn streaming_upstream_name_wins() {
1891 let outer = SourceMap::from_json(
1892 r#"{"version":3,"sources":["compiled.js"],"names":["outerName"],"mappings":"AAAAA"}"#,
1893 )
1894 .unwrap();
1895
1896 let inner = SourceMap::from_json(
1897 r#"{"version":3,"sources":["original.ts"],"names":["innerName"],"mappings":"AAAAA"}"#,
1898 )
1899 .unwrap();
1900
1901 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
1902
1903 let loc = result.original_position_for(0, 0).unwrap();
1904 assert!(loc.name.is_some());
1905 assert_eq!(result.name(loc.name.unwrap()), "innerName");
1906 }
1907
1908 #[test]
1909 fn streaming_sources_content_from_upstream() {
1910 let outer = SourceMap::from_json(
1911 r#"{"version":3,"sources":["compiled.js"],"names":[],"mappings":"AAAA"}"#,
1912 )
1913 .unwrap();
1914
1915 let inner = SourceMap::from_json(
1916 r#"{"version":3,"sources":["original.ts"],"sourcesContent":["const x = 1;"],"names":[],"mappings":"AAAA"}"#,
1917 )
1918 .unwrap();
1919
1920 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
1921
1922 assert_eq!(result.sources_content, vec![Some("const x = 1;".to_string())]);
1923 }
1924
1925 #[test]
1926 fn streaming_no_upstream_with_sources_content() {
1927 let outer = SourceMap::from_json(
1928 r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":["fn1"],"mappings":"AAAAA"}"#,
1929 )
1930 .unwrap();
1931
1932 let result = streaming_from_sm(&outer, |_| None);
1933
1934 assert_eq!(result.sources, vec!["a.js"]);
1935 assert_eq!(result.sources_content, vec![Some("var a;".to_string())]);
1936 let loc = result.original_position_for(0, 0).unwrap();
1937 assert!(loc.name.is_some());
1938 assert_eq!(result.name(loc.name.unwrap()), "fn1");
1939 }
1940
1941 #[test]
1942 fn streaming_generated_only_passthrough() {
1943 let outer = SourceMap::from_json(
1944 r#"{"version":3,"sources":["a.js","other.js"],"names":[],"mappings":"A,AAAA,KCAA"}"#,
1945 )
1946 .unwrap();
1947
1948 let inner = SourceMap::from_json(
1949 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AAAA"}"#,
1950 )
1951 .unwrap();
1952
1953 let result =
1954 streaming_from_sm(
1955 &outer,
1956 |source| {
1957 if source == "a.js" { Some(inner.clone()) } else { None }
1958 },
1959 );
1960
1961 assert!(result.mapping_count() >= 2);
1962 assert!(result.sources.contains(&"original.js".to_string()));
1963 assert!(result.sources.contains(&"other.js".to_string()));
1964 }
1965
1966 #[test]
1967 fn streaming_matches_remap() {
1968 let outer = SourceMap::from_json(
1970 r#"{"version":3,"sources":["intermediate.js","other.js"],"names":["foo"],"mappings":"AAAAA,KCAA;ADCA"}"#,
1971 )
1972 .unwrap();
1973
1974 let inner = SourceMap::from_json(
1975 r#"{"version":3,"sources":["original.js"],"sourcesContent":["// src"],"names":["bar"],"mappings":"AAAAA;AACA"}"#,
1976 )
1977 .unwrap();
1978
1979 let loader = |source: &str| -> Option<SourceMap> {
1980 if source == "intermediate.js" { Some(inner.clone()) } else { None }
1981 };
1982
1983 let result_normal = remap(&outer, loader);
1984 let result_stream = streaming_from_sm(&outer, loader);
1985
1986 assert_eq!(result_normal.sources, result_stream.sources);
1987 assert_eq!(result_normal.names, result_stream.names);
1988 assert_eq!(result_normal.sources_content, result_stream.sources_content);
1989 assert_eq!(result_normal.mapping_count(), result_stream.mapping_count());
1990
1991 for m in result_normal.all_mappings() {
1993 let loc_n = result_normal.original_position_for(m.generated_line, m.generated_column);
1994 let loc_s = result_stream.original_position_for(m.generated_line, m.generated_column);
1995 assert_eq!(loc_n.is_some(), loc_s.is_some());
1996 if let (Some(ln), Some(ls)) = (loc_n, loc_s) {
1997 assert_eq!(result_normal.source(ln.source), result_stream.source(ls.source));
1998 assert_eq!(ln.line, ls.line);
1999 assert_eq!(ln.column, ls.column);
2000 }
2001 }
2002 }
2003
2004 #[test]
2005 fn streaming_no_upstream_mapping_fallback() {
2006 let outer = SourceMap::from_json(
2007 r#"{"version":3,"sources":["compiled.js"],"names":["myFunc"],"mappings":"AAAAA"}"#,
2008 )
2009 .unwrap();
2010
2011 let inner = SourceMap::from_json(
2013 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":";;;;AAAA"}"#,
2014 )
2015 .unwrap();
2016
2017 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
2018
2019 let loc = result.original_position_for(0, 0);
2021 assert!(loc.is_none());
2022 }
2023
2024 #[test]
2025 fn streaming_no_upstream_mapping_no_name() {
2026 let outer = SourceMap::from_json(
2027 r#"{"version":3,"sources":["compiled.js"],"names":[],"mappings":"AAAA"}"#,
2028 )
2029 .unwrap();
2030
2031 let inner = SourceMap::from_json(
2032 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":";;;;AAAA"}"#,
2033 )
2034 .unwrap();
2035
2036 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
2037
2038 let loc = result.original_position_for(0, 0);
2040 assert!(loc.is_none());
2041 }
2042
2043 #[test]
2046 fn remap_chain_empty() {
2047 assert!(remap_chain(&[]).is_none());
2048 }
2049
2050 #[test]
2051 fn remap_chain_single() {
2052 let sm = SourceMap::from_json(
2053 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#,
2054 )
2055 .unwrap();
2056 let result = remap_chain(&[&sm]).unwrap();
2057 assert_eq!(result.sources, vec!["a.js"]);
2058 assert_eq!(result.mapping_count(), 1);
2059 }
2060
2061 #[test]
2062 fn remap_chain_two_maps() {
2063 let step1 = SourceMap::from_json(
2065 r#"{"version":3,"file":"intermediate.js","sources":["original.js"],"names":[],"mappings":"AACA;AACA"}"#,
2066 )
2067 .unwrap();
2068 let step2 = SourceMap::from_json(
2070 r#"{"version":3,"file":"output.js","sources":["intermediate.js"],"names":[],"mappings":"AAAA;AACA"}"#,
2071 )
2072 .unwrap();
2073
2074 let result = remap_chain(&[&step2, &step1]).unwrap();
2076 assert_eq!(result.sources, vec!["original.js"]);
2077
2078 let loc = result.original_position_for(0, 0).unwrap();
2080 assert_eq!(result.source(loc.source), "original.js");
2081 assert_eq!(loc.line, 1);
2082 }
2083
2084 #[test]
2085 fn remap_chain_three_maps() {
2086 let a_to_b = SourceMap::from_json(
2088 r#"{"version":3,"file":"b.js","sources":["a.js"],"names":[],"mappings":"AACA"}"#,
2089 )
2090 .unwrap();
2091 let b_to_c = SourceMap::from_json(
2093 r#"{"version":3,"file":"c.js","sources":["b.js"],"names":[],"mappings":"AAAA"}"#,
2094 )
2095 .unwrap();
2096 let c_to_d = SourceMap::from_json(
2098 r#"{"version":3,"file":"d.js","sources":["c.js"],"names":[],"mappings":"AAAA"}"#,
2099 )
2100 .unwrap();
2101
2102 let result = remap_chain(&[&c_to_d, &b_to_c, &a_to_b]).unwrap();
2104 assert_eq!(result.sources, vec!["a.js"]);
2105
2106 let loc = result.original_position_for(0, 0).unwrap();
2107 assert_eq!(result.source(loc.source), "a.js");
2108 assert_eq!(loc.line, 1);
2109 }
2110
2111 #[test]
2112 fn remap_chain_only_composes_matching_inner_file() {
2113 let inner = SourceMap::from_json(
2114 r#"{"version":3,"file":"intermediate.js","sources":["original.js"],"names":[],"mappings":"AAAA"}"#,
2115 )
2116 .unwrap();
2117 let outer = SourceMap::from_json(
2118 r#"{"version":3,"file":"output.js","sources":["intermediate.js","passthrough.js"],"names":[],"mappings":"AAAA,KCAA"}"#,
2119 )
2120 .unwrap();
2121
2122 let result = remap_chain(&[&outer, &inner]).unwrap();
2123
2124 assert!(result.sources.contains(&"original.js".to_string()));
2125 assert!(result.sources.contains(&"passthrough.js".to_string()));
2126
2127 let remapped = result.original_position_for(0, 0).unwrap();
2128 assert_eq!(result.source(remapped.source), "original.js");
2129
2130 let passthrough = result.original_position_for(0, 5).unwrap();
2131 assert_eq!(result.source(passthrough.source), "passthrough.js");
2132 }
2133
2134 #[test]
2137 fn remap_empty_string_source_filtered() {
2138 let outer =
2140 SourceMap::from_json(r#"{"version":3,"sources":[""],"names":[],"mappings":"AAAA"}"#)
2141 .unwrap();
2142
2143 let result = remap(&outer, |_| None);
2144
2145 assert!(
2147 !result.sources.iter().any(|s| s.is_empty()),
2148 "empty-string sources should be filtered out"
2149 );
2150 let loc = result.original_position_for(0, 0);
2152 assert!(loc.is_none());
2153 }
2154
2155 #[test]
2156 fn remap_null_source_filtered() {
2157 let outer =
2159 SourceMap::from_json(r#"{"version":3,"sources":[null],"names":[],"mappings":"AAAA"}"#)
2160 .unwrap();
2161
2162 let result = remap(&outer, |_| None);
2163
2164 assert!(
2165 !result.sources.iter().any(|s| s.is_empty()),
2166 "null sources should be filtered out"
2167 );
2168 }
2169
2170 #[test]
2171 fn streaming_empty_string_source_filtered() {
2172 let outer =
2173 SourceMap::from_json(r#"{"version":3,"sources":[""],"names":[],"mappings":"AAAA"}"#)
2174 .unwrap();
2175
2176 let result = streaming_from_sm(&outer, |_| None);
2177
2178 assert!(
2179 !result.sources.iter().any(|s| s.is_empty()),
2180 "streaming: empty-string sources should be filtered out"
2181 );
2182 }
2183
2184 #[test]
2187 fn remap_skips_redundant_sourced_segments() {
2188 let outer = SourceMap::from_json(
2192 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAA,EAAA"}"#,
2193 )
2194 .unwrap();
2195
2196 let result = remap(&outer, |_| None);
2197
2198 assert_eq!(result.mapping_count(), 1);
2200 }
2201
2202 #[test]
2203 fn remap_keeps_different_sourced_segments() {
2204 let outer = SourceMap::from_json(
2207 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAC"}"#,
2208 )
2209 .unwrap();
2210
2211 let result = remap(&outer, |_| None);
2212
2213 assert_eq!(result.mapping_count(), 2);
2215 }
2216
2217 #[test]
2218 fn remap_skips_sourceless_at_line_start() {
2219 let outer = SourceMap::from_json(r#"{"version":3,"sources":[],"names":[],"mappings":"A"}"#)
2222 .unwrap();
2223
2224 let result = remap(&outer, |_| None);
2225
2226 assert_eq!(result.mapping_count(), 0);
2228 }
2229
2230 #[test]
2231 fn streaming_skips_redundant_sourced_segments() {
2232 let outer = SourceMap::from_json(
2233 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAA,EAAA"}"#,
2234 )
2235 .unwrap();
2236
2237 let result = streaming_from_sm(&outer, |_| None);
2238
2239 assert_eq!(result.mapping_count(), 1);
2240 }
2241}