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 {
828 let fallback_source = if inner.file.is_none() {
829 let mut sources = outer.sources.iter().filter(|source| !source.is_empty());
830 match (sources.next(), sources.next()) {
831 (Some(source), None) => Some(source.clone()),
832 _ => None,
833 }
834 } else {
835 None
836 };
837
838 remap(outer, |source| {
839 if inner.file.as_deref() == Some(source) || fallback_source.as_deref() == Some(source) {
840 Some(inner.clone())
841 } else {
842 None
843 }
844 })
845}
846
847enum StreamingSourceEntry {
849 Upstream { map: Box<SourceMap>, cache: UpstreamCache },
851 Passthrough { builder_src: u32 },
853 EmptySource,
855 Unloaded,
857}
858
859pub fn remap_streaming<'a, F>(
872 mappings_iter: srcmap_sourcemap::MappingsIter<'a>,
873 sources: &[String],
874 names: &[String],
875 sources_content: &[Option<String>],
876 ignore_list: &[u32],
877 file: Option<String>,
878 loader: F,
879) -> SourceMap
880where
881 F: Fn(&str) -> Option<SourceMap>,
882{
883 let mut builder = StreamingGenerator::with_capacity(file, 4096);
884
885 let mut source_entries: Vec<StreamingSourceEntry> =
887 std::iter::repeat_with(|| StreamingSourceEntry::Unloaded).take(sources.len()).collect();
888
889 let mut ignored_sources: HashSet<u32> = HashSet::new();
890
891 let mut outer_name_remap: Vec<Option<u32>> = vec![None; names.len()];
893
894 let outer_ignore_set: HashSet<u32> = ignore_list.iter().copied().collect();
896
897 let mut dedup = DedupeState::new();
898
899 for item in mappings_iter {
900 let m = match item {
901 Ok(m) => m,
902 Err(_) => continue,
903 };
904
905 if m.source == NO_SOURCE {
906 trace_and_emit_sourceless(
907 &mut builder,
908 &mut dedup,
909 m.generated_line,
910 m.generated_column,
911 );
912 continue;
913 }
914
915 let si = m.source as usize;
916 if si >= sources.len() {
917 continue;
918 }
919
920 if matches!(source_entries[si], StreamingSourceEntry::Unloaded) {
922 let source_name = &sources[si];
923 if source_name.is_empty() {
924 source_entries[si] = StreamingSourceEntry::EmptySource;
925 } else {
926 match loader(source_name) {
927 Some(upstream_sm) => {
928 let cache = build_upstream_cache(&upstream_sm);
929 source_entries[si] =
930 StreamingSourceEntry::Upstream { map: Box::new(upstream_sm), cache };
931 }
932 None => {
933 let idx = builder.add_source(source_name);
934 if let Some(Some(content)) = sources_content.get(si) {
935 builder.set_source_content(idx, content.clone());
936 }
937 if outer_ignore_set.contains(&m.source) && ignored_sources.insert(idx) {
938 builder.add_to_ignore_list(idx);
939 }
940 source_entries[si] = StreamingSourceEntry::Passthrough { builder_src: idx };
941 }
942 }
943 }
944 }
945
946 match &mut source_entries[si] {
947 StreamingSourceEntry::Upstream { map, cache } => {
948 if let Some(upstream_m) = lookup_upstream(map, m.original_line, m.original_column) {
949 trace_and_emit_upstream(
950 &mut builder,
951 &mut dedup,
952 UpstreamEmitContext {
953 gen_line: m.generated_line,
954 gen_col: m.generated_column,
955 upstream_m,
956 cache,
957 upstream_map: map,
958 outer_name_remap: &mut outer_name_remap,
959 outer_name_idx: m.name,
960 names,
961 ignored_sources: &mut ignored_sources,
962 is_range: m.is_range_mapping,
963 },
964 );
965 }
966 }
967 StreamingSourceEntry::Passthrough { builder_src } => {
968 trace_and_emit_passthrough(
969 &mut builder,
970 &mut dedup,
971 PassthroughEmitContext {
972 gen_line: m.generated_line,
973 gen_col: m.generated_column,
974 orig_line: m.original_line,
975 orig_col: m.original_column,
976 builder_src: *builder_src,
977 outer_name_remap: &mut outer_name_remap,
978 outer_name_idx: m.name,
979 names,
980 is_range: m.is_range_mapping,
981 },
982 );
983 }
984 StreamingSourceEntry::EmptySource => {
985 trace_and_emit_sourceless(
986 &mut builder,
987 &mut dedup,
988 m.generated_line,
989 m.generated_column,
990 );
991 }
992 StreamingSourceEntry::Unloaded => unreachable!(),
993 }
994 }
995
996 builder.to_decoded_map().expect("streaming VLQ should be valid")
997}
998
999#[inline]
1000fn emit_generated_mapping<B: RemapBuilder>(builder: &mut B, gen_line: u32, gen_col: u32) {
1001 builder.add_generated_mapping(gen_line, gen_col);
1002}
1003
1004struct UpstreamEmitContext<'a> {
1005 gen_line: u32,
1006 gen_col: u32,
1007 upstream_m: UpstreamLookup,
1008 cache: &'a mut UpstreamCache,
1009 upstream_map: &'a SourceMap,
1010 outer_name_remap: &'a mut [Option<u32>],
1011 outer_name_idx: u32,
1012 names: &'a [String],
1013 ignored_sources: &'a mut HashSet<u32>,
1014 is_range: bool,
1015}
1016
1017#[inline]
1018fn trace_and_emit_upstream<B: RemapBuilder>(
1019 builder: &mut B,
1020 dedup: &mut DedupeState,
1021 ctx: UpstreamEmitContext<'_>,
1022) {
1023 let UpstreamEmitContext {
1024 gen_line,
1025 gen_col,
1026 upstream_m,
1027 cache,
1028 upstream_map,
1029 outer_name_remap,
1030 outer_name_idx,
1031 names,
1032 ignored_sources,
1033 is_range,
1034 } = ctx;
1035 let builder_src =
1036 resolve_upstream_source(cache, upstream_map, upstream_m.source, builder, ignored_sources);
1037
1038 let builder_name = if upstream_m.name != NO_NAME {
1039 Some(resolve_upstream_name(cache, upstream_map, upstream_m.name, builder))
1040 } else {
1041 resolve_outer_name_cached(outer_name_remap, outer_name_idx, names, builder)
1042 };
1043
1044 if !dedup.skip_source(
1045 gen_line,
1046 builder_src,
1047 upstream_m.original_line,
1048 upstream_m.original_column,
1049 builder_name,
1050 ) {
1051 emit_remapped_mapping(
1052 builder,
1053 gen_line,
1054 gen_col,
1055 builder_src,
1056 upstream_m.original_line,
1057 upstream_m.original_column,
1058 builder_name,
1059 is_range,
1060 );
1061 }
1062 dedup.record_source(
1063 gen_line,
1064 builder_src,
1065 upstream_m.original_line,
1066 upstream_m.original_column,
1067 builder_name,
1068 );
1069}
1070
1071struct PassthroughEmitContext<'a> {
1072 gen_line: u32,
1073 gen_col: u32,
1074 orig_line: u32,
1075 orig_col: u32,
1076 builder_src: u32,
1077 outer_name_remap: &'a mut [Option<u32>],
1078 outer_name_idx: u32,
1079 names: &'a [String],
1080 is_range: bool,
1081}
1082
1083#[inline]
1084fn trace_and_emit_passthrough<B: RemapBuilder>(
1085 builder: &mut B,
1086 dedup: &mut DedupeState,
1087 ctx: PassthroughEmitContext<'_>,
1088) {
1089 let PassthroughEmitContext {
1090 gen_line,
1091 gen_col,
1092 orig_line,
1093 orig_col,
1094 builder_src,
1095 outer_name_remap,
1096 outer_name_idx,
1097 names,
1098 is_range,
1099 } = ctx;
1100 let builder_name = resolve_outer_name_cached(outer_name_remap, outer_name_idx, names, builder);
1101
1102 if !dedup.skip_source(gen_line, builder_src, orig_line, orig_col, builder_name) {
1103 emit_remapped_mapping(
1104 builder,
1105 gen_line,
1106 gen_col,
1107 builder_src,
1108 orig_line,
1109 orig_col,
1110 builder_name,
1111 is_range,
1112 );
1113 }
1114 dedup.record_source(gen_line, builder_src, orig_line, orig_col, builder_name);
1115}
1116
1117#[inline]
1118fn trace_and_emit_sourceless<B: RemapBuilder>(
1119 builder: &mut B,
1120 dedup: &mut DedupeState,
1121 gen_line: u32,
1122 gen_col: u32,
1123) {
1124 if !dedup.skip_sourceless(gen_line) {
1125 emit_generated_mapping(builder, gen_line, gen_col);
1126 }
1127 dedup.record_sourceless(gen_line);
1128}
1129
1130#[cfg(test)]
1133mod tests {
1134 use super::*;
1135
1136 #[test]
1139 fn concat_two_simple_maps() {
1140 let a = SourceMap::from_json(
1141 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#,
1142 )
1143 .unwrap();
1144 let b = SourceMap::from_json(
1145 r#"{"version":3,"sources":["b.js"],"names":[],"mappings":"AAAA"}"#,
1146 )
1147 .unwrap();
1148
1149 let mut builder = ConcatBuilder::new(Some("bundle.js".to_string()));
1150 builder.add_map(&a, 0);
1151 builder.add_map(&b, 1);
1152
1153 let result = builder.build();
1154 assert_eq!(result.sources, vec!["a.js", "b.js"]);
1155 assert_eq!(result.mapping_count(), 2);
1156
1157 let loc0 = result.original_position_for(0, 0).unwrap();
1158 assert_eq!(result.source(loc0.source), "a.js");
1159
1160 let loc1 = result.original_position_for(1, 0).unwrap();
1161 assert_eq!(result.source(loc1.source), "b.js");
1162 }
1163
1164 #[test]
1165 fn concat_deduplicates_sources() {
1166 let a = SourceMap::from_json(
1167 r#"{"version":3,"sources":["shared.js"],"names":[],"mappings":"AAAA"}"#,
1168 )
1169 .unwrap();
1170 let b = SourceMap::from_json(
1171 r#"{"version":3,"sources":["shared.js"],"names":[],"mappings":"AAAA"}"#,
1172 )
1173 .unwrap();
1174
1175 let mut builder = ConcatBuilder::new(None);
1176 builder.add_map(&a, 0);
1177 builder.add_map(&b, 10);
1178
1179 let result = builder.build();
1180 assert_eq!(result.sources.len(), 1);
1181 assert_eq!(result.sources[0], "shared.js");
1182 }
1183
1184 #[test]
1185 fn concat_with_names() {
1186 let a = SourceMap::from_json(
1187 r#"{"version":3,"sources":["a.js"],"names":["foo"],"mappings":"AAAAA"}"#,
1188 )
1189 .unwrap();
1190 let b = SourceMap::from_json(
1191 r#"{"version":3,"sources":["b.js"],"names":["bar"],"mappings":"AAAAA"}"#,
1192 )
1193 .unwrap();
1194
1195 let mut builder = ConcatBuilder::new(None);
1196 builder.add_map(&a, 0);
1197 builder.add_map(&b, 1);
1198
1199 let result = builder.build();
1200 assert_eq!(result.names.len(), 2);
1201
1202 let loc0 = result.original_position_for(0, 0).unwrap();
1203 assert_eq!(loc0.name, Some(0));
1204 assert_eq!(result.name(0), "foo");
1205
1206 let loc1 = result.original_position_for(1, 0).unwrap();
1207 assert_eq!(loc1.name, Some(1));
1208 assert_eq!(result.name(1), "bar");
1209 }
1210
1211 #[test]
1212 fn concat_preserves_multi_line_maps() {
1213 let a = SourceMap::from_json(
1214 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;AACA;AACA"}"#,
1215 )
1216 .unwrap();
1217
1218 let mut builder = ConcatBuilder::new(None);
1219 builder.add_map(&a, 5); let result = builder.build();
1222 assert!(result.original_position_for(5, 0).is_some());
1223 assert!(result.original_position_for(6, 0).is_some());
1224 assert!(result.original_position_for(7, 0).is_some());
1225 assert!(result.original_position_for(4, 0).is_none());
1226 }
1227
1228 #[test]
1229 fn concat_with_sources_content() {
1230 let a = SourceMap::from_json(
1231 r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":[],"mappings":"AAAA"}"#,
1232 )
1233 .unwrap();
1234
1235 let mut builder = ConcatBuilder::new(None);
1236 builder.add_map(&a, 0);
1237
1238 let result = builder.build();
1239 assert_eq!(result.sources_content, vec![Some("var a;".to_string())]);
1240 }
1241
1242 #[test]
1243 fn concat_empty_builder() {
1244 let builder = ConcatBuilder::new(Some("empty.js".to_string()));
1245 let result = builder.build();
1246 assert_eq!(result.mapping_count(), 0);
1247 assert_eq!(result.sources.len(), 0);
1248 }
1249
1250 #[test]
1253 fn remap_single_level() {
1254 let outer = SourceMap::from_json(
1259 r#"{"version":3,"sources":["intermediate.js","other.js"],"names":[],"mappings":"AAAA,KCAA;ADCA"}"#,
1260 )
1261 .unwrap();
1262
1263 let inner = SourceMap::from_json(
1265 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AACA;AACA"}"#,
1266 )
1267 .unwrap();
1268
1269 let result =
1270 remap(
1271 &outer,
1272 |source| {
1273 if source == "intermediate.js" { Some(inner.clone()) } else { None }
1274 },
1275 );
1276
1277 assert!(result.sources.contains(&"original.js".to_string()));
1278 assert!(result.sources.contains(&"other.js".to_string()));
1280
1281 let loc = result.original_position_for(0, 0).unwrap();
1283 assert_eq!(result.source(loc.source), "original.js");
1284 assert_eq!(loc.line, 1);
1285 }
1286
1287 #[test]
1288 fn remap_no_upstream_passthrough() {
1289 let outer = SourceMap::from_json(
1290 r#"{"version":3,"sources":["already-original.js"],"names":[],"mappings":"AAAA"}"#,
1291 )
1292 .unwrap();
1293
1294 let result = remap(&outer, |_| None);
1296
1297 assert_eq!(result.sources, vec!["already-original.js"]);
1298 let loc = result.original_position_for(0, 0).unwrap();
1299 assert_eq!(result.source(loc.source), "already-original.js");
1300 assert_eq!(loc.line, 0);
1301 assert_eq!(loc.column, 0);
1302 }
1303
1304 #[test]
1305 fn remap_partial_sources() {
1306 let outer = SourceMap::from_json(
1308 r#"{"version":3,"sources":["compiled.js","passthrough.js"],"names":[],"mappings":"AAAA,KCCA"}"#,
1309 )
1310 .unwrap();
1311
1312 let inner = SourceMap::from_json(
1313 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":"AAAA"}"#,
1314 )
1315 .unwrap();
1316
1317 let result =
1318 remap(
1319 &outer,
1320 |source| {
1321 if source == "compiled.js" { Some(inner.clone()) } else { None }
1322 },
1323 );
1324
1325 assert!(result.sources.contains(&"original.ts".to_string()));
1327 assert!(result.sources.contains(&"passthrough.js".to_string()));
1328 }
1329
1330 #[test]
1331 fn remap_preserves_names() {
1332 let outer = SourceMap::from_json(
1333 r#"{"version":3,"sources":["compiled.js"],"names":["myFunc"],"mappings":"AAAAA"}"#,
1334 )
1335 .unwrap();
1336
1337 let inner = SourceMap::from_json(
1339 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":"AAAA"}"#,
1340 )
1341 .unwrap();
1342
1343 let result = remap(&outer, |_| Some(inner.clone()));
1344
1345 let loc = result.original_position_for(0, 0).unwrap();
1346 assert!(loc.name.is_some());
1347 assert_eq!(result.name(loc.name.unwrap()), "myFunc");
1348 }
1349
1350 #[test]
1351 fn remap_upstream_name_wins() {
1352 let outer = SourceMap::from_json(
1353 r#"{"version":3,"sources":["compiled.js"],"names":["outerName"],"mappings":"AAAAA"}"#,
1354 )
1355 .unwrap();
1356
1357 let inner = SourceMap::from_json(
1359 r#"{"version":3,"sources":["original.ts"],"names":["innerName"],"mappings":"AAAAA"}"#,
1360 )
1361 .unwrap();
1362
1363 let result = remap(&outer, |_| Some(inner.clone()));
1364
1365 let loc = result.original_position_for(0, 0).unwrap();
1366 assert!(loc.name.is_some());
1367 assert_eq!(result.name(loc.name.unwrap()), "innerName");
1368 }
1369
1370 #[test]
1371 fn remap_sources_content_from_upstream() {
1372 let outer = SourceMap::from_json(
1373 r#"{"version":3,"sources":["compiled.js"],"names":[],"mappings":"AAAA"}"#,
1374 )
1375 .unwrap();
1376
1377 let inner = SourceMap::from_json(
1378 r#"{"version":3,"sources":["original.ts"],"sourcesContent":["const x = 1;"],"names":[],"mappings":"AAAA"}"#,
1379 )
1380 .unwrap();
1381
1382 let result = remap(&outer, |_| Some(inner.clone()));
1383
1384 assert_eq!(result.sources_content, vec![Some("const x = 1;".to_string())]);
1385 }
1386
1387 #[test]
1390 fn concat_updates_source_content_on_duplicate() {
1391 let a = SourceMap::from_json(
1393 r#"{"version":3,"sources":["shared.js"],"names":[],"mappings":"AAAA"}"#,
1394 )
1395 .unwrap();
1396 let b = SourceMap::from_json(
1397 r#"{"version":3,"sources":["shared.js"],"sourcesContent":["var x = 1;"],"names":[],"mappings":"AAAA"}"#,
1398 )
1399 .unwrap();
1400
1401 let mut builder = ConcatBuilder::new(None);
1402 builder.add_map(&a, 0);
1403 builder.add_map(&b, 1);
1404
1405 let result = builder.build();
1406 assert_eq!(result.sources.len(), 1);
1407 assert_eq!(result.sources_content, vec![Some("var x = 1;".to_string())]);
1408 }
1409
1410 #[test]
1411 fn concat_deduplicates_names() {
1412 let a = SourceMap::from_json(
1413 r#"{"version":3,"sources":["a.js"],"names":["sharedName"],"mappings":"AAAAA"}"#,
1414 )
1415 .unwrap();
1416 let b = SourceMap::from_json(
1417 r#"{"version":3,"sources":["b.js"],"names":["sharedName"],"mappings":"AAAAA"}"#,
1418 )
1419 .unwrap();
1420
1421 let mut builder = ConcatBuilder::new(None);
1422 builder.add_map(&a, 0);
1423 builder.add_map(&b, 1);
1424
1425 let result = builder.build();
1426 assert_eq!(result.names.len(), 1);
1428 assert_eq!(result.names[0], "sharedName");
1429 }
1430
1431 #[test]
1432 fn concat_with_ignore_list() {
1433 let a = SourceMap::from_json(
1434 r#"{"version":3,"sources":["vendor.js"],"names":[],"mappings":"AAAA","ignoreList":[0]}"#,
1435 )
1436 .unwrap();
1437
1438 let mut builder = ConcatBuilder::new(None);
1439 builder.add_map(&a, 0);
1440
1441 let result = builder.build();
1442 assert_eq!(result.ignore_list, vec![0]);
1443 }
1444
1445 #[test]
1446 fn concat_with_generated_only_mappings() {
1447 let a = SourceMap::from_json(
1449 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A,AAAA"}"#,
1450 )
1451 .unwrap();
1452
1453 let mut builder = ConcatBuilder::new(None);
1454 builder.add_map(&a, 0);
1455
1456 let result = builder.build();
1457 assert!(result.mapping_count() >= 1);
1459 }
1460
1461 #[test]
1462 fn remap_generated_only_passthrough() {
1463 let outer = SourceMap::from_json(
1468 r#"{"version":3,"sources":["a.js","other.js"],"names":[],"mappings":"A,AAAA,KCAA"}"#,
1469 )
1470 .unwrap();
1471
1472 let inner = SourceMap::from_json(
1473 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AAAA"}"#,
1474 )
1475 .unwrap();
1476
1477 let result =
1478 remap(&outer, |source| if source == "a.js" { Some(inner.clone()) } else { None });
1479
1480 assert!(result.mapping_count() >= 2);
1482 assert!(result.sources.contains(&"original.js".to_string()));
1483 assert!(result.sources.contains(&"other.js".to_string()));
1484 }
1485
1486 #[test]
1487 fn remap_no_upstream_mapping_with_name() {
1488 let outer = SourceMap::from_json(
1490 r#"{"version":3,"sources":["compiled.js"],"names":["myFunc"],"mappings":"AAAAA"}"#,
1491 )
1492 .unwrap();
1493
1494 let inner = SourceMap::from_json(
1496 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":";;;;AAAA"}"#,
1497 )
1498 .unwrap();
1499
1500 let result = remap(&outer, |_| Some(inner.clone()));
1501
1502 let loc = result.original_position_for(0, 0);
1506 assert!(loc.is_none());
1507 }
1508
1509 #[test]
1510 fn remap_no_upstream_with_sources_content_and_name() {
1511 let outer = SourceMap::from_json(
1512 r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":["fn1"],"mappings":"AAAAA"}"#,
1513 )
1514 .unwrap();
1515
1516 let result = remap(&outer, |_| None);
1518
1519 assert_eq!(result.sources, vec!["a.js"]);
1520 assert_eq!(result.sources_content, vec![Some("var a;".to_string())]);
1521 let loc = result.original_position_for(0, 0).unwrap();
1522 assert!(loc.name.is_some());
1523 assert_eq!(result.name(loc.name.unwrap()), "fn1");
1524 }
1525
1526 #[test]
1527 fn remap_no_upstream_no_name() {
1528 let outer = SourceMap::from_json(
1529 r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":[],"mappings":"AAAA"}"#,
1530 )
1531 .unwrap();
1532
1533 let result = remap(&outer, |_| None);
1534 let loc = result.original_position_for(0, 0).unwrap();
1535 assert!(loc.name.is_none());
1536 }
1537
1538 #[test]
1539 fn remap_no_upstream_mapping_no_name() {
1540 let outer = SourceMap::from_json(
1543 r#"{"version":3,"sources":["compiled.js"],"names":[],"mappings":"AAAA"}"#,
1544 )
1545 .unwrap();
1546
1547 let inner = SourceMap::from_json(
1550 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":";;;;AAAA"}"#,
1551 )
1552 .unwrap();
1553
1554 let result = remap(&outer, |_| Some(inner.clone()));
1555
1556 let loc = result.original_position_for(0, 0);
1558 assert!(loc.is_none());
1559 }
1560
1561 #[test]
1562 fn remap_upstream_found_no_name() {
1563 let outer = SourceMap::from_json(
1571 r#"{"version":3,"sources":["intermediate.js"],"names":[],"mappings":"AAAA"}"#,
1572 )
1573 .unwrap();
1574
1575 let inner = SourceMap::from_json(
1577 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AAAA"}"#,
1578 )
1579 .unwrap();
1580
1581 let result = remap(&outer, |_| Some(inner.clone()));
1582
1583 assert_eq!(result.sources, vec!["original.js"]);
1584 let loc = result.original_position_for(0, 0).unwrap();
1585 assert_eq!(result.source(loc.source), "original.js");
1586 assert_eq!(loc.line, 0);
1587 assert_eq!(loc.column, 0);
1588 assert!(loc.name.is_none());
1590 assert!(result.names.is_empty());
1591 }
1592
1593 #[test]
1596 fn concat_preserves_range_mappings() {
1597 let a = SourceMap::from_json(
1598 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,CAAC","rangeMappings":"A"}"#,
1599 )
1600 .unwrap();
1601
1602 let mut builder = ConcatBuilder::new(None);
1603 builder.add_map(&a, 0);
1604
1605 let result = builder.build();
1606 assert!(result.has_range_mappings());
1607 let mappings = result.all_mappings();
1608 assert!(mappings[0].is_range_mapping);
1609 assert!(!mappings[1].is_range_mapping);
1610 }
1611
1612 #[test]
1613 fn remap_preserves_range_mappings_passthrough() {
1614 let outer = SourceMap::from_json(
1615 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","rangeMappings":"A"}"#,
1616 )
1617 .unwrap();
1618
1619 let result = remap(&outer, |_| None);
1621 assert!(result.has_range_mappings());
1622 let mappings = result.all_mappings();
1623 assert!(mappings[0].is_range_mapping);
1624 }
1625
1626 #[test]
1627 fn remap_preserves_range_through_upstream() {
1628 let outer = SourceMap::from_json(
1629 r#"{"version":3,"sources":["intermediate.js"],"names":[],"mappings":"AAAA","rangeMappings":"A"}"#,
1630 )
1631 .unwrap();
1632
1633 let inner = SourceMap::from_json(
1634 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AACA"}"#,
1635 )
1636 .unwrap();
1637
1638 let result = remap(&outer, |_| Some(inner.clone()));
1639 assert!(result.has_range_mappings());
1640 }
1641
1642 #[test]
1643 fn remap_non_range_stays_non_range() {
1644 let outer = SourceMap::from_json(
1645 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#,
1646 )
1647 .unwrap();
1648
1649 let result = remap(&outer, |_| None);
1650 assert!(!result.has_range_mappings());
1651 }
1652
1653 fn streaming_from_sm<F>(sm: &SourceMap, loader: F) -> SourceMap
1658 where
1659 F: Fn(&str) -> Option<SourceMap>,
1660 {
1661 let vlq = sm.encode_mappings();
1662 let iter = srcmap_sourcemap::MappingsIter::new(&vlq);
1663 remap_streaming(
1664 iter,
1665 &sm.sources,
1666 &sm.names,
1667 &sm.sources_content,
1668 &sm.ignore_list,
1669 sm.file.clone(),
1670 loader,
1671 )
1672 }
1673
1674 #[test]
1675 fn streaming_single_level() {
1676 let outer = SourceMap::from_json(
1677 r#"{"version":3,"sources":["intermediate.js","other.js"],"names":[],"mappings":"AAAA,KCAA;ADCA"}"#,
1678 )
1679 .unwrap();
1680
1681 let inner = SourceMap::from_json(
1682 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AACA;AACA"}"#,
1683 )
1684 .unwrap();
1685
1686 let result = streaming_from_sm(&outer, |source| {
1687 if source == "intermediate.js" { Some(inner.clone()) } else { None }
1688 });
1689
1690 assert!(result.sources.contains(&"original.js".to_string()));
1691 assert!(result.sources.contains(&"other.js".to_string()));
1692
1693 let loc = result.original_position_for(0, 0).unwrap();
1694 assert_eq!(result.source(loc.source), "original.js");
1695 assert_eq!(loc.line, 1);
1696 }
1697
1698 #[test]
1699 fn streaming_no_upstream_passthrough() {
1700 let outer = SourceMap::from_json(
1701 r#"{"version":3,"sources":["already-original.js"],"names":[],"mappings":"AAAA"}"#,
1702 )
1703 .unwrap();
1704
1705 let result = streaming_from_sm(&outer, |_| None);
1706
1707 assert_eq!(result.sources, vec!["already-original.js"]);
1708 let loc = result.original_position_for(0, 0).unwrap();
1709 assert_eq!(result.source(loc.source), "already-original.js");
1710 assert_eq!(loc.line, 0);
1711 assert_eq!(loc.column, 0);
1712 }
1713
1714 #[test]
1715 fn streaming_preserves_names() {
1716 let outer = SourceMap::from_json(
1717 r#"{"version":3,"sources":["compiled.js"],"names":["myFunc"],"mappings":"AAAAA"}"#,
1718 )
1719 .unwrap();
1720
1721 let inner = SourceMap::from_json(
1722 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":"AAAA"}"#,
1723 )
1724 .unwrap();
1725
1726 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
1727
1728 let loc = result.original_position_for(0, 0).unwrap();
1729 assert!(loc.name.is_some());
1730 assert_eq!(result.name(loc.name.unwrap()), "myFunc");
1731 }
1732
1733 #[test]
1734 fn streaming_upstream_name_wins() {
1735 let outer = SourceMap::from_json(
1736 r#"{"version":3,"sources":["compiled.js"],"names":["outerName"],"mappings":"AAAAA"}"#,
1737 )
1738 .unwrap();
1739
1740 let inner = SourceMap::from_json(
1741 r#"{"version":3,"sources":["original.ts"],"names":["innerName"],"mappings":"AAAAA"}"#,
1742 )
1743 .unwrap();
1744
1745 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
1746
1747 let loc = result.original_position_for(0, 0).unwrap();
1748 assert!(loc.name.is_some());
1749 assert_eq!(result.name(loc.name.unwrap()), "innerName");
1750 }
1751
1752 #[test]
1753 fn streaming_sources_content_from_upstream() {
1754 let outer = SourceMap::from_json(
1755 r#"{"version":3,"sources":["compiled.js"],"names":[],"mappings":"AAAA"}"#,
1756 )
1757 .unwrap();
1758
1759 let inner = SourceMap::from_json(
1760 r#"{"version":3,"sources":["original.ts"],"sourcesContent":["const x = 1;"],"names":[],"mappings":"AAAA"}"#,
1761 )
1762 .unwrap();
1763
1764 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
1765
1766 assert_eq!(result.sources_content, vec![Some("const x = 1;".to_string())]);
1767 }
1768
1769 #[test]
1770 fn streaming_no_upstream_with_sources_content() {
1771 let outer = SourceMap::from_json(
1772 r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":["fn1"],"mappings":"AAAAA"}"#,
1773 )
1774 .unwrap();
1775
1776 let result = streaming_from_sm(&outer, |_| None);
1777
1778 assert_eq!(result.sources, vec!["a.js"]);
1779 assert_eq!(result.sources_content, vec![Some("var a;".to_string())]);
1780 let loc = result.original_position_for(0, 0).unwrap();
1781 assert!(loc.name.is_some());
1782 assert_eq!(result.name(loc.name.unwrap()), "fn1");
1783 }
1784
1785 #[test]
1786 fn streaming_generated_only_passthrough() {
1787 let outer = SourceMap::from_json(
1788 r#"{"version":3,"sources":["a.js","other.js"],"names":[],"mappings":"A,AAAA,KCAA"}"#,
1789 )
1790 .unwrap();
1791
1792 let inner = SourceMap::from_json(
1793 r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AAAA"}"#,
1794 )
1795 .unwrap();
1796
1797 let result =
1798 streaming_from_sm(
1799 &outer,
1800 |source| {
1801 if source == "a.js" { Some(inner.clone()) } else { None }
1802 },
1803 );
1804
1805 assert!(result.mapping_count() >= 2);
1806 assert!(result.sources.contains(&"original.js".to_string()));
1807 assert!(result.sources.contains(&"other.js".to_string()));
1808 }
1809
1810 #[test]
1811 fn streaming_matches_remap() {
1812 let outer = SourceMap::from_json(
1814 r#"{"version":3,"sources":["intermediate.js","other.js"],"names":["foo"],"mappings":"AAAAA,KCAA;ADCA"}"#,
1815 )
1816 .unwrap();
1817
1818 let inner = SourceMap::from_json(
1819 r#"{"version":3,"sources":["original.js"],"sourcesContent":["// src"],"names":["bar"],"mappings":"AAAAA;AACA"}"#,
1820 )
1821 .unwrap();
1822
1823 let loader = |source: &str| -> Option<SourceMap> {
1824 if source == "intermediate.js" { Some(inner.clone()) } else { None }
1825 };
1826
1827 let result_normal = remap(&outer, loader);
1828 let result_stream = streaming_from_sm(&outer, loader);
1829
1830 assert_eq!(result_normal.sources, result_stream.sources);
1831 assert_eq!(result_normal.names, result_stream.names);
1832 assert_eq!(result_normal.sources_content, result_stream.sources_content);
1833 assert_eq!(result_normal.mapping_count(), result_stream.mapping_count());
1834
1835 for m in result_normal.all_mappings() {
1837 let loc_n = result_normal.original_position_for(m.generated_line, m.generated_column);
1838 let loc_s = result_stream.original_position_for(m.generated_line, m.generated_column);
1839 assert_eq!(loc_n.is_some(), loc_s.is_some());
1840 if let (Some(ln), Some(ls)) = (loc_n, loc_s) {
1841 assert_eq!(result_normal.source(ln.source), result_stream.source(ls.source));
1842 assert_eq!(ln.line, ls.line);
1843 assert_eq!(ln.column, ls.column);
1844 }
1845 }
1846 }
1847
1848 #[test]
1849 fn streaming_no_upstream_mapping_fallback() {
1850 let outer = SourceMap::from_json(
1851 r#"{"version":3,"sources":["compiled.js"],"names":["myFunc"],"mappings":"AAAAA"}"#,
1852 )
1853 .unwrap();
1854
1855 let inner = SourceMap::from_json(
1857 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":";;;;AAAA"}"#,
1858 )
1859 .unwrap();
1860
1861 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
1862
1863 let loc = result.original_position_for(0, 0);
1865 assert!(loc.is_none());
1866 }
1867
1868 #[test]
1869 fn streaming_no_upstream_mapping_no_name() {
1870 let outer = SourceMap::from_json(
1871 r#"{"version":3,"sources":["compiled.js"],"names":[],"mappings":"AAAA"}"#,
1872 )
1873 .unwrap();
1874
1875 let inner = SourceMap::from_json(
1876 r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":";;;;AAAA"}"#,
1877 )
1878 .unwrap();
1879
1880 let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
1881
1882 let loc = result.original_position_for(0, 0);
1884 assert!(loc.is_none());
1885 }
1886
1887 #[test]
1890 fn remap_chain_empty() {
1891 assert!(remap_chain(&[]).is_none());
1892 }
1893
1894 #[test]
1895 fn remap_chain_single() {
1896 let sm = SourceMap::from_json(
1897 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#,
1898 )
1899 .unwrap();
1900 let result = remap_chain(&[&sm]).unwrap();
1901 assert_eq!(result.sources, vec!["a.js"]);
1902 assert_eq!(result.mapping_count(), 1);
1903 }
1904
1905 #[test]
1906 fn remap_chain_two_maps() {
1907 let step1 = SourceMap::from_json(
1909 r#"{"version":3,"file":"intermediate.js","sources":["original.js"],"names":[],"mappings":"AACA;AACA"}"#,
1910 )
1911 .unwrap();
1912 let step2 = SourceMap::from_json(
1914 r#"{"version":3,"file":"output.js","sources":["intermediate.js"],"names":[],"mappings":"AAAA;AACA"}"#,
1915 )
1916 .unwrap();
1917
1918 let result = remap_chain(&[&step2, &step1]).unwrap();
1920 assert_eq!(result.sources, vec!["original.js"]);
1921
1922 let loc = result.original_position_for(0, 0).unwrap();
1924 assert_eq!(result.source(loc.source), "original.js");
1925 assert_eq!(loc.line, 1);
1926 }
1927
1928 #[test]
1929 fn remap_chain_three_maps() {
1930 let a_to_b = SourceMap::from_json(
1932 r#"{"version":3,"file":"b.js","sources":["a.js"],"names":[],"mappings":"AACA"}"#,
1933 )
1934 .unwrap();
1935 let b_to_c = SourceMap::from_json(
1937 r#"{"version":3,"file":"c.js","sources":["b.js"],"names":[],"mappings":"AAAA"}"#,
1938 )
1939 .unwrap();
1940 let c_to_d = SourceMap::from_json(
1942 r#"{"version":3,"file":"d.js","sources":["c.js"],"names":[],"mappings":"AAAA"}"#,
1943 )
1944 .unwrap();
1945
1946 let result = remap_chain(&[&c_to_d, &b_to_c, &a_to_b]).unwrap();
1948 assert_eq!(result.sources, vec!["a.js"]);
1949
1950 let loc = result.original_position_for(0, 0).unwrap();
1951 assert_eq!(result.source(loc.source), "a.js");
1952 assert_eq!(loc.line, 1);
1953 }
1954
1955 #[test]
1956 fn remap_chain_only_composes_matching_inner_file() {
1957 let inner = SourceMap::from_json(
1958 r#"{"version":3,"file":"intermediate.js","sources":["original.js"],"names":[],"mappings":"AAAA"}"#,
1959 )
1960 .unwrap();
1961 let outer = SourceMap::from_json(
1962 r#"{"version":3,"file":"output.js","sources":["intermediate.js","passthrough.js"],"names":[],"mappings":"AAAA,KCAA"}"#,
1963 )
1964 .unwrap();
1965
1966 let result = remap_chain(&[&outer, &inner]).unwrap();
1967
1968 assert!(result.sources.contains(&"original.js".to_string()));
1969 assert!(result.sources.contains(&"passthrough.js".to_string()));
1970
1971 let remapped = result.original_position_for(0, 0).unwrap();
1972 assert_eq!(result.source(remapped.source), "original.js");
1973
1974 let passthrough = result.original_position_for(0, 5).unwrap();
1975 assert_eq!(result.source(passthrough.source), "passthrough.js");
1976 }
1977
1978 #[test]
1981 fn remap_empty_string_source_filtered() {
1982 let outer =
1984 SourceMap::from_json(r#"{"version":3,"sources":[""],"names":[],"mappings":"AAAA"}"#)
1985 .unwrap();
1986
1987 let result = remap(&outer, |_| None);
1988
1989 assert!(
1991 !result.sources.iter().any(|s| s.is_empty()),
1992 "empty-string sources should be filtered out"
1993 );
1994 let loc = result.original_position_for(0, 0);
1996 assert!(loc.is_none());
1997 }
1998
1999 #[test]
2000 fn remap_null_source_filtered() {
2001 let outer =
2003 SourceMap::from_json(r#"{"version":3,"sources":[null],"names":[],"mappings":"AAAA"}"#)
2004 .unwrap();
2005
2006 let result = remap(&outer, |_| None);
2007
2008 assert!(
2009 !result.sources.iter().any(|s| s.is_empty()),
2010 "null sources should be filtered out"
2011 );
2012 }
2013
2014 #[test]
2015 fn streaming_empty_string_source_filtered() {
2016 let outer =
2017 SourceMap::from_json(r#"{"version":3,"sources":[""],"names":[],"mappings":"AAAA"}"#)
2018 .unwrap();
2019
2020 let result = streaming_from_sm(&outer, |_| None);
2021
2022 assert!(
2023 !result.sources.iter().any(|s| s.is_empty()),
2024 "streaming: empty-string sources should be filtered out"
2025 );
2026 }
2027
2028 #[test]
2031 fn remap_skips_redundant_sourced_segments() {
2032 let outer = SourceMap::from_json(
2036 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAA,EAAA"}"#,
2037 )
2038 .unwrap();
2039
2040 let result = remap(&outer, |_| None);
2041
2042 assert_eq!(result.mapping_count(), 1);
2044 }
2045
2046 #[test]
2047 fn remap_keeps_different_sourced_segments() {
2048 let outer = SourceMap::from_json(
2051 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAC"}"#,
2052 )
2053 .unwrap();
2054
2055 let result = remap(&outer, |_| None);
2056
2057 assert_eq!(result.mapping_count(), 2);
2059 }
2060
2061 #[test]
2062 fn remap_skips_sourceless_at_line_start() {
2063 let outer = SourceMap::from_json(r#"{"version":3,"sources":[],"names":[],"mappings":"A"}"#)
2066 .unwrap();
2067
2068 let result = remap(&outer, |_| None);
2069
2070 assert_eq!(result.mapping_count(), 0);
2072 }
2073
2074 #[test]
2075 fn streaming_skips_redundant_sourced_segments() {
2076 let outer = SourceMap::from_json(
2077 r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAA,EAAA"}"#,
2078 )
2079 .unwrap();
2080
2081 let result = streaming_from_sm(&outer, |_| None);
2082
2083 assert_eq!(result.mapping_count(), 1);
2084 }
2085}