use srcmap_generator::{SourceMapGenerator, StreamingGenerator};
use srcmap_sourcemap::SourceMap;
use std::collections::HashSet;
const NO_SOURCE: u32 = u32::MAX;
const NO_NAME: u32 = u32::MAX;
pub struct ConcatBuilder {
builder: SourceMapGenerator,
}
impl ConcatBuilder {
pub fn new(file: Option<String>) -> Self {
Self { builder: SourceMapGenerator::new(file) }
}
pub fn add_map(&mut self, sm: &SourceMap, line_offset: u32) {
let source_indices: Vec<u32> = sm
.sources
.iter()
.enumerate()
.map(|(i, s)| {
let idx = self.builder.add_source(s);
if let Some(Some(content)) = sm.sources_content.get(i) {
self.builder.set_source_content(idx, content.clone());
}
idx
})
.collect();
let name_indices: Vec<u32> = sm.names.iter().map(|n| self.builder.add_name(n)).collect();
for &ignored in &sm.ignore_list {
let global_idx = source_indices[ignored as usize];
self.builder.add_to_ignore_list(global_idx);
}
for m in sm.all_mappings() {
let gen_line = m.generated_line + line_offset;
if m.source == NO_SOURCE {
self.builder.add_generated_mapping(gen_line, m.generated_column);
} else {
let src = source_indices[m.source as usize];
let has_name = m.name != NO_NAME;
match (has_name, m.is_range_mapping) {
(true, true) => self.builder.add_named_range_mapping(
gen_line,
m.generated_column,
src,
m.original_line,
m.original_column,
name_indices[m.name as usize],
),
(true, false) => self.builder.add_named_mapping(
gen_line,
m.generated_column,
src,
m.original_line,
m.original_column,
name_indices[m.name as usize],
),
(false, true) => self.builder.add_range_mapping(
gen_line,
m.generated_column,
src,
m.original_line,
m.original_column,
),
(false, false) => self.builder.add_mapping(
gen_line,
m.generated_column,
src,
m.original_line,
m.original_column,
),
}
}
}
}
pub fn to_json(&self) -> String {
self.builder.to_json()
}
pub fn build(&self) -> SourceMap {
self.builder.to_decoded_map()
}
}
struct UpstreamCache {
source_remap: Vec<Option<u32>>,
name_remap: Vec<Option<u32>>,
}
fn build_upstream_cache(upstream_sm: &SourceMap) -> UpstreamCache {
UpstreamCache {
source_remap: vec![None; upstream_sm.sources.len()],
name_remap: vec![None; upstream_sm.names.len()],
}
}
trait RemapBuilder {
fn add_source(&mut self, source: &str) -> u32;
fn set_source_content(&mut self, idx: u32, content: String);
fn add_name(&mut self, name: &str) -> u32;
fn add_to_ignore_list(&mut self, idx: u32);
fn add_generated_mapping(&mut self, gen_line: u32, gen_col: u32);
fn add_mapping(&mut self, gen_line: u32, gen_col: u32, src: u32, orig_line: u32, orig_col: u32);
fn add_named_mapping(
&mut self,
gen_line: u32,
gen_col: u32,
src: u32,
orig_line: u32,
orig_col: u32,
name: u32,
);
fn add_range_mapping(
&mut self,
gen_line: u32,
gen_col: u32,
src: u32,
orig_line: u32,
orig_col: u32,
);
fn add_named_range_mapping(
&mut self,
gen_line: u32,
gen_col: u32,
src: u32,
orig_line: u32,
orig_col: u32,
name: u32,
);
}
impl RemapBuilder for SourceMapGenerator {
fn add_source(&mut self, source: &str) -> u32 {
SourceMapGenerator::add_source(self, source)
}
fn set_source_content(&mut self, idx: u32, content: String) {
SourceMapGenerator::set_source_content(self, idx, content)
}
fn add_name(&mut self, name: &str) -> u32 {
SourceMapGenerator::add_name(self, name)
}
fn add_to_ignore_list(&mut self, idx: u32) {
SourceMapGenerator::add_to_ignore_list(self, idx)
}
fn add_generated_mapping(&mut self, gen_line: u32, gen_col: u32) {
SourceMapGenerator::add_generated_mapping(self, gen_line, gen_col)
}
fn add_mapping(
&mut self,
gen_line: u32,
gen_col: u32,
src: u32,
orig_line: u32,
orig_col: u32,
) {
SourceMapGenerator::add_mapping(self, gen_line, gen_col, src, orig_line, orig_col)
}
fn add_named_mapping(
&mut self,
gen_line: u32,
gen_col: u32,
src: u32,
orig_line: u32,
orig_col: u32,
name: u32,
) {
SourceMapGenerator::add_named_mapping(
self, gen_line, gen_col, src, orig_line, orig_col, name,
)
}
fn add_range_mapping(
&mut self,
gen_line: u32,
gen_col: u32,
src: u32,
orig_line: u32,
orig_col: u32,
) {
SourceMapGenerator::add_range_mapping(self, gen_line, gen_col, src, orig_line, orig_col)
}
fn add_named_range_mapping(
&mut self,
gen_line: u32,
gen_col: u32,
src: u32,
orig_line: u32,
orig_col: u32,
name: u32,
) {
SourceMapGenerator::add_named_range_mapping(
self, gen_line, gen_col, src, orig_line, orig_col, name,
)
}
}
impl RemapBuilder for StreamingGenerator {
fn add_source(&mut self, source: &str) -> u32 {
StreamingGenerator::add_source(self, source)
}
fn set_source_content(&mut self, idx: u32, content: String) {
StreamingGenerator::set_source_content(self, idx, content)
}
fn add_name(&mut self, name: &str) -> u32 {
StreamingGenerator::add_name(self, name)
}
fn add_to_ignore_list(&mut self, idx: u32) {
StreamingGenerator::add_to_ignore_list(self, idx)
}
fn add_generated_mapping(&mut self, gen_line: u32, gen_col: u32) {
StreamingGenerator::add_generated_mapping(self, gen_line, gen_col)
}
fn add_mapping(
&mut self,
gen_line: u32,
gen_col: u32,
src: u32,
orig_line: u32,
orig_col: u32,
) {
StreamingGenerator::add_mapping(self, gen_line, gen_col, src, orig_line, orig_col)
}
fn add_named_mapping(
&mut self,
gen_line: u32,
gen_col: u32,
src: u32,
orig_line: u32,
orig_col: u32,
name: u32,
) {
StreamingGenerator::add_named_mapping(
self, gen_line, gen_col, src, orig_line, orig_col, name,
)
}
fn add_range_mapping(
&mut self,
gen_line: u32,
gen_col: u32,
src: u32,
orig_line: u32,
orig_col: u32,
) {
StreamingGenerator::add_range_mapping(self, gen_line, gen_col, src, orig_line, orig_col)
}
fn add_named_range_mapping(
&mut self,
gen_line: u32,
gen_col: u32,
src: u32,
orig_line: u32,
orig_col: u32,
name: u32,
) {
StreamingGenerator::add_named_range_mapping(
self, gen_line, gen_col, src, orig_line, orig_col, name,
)
}
}
#[inline]
fn resolve_upstream_source<B: RemapBuilder>(
cache: &mut UpstreamCache,
upstream_sm: &SourceMap,
upstream_src: u32,
builder: &mut B,
ignored_sources: &mut HashSet<u32>,
) -> u32 {
let si = upstream_src as usize;
if let Some(idx) = cache.source_remap[si] {
return idx;
}
let idx = builder.add_source(&upstream_sm.sources[si]);
if let Some(Some(content)) = upstream_sm.sources_content.get(si) {
builder.set_source_content(idx, content.clone());
}
if upstream_sm.ignore_list.contains(&upstream_src) && ignored_sources.insert(idx) {
builder.add_to_ignore_list(idx);
}
cache.source_remap[si] = Some(idx);
idx
}
#[inline]
fn resolve_upstream_name<B: RemapBuilder>(
cache: &mut UpstreamCache,
upstream_sm: &SourceMap,
upstream_name: u32,
builder: &mut B,
) -> u32 {
let ni = upstream_name as usize;
if let Some(idx) = cache.name_remap[ni] {
return idx;
}
let idx = builder.add_name(&upstream_sm.names[ni]);
cache.name_remap[ni] = Some(idx);
idx
}
#[inline]
fn lookup_upstream(upstream_sm: &SourceMap, line: u32, column: u32) -> Option<UpstreamLookup> {
let line_mappings = upstream_sm.mappings_for_line(line);
if line_mappings.is_empty() {
return fallback_to_full_lookup(upstream_sm, line, column);
}
let idx = match line_mappings.binary_search_by_key(&column, |m| m.generated_column) {
Ok(i) => i,
Err(0) => return fallback_to_full_lookup(upstream_sm, line, column),
Err(i) => i - 1,
};
let mapping = &line_mappings[idx];
if mapping.source == NO_SOURCE {
return None;
}
let original_column = if mapping.is_range_mapping && column >= mapping.generated_column {
mapping.original_column + (column - mapping.generated_column)
} else {
mapping.original_column
};
Some(UpstreamLookup {
source: mapping.source,
original_line: mapping.original_line,
original_column,
name: mapping.name,
})
}
struct UpstreamLookup {
source: u32,
original_line: u32,
original_column: u32,
name: u32,
}
fn fallback_to_full_lookup(
upstream_sm: &SourceMap,
line: u32,
column: u32,
) -> Option<UpstreamLookup> {
let loc = upstream_sm.original_position_for(line, column)?;
Some(UpstreamLookup {
source: loc.source,
original_line: loc.line,
original_column: loc.column,
name: loc.name.unwrap_or(NO_NAME),
})
}
#[inline]
fn resolve_outer_name_cached<B: RemapBuilder>(
outer_name_remap: &mut [Option<u32>],
name_idx: u32,
names: &[String],
builder: &mut B,
) -> Option<u32> {
if name_idx == NO_NAME {
return None;
}
let slot = outer_name_remap.get_mut(name_idx as usize)?;
if let Some(idx) = *slot {
return Some(idx);
}
let outer_name = names.get(name_idx as usize)?;
let idx = builder.add_name(outer_name);
*slot = Some(idx);
Some(idx)
}
#[inline]
#[allow(
clippy::too_many_arguments,
reason = "passing remapped indices avoids per-mapping hashing in the hot path"
)]
fn emit_remapped_mapping<B: RemapBuilder>(
builder: &mut B,
gen_line: u32,
gen_col: u32,
builder_src: u32,
orig_line: u32,
orig_col: u32,
builder_name: Option<u32>,
is_range: bool,
) {
match (builder_name, is_range) {
(Some(n), true) => {
builder.add_named_range_mapping(gen_line, gen_col, builder_src, orig_line, orig_col, n);
}
(Some(n), false) => {
builder.add_named_mapping(gen_line, gen_col, builder_src, orig_line, orig_col, n);
}
(None, true) => {
builder.add_range_mapping(gen_line, gen_col, builder_src, orig_line, orig_col);
}
(None, false) => {
builder.add_mapping(gen_line, gen_col, builder_src, orig_line, orig_col);
}
}
}
enum SourceEntry {
Upstream { map: Box<SourceMap>, cache: UpstreamCache },
Passthrough { builder_src: u32 },
EmptySource,
Unloaded,
}
struct DedupeState {
last_gen_line: u32,
line_index: u32,
last_was_sourceless: bool,
last_source: Option<(u32, u32, u32, Option<u32>)>,
}
impl DedupeState {
fn new() -> Self {
Self {
last_gen_line: u32::MAX,
line_index: 0,
last_was_sourceless: false,
last_source: None,
}
}
fn skip_sourceless(&self, gen_line: u32) -> bool {
if gen_line != self.last_gen_line {
return true;
}
self.last_was_sourceless
}
fn skip_source(
&self,
gen_line: u32,
source: u32,
orig_line: u32,
orig_col: u32,
name: Option<u32>,
) -> bool {
if gen_line != self.last_gen_line {
return false;
}
if self.last_was_sourceless {
return false;
}
self.last_source == Some((source, orig_line, orig_col, name))
}
fn record_sourceless(&mut self, gen_line: u32) {
if gen_line != self.last_gen_line {
self.last_gen_line = gen_line;
self.line_index = 0;
self.last_source = None;
}
self.line_index += 1;
self.last_was_sourceless = true;
}
fn record_source(
&mut self,
gen_line: u32,
source: u32,
orig_line: u32,
orig_col: u32,
name: Option<u32>,
) {
if gen_line != self.last_gen_line {
self.last_gen_line = gen_line;
self.line_index = 0;
}
self.line_index += 1;
self.last_was_sourceless = false;
self.last_source = Some((source, orig_line, orig_col, name));
}
}
pub fn remap<F>(outer: &SourceMap, loader: F) -> SourceMap
where
F: Fn(&str) -> Option<SourceMap>,
{
let mapping_count = outer.mapping_count();
let source_count = outer.sources.len();
let mut builder = SourceMapGenerator::with_capacity(outer.file.clone(), mapping_count);
builder.set_assume_sorted(true);
let mut source_entries: Vec<SourceEntry> =
std::iter::repeat_with(|| SourceEntry::Unloaded).take(source_count).collect();
let mut ignored_sources: HashSet<u32> = HashSet::new();
let mut outer_name_remap: Vec<Option<u32>> = vec![None; outer.names.len()];
let outer_ignore_set: HashSet<u32> = outer.ignore_list.iter().copied().collect();
let mut dedup = DedupeState::new();
for m in outer.all_mappings() {
if m.source == NO_SOURCE {
trace_and_emit_sourceless(
&mut builder,
&mut dedup,
m.generated_line,
m.generated_column,
);
continue;
}
let si = m.source as usize;
if matches!(source_entries[si], SourceEntry::Unloaded) {
let source_name = outer.source(m.source);
if source_name.is_empty() {
source_entries[si] = SourceEntry::EmptySource;
} else {
match loader(source_name) {
Some(upstream_sm) => {
let cache = build_upstream_cache(&upstream_sm);
source_entries[si] =
SourceEntry::Upstream { map: Box::new(upstream_sm), cache };
}
None => {
let idx = builder.add_source(source_name);
if let Some(Some(content)) = outer.sources_content.get(si) {
builder.set_source_content(idx, content.clone());
}
if outer_ignore_set.contains(&m.source) && ignored_sources.insert(idx) {
builder.add_to_ignore_list(idx);
}
source_entries[si] = SourceEntry::Passthrough { builder_src: idx };
}
}
}
}
match &mut source_entries[si] {
SourceEntry::Upstream { map, cache } => {
if let Some(upstream_m) = lookup_upstream(map, m.original_line, m.original_column) {
trace_and_emit_upstream(
&mut builder,
&mut dedup,
UpstreamEmitContext {
gen_line: m.generated_line,
gen_col: m.generated_column,
upstream_m,
cache,
upstream_map: map,
outer_name_remap: &mut outer_name_remap,
outer_name_idx: m.name,
names: &outer.names,
ignored_sources: &mut ignored_sources,
is_range: m.is_range_mapping,
},
);
}
}
SourceEntry::Passthrough { builder_src } => {
trace_and_emit_passthrough(
&mut builder,
&mut dedup,
PassthroughEmitContext {
gen_line: m.generated_line,
gen_col: m.generated_column,
orig_line: m.original_line,
orig_col: m.original_column,
builder_src: *builder_src,
outer_name_remap: &mut outer_name_remap,
outer_name_idx: m.name,
names: &outer.names,
is_range: m.is_range_mapping,
},
);
}
SourceEntry::EmptySource => {
trace_and_emit_sourceless(
&mut builder,
&mut dedup,
m.generated_line,
m.generated_column,
);
}
SourceEntry::Unloaded => unreachable!(),
}
}
builder.to_decoded_map()
}
pub fn remap_chain(maps: &[&SourceMap]) -> Option<SourceMap> {
if maps.is_empty() {
return None;
}
if maps.len() == 1 {
return Some(maps[0].clone());
}
let mut current = compose_pair(maps[maps.len() - 2], maps[maps.len() - 1]);
for i in (0..maps.len() - 2).rev() {
current = compose_pair(maps[i], ¤t);
}
Some(current)
}
fn compose_pair(outer: &SourceMap, inner: &SourceMap) -> SourceMap {
let fallback_source = if inner.file.is_none() {
let mut sources = outer.sources.iter().filter(|source| !source.is_empty());
match (sources.next(), sources.next()) {
(Some(source), None) => Some(source.clone()),
_ => None,
}
} else {
None
};
remap(outer, |source| {
if inner.file.as_deref() == Some(source) || fallback_source.as_deref() == Some(source) {
Some(inner.clone())
} else {
None
}
})
}
enum StreamingSourceEntry {
Upstream { map: Box<SourceMap>, cache: UpstreamCache },
Passthrough { builder_src: u32 },
EmptySource,
Unloaded,
}
pub fn remap_streaming<'a, F>(
mappings_iter: srcmap_sourcemap::MappingsIter<'a>,
sources: &[String],
names: &[String],
sources_content: &[Option<String>],
ignore_list: &[u32],
file: Option<String>,
loader: F,
) -> SourceMap
where
F: Fn(&str) -> Option<SourceMap>,
{
let mut builder = StreamingGenerator::with_capacity(file, 4096);
let mut source_entries: Vec<StreamingSourceEntry> =
std::iter::repeat_with(|| StreamingSourceEntry::Unloaded).take(sources.len()).collect();
let mut ignored_sources: HashSet<u32> = HashSet::new();
let mut outer_name_remap: Vec<Option<u32>> = vec![None; names.len()];
let outer_ignore_set: HashSet<u32> = ignore_list.iter().copied().collect();
let mut dedup = DedupeState::new();
for item in mappings_iter {
let m = match item {
Ok(m) => m,
Err(_) => continue,
};
if m.source == NO_SOURCE {
trace_and_emit_sourceless(
&mut builder,
&mut dedup,
m.generated_line,
m.generated_column,
);
continue;
}
let si = m.source as usize;
if si >= sources.len() {
continue;
}
if matches!(source_entries[si], StreamingSourceEntry::Unloaded) {
let source_name = &sources[si];
if source_name.is_empty() {
source_entries[si] = StreamingSourceEntry::EmptySource;
} else {
match loader(source_name) {
Some(upstream_sm) => {
let cache = build_upstream_cache(&upstream_sm);
source_entries[si] =
StreamingSourceEntry::Upstream { map: Box::new(upstream_sm), cache };
}
None => {
let idx = builder.add_source(source_name);
if let Some(Some(content)) = sources_content.get(si) {
builder.set_source_content(idx, content.clone());
}
if outer_ignore_set.contains(&m.source) && ignored_sources.insert(idx) {
builder.add_to_ignore_list(idx);
}
source_entries[si] = StreamingSourceEntry::Passthrough { builder_src: idx };
}
}
}
}
match &mut source_entries[si] {
StreamingSourceEntry::Upstream { map, cache } => {
if let Some(upstream_m) = lookup_upstream(map, m.original_line, m.original_column) {
trace_and_emit_upstream(
&mut builder,
&mut dedup,
UpstreamEmitContext {
gen_line: m.generated_line,
gen_col: m.generated_column,
upstream_m,
cache,
upstream_map: map,
outer_name_remap: &mut outer_name_remap,
outer_name_idx: m.name,
names,
ignored_sources: &mut ignored_sources,
is_range: m.is_range_mapping,
},
);
}
}
StreamingSourceEntry::Passthrough { builder_src } => {
trace_and_emit_passthrough(
&mut builder,
&mut dedup,
PassthroughEmitContext {
gen_line: m.generated_line,
gen_col: m.generated_column,
orig_line: m.original_line,
orig_col: m.original_column,
builder_src: *builder_src,
outer_name_remap: &mut outer_name_remap,
outer_name_idx: m.name,
names,
is_range: m.is_range_mapping,
},
);
}
StreamingSourceEntry::EmptySource => {
trace_and_emit_sourceless(
&mut builder,
&mut dedup,
m.generated_line,
m.generated_column,
);
}
StreamingSourceEntry::Unloaded => unreachable!(),
}
}
builder.to_decoded_map().expect("streaming VLQ should be valid")
}
#[inline]
fn emit_generated_mapping<B: RemapBuilder>(builder: &mut B, gen_line: u32, gen_col: u32) {
builder.add_generated_mapping(gen_line, gen_col);
}
struct UpstreamEmitContext<'a> {
gen_line: u32,
gen_col: u32,
upstream_m: UpstreamLookup,
cache: &'a mut UpstreamCache,
upstream_map: &'a SourceMap,
outer_name_remap: &'a mut [Option<u32>],
outer_name_idx: u32,
names: &'a [String],
ignored_sources: &'a mut HashSet<u32>,
is_range: bool,
}
#[inline]
fn trace_and_emit_upstream<B: RemapBuilder>(
builder: &mut B,
dedup: &mut DedupeState,
ctx: UpstreamEmitContext<'_>,
) {
let UpstreamEmitContext {
gen_line,
gen_col,
upstream_m,
cache,
upstream_map,
outer_name_remap,
outer_name_idx,
names,
ignored_sources,
is_range,
} = ctx;
let builder_src =
resolve_upstream_source(cache, upstream_map, upstream_m.source, builder, ignored_sources);
let builder_name = if upstream_m.name != NO_NAME {
Some(resolve_upstream_name(cache, upstream_map, upstream_m.name, builder))
} else {
resolve_outer_name_cached(outer_name_remap, outer_name_idx, names, builder)
};
if !dedup.skip_source(
gen_line,
builder_src,
upstream_m.original_line,
upstream_m.original_column,
builder_name,
) {
emit_remapped_mapping(
builder,
gen_line,
gen_col,
builder_src,
upstream_m.original_line,
upstream_m.original_column,
builder_name,
is_range,
);
}
dedup.record_source(
gen_line,
builder_src,
upstream_m.original_line,
upstream_m.original_column,
builder_name,
);
}
struct PassthroughEmitContext<'a> {
gen_line: u32,
gen_col: u32,
orig_line: u32,
orig_col: u32,
builder_src: u32,
outer_name_remap: &'a mut [Option<u32>],
outer_name_idx: u32,
names: &'a [String],
is_range: bool,
}
#[inline]
fn trace_and_emit_passthrough<B: RemapBuilder>(
builder: &mut B,
dedup: &mut DedupeState,
ctx: PassthroughEmitContext<'_>,
) {
let PassthroughEmitContext {
gen_line,
gen_col,
orig_line,
orig_col,
builder_src,
outer_name_remap,
outer_name_idx,
names,
is_range,
} = ctx;
let builder_name = resolve_outer_name_cached(outer_name_remap, outer_name_idx, names, builder);
if !dedup.skip_source(gen_line, builder_src, orig_line, orig_col, builder_name) {
emit_remapped_mapping(
builder,
gen_line,
gen_col,
builder_src,
orig_line,
orig_col,
builder_name,
is_range,
);
}
dedup.record_source(gen_line, builder_src, orig_line, orig_col, builder_name);
}
#[inline]
fn trace_and_emit_sourceless<B: RemapBuilder>(
builder: &mut B,
dedup: &mut DedupeState,
gen_line: u32,
gen_col: u32,
) {
if !dedup.skip_sourceless(gen_line) {
emit_generated_mapping(builder, gen_line, gen_col);
}
dedup.record_sourceless(gen_line);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn concat_two_simple_maps() {
let a = SourceMap::from_json(
r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#,
)
.unwrap();
let b = SourceMap::from_json(
r#"{"version":3,"sources":["b.js"],"names":[],"mappings":"AAAA"}"#,
)
.unwrap();
let mut builder = ConcatBuilder::new(Some("bundle.js".to_string()));
builder.add_map(&a, 0);
builder.add_map(&b, 1);
let result = builder.build();
assert_eq!(result.sources, vec!["a.js", "b.js"]);
assert_eq!(result.mapping_count(), 2);
let loc0 = result.original_position_for(0, 0).unwrap();
assert_eq!(result.source(loc0.source), "a.js");
let loc1 = result.original_position_for(1, 0).unwrap();
assert_eq!(result.source(loc1.source), "b.js");
}
#[test]
fn concat_deduplicates_sources() {
let a = SourceMap::from_json(
r#"{"version":3,"sources":["shared.js"],"names":[],"mappings":"AAAA"}"#,
)
.unwrap();
let b = SourceMap::from_json(
r#"{"version":3,"sources":["shared.js"],"names":[],"mappings":"AAAA"}"#,
)
.unwrap();
let mut builder = ConcatBuilder::new(None);
builder.add_map(&a, 0);
builder.add_map(&b, 10);
let result = builder.build();
assert_eq!(result.sources.len(), 1);
assert_eq!(result.sources[0], "shared.js");
}
#[test]
fn concat_with_names() {
let a = SourceMap::from_json(
r#"{"version":3,"sources":["a.js"],"names":["foo"],"mappings":"AAAAA"}"#,
)
.unwrap();
let b = SourceMap::from_json(
r#"{"version":3,"sources":["b.js"],"names":["bar"],"mappings":"AAAAA"}"#,
)
.unwrap();
let mut builder = ConcatBuilder::new(None);
builder.add_map(&a, 0);
builder.add_map(&b, 1);
let result = builder.build();
assert_eq!(result.names.len(), 2);
let loc0 = result.original_position_for(0, 0).unwrap();
assert_eq!(loc0.name, Some(0));
assert_eq!(result.name(0), "foo");
let loc1 = result.original_position_for(1, 0).unwrap();
assert_eq!(loc1.name, Some(1));
assert_eq!(result.name(1), "bar");
}
#[test]
fn concat_preserves_multi_line_maps() {
let a = SourceMap::from_json(
r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA;AACA;AACA"}"#,
)
.unwrap();
let mut builder = ConcatBuilder::new(None);
builder.add_map(&a, 5);
let result = builder.build();
assert!(result.original_position_for(5, 0).is_some());
assert!(result.original_position_for(6, 0).is_some());
assert!(result.original_position_for(7, 0).is_some());
assert!(result.original_position_for(4, 0).is_none());
}
#[test]
fn concat_with_sources_content() {
let a = SourceMap::from_json(
r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":[],"mappings":"AAAA"}"#,
)
.unwrap();
let mut builder = ConcatBuilder::new(None);
builder.add_map(&a, 0);
let result = builder.build();
assert_eq!(result.sources_content, vec![Some("var a;".to_string())]);
}
#[test]
fn concat_empty_builder() {
let builder = ConcatBuilder::new(Some("empty.js".to_string()));
let result = builder.build();
assert_eq!(result.mapping_count(), 0);
assert_eq!(result.sources.len(), 0);
}
#[test]
fn remap_single_level() {
let outer = SourceMap::from_json(
r#"{"version":3,"sources":["intermediate.js","other.js"],"names":[],"mappings":"AAAA,KCAA;ADCA"}"#,
)
.unwrap();
let inner = SourceMap::from_json(
r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AACA;AACA"}"#,
)
.unwrap();
let result =
remap(
&outer,
|source| {
if source == "intermediate.js" { Some(inner.clone()) } else { None }
},
);
assert!(result.sources.contains(&"original.js".to_string()));
assert!(result.sources.contains(&"other.js".to_string()));
let loc = result.original_position_for(0, 0).unwrap();
assert_eq!(result.source(loc.source), "original.js");
assert_eq!(loc.line, 1);
}
#[test]
fn remap_accepts_source_map_built_from_parts() {
let outer = SourceMap::from_parts(
Some("output.js".to_string()),
None,
vec!["intermediate.js".to_string()],
vec![],
vec![],
vec![srcmap_sourcemap::Mapping {
generated_line: 0,
generated_column: 0,
source: 0,
original_line: 0,
original_column: 0,
name: u32::MAX,
is_range_mapping: false,
}],
vec![],
None,
None,
);
let inner = SourceMap::builder()
.file("intermediate.js")
.sources(["original.ts"])
.sources_content([Some("export const value = 1;")])
.mappings([srcmap_sourcemap::Mapping {
generated_line: 0,
generated_column: 0,
source: 0,
original_line: 3,
original_column: 12,
name: u32::MAX,
is_range_mapping: false,
}])
.build();
let result =
remap(
&outer,
|source| {
if source == "intermediate.js" { Some(inner.clone()) } else { None }
},
);
let loc = result.original_position_for(0, 0).unwrap();
assert_eq!(result.source(loc.source), "original.ts");
assert_eq!(loc.line, 3);
assert_eq!(loc.column, 12);
assert_eq!(result.sources_content, vec![Some("export const value = 1;".to_string())]);
}
#[test]
fn remap_no_upstream_passthrough() {
let outer = SourceMap::from_json(
r#"{"version":3,"sources":["already-original.js"],"names":[],"mappings":"AAAA"}"#,
)
.unwrap();
let result = remap(&outer, |_| None);
assert_eq!(result.sources, vec!["already-original.js"]);
let loc = result.original_position_for(0, 0).unwrap();
assert_eq!(result.source(loc.source), "already-original.js");
assert_eq!(loc.line, 0);
assert_eq!(loc.column, 0);
}
#[test]
fn remap_partial_sources() {
let outer = SourceMap::from_json(
r#"{"version":3,"sources":["compiled.js","passthrough.js"],"names":[],"mappings":"AAAA,KCCA"}"#,
)
.unwrap();
let inner = SourceMap::from_json(
r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":"AAAA"}"#,
)
.unwrap();
let result =
remap(
&outer,
|source| {
if source == "compiled.js" { Some(inner.clone()) } else { None }
},
);
assert!(result.sources.contains(&"original.ts".to_string()));
assert!(result.sources.contains(&"passthrough.js".to_string()));
}
#[test]
fn remap_preserves_names() {
let outer = SourceMap::from_json(
r#"{"version":3,"sources":["compiled.js"],"names":["myFunc"],"mappings":"AAAAA"}"#,
)
.unwrap();
let inner = SourceMap::from_json(
r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":"AAAA"}"#,
)
.unwrap();
let result = remap(&outer, |_| Some(inner.clone()));
let loc = result.original_position_for(0, 0).unwrap();
assert!(loc.name.is_some());
assert_eq!(result.name(loc.name.unwrap()), "myFunc");
}
#[test]
fn remap_upstream_name_wins() {
let outer = SourceMap::from_json(
r#"{"version":3,"sources":["compiled.js"],"names":["outerName"],"mappings":"AAAAA"}"#,
)
.unwrap();
let inner = SourceMap::from_json(
r#"{"version":3,"sources":["original.ts"],"names":["innerName"],"mappings":"AAAAA"}"#,
)
.unwrap();
let result = remap(&outer, |_| Some(inner.clone()));
let loc = result.original_position_for(0, 0).unwrap();
assert!(loc.name.is_some());
assert_eq!(result.name(loc.name.unwrap()), "innerName");
}
#[test]
fn remap_sources_content_from_upstream() {
let outer = SourceMap::from_json(
r#"{"version":3,"sources":["compiled.js"],"names":[],"mappings":"AAAA"}"#,
)
.unwrap();
let inner = SourceMap::from_json(
r#"{"version":3,"sources":["original.ts"],"sourcesContent":["const x = 1;"],"names":[],"mappings":"AAAA"}"#,
)
.unwrap();
let result = remap(&outer, |_| Some(inner.clone()));
assert_eq!(result.sources_content, vec![Some("const x = 1;".to_string())]);
}
#[test]
fn concat_updates_source_content_on_duplicate() {
let a = SourceMap::from_json(
r#"{"version":3,"sources":["shared.js"],"names":[],"mappings":"AAAA"}"#,
)
.unwrap();
let b = SourceMap::from_json(
r#"{"version":3,"sources":["shared.js"],"sourcesContent":["var x = 1;"],"names":[],"mappings":"AAAA"}"#,
)
.unwrap();
let mut builder = ConcatBuilder::new(None);
builder.add_map(&a, 0);
builder.add_map(&b, 1);
let result = builder.build();
assert_eq!(result.sources.len(), 1);
assert_eq!(result.sources_content, vec![Some("var x = 1;".to_string())]);
}
#[test]
fn concat_deduplicates_names() {
let a = SourceMap::from_json(
r#"{"version":3,"sources":["a.js"],"names":["sharedName"],"mappings":"AAAAA"}"#,
)
.unwrap();
let b = SourceMap::from_json(
r#"{"version":3,"sources":["b.js"],"names":["sharedName"],"mappings":"AAAAA"}"#,
)
.unwrap();
let mut builder = ConcatBuilder::new(None);
builder.add_map(&a, 0);
builder.add_map(&b, 1);
let result = builder.build();
assert_eq!(result.names.len(), 1);
assert_eq!(result.names[0], "sharedName");
}
#[test]
fn concat_with_ignore_list() {
let a = SourceMap::from_json(
r#"{"version":3,"sources":["vendor.js"],"names":[],"mappings":"AAAA","ignoreList":[0]}"#,
)
.unwrap();
let mut builder = ConcatBuilder::new(None);
builder.add_map(&a, 0);
let result = builder.build();
assert_eq!(result.ignore_list, vec![0]);
}
#[test]
fn concat_with_generated_only_mappings() {
let a = SourceMap::from_json(
r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"A,AAAA"}"#,
)
.unwrap();
let mut builder = ConcatBuilder::new(None);
builder.add_map(&a, 0);
let result = builder.build();
assert!(result.mapping_count() >= 1);
}
#[test]
fn remap_generated_only_passthrough() {
let outer = SourceMap::from_json(
r#"{"version":3,"sources":["a.js","other.js"],"names":[],"mappings":"A,AAAA,KCAA"}"#,
)
.unwrap();
let inner = SourceMap::from_json(
r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AAAA"}"#,
)
.unwrap();
let result =
remap(&outer, |source| if source == "a.js" { Some(inner.clone()) } else { None });
assert!(result.mapping_count() >= 2);
assert!(result.sources.contains(&"original.js".to_string()));
assert!(result.sources.contains(&"other.js".to_string()));
}
#[test]
fn remap_no_upstream_mapping_with_name() {
let outer = SourceMap::from_json(
r#"{"version":3,"sources":["compiled.js"],"names":["myFunc"],"mappings":"AAAAA"}"#,
)
.unwrap();
let inner = SourceMap::from_json(
r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":";;;;AAAA"}"#,
)
.unwrap();
let result = remap(&outer, |_| Some(inner.clone()));
let loc = result.original_position_for(0, 0);
assert!(loc.is_none());
}
#[test]
fn remap_no_upstream_with_sources_content_and_name() {
let outer = SourceMap::from_json(
r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":["fn1"],"mappings":"AAAAA"}"#,
)
.unwrap();
let result = remap(&outer, |_| None);
assert_eq!(result.sources, vec!["a.js"]);
assert_eq!(result.sources_content, vec![Some("var a;".to_string())]);
let loc = result.original_position_for(0, 0).unwrap();
assert!(loc.name.is_some());
assert_eq!(result.name(loc.name.unwrap()), "fn1");
}
#[test]
fn remap_no_upstream_no_name() {
let outer = SourceMap::from_json(
r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":[],"mappings":"AAAA"}"#,
)
.unwrap();
let result = remap(&outer, |_| None);
let loc = result.original_position_for(0, 0).unwrap();
assert!(loc.name.is_none());
}
#[test]
fn remap_no_upstream_mapping_no_name() {
let outer = SourceMap::from_json(
r#"{"version":3,"sources":["compiled.js"],"names":[],"mappings":"AAAA"}"#,
)
.unwrap();
let inner = SourceMap::from_json(
r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":";;;;AAAA"}"#,
)
.unwrap();
let result = remap(&outer, |_| Some(inner.clone()));
let loc = result.original_position_for(0, 0);
assert!(loc.is_none());
}
#[test]
fn remap_upstream_found_no_name() {
let outer = SourceMap::from_json(
r#"{"version":3,"sources":["intermediate.js"],"names":[],"mappings":"AAAA"}"#,
)
.unwrap();
let inner = SourceMap::from_json(
r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AAAA"}"#,
)
.unwrap();
let result = remap(&outer, |_| Some(inner.clone()));
assert_eq!(result.sources, vec!["original.js"]);
let loc = result.original_position_for(0, 0).unwrap();
assert_eq!(result.source(loc.source), "original.js");
assert_eq!(loc.line, 0);
assert_eq!(loc.column, 0);
assert!(loc.name.is_none());
assert!(result.names.is_empty());
}
#[test]
fn concat_preserves_range_mappings() {
let a = SourceMap::from_json(
r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,CAAC","rangeMappings":"A"}"#,
)
.unwrap();
let mut builder = ConcatBuilder::new(None);
builder.add_map(&a, 0);
let result = builder.build();
assert!(result.has_range_mappings());
let mappings = result.all_mappings();
assert!(mappings[0].is_range_mapping);
assert!(!mappings[1].is_range_mapping);
}
#[test]
fn remap_preserves_range_mappings_passthrough() {
let outer = SourceMap::from_json(
r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA","rangeMappings":"A"}"#,
)
.unwrap();
let result = remap(&outer, |_| None);
assert!(result.has_range_mappings());
let mappings = result.all_mappings();
assert!(mappings[0].is_range_mapping);
}
#[test]
fn remap_preserves_range_through_upstream() {
let outer = SourceMap::from_json(
r#"{"version":3,"sources":["intermediate.js"],"names":[],"mappings":"AAAA","rangeMappings":"A"}"#,
)
.unwrap();
let inner = SourceMap::from_json(
r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AACA"}"#,
)
.unwrap();
let result = remap(&outer, |_| Some(inner.clone()));
assert!(result.has_range_mappings());
}
#[test]
fn remap_non_range_stays_non_range() {
let outer = SourceMap::from_json(
r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#,
)
.unwrap();
let result = remap(&outer, |_| None);
assert!(!result.has_range_mappings());
}
fn streaming_from_sm<F>(sm: &SourceMap, loader: F) -> SourceMap
where
F: Fn(&str) -> Option<SourceMap>,
{
let vlq = sm.encode_mappings();
let iter = srcmap_sourcemap::MappingsIter::new(&vlq);
remap_streaming(
iter,
&sm.sources,
&sm.names,
&sm.sources_content,
&sm.ignore_list,
sm.file.clone(),
loader,
)
}
#[test]
fn streaming_single_level() {
let outer = SourceMap::from_json(
r#"{"version":3,"sources":["intermediate.js","other.js"],"names":[],"mappings":"AAAA,KCAA;ADCA"}"#,
)
.unwrap();
let inner = SourceMap::from_json(
r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AACA;AACA"}"#,
)
.unwrap();
let result = streaming_from_sm(&outer, |source| {
if source == "intermediate.js" { Some(inner.clone()) } else { None }
});
assert!(result.sources.contains(&"original.js".to_string()));
assert!(result.sources.contains(&"other.js".to_string()));
let loc = result.original_position_for(0, 0).unwrap();
assert_eq!(result.source(loc.source), "original.js");
assert_eq!(loc.line, 1);
}
#[test]
fn streaming_no_upstream_passthrough() {
let outer = SourceMap::from_json(
r#"{"version":3,"sources":["already-original.js"],"names":[],"mappings":"AAAA"}"#,
)
.unwrap();
let result = streaming_from_sm(&outer, |_| None);
assert_eq!(result.sources, vec!["already-original.js"]);
let loc = result.original_position_for(0, 0).unwrap();
assert_eq!(result.source(loc.source), "already-original.js");
assert_eq!(loc.line, 0);
assert_eq!(loc.column, 0);
}
#[test]
fn streaming_preserves_names() {
let outer = SourceMap::from_json(
r#"{"version":3,"sources":["compiled.js"],"names":["myFunc"],"mappings":"AAAAA"}"#,
)
.unwrap();
let inner = SourceMap::from_json(
r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":"AAAA"}"#,
)
.unwrap();
let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
let loc = result.original_position_for(0, 0).unwrap();
assert!(loc.name.is_some());
assert_eq!(result.name(loc.name.unwrap()), "myFunc");
}
#[test]
fn streaming_upstream_name_wins() {
let outer = SourceMap::from_json(
r#"{"version":3,"sources":["compiled.js"],"names":["outerName"],"mappings":"AAAAA"}"#,
)
.unwrap();
let inner = SourceMap::from_json(
r#"{"version":3,"sources":["original.ts"],"names":["innerName"],"mappings":"AAAAA"}"#,
)
.unwrap();
let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
let loc = result.original_position_for(0, 0).unwrap();
assert!(loc.name.is_some());
assert_eq!(result.name(loc.name.unwrap()), "innerName");
}
#[test]
fn streaming_sources_content_from_upstream() {
let outer = SourceMap::from_json(
r#"{"version":3,"sources":["compiled.js"],"names":[],"mappings":"AAAA"}"#,
)
.unwrap();
let inner = SourceMap::from_json(
r#"{"version":3,"sources":["original.ts"],"sourcesContent":["const x = 1;"],"names":[],"mappings":"AAAA"}"#,
)
.unwrap();
let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
assert_eq!(result.sources_content, vec![Some("const x = 1;".to_string())]);
}
#[test]
fn streaming_no_upstream_with_sources_content() {
let outer = SourceMap::from_json(
r#"{"version":3,"sources":["a.js"],"sourcesContent":["var a;"],"names":["fn1"],"mappings":"AAAAA"}"#,
)
.unwrap();
let result = streaming_from_sm(&outer, |_| None);
assert_eq!(result.sources, vec!["a.js"]);
assert_eq!(result.sources_content, vec![Some("var a;".to_string())]);
let loc = result.original_position_for(0, 0).unwrap();
assert!(loc.name.is_some());
assert_eq!(result.name(loc.name.unwrap()), "fn1");
}
#[test]
fn streaming_generated_only_passthrough() {
let outer = SourceMap::from_json(
r#"{"version":3,"sources":["a.js","other.js"],"names":[],"mappings":"A,AAAA,KCAA"}"#,
)
.unwrap();
let inner = SourceMap::from_json(
r#"{"version":3,"sources":["original.js"],"names":[],"mappings":"AAAA"}"#,
)
.unwrap();
let result =
streaming_from_sm(
&outer,
|source| {
if source == "a.js" { Some(inner.clone()) } else { None }
},
);
assert!(result.mapping_count() >= 2);
assert!(result.sources.contains(&"original.js".to_string()));
assert!(result.sources.contains(&"other.js".to_string()));
}
#[test]
fn streaming_matches_remap() {
let outer = SourceMap::from_json(
r#"{"version":3,"sources":["intermediate.js","other.js"],"names":["foo"],"mappings":"AAAAA,KCAA;ADCA"}"#,
)
.unwrap();
let inner = SourceMap::from_json(
r#"{"version":3,"sources":["original.js"],"sourcesContent":["// src"],"names":["bar"],"mappings":"AAAAA;AACA"}"#,
)
.unwrap();
let loader = |source: &str| -> Option<SourceMap> {
if source == "intermediate.js" { Some(inner.clone()) } else { None }
};
let result_normal = remap(&outer, loader);
let result_stream = streaming_from_sm(&outer, loader);
assert_eq!(result_normal.sources, result_stream.sources);
assert_eq!(result_normal.names, result_stream.names);
assert_eq!(result_normal.sources_content, result_stream.sources_content);
assert_eq!(result_normal.mapping_count(), result_stream.mapping_count());
for m in result_normal.all_mappings() {
let loc_n = result_normal.original_position_for(m.generated_line, m.generated_column);
let loc_s = result_stream.original_position_for(m.generated_line, m.generated_column);
assert_eq!(loc_n.is_some(), loc_s.is_some());
if let (Some(ln), Some(ls)) = (loc_n, loc_s) {
assert_eq!(result_normal.source(ln.source), result_stream.source(ls.source));
assert_eq!(ln.line, ls.line);
assert_eq!(ln.column, ls.column);
}
}
}
#[test]
fn streaming_no_upstream_mapping_fallback() {
let outer = SourceMap::from_json(
r#"{"version":3,"sources":["compiled.js"],"names":["myFunc"],"mappings":"AAAAA"}"#,
)
.unwrap();
let inner = SourceMap::from_json(
r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":";;;;AAAA"}"#,
)
.unwrap();
let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
let loc = result.original_position_for(0, 0);
assert!(loc.is_none());
}
#[test]
fn streaming_no_upstream_mapping_no_name() {
let outer = SourceMap::from_json(
r#"{"version":3,"sources":["compiled.js"],"names":[],"mappings":"AAAA"}"#,
)
.unwrap();
let inner = SourceMap::from_json(
r#"{"version":3,"sources":["original.ts"],"names":[],"mappings":";;;;AAAA"}"#,
)
.unwrap();
let result = streaming_from_sm(&outer, |_| Some(inner.clone()));
let loc = result.original_position_for(0, 0);
assert!(loc.is_none());
}
#[test]
fn remap_chain_empty() {
assert!(remap_chain(&[]).is_none());
}
#[test]
fn remap_chain_single() {
let sm = SourceMap::from_json(
r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA"}"#,
)
.unwrap();
let result = remap_chain(&[&sm]).unwrap();
assert_eq!(result.sources, vec!["a.js"]);
assert_eq!(result.mapping_count(), 1);
}
#[test]
fn remap_chain_two_maps() {
let step1 = SourceMap::from_json(
r#"{"version":3,"file":"intermediate.js","sources":["original.js"],"names":[],"mappings":"AACA;AACA"}"#,
)
.unwrap();
let step2 = SourceMap::from_json(
r#"{"version":3,"file":"output.js","sources":["intermediate.js"],"names":[],"mappings":"AAAA;AACA"}"#,
)
.unwrap();
let result = remap_chain(&[&step2, &step1]).unwrap();
assert_eq!(result.sources, vec!["original.js"]);
let loc = result.original_position_for(0, 0).unwrap();
assert_eq!(result.source(loc.source), "original.js");
assert_eq!(loc.line, 1);
}
#[test]
fn remap_chain_three_maps() {
let a_to_b = SourceMap::from_json(
r#"{"version":3,"file":"b.js","sources":["a.js"],"names":[],"mappings":"AACA"}"#,
)
.unwrap();
let b_to_c = SourceMap::from_json(
r#"{"version":3,"file":"c.js","sources":["b.js"],"names":[],"mappings":"AAAA"}"#,
)
.unwrap();
let c_to_d = SourceMap::from_json(
r#"{"version":3,"file":"d.js","sources":["c.js"],"names":[],"mappings":"AAAA"}"#,
)
.unwrap();
let result = remap_chain(&[&c_to_d, &b_to_c, &a_to_b]).unwrap();
assert_eq!(result.sources, vec!["a.js"]);
let loc = result.original_position_for(0, 0).unwrap();
assert_eq!(result.source(loc.source), "a.js");
assert_eq!(loc.line, 1);
}
#[test]
fn remap_chain_only_composes_matching_inner_file() {
let inner = SourceMap::from_json(
r#"{"version":3,"file":"intermediate.js","sources":["original.js"],"names":[],"mappings":"AAAA"}"#,
)
.unwrap();
let outer = SourceMap::from_json(
r#"{"version":3,"file":"output.js","sources":["intermediate.js","passthrough.js"],"names":[],"mappings":"AAAA,KCAA"}"#,
)
.unwrap();
let result = remap_chain(&[&outer, &inner]).unwrap();
assert!(result.sources.contains(&"original.js".to_string()));
assert!(result.sources.contains(&"passthrough.js".to_string()));
let remapped = result.original_position_for(0, 0).unwrap();
assert_eq!(result.source(remapped.source), "original.js");
let passthrough = result.original_position_for(0, 5).unwrap();
assert_eq!(result.source(passthrough.source), "passthrough.js");
}
#[test]
fn remap_empty_string_source_filtered() {
let outer =
SourceMap::from_json(r#"{"version":3,"sources":[""],"names":[],"mappings":"AAAA"}"#)
.unwrap();
let result = remap(&outer, |_| None);
assert!(
!result.sources.iter().any(|s| s.is_empty()),
"empty-string sources should be filtered out"
);
let loc = result.original_position_for(0, 0);
assert!(loc.is_none());
}
#[test]
fn remap_null_source_filtered() {
let outer =
SourceMap::from_json(r#"{"version":3,"sources":[null],"names":[],"mappings":"AAAA"}"#)
.unwrap();
let result = remap(&outer, |_| None);
assert!(
!result.sources.iter().any(|s| s.is_empty()),
"null sources should be filtered out"
);
}
#[test]
fn streaming_empty_string_source_filtered() {
let outer =
SourceMap::from_json(r#"{"version":3,"sources":[""],"names":[],"mappings":"AAAA"}"#)
.unwrap();
let result = streaming_from_sm(&outer, |_| None);
assert!(
!result.sources.iter().any(|s| s.is_empty()),
"streaming: empty-string sources should be filtered out"
);
}
#[test]
fn remap_skips_redundant_sourced_segments() {
let outer = SourceMap::from_json(
r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAA,EAAA"}"#,
)
.unwrap();
let result = remap(&outer, |_| None);
assert_eq!(result.mapping_count(), 1);
}
#[test]
fn remap_keeps_different_sourced_segments() {
let outer = SourceMap::from_json(
r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAC"}"#,
)
.unwrap();
let result = remap(&outer, |_| None);
assert_eq!(result.mapping_count(), 2);
}
#[test]
fn remap_skips_sourceless_at_line_start() {
let outer = SourceMap::from_json(r#"{"version":3,"sources":[],"names":[],"mappings":"A"}"#)
.unwrap();
let result = remap(&outer, |_| None);
assert_eq!(result.mapping_count(), 0);
}
#[test]
fn streaming_skips_redundant_sourced_segments() {
let outer = SourceMap::from_json(
r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,EAAA,EAAA"}"#,
)
.unwrap();
let result = streaming_from_sm(&outer, |_| None);
assert_eq!(result.mapping_count(), 1);
}
}