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
187trait RemapBuilder {
189 fn add_source(&mut self, source: &str) -> u32;
190 fn set_source_content(&mut self, idx: u32, content: String);
191 fn add_name(&mut self, name: &str) -> u32;
192 fn add_to_ignore_list(&mut self, idx: u32);
193 fn add_generated_mapping(&mut self, gen_line: u32, gen_col: u32);
194 fn add_mapping(&mut self, gen_line: u32, gen_col: u32, src: u32, orig_line: u32, orig_col: u32);
195 fn add_named_mapping(
196 &mut self,
197 gen_line: u32,
198 gen_col: u32,
199 src: u32,
200 orig_line: u32,
201 orig_col: u32,
202 name: u32,
203 );
204 fn add_range_mapping(
205 &mut self,
206 gen_line: u32,
207 gen_col: u32,
208 src: u32,
209 orig_line: u32,
210 orig_col: u32,
211 );
212 fn add_named_range_mapping(
213 &mut self,
214 gen_line: u32,
215 gen_col: u32,
216 src: u32,
217 orig_line: u32,
218 orig_col: u32,
219 name: u32,
220 );
221}
222
223impl RemapBuilder for SourceMapGenerator {
224 fn add_source(&mut self, source: &str) -> u32 {
225 SourceMapGenerator::add_source(self, source)
226 }
227
228 fn set_source_content(&mut self, idx: u32, content: String) {
229 SourceMapGenerator::set_source_content(self, idx, content)
230 }
231
232 fn add_name(&mut self, name: &str) -> u32 {
233 SourceMapGenerator::add_name(self, name)
234 }
235
236 fn add_to_ignore_list(&mut self, idx: u32) {
237 SourceMapGenerator::add_to_ignore_list(self, idx)
238 }
239
240 fn add_generated_mapping(&mut self, gen_line: u32, gen_col: u32) {
241 SourceMapGenerator::add_generated_mapping(self, gen_line, gen_col)
242 }
243
244 fn add_mapping(
245 &mut self,
246 gen_line: u32,
247 gen_col: u32,
248 src: u32,
249 orig_line: u32,
250 orig_col: u32,
251 ) {
252 SourceMapGenerator::add_mapping(self, gen_line, gen_col, src, orig_line, orig_col)
253 }
254
255 fn add_named_mapping(
256 &mut self,
257 gen_line: u32,
258 gen_col: u32,
259 src: u32,
260 orig_line: u32,
261 orig_col: u32,
262 name: u32,
263 ) {
264 SourceMapGenerator::add_named_mapping(
265 self, gen_line, gen_col, src, orig_line, orig_col, name,
266 )
267 }
268
269 fn add_range_mapping(
270 &mut self,
271 gen_line: u32,
272 gen_col: u32,
273 src: u32,
274 orig_line: u32,
275 orig_col: u32,
276 ) {
277 SourceMapGenerator::add_range_mapping(self, gen_line, gen_col, src, orig_line, orig_col)
278 }
279
280 fn add_named_range_mapping(
281 &mut self,
282 gen_line: u32,
283 gen_col: u32,
284 src: u32,
285 orig_line: u32,
286 orig_col: u32,
287 name: u32,
288 ) {
289 SourceMapGenerator::add_named_range_mapping(
290 self, gen_line, gen_col, src, orig_line, orig_col, name,
291 )
292 }
293}
294
295impl RemapBuilder for StreamingGenerator {
296 fn add_source(&mut self, source: &str) -> u32 {
297 StreamingGenerator::add_source(self, source)
298 }
299
300 fn set_source_content(&mut self, idx: u32, content: String) {
301 StreamingGenerator::set_source_content(self, idx, content)
302 }
303
304 fn add_name(&mut self, name: &str) -> u32 {
305 StreamingGenerator::add_name(self, name)
306 }
307
308 fn add_to_ignore_list(&mut self, idx: u32) {
309 StreamingGenerator::add_to_ignore_list(self, idx)
310 }
311
312 fn add_generated_mapping(&mut self, gen_line: u32, gen_col: u32) {
313 StreamingGenerator::add_generated_mapping(self, gen_line, gen_col)
314 }
315
316 fn add_mapping(
317 &mut self,
318 gen_line: u32,
319 gen_col: u32,
320 src: u32,
321 orig_line: u32,
322 orig_col: u32,
323 ) {
324 StreamingGenerator::add_mapping(self, gen_line, gen_col, src, orig_line, orig_col)
325 }
326
327 fn add_named_mapping(
328 &mut self,
329 gen_line: u32,
330 gen_col: u32,
331 src: u32,
332 orig_line: u32,
333 orig_col: u32,
334 name: u32,
335 ) {
336 StreamingGenerator::add_named_mapping(
337 self, gen_line, gen_col, src, orig_line, orig_col, name,
338 )
339 }
340
341 fn add_range_mapping(
342 &mut self,
343 gen_line: u32,
344 gen_col: u32,
345 src: u32,
346 orig_line: u32,
347 orig_col: u32,
348 ) {
349 StreamingGenerator::add_range_mapping(self, gen_line, gen_col, src, orig_line, orig_col)
350 }
351
352 fn add_named_range_mapping(
353 &mut self,
354 gen_line: u32,
355 gen_col: u32,
356 src: u32,
357 orig_line: u32,
358 orig_col: u32,
359 name: u32,
360 ) {
361 StreamingGenerator::add_named_range_mapping(
362 self, gen_line, gen_col, src, orig_line, orig_col, name,
363 )
364 }
365}
366
367#[inline]
370fn resolve_upstream_source<B: RemapBuilder>(
371 cache: &mut UpstreamCache,
372 upstream_sm: &SourceMap,
373 upstream_src: u32,
374 builder: &mut B,
375 ignored_sources: &mut HashSet<u32>,
376) -> u32 {
377 let si = upstream_src as usize;
378 if let Some(idx) = cache.source_remap[si] {
379 return idx;
380 }
381 let idx = builder.add_source(&upstream_sm.sources[si]);
382 if let Some(Some(content)) = upstream_sm.sources_content.get(si) {
383 builder.set_source_content(idx, content.clone());
384 }
385 if upstream_sm.ignore_list.contains(&upstream_src) && ignored_sources.insert(idx) {
386 builder.add_to_ignore_list(idx);
387 }
388 cache.source_remap[si] = Some(idx);
389 idx
390}
391
392#[inline]
395fn resolve_upstream_name<B: RemapBuilder>(
396 cache: &mut UpstreamCache,
397 upstream_sm: &SourceMap,
398 upstream_name: u32,
399 builder: &mut B,
400) -> u32 {
401 let ni = upstream_name as usize;
402 if let Some(idx) = cache.name_remap[ni] {
403 return idx;
404 }
405 let idx = builder.add_name(&upstream_sm.names[ni]);
406 cache.name_remap[ni] = Some(idx);
407 idx
408}
409
410#[inline]
419fn lookup_upstream(upstream_sm: &SourceMap, line: u32, column: u32) -> Option<UpstreamLookup> {
420 let line_mappings = upstream_sm.mappings_for_line(line);
421 if line_mappings.is_empty() {
422 return fallback_to_full_lookup(upstream_sm, line, column);
423 }
424
425 let idx = match line_mappings.binary_search_by_key(&column, |m| m.generated_column) {
426 Ok(i) => i,
427 Err(0) => return fallback_to_full_lookup(upstream_sm, line, column),
428 Err(i) => i - 1,
429 };
430
431 let mapping = &line_mappings[idx];
432 if mapping.source == NO_SOURCE {
433 return None;
434 }
435
436 let original_column = if mapping.is_range_mapping && column >= mapping.generated_column {
437 mapping.original_column + (column - mapping.generated_column)
438 } else {
439 mapping.original_column
440 };
441
442 Some(UpstreamLookup {
443 source: mapping.source,
444 original_line: mapping.original_line,
445 original_column,
446 name: mapping.name,
447 })
448}
449
450struct UpstreamLookup {
454 source: u32,
455 original_line: u32,
456 original_column: u32,
457 name: u32,
458}
459
460fn fallback_to_full_lookup(
465 upstream_sm: &SourceMap,
466 line: u32,
467 column: u32,
468) -> Option<UpstreamLookup> {
469 let loc = upstream_sm.original_position_for(line, column)?;
470 Some(UpstreamLookup {
471 source: loc.source,
472 original_line: loc.line,
473 original_column: loc.column,
474 name: loc.name.unwrap_or(NO_NAME),
475 })
476}
477
478#[inline]
480fn resolve_outer_name_cached<B: RemapBuilder>(
481 outer_name_remap: &mut [Option<u32>],
482 name_idx: u32,
483 names: &[String],
484 builder: &mut B,
485) -> Option<u32> {
486 if name_idx == NO_NAME {
487 return None;
488 }
489 let slot = outer_name_remap.get_mut(name_idx as usize)?;
490 if let Some(idx) = *slot {
491 return Some(idx);
492 }
493 let outer_name = names.get(name_idx as usize)?;
494 let idx = builder.add_name(outer_name);
495 *slot = Some(idx);
496 Some(idx)
497}
498
499#[inline]
502#[allow(
503 clippy::too_many_arguments,
504 reason = "passing remapped indices avoids per-mapping hashing in the hot path"
505)]
506fn emit_remapped_mapping<B: RemapBuilder>(
507 builder: &mut B,
508 gen_line: u32,
509 gen_col: u32,
510 builder_src: u32,
511 orig_line: u32,
512 orig_col: u32,
513 builder_name: Option<u32>,
514 is_range: bool,
515) {
516 match (builder_name, is_range) {
517 (Some(n), true) => {
518 builder.add_named_range_mapping(gen_line, gen_col, builder_src, orig_line, orig_col, n);
519 }
520 (Some(n), false) => {
521 builder.add_named_mapping(gen_line, gen_col, builder_src, orig_line, orig_col, n);
522 }
523 (None, true) => {
524 builder.add_range_mapping(gen_line, gen_col, builder_src, orig_line, orig_col);
525 }
526 (None, false) => {
527 builder.add_mapping(gen_line, gen_col, builder_src, orig_line, orig_col);
528 }
529 }
530}
531
532enum SourceEntry {
535 Upstream { map: Box<SourceMap>, cache: UpstreamCache },
537 Passthrough { builder_src: u32 },
539 EmptySource,
542 Unloaded,
544}
545
546struct DedupeState {
549 last_gen_line: u32,
551 line_index: u32,
553 last_was_sourceless: bool,
555 last_source: Option<(u32, u32, u32, Option<u32>)>,
557}
558
559impl DedupeState {
560 fn new() -> Self {
561 Self {
562 last_gen_line: u32::MAX,
563 line_index: 0,
564 last_was_sourceless: false,
565 last_source: None,
566 }
567 }
568
569 fn skip_sourceless(&self, gen_line: u32) -> bool {
572 if gen_line != self.last_gen_line {
573 return true;
575 }
576 self.last_was_sourceless
578 }
579
580 fn skip_source(
583 &self,
584 gen_line: u32,
585 source: u32,
586 orig_line: u32,
587 orig_col: u32,
588 name: Option<u32>,
589 ) -> bool {
590 if gen_line != self.last_gen_line {
591 return false;
593 }
594 if self.last_was_sourceless {
595 return false;
597 }
598 self.last_source == Some((source, orig_line, orig_col, name))
600 }
601
602 fn record_sourceless(&mut self, gen_line: u32) {
604 if gen_line != self.last_gen_line {
605 self.last_gen_line = gen_line;
606 self.line_index = 0;
607 self.last_source = None;
608 }
609 self.line_index += 1;
610 self.last_was_sourceless = true;
611 }
612
613 fn record_source(
615 &mut self,
616 gen_line: u32,
617 source: u32,
618 orig_line: u32,
619 orig_col: u32,
620 name: Option<u32>,
621 ) {
622 if gen_line != self.last_gen_line {
623 self.last_gen_line = gen_line;
624 self.line_index = 0;
625 }
626 self.line_index += 1;
627 self.last_was_sourceless = false;
628 self.last_source = Some((source, orig_line, orig_col, name));
629 }
630}
631
632pub fn remap<F>(outer: &SourceMap, loader: F) -> SourceMap
649where
650 F: Fn(&str) -> Option<SourceMap>,
651{
652 let mapping_count = outer.mapping_count();
653 let source_count = outer.sources.len();
654 let mut builder = SourceMapGenerator::with_capacity(outer.file.clone(), mapping_count);
655 builder.set_assume_sorted(true);
657
658 let mut source_entries: Vec<SourceEntry> =
660 std::iter::repeat_with(|| SourceEntry::Unloaded).take(source_count).collect();
661
662 let mut ignored_sources: HashSet<u32> = HashSet::new();
663
664 let mut outer_name_remap: Vec<Option<u32>> = vec![None; outer.names.len()];
666
667 let outer_ignore_set: HashSet<u32> = outer.ignore_list.iter().copied().collect();
669
670 let mut dedup = DedupeState::new();
671
672 for m in outer.all_mappings() {
673 if m.source == NO_SOURCE {
674 trace_and_emit_sourceless(
675 &mut builder,
676 &mut dedup,
677 m.generated_line,
678 m.generated_column,
679 );
680 continue;
681 }
682
683 let si = m.source as usize;
684
685 if matches!(source_entries[si], SourceEntry::Unloaded) {
687 let source_name = outer.source(m.source);
688 if source_name.is_empty() {
691 source_entries[si] = SourceEntry::EmptySource;
692 } else {
693 match loader(source_name) {
694 Some(upstream_sm) => {
695 let cache = build_upstream_cache(&upstream_sm);
696 source_entries[si] =
697 SourceEntry::Upstream { map: Box::new(upstream_sm), cache };
698 }
699 None => {
700 let idx = builder.add_source(source_name);
701 if let Some(Some(content)) = outer.sources_content.get(si) {
702 builder.set_source_content(idx, content.clone());
703 }
704 if outer_ignore_set.contains(&m.source) && ignored_sources.insert(idx) {
705 builder.add_to_ignore_list(idx);
706 }
707 source_entries[si] = SourceEntry::Passthrough { builder_src: idx };
708 }
709 }
710 }
711 }
712
713 match &mut source_entries[si] {
714 SourceEntry::Upstream { map, cache } => {
715 if let Some(upstream_m) = lookup_upstream(map, m.original_line, m.original_column) {
716 trace_and_emit_upstream(
717 &mut builder,
718 &mut dedup,
719 UpstreamEmitContext {
720 gen_line: m.generated_line,
721 gen_col: m.generated_column,
722 upstream_m,
723 cache,
724 upstream_map: map,
725 outer_name_remap: &mut outer_name_remap,
726 outer_name_idx: m.name,
727 names: &outer.names,
728 ignored_sources: &mut ignored_sources,
729 is_range: m.is_range_mapping,
730 },
731 );
732 }
733 }
734 SourceEntry::Passthrough { builder_src } => {
735 trace_and_emit_passthrough(
736 &mut builder,
737 &mut dedup,
738 PassthroughEmitContext {
739 gen_line: m.generated_line,
740 gen_col: m.generated_column,
741 orig_line: m.original_line,
742 orig_col: m.original_column,
743 builder_src: *builder_src,
744 outer_name_remap: &mut outer_name_remap,
745 outer_name_idx: m.name,
746 names: &outer.names,
747 is_range: m.is_range_mapping,
748 },
749 );
750 }
751 SourceEntry::EmptySource => {
752 trace_and_emit_sourceless(
753 &mut builder,
754 &mut dedup,
755 m.generated_line,
756 m.generated_column,
757 );
758 }
759 SourceEntry::Unloaded => unreachable!(),
760 }
761 }
762
763 builder.to_decoded_map()
764}
765
766pub fn remap_chain(maps: &[&SourceMap]) -> Option<SourceMap> {
797 if maps.is_empty() {
798 return None;
799 }
800 if maps.len() == 1 {
801 return Some(maps[0].clone());
802 }
803
804 let mut current = compose_pair(maps[maps.len() - 2], maps[maps.len() - 1]);
816
817 for i in (0..maps.len() - 2).rev() {
819 current = compose_pair(maps[i], ¤t);
820 }
821
822 Some(current)
823}
824
825fn compose_pair(outer: &SourceMap, inner: &SourceMap) -> SourceMap {
830 let fallback_source = if inner.file.is_none() {
831 let mut sources = outer.sources.iter().filter(|source| !source.is_empty());
832 match (sources.next(), sources.next()) {
833 (Some(source), None) => Some(source.clone()),
834 _ => None,
835 }
836 } else {
837 None
838 };
839
840 remap(outer, |source| {
841 if inner.file.as_deref() == Some(source) || fallback_source.as_deref() == Some(source) {
842 Some(inner.clone())
843 } else {
844 None
845 }
846 })
847}
848
849enum StreamingSourceEntry {
851 Upstream { map: Box<SourceMap>, cache: UpstreamCache },
853 Passthrough { builder_src: u32 },
855 EmptySource,
857 Unloaded,
859}
860
861pub fn remap_streaming<'a, F>(
874 mappings_iter: srcmap_sourcemap::MappingsIter<'a>,
875 sources: &[String],
876 names: &[String],
877 sources_content: &[Option<String>],
878 ignore_list: &[u32],
879 file: Option<String>,
880 loader: F,
881) -> SourceMap
882where
883 F: Fn(&str) -> Option<SourceMap>,
884{
885 let mut builder = StreamingGenerator::with_capacity(file, 4096);
886
887 let mut source_entries: Vec<StreamingSourceEntry> =
889 std::iter::repeat_with(|| StreamingSourceEntry::Unloaded).take(sources.len()).collect();
890
891 let mut ignored_sources: HashSet<u32> = HashSet::new();
892
893 let mut outer_name_remap: Vec<Option<u32>> = vec![None; names.len()];
895
896 let outer_ignore_set: HashSet<u32> = ignore_list.iter().copied().collect();
898
899 let mut dedup = DedupeState::new();
900
901 for item in mappings_iter {
902 let m = match item {
903 Ok(m) => m,
904 Err(_) => continue,
905 };
906
907 if m.source == NO_SOURCE {
908 trace_and_emit_sourceless(
909 &mut builder,
910 &mut dedup,
911 m.generated_line,
912 m.generated_column,
913 );
914 continue;
915 }
916
917 let si = m.source as usize;
918 if si >= sources.len() {
919 continue;
920 }
921
922 if matches!(source_entries[si], StreamingSourceEntry::Unloaded) {
924 let source_name = &sources[si];
925 if source_name.is_empty() {
926 source_entries[si] = StreamingSourceEntry::EmptySource;
927 } else {
928 match loader(source_name) {
929 Some(upstream_sm) => {
930 let cache = build_upstream_cache(&upstream_sm);
931 source_entries[si] =
932 StreamingSourceEntry::Upstream { map: Box::new(upstream_sm), cache };
933 }
934 None => {
935 let idx = builder.add_source(source_name);
936 if let Some(Some(content)) = sources_content.get(si) {
937 builder.set_source_content(idx, content.clone());
938 }
939 if outer_ignore_set.contains(&m.source) && ignored_sources.insert(idx) {
940 builder.add_to_ignore_list(idx);
941 }
942 source_entries[si] = StreamingSourceEntry::Passthrough { builder_src: idx };
943 }
944 }
945 }
946 }
947
948 match &mut source_entries[si] {
949 StreamingSourceEntry::Upstream { map, cache } => {
950 if let Some(upstream_m) = lookup_upstream(map, m.original_line, m.original_column) {
951 trace_and_emit_upstream(
952 &mut builder,
953 &mut dedup,
954 UpstreamEmitContext {
955 gen_line: m.generated_line,
956 gen_col: m.generated_column,
957 upstream_m,
958 cache,
959 upstream_map: map,
960 outer_name_remap: &mut outer_name_remap,
961 outer_name_idx: m.name,
962 names,
963 ignored_sources: &mut ignored_sources,
964 is_range: m.is_range_mapping,
965 },
966 );
967 }
968 }
969 StreamingSourceEntry::Passthrough { builder_src } => {
970 trace_and_emit_passthrough(
971 &mut builder,
972 &mut dedup,
973 PassthroughEmitContext {
974 gen_line: m.generated_line,
975 gen_col: m.generated_column,
976 orig_line: m.original_line,
977 orig_col: m.original_column,
978 builder_src: *builder_src,
979 outer_name_remap: &mut outer_name_remap,
980 outer_name_idx: m.name,
981 names,
982 is_range: m.is_range_mapping,
983 },
984 );
985 }
986 StreamingSourceEntry::EmptySource => {
987 trace_and_emit_sourceless(
988 &mut builder,
989 &mut dedup,
990 m.generated_line,
991 m.generated_column,
992 );
993 }
994 StreamingSourceEntry::Unloaded => unreachable!(),
995 }
996 }
997
998 builder.to_decoded_map().expect("streaming VLQ should be valid")
999}
1000
1001#[inline]
1002fn emit_generated_mapping<B: RemapBuilder>(builder: &mut B, gen_line: u32, gen_col: u32) {
1003 builder.add_generated_mapping(gen_line, gen_col);
1004}
1005
1006struct UpstreamEmitContext<'a> {
1007 gen_line: u32,
1008 gen_col: u32,
1009 upstream_m: UpstreamLookup,
1010 cache: &'a mut UpstreamCache,
1011 upstream_map: &'a SourceMap,
1012 outer_name_remap: &'a mut [Option<u32>],
1013 outer_name_idx: u32,
1014 names: &'a [String],
1015 ignored_sources: &'a mut HashSet<u32>,
1016 is_range: bool,
1017}
1018
1019#[inline]
1020fn trace_and_emit_upstream<B: RemapBuilder>(
1021 builder: &mut B,
1022 dedup: &mut DedupeState,
1023 ctx: UpstreamEmitContext<'_>,
1024) {
1025 let UpstreamEmitContext {
1026 gen_line,
1027 gen_col,
1028 upstream_m,
1029 cache,
1030 upstream_map,
1031 outer_name_remap,
1032 outer_name_idx,
1033 names,
1034 ignored_sources,
1035 is_range,
1036 } = ctx;
1037 let builder_src =
1038 resolve_upstream_source(cache, upstream_map, upstream_m.source, builder, ignored_sources);
1039
1040 let builder_name = if upstream_m.name != NO_NAME {
1041 Some(resolve_upstream_name(cache, upstream_map, upstream_m.name, builder))
1042 } else {
1043 resolve_outer_name_cached(outer_name_remap, outer_name_idx, names, builder)
1044 };
1045
1046 if !dedup.skip_source(
1047 gen_line,
1048 builder_src,
1049 upstream_m.original_line,
1050 upstream_m.original_column,
1051 builder_name,
1052 ) {
1053 emit_remapped_mapping(
1054 builder,
1055 gen_line,
1056 gen_col,
1057 builder_src,
1058 upstream_m.original_line,
1059 upstream_m.original_column,
1060 builder_name,
1061 is_range,
1062 );
1063 }
1064 dedup.record_source(
1065 gen_line,
1066 builder_src,
1067 upstream_m.original_line,
1068 upstream_m.original_column,
1069 builder_name,
1070 );
1071}
1072
1073struct PassthroughEmitContext<'a> {
1074 gen_line: u32,
1075 gen_col: u32,
1076 orig_line: u32,
1077 orig_col: u32,
1078 builder_src: u32,
1079 outer_name_remap: &'a mut [Option<u32>],
1080 outer_name_idx: u32,
1081 names: &'a [String],
1082 is_range: bool,
1083}
1084
1085#[inline]
1086fn trace_and_emit_passthrough<B: RemapBuilder>(
1087 builder: &mut B,
1088 dedup: &mut DedupeState,
1089 ctx: PassthroughEmitContext<'_>,
1090) {
1091 let PassthroughEmitContext {
1092 gen_line,
1093 gen_col,
1094 orig_line,
1095 orig_col,
1096 builder_src,
1097 outer_name_remap,
1098 outer_name_idx,
1099 names,
1100 is_range,
1101 } = ctx;
1102 let builder_name = resolve_outer_name_cached(outer_name_remap, outer_name_idx, names, builder);
1103
1104 if !dedup.skip_source(gen_line, builder_src, orig_line, orig_col, builder_name) {
1105 emit_remapped_mapping(
1106 builder,
1107 gen_line,
1108 gen_col,
1109 builder_src,
1110 orig_line,
1111 orig_col,
1112 builder_name,
1113 is_range,
1114 );
1115 }
1116 dedup.record_source(gen_line, builder_src, orig_line, orig_col, builder_name);
1117}
1118
1119#[inline]
1120fn trace_and_emit_sourceless<B: RemapBuilder>(
1121 builder: &mut B,
1122 dedup: &mut DedupeState,
1123 gen_line: u32,
1124 gen_col: u32,
1125) {
1126 if !dedup.skip_sourceless(gen_line) {
1127 emit_generated_mapping(builder, gen_line, gen_col);
1128 }
1129 dedup.record_sourceless(gen_line);
1130}
1131
1132#[cfg(test)]
1135mod tests {
1136 use super::*;
1137
1138 #[test]
1141 fn concat_two_simple_maps() {
1142 let a = SourceMap::from_json(
1143 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#,
1144 )
1145 .unwrap();
1146 let b = SourceMap::from_json(
1147 r#"{"version":3,"sources":["b.js"],"names":[],"mappings":"AAAA"}"#,
1148 )
1149 .unwrap();
1150
1151 let mut builder = ConcatBuilder::new(Some("bundle.js".to_string()));
1152 builder.add_map(&a, 0);
1153 builder.add_map(&b, 1);
1154
1155 let result = builder.build();
1156 assert_eq!(result.sources, vec!["a.js", "b.js"]);
1157 assert_eq!(result.mapping_count(), 2);
1158
1159 let loc0 = result.original_position_for(0, 0).unwrap();
1160 assert_eq!(result.source(loc0.source), "a.js");
1161
1162 let loc1 = result.original_position_for(1, 0).unwrap();
1163 assert_eq!(result.source(loc1.source), "b.js");
1164 }
1165
1166 #[test]
1167 fn concat_deduplicates_sources() {
1168 let a = SourceMap::from_json(
1169 r#"{"version":3,"sources":["shared.js"],"names":[],"mappings":"AAAA"}"#,
1170 )
1171 .unwrap();
1172 let b = SourceMap::from_json(
1173 r#"{"version":3,"sources":["shared.js"],"names":[],"mappings":"AAAA"}"#,
1174 )
1175 .unwrap();
1176
1177 let mut builder = ConcatBuilder::new(None);
1178 builder.add_map(&a, 0);
1179 builder.add_map(&b, 10);
1180
1181 let result = builder.build();
1182 assert_eq!(result.sources.len(), 1);
1183 assert_eq!(result.sources[0], "shared.js");
1184 }
1185
1186 #[test]
1187 fn concat_with_names() {
1188 let a = SourceMap::from_json(
1189 r#"{"version":3,"sources":["a.js"],"names":["foo"],"mappings":"AAAAA"}"#,
1190 )
1191 .unwrap();
1192 let b = SourceMap::from_json(
1193 r#"{"version":3,"sources":["b.js"],"names":["bar"],"mappings":"AAAAA"}"#,
1194 )
1195 .unwrap();
1196
1197 let mut builder = ConcatBuilder::new(None);
1198 builder.add_map(&a, 0);
1199 builder.add_map(&b, 1);
1200
1201 let result = builder.build();
1202 assert_eq!(result.names.len(), 2);
1203
1204 let loc0 = result.original_position_for(0, 0).unwrap();
1205 assert_eq!(loc0.name, Some(0));
1206 assert_eq!(result.name(0), "foo");
1207
1208 let loc1 = result.original_position_for(1, 0).unwrap();
1209 assert_eq!(loc1.name, Some(1));
1210 assert_eq!(result.name(1), "bar");
1211 }
1212
1213 #[test]
1214 fn concat_preserves_multi_line_maps() {
1215 let a = SourceMap::from_json(
1216 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;AACA;AACA"}"#,
1217 )
1218 .unwrap();
1219
1220 let mut builder = ConcatBuilder::new(None);
1221 builder.add_map(&a, 5); let result = builder.build();
1224 assert!(result.original_position_for(5, 0).is_some());
1225 assert!(result.original_position_for(6, 0).is_some());
1226 assert!(result.original_position_for(7, 0).is_some());
1227 assert!(result.original_position_for(4, 0).is_none());
1228 }
1229
1230 #[test]
1231 fn concat_with_sources_content() {
1232 let a = SourceMap::from_json(
1233 r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":[],"mappings":"AAAA"}"#,
1234 )
1235 .unwrap();
1236
1237 let mut builder = ConcatBuilder::new(None);
1238 builder.add_map(&a, 0);
1239
1240 let result = builder.build();
1241 assert_eq!(result.sources_content, vec![Some("var a;".to_string())]);
1242 }
1243
1244 #[test]
1245 fn concat_empty_builder() {
1246 let builder = ConcatBuilder::new(Some("empty.js".to_string()));
1247 let result = builder.build();
1248 assert_eq!(result.mapping_count(), 0);
1249 assert_eq!(result.sources.len(), 0);
1250 }
1251
1252 #[test]
1255 fn remap_single_level() {
1256 let outer = SourceMap::from_json(
1261 r#"{"version":3,"sources":["intermediate.js","other.js"],"names":[],"mappings":"AAAA,KCAA;ADCA"}"#,
1262 )
1263 .unwrap();
1264
1265 let inner = SourceMap::from_json(
1267 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AACA;AACA"}"#,
1268 )
1269 .unwrap();
1270
1271 let result =
1272 remap(
1273 &outer,
1274 |source| {
1275 if source == "intermediate.js" { Some(inner.clone()) } else { None }
1276 },
1277 );
1278
1279 assert!(result.sources.contains(&"original.js".to_string()));
1280 assert!(result.sources.contains(&"other.js".to_string()));
1282
1283 let loc = result.original_position_for(0, 0).unwrap();
1285 assert_eq!(result.source(loc.source), "original.js");
1286 assert_eq!(loc.line, 1);
1287 }
1288
1289 #[test]
1290 fn remap_accepts_source_map_built_from_parts() {
1291 let outer = SourceMap::from_parts(
1292 Some("output.js".to_string()),
1293 None,
1294 vec!["intermediate.js".to_string()],
1295 vec![],
1296 vec![],
1297 vec![srcmap_sourcemap::Mapping {
1298 generated_line: 0,
1299 generated_column: 0,
1300 source: 0,
1301 original_line: 0,
1302 original_column: 0,
1303 name: u32::MAX,
1304 is_range_mapping: false,
1305 }],
1306 vec![],
1307 None,
1308 None,
1309 );
1310 let inner = SourceMap::builder()
1311 .file("intermediate.js")
1312 .sources(["original.ts"])
1313 .sources_content([Some("export const value = 1;")])
1314 .mappings([srcmap_sourcemap::Mapping {
1315 generated_line: 0,
1316 generated_column: 0,
1317 source: 0,
1318 original_line: 3,
1319 original_column: 12,
1320 name: u32::MAX,
1321 is_range_mapping: false,
1322 }])
1323 .build();
1324
1325 let result =
1326 remap(
1327 &outer,
1328 |source| {
1329 if source == "intermediate.js" { Some(inner.clone()) } else { None }
1330 },
1331 );
1332
1333 let loc = result.original_position_for(0, 0).unwrap();
1334 assert_eq!(result.source(loc.source), "original.ts");
1335 assert_eq!(loc.line, 3);
1336 assert_eq!(loc.column, 12);
1337 assert_eq!(result.sources_content, vec![Some("export const value = 1;".to_string())]);
1338 }
1339
1340 #[test]
1341 fn remap_no_upstream_passthrough() {
1342 let outer = SourceMap::from_json(
1343 r#"{"version":3,"sources":["already-original.js"],"names":[],"mappings":"AAAA"}"#,
1344 )
1345 .unwrap();
1346
1347 let result = remap(&outer, |_| None);
1349
1350 assert_eq!(result.sources, vec!["already-original.js"]);
1351 let loc = result.original_position_for(0, 0).unwrap();
1352 assert_eq!(result.source(loc.source), "already-original.js");
1353 assert_eq!(loc.line, 0);
1354 assert_eq!(loc.column, 0);
1355 }
1356
1357 #[test]
1358 fn remap_partial_sources() {
1359 let outer = SourceMap::from_json(
1361 r#"{"version":3,"sources":["compiled.js","passthrough.js"],"names":[],"mappings":"AAAA,KCCA"}"#,
1362 )
1363 .unwrap();
1364
1365 let inner = SourceMap::from_json(
1366 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":"AAAA"}"#,
1367 )
1368 .unwrap();
1369
1370 let result =
1371 remap(
1372 &outer,
1373 |source| {
1374 if source == "compiled.js" { Some(inner.clone()) } else { None }
1375 },
1376 );
1377
1378 assert!(result.sources.contains(&"original.ts".to_string()));
1380 assert!(result.sources.contains(&"passthrough.js".to_string()));
1381 }
1382
1383 #[test]
1384 fn remap_preserves_names() {
1385 let outer = SourceMap::from_json(
1386 r#"{"version":3,"sources":["compiled.js"],"names":["myFunc"],"mappings":"AAAAA"}"#,
1387 )
1388 .unwrap();
1389
1390 let inner = SourceMap::from_json(
1392 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":"AAAA"}"#,
1393 )
1394 .unwrap();
1395
1396 let result = remap(&outer, |_| Some(inner.clone()));
1397
1398 let loc = result.original_position_for(0, 0).unwrap();
1399 assert!(loc.name.is_some());
1400 assert_eq!(result.name(loc.name.unwrap()), "myFunc");
1401 }
1402
1403 #[test]
1404 fn remap_upstream_name_wins() {
1405 let outer = SourceMap::from_json(
1406 r#"{"version":3,"sources":["compiled.js"],"names":["outerName"],"mappings":"AAAAA"}"#,
1407 )
1408 .unwrap();
1409
1410 let inner = SourceMap::from_json(
1412 r#"{"version":3,"sources":["original.ts"],"names":["innerName"],"mappings":"AAAAA"}"#,
1413 )
1414 .unwrap();
1415
1416 let result = remap(&outer, |_| Some(inner.clone()));
1417
1418 let loc = result.original_position_for(0, 0).unwrap();
1419 assert!(loc.name.is_some());
1420 assert_eq!(result.name(loc.name.unwrap()), "innerName");
1421 }
1422
1423 #[test]
1424 fn remap_sources_content_from_upstream() {
1425 let outer = SourceMap::from_json(
1426 r#"{"version":3,"sources":["compiled.js"],"names":[],"mappings":"AAAA"}"#,
1427 )
1428 .unwrap();
1429
1430 let inner = SourceMap::from_json(
1431 r#"{"version":3,"sources":["original.ts"],"sourcesContent":["const x = 1;"],"names":[],"mappings":"AAAA"}"#,
1432 )
1433 .unwrap();
1434
1435 let result = remap(&outer, |_| Some(inner.clone()));
1436
1437 assert_eq!(result.sources_content, vec![Some("const x = 1;".to_string())]);
1438 }
1439
1440 #[test]
1443 fn concat_updates_source_content_on_duplicate() {
1444 let a = SourceMap::from_json(
1446 r#"{"version":3,"sources":["shared.js"],"names":[],"mappings":"AAAA"}"#,
1447 )
1448 .unwrap();
1449 let b = SourceMap::from_json(
1450 r#"{"version":3,"sources":["shared.js"],"sourcesContent":["var x = 1;"],"names":[],"mappings":"AAAA"}"#,
1451 )
1452 .unwrap();
1453
1454 let mut builder = ConcatBuilder::new(None);
1455 builder.add_map(&a, 0);
1456 builder.add_map(&b, 1);
1457
1458 let result = builder.build();
1459 assert_eq!(result.sources.len(), 1);
1460 assert_eq!(result.sources_content, vec![Some("var x = 1;".to_string())]);
1461 }
1462
1463 #[test]
1464 fn concat_deduplicates_names() {
1465 let a = SourceMap::from_json(
1466 r#"{"version":3,"sources":["a.js"],"names":["sharedName"],"mappings":"AAAAA"}"#,
1467 )
1468 .unwrap();
1469 let b = SourceMap::from_json(
1470 r#"{"version":3,"sources":["b.js"],"names":["sharedName"],"mappings":"AAAAA"}"#,
1471 )
1472 .unwrap();
1473
1474 let mut builder = ConcatBuilder::new(None);
1475 builder.add_map(&a, 0);
1476 builder.add_map(&b, 1);
1477
1478 let result = builder.build();
1479 assert_eq!(result.names.len(), 1);
1481 assert_eq!(result.names[0], "sharedName");
1482 }
1483
1484 #[test]
1485 fn concat_with_ignore_list() {
1486 let a = SourceMap::from_json(
1487 r#"{"version":3,"sources":["vendor.js"],"names":[],"mappings":"AAAA","ignoreList":[0]}"#,
1488 )
1489 .unwrap();
1490
1491 let mut builder = ConcatBuilder::new(None);
1492 builder.add_map(&a, 0);
1493
1494 let result = builder.build();
1495 assert_eq!(result.ignore_list, vec![0]);
1496 }
1497
1498 #[test]
1499 fn concat_with_generated_only_mappings() {
1500 let a = SourceMap::from_json(
1502 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A,AAAA"}"#,
1503 )
1504 .unwrap();
1505
1506 let mut builder = ConcatBuilder::new(None);
1507 builder.add_map(&a, 0);
1508
1509 let result = builder.build();
1510 assert!(result.mapping_count() >= 1);
1512 }
1513
1514 #[test]
1515 fn remap_generated_only_passthrough() {
1516 let outer = SourceMap::from_json(
1521 r#"{"version":3,"sources":["a.js","other.js"],"names":[],"mappings":"A,AAAA,KCAA"}"#,
1522 )
1523 .unwrap();
1524
1525 let inner = SourceMap::from_json(
1526 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AAAA"}"#,
1527 )
1528 .unwrap();
1529
1530 let result =
1531 remap(&outer, |source| if source == "a.js" { Some(inner.clone()) } else { None });
1532
1533 assert!(result.mapping_count() >= 2);
1535 assert!(result.sources.contains(&"original.js".to_string()));
1536 assert!(result.sources.contains(&"other.js".to_string()));
1537 }
1538
1539 #[test]
1540 fn remap_no_upstream_mapping_with_name() {
1541 let outer = SourceMap::from_json(
1543 r#"{"version":3,"sources":["compiled.js"],"names":["myFunc"],"mappings":"AAAAA"}"#,
1544 )
1545 .unwrap();
1546
1547 let inner = SourceMap::from_json(
1549 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":";;;;AAAA"}"#,
1550 )
1551 .unwrap();
1552
1553 let result = remap(&outer, |_| Some(inner.clone()));
1554
1555 let loc = result.original_position_for(0, 0);
1559 assert!(loc.is_none());
1560 }
1561
1562 #[test]
1563 fn remap_no_upstream_with_sources_content_and_name() {
1564 let outer = SourceMap::from_json(
1565 r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":["fn1"],"mappings":"AAAAA"}"#,
1566 )
1567 .unwrap();
1568
1569 let result = remap(&outer, |_| None);
1571
1572 assert_eq!(result.sources, vec!["a.js"]);
1573 assert_eq!(result.sources_content, vec![Some("var a;".to_string())]);
1574 let loc = result.original_position_for(0, 0).unwrap();
1575 assert!(loc.name.is_some());
1576 assert_eq!(result.name(loc.name.unwrap()), "fn1");
1577 }
1578
1579 #[test]
1580 fn remap_no_upstream_no_name() {
1581 let outer = SourceMap::from_json(
1582 r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":[],"mappings":"AAAA"}"#,
1583 )
1584 .unwrap();
1585
1586 let result = remap(&outer, |_| None);
1587 let loc = result.original_position_for(0, 0).unwrap();
1588 assert!(loc.name.is_none());
1589 }
1590
1591 #[test]
1592 fn remap_no_upstream_mapping_no_name() {
1593 let outer = SourceMap::from_json(
1596 r#"{"version":3,"sources":["compiled.js"],"names":[],"mappings":"AAAA"}"#,
1597 )
1598 .unwrap();
1599
1600 let inner = SourceMap::from_json(
1603 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":";;;;AAAA"}"#,
1604 )
1605 .unwrap();
1606
1607 let result = remap(&outer, |_| Some(inner.clone()));
1608
1609 let loc = result.original_position_for(0, 0);
1611 assert!(loc.is_none());
1612 }
1613
1614 #[test]
1615 fn remap_upstream_found_no_name() {
1616 let outer = SourceMap::from_json(
1624 r#"{"version":3,"sources":["intermediate.js"],"names":[],"mappings":"AAAA"}"#,
1625 )
1626 .unwrap();
1627
1628 let inner = SourceMap::from_json(
1630 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AAAA"}"#,
1631 )
1632 .unwrap();
1633
1634 let result = remap(&outer, |_| Some(inner.clone()));
1635
1636 assert_eq!(result.sources, vec!["original.js"]);
1637 let loc = result.original_position_for(0, 0).unwrap();
1638 assert_eq!(result.source(loc.source), "original.js");
1639 assert_eq!(loc.line, 0);
1640 assert_eq!(loc.column, 0);
1641 assert!(loc.name.is_none());
1643 assert!(result.names.is_empty());
1644 }
1645
1646 #[test]
1649 fn concat_preserves_range_mappings() {
1650 let a = SourceMap::from_json(
1651 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,CAAC","rangeMappings":"A"}"#,
1652 )
1653 .unwrap();
1654
1655 let mut builder = ConcatBuilder::new(None);
1656 builder.add_map(&a, 0);
1657
1658 let result = builder.build();
1659 assert!(result.has_range_mappings());
1660 let mappings = result.all_mappings();
1661 assert!(mappings[0].is_range_mapping);
1662 assert!(!mappings[1].is_range_mapping);
1663 }
1664
1665 #[test]
1666 fn remap_preserves_range_mappings_passthrough() {
1667 let outer = SourceMap::from_json(
1668 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","rangeMappings":"A"}"#,
1669 )
1670 .unwrap();
1671
1672 let result = remap(&outer, |_| None);
1674 assert!(result.has_range_mappings());
1675 let mappings = result.all_mappings();
1676 assert!(mappings[0].is_range_mapping);
1677 }
1678
1679 #[test]
1680 fn remap_preserves_range_through_upstream() {
1681 let outer = SourceMap::from_json(
1682 r#"{"version":3,"sources":["intermediate.js"],"names":[],"mappings":"AAAA","rangeMappings":"A"}"#,
1683 )
1684 .unwrap();
1685
1686 let inner = SourceMap::from_json(
1687 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AACA"}"#,
1688 )
1689 .unwrap();
1690
1691 let result = remap(&outer, |_| Some(inner.clone()));
1692 assert!(result.has_range_mappings());
1693 }
1694
1695 #[test]
1696 fn remap_non_range_stays_non_range() {
1697 let outer = SourceMap::from_json(
1698 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#,
1699 )
1700 .unwrap();
1701
1702 let result = remap(&outer, |_| None);
1703 assert!(!result.has_range_mappings());
1704 }
1705
1706 fn streaming_from_sm<F>(sm: &SourceMap, loader: F) -> SourceMap
1711 where
1712 F: Fn(&str) -> Option<SourceMap>,
1713 {
1714 let vlq = sm.encode_mappings();
1715 let iter = srcmap_sourcemap::MappingsIter::new(&vlq);
1716 remap_streaming(
1717 iter,
1718 &sm.sources,
1719 &sm.names,
1720 &sm.sources_content,
1721 &sm.ignore_list,
1722 sm.file.clone(),
1723 loader,
1724 )
1725 }
1726
1727 #[test]
1728 fn streaming_single_level() {
1729 let outer = SourceMap::from_json(
1730 r#"{"version":3,"sources":["intermediate.js","other.js"],"names":[],"mappings":"AAAA,KCAA;ADCA"}"#,
1731 )
1732 .unwrap();
1733
1734 let inner = SourceMap::from_json(
1735 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AACA;AACA"}"#,
1736 )
1737 .unwrap();
1738
1739 let result = streaming_from_sm(&outer, |source| {
1740 if source == "intermediate.js" { Some(inner.clone()) } else { None }
1741 });
1742
1743 assert!(result.sources.contains(&"original.js".to_string()));
1744 assert!(result.sources.contains(&"other.js".to_string()));
1745
1746 let loc = result.original_position_for(0, 0).unwrap();
1747 assert_eq!(result.source(loc.source), "original.js");
1748 assert_eq!(loc.line, 1);
1749 }
1750
1751 #[test]
1752 fn streaming_no_upstream_passthrough() {
1753 let outer = SourceMap::from_json(
1754 r#"{"version":3,"sources":["already-original.js"],"names":[],"mappings":"AAAA"}"#,
1755 )
1756 .unwrap();
1757
1758 let result = streaming_from_sm(&outer, |_| None);
1759
1760 assert_eq!(result.sources, vec!["already-original.js"]);
1761 let loc = result.original_position_for(0, 0).unwrap();
1762 assert_eq!(result.source(loc.source), "already-original.js");
1763 assert_eq!(loc.line, 0);
1764 assert_eq!(loc.column, 0);
1765 }
1766
1767 #[test]
1768 fn streaming_preserves_names() {
1769 let outer = SourceMap::from_json(
1770 r#"{"version":3,"sources":["compiled.js"],"names":["myFunc"],"mappings":"AAAAA"}"#,
1771 )
1772 .unwrap();
1773
1774 let inner = SourceMap::from_json(
1775 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":"AAAA"}"#,
1776 )
1777 .unwrap();
1778
1779 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
1780
1781 let loc = result.original_position_for(0, 0).unwrap();
1782 assert!(loc.name.is_some());
1783 assert_eq!(result.name(loc.name.unwrap()), "myFunc");
1784 }
1785
1786 #[test]
1787 fn streaming_upstream_name_wins() {
1788 let outer = SourceMap::from_json(
1789 r#"{"version":3,"sources":["compiled.js"],"names":["outerName"],"mappings":"AAAAA"}"#,
1790 )
1791 .unwrap();
1792
1793 let inner = SourceMap::from_json(
1794 r#"{"version":3,"sources":["original.ts"],"names":["innerName"],"mappings":"AAAAA"}"#,
1795 )
1796 .unwrap();
1797
1798 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
1799
1800 let loc = result.original_position_for(0, 0).unwrap();
1801 assert!(loc.name.is_some());
1802 assert_eq!(result.name(loc.name.unwrap()), "innerName");
1803 }
1804
1805 #[test]
1806 fn streaming_sources_content_from_upstream() {
1807 let outer = SourceMap::from_json(
1808 r#"{"version":3,"sources":["compiled.js"],"names":[],"mappings":"AAAA"}"#,
1809 )
1810 .unwrap();
1811
1812 let inner = SourceMap::from_json(
1813 r#"{"version":3,"sources":["original.ts"],"sourcesContent":["const x = 1;"],"names":[],"mappings":"AAAA"}"#,
1814 )
1815 .unwrap();
1816
1817 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
1818
1819 assert_eq!(result.sources_content, vec![Some("const x = 1;".to_string())]);
1820 }
1821
1822 #[test]
1823 fn streaming_no_upstream_with_sources_content() {
1824 let outer = SourceMap::from_json(
1825 r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":["fn1"],"mappings":"AAAAA"}"#,
1826 )
1827 .unwrap();
1828
1829 let result = streaming_from_sm(&outer, |_| None);
1830
1831 assert_eq!(result.sources, vec!["a.js"]);
1832 assert_eq!(result.sources_content, vec![Some("var a;".to_string())]);
1833 let loc = result.original_position_for(0, 0).unwrap();
1834 assert!(loc.name.is_some());
1835 assert_eq!(result.name(loc.name.unwrap()), "fn1");
1836 }
1837
1838 #[test]
1839 fn streaming_generated_only_passthrough() {
1840 let outer = SourceMap::from_json(
1841 r#"{"version":3,"sources":["a.js","other.js"],"names":[],"mappings":"A,AAAA,KCAA"}"#,
1842 )
1843 .unwrap();
1844
1845 let inner = SourceMap::from_json(
1846 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AAAA"}"#,
1847 )
1848 .unwrap();
1849
1850 let result =
1851 streaming_from_sm(
1852 &outer,
1853 |source| {
1854 if source == "a.js" { Some(inner.clone()) } else { None }
1855 },
1856 );
1857
1858 assert!(result.mapping_count() >= 2);
1859 assert!(result.sources.contains(&"original.js".to_string()));
1860 assert!(result.sources.contains(&"other.js".to_string()));
1861 }
1862
1863 #[test]
1864 fn streaming_matches_remap() {
1865 let outer = SourceMap::from_json(
1867 r#"{"version":3,"sources":["intermediate.js","other.js"],"names":["foo"],"mappings":"AAAAA,KCAA;ADCA"}"#,
1868 )
1869 .unwrap();
1870
1871 let inner = SourceMap::from_json(
1872 r#"{"version":3,"sources":["original.js"],"sourcesContent":["// src"],"names":["bar"],"mappings":"AAAAA;AACA"}"#,
1873 )
1874 .unwrap();
1875
1876 let loader = |source: &str| -> Option<SourceMap> {
1877 if source == "intermediate.js" { Some(inner.clone()) } else { None }
1878 };
1879
1880 let result_normal = remap(&outer, loader);
1881 let result_stream = streaming_from_sm(&outer, loader);
1882
1883 assert_eq!(result_normal.sources, result_stream.sources);
1884 assert_eq!(result_normal.names, result_stream.names);
1885 assert_eq!(result_normal.sources_content, result_stream.sources_content);
1886 assert_eq!(result_normal.mapping_count(), result_stream.mapping_count());
1887
1888 for m in result_normal.all_mappings() {
1890 let loc_n = result_normal.original_position_for(m.generated_line, m.generated_column);
1891 let loc_s = result_stream.original_position_for(m.generated_line, m.generated_column);
1892 assert_eq!(loc_n.is_some(), loc_s.is_some());
1893 if let (Some(ln), Some(ls)) = (loc_n, loc_s) {
1894 assert_eq!(result_normal.source(ln.source), result_stream.source(ls.source));
1895 assert_eq!(ln.line, ls.line);
1896 assert_eq!(ln.column, ls.column);
1897 }
1898 }
1899 }
1900
1901 #[test]
1902 fn streaming_no_upstream_mapping_fallback() {
1903 let outer = SourceMap::from_json(
1904 r#"{"version":3,"sources":["compiled.js"],"names":["myFunc"],"mappings":"AAAAA"}"#,
1905 )
1906 .unwrap();
1907
1908 let inner = SourceMap::from_json(
1910 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":";;;;AAAA"}"#,
1911 )
1912 .unwrap();
1913
1914 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
1915
1916 let loc = result.original_position_for(0, 0);
1918 assert!(loc.is_none());
1919 }
1920
1921 #[test]
1922 fn streaming_no_upstream_mapping_no_name() {
1923 let outer = SourceMap::from_json(
1924 r#"{"version":3,"sources":["compiled.js"],"names":[],"mappings":"AAAA"}"#,
1925 )
1926 .unwrap();
1927
1928 let inner = SourceMap::from_json(
1929 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":";;;;AAAA"}"#,
1930 )
1931 .unwrap();
1932
1933 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
1934
1935 let loc = result.original_position_for(0, 0);
1937 assert!(loc.is_none());
1938 }
1939
1940 #[test]
1943 fn remap_chain_empty() {
1944 assert!(remap_chain(&[]).is_none());
1945 }
1946
1947 #[test]
1948 fn remap_chain_single() {
1949 let sm = SourceMap::from_json(
1950 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#,
1951 )
1952 .unwrap();
1953 let result = remap_chain(&[&sm]).unwrap();
1954 assert_eq!(result.sources, vec!["a.js"]);
1955 assert_eq!(result.mapping_count(), 1);
1956 }
1957
1958 #[test]
1959 fn remap_chain_two_maps() {
1960 let step1 = SourceMap::from_json(
1962 r#"{"version":3,"file":"intermediate.js","sources":["original.js"],"names":[],"mappings":"AACA;AACA"}"#,
1963 )
1964 .unwrap();
1965 let step2 = SourceMap::from_json(
1967 r#"{"version":3,"file":"output.js","sources":["intermediate.js"],"names":[],"mappings":"AAAA;AACA"}"#,
1968 )
1969 .unwrap();
1970
1971 let result = remap_chain(&[&step2, &step1]).unwrap();
1973 assert_eq!(result.sources, vec!["original.js"]);
1974
1975 let loc = result.original_position_for(0, 0).unwrap();
1977 assert_eq!(result.source(loc.source), "original.js");
1978 assert_eq!(loc.line, 1);
1979 }
1980
1981 #[test]
1982 fn remap_chain_three_maps() {
1983 let a_to_b = SourceMap::from_json(
1985 r#"{"version":3,"file":"b.js","sources":["a.js"],"names":[],"mappings":"AACA"}"#,
1986 )
1987 .unwrap();
1988 let b_to_c = SourceMap::from_json(
1990 r#"{"version":3,"file":"c.js","sources":["b.js"],"names":[],"mappings":"AAAA"}"#,
1991 )
1992 .unwrap();
1993 let c_to_d = SourceMap::from_json(
1995 r#"{"version":3,"file":"d.js","sources":["c.js"],"names":[],"mappings":"AAAA"}"#,
1996 )
1997 .unwrap();
1998
1999 let result = remap_chain(&[&c_to_d, &b_to_c, &a_to_b]).unwrap();
2001 assert_eq!(result.sources, vec!["a.js"]);
2002
2003 let loc = result.original_position_for(0, 0).unwrap();
2004 assert_eq!(result.source(loc.source), "a.js");
2005 assert_eq!(loc.line, 1);
2006 }
2007
2008 #[test]
2009 fn remap_chain_only_composes_matching_inner_file() {
2010 let inner = SourceMap::from_json(
2011 r#"{"version":3,"file":"intermediate.js","sources":["original.js"],"names":[],"mappings":"AAAA"}"#,
2012 )
2013 .unwrap();
2014 let outer = SourceMap::from_json(
2015 r#"{"version":3,"file":"output.js","sources":["intermediate.js","passthrough.js"],"names":[],"mappings":"AAAA,KCAA"}"#,
2016 )
2017 .unwrap();
2018
2019 let result = remap_chain(&[&outer, &inner]).unwrap();
2020
2021 assert!(result.sources.contains(&"original.js".to_string()));
2022 assert!(result.sources.contains(&"passthrough.js".to_string()));
2023
2024 let remapped = result.original_position_for(0, 0).unwrap();
2025 assert_eq!(result.source(remapped.source), "original.js");
2026
2027 let passthrough = result.original_position_for(0, 5).unwrap();
2028 assert_eq!(result.source(passthrough.source), "passthrough.js");
2029 }
2030
2031 #[test]
2034 fn remap_empty_string_source_filtered() {
2035 let outer =
2037 SourceMap::from_json(r#"{"version":3,"sources":[""],"names":[],"mappings":"AAAA"}"#)
2038 .unwrap();
2039
2040 let result = remap(&outer, |_| None);
2041
2042 assert!(
2044 !result.sources.iter().any(|s| s.is_empty()),
2045 "empty-string sources should be filtered out"
2046 );
2047 let loc = result.original_position_for(0, 0);
2049 assert!(loc.is_none());
2050 }
2051
2052 #[test]
2053 fn remap_null_source_filtered() {
2054 let outer =
2056 SourceMap::from_json(r#"{"version":3,"sources":[null],"names":[],"mappings":"AAAA"}"#)
2057 .unwrap();
2058
2059 let result = remap(&outer, |_| None);
2060
2061 assert!(
2062 !result.sources.iter().any(|s| s.is_empty()),
2063 "null sources should be filtered out"
2064 );
2065 }
2066
2067 #[test]
2068 fn streaming_empty_string_source_filtered() {
2069 let outer =
2070 SourceMap::from_json(r#"{"version":3,"sources":[""],"names":[],"mappings":"AAAA"}"#)
2071 .unwrap();
2072
2073 let result = streaming_from_sm(&outer, |_| None);
2074
2075 assert!(
2076 !result.sources.iter().any(|s| s.is_empty()),
2077 "streaming: empty-string sources should be filtered out"
2078 );
2079 }
2080
2081 #[test]
2084 fn remap_skips_redundant_sourced_segments() {
2085 let outer = SourceMap::from_json(
2089 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAA,EAAA"}"#,
2090 )
2091 .unwrap();
2092
2093 let result = remap(&outer, |_| None);
2094
2095 assert_eq!(result.mapping_count(), 1);
2097 }
2098
2099 #[test]
2100 fn remap_keeps_different_sourced_segments() {
2101 let outer = SourceMap::from_json(
2104 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAC"}"#,
2105 )
2106 .unwrap();
2107
2108 let result = remap(&outer, |_| None);
2109
2110 assert_eq!(result.mapping_count(), 2);
2112 }
2113
2114 #[test]
2115 fn remap_skips_sourceless_at_line_start() {
2116 let outer = SourceMap::from_json(r#"{"version":3,"sources":[],"names":[],"mappings":"A"}"#)
2119 .unwrap();
2120
2121 let result = remap(&outer, |_| None);
2122
2123 assert_eq!(result.mapping_count(), 0);
2125 }
2126
2127 #[test]
2128 fn streaming_skips_redundant_sourced_segments() {
2129 let outer = SourceMap::from_json(
2130 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAA,EAAA"}"#,
2131 )
2132 .unwrap();
2133
2134 let result = streaming_from_sm(&outer, |_| None);
2135
2136 assert_eq!(result.mapping_count(), 1);
2137 }
2138}