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