use rustc_hash::FxHashMap;
use srcmap_codec::{vlq_encode_unchecked, vlq_encode_unsigned};
#[inline]
fn is_sorted_by_position(mappings: &[Mapping]) -> bool {
mappings.windows(2).all(|w| {
(w[0].generated_line, w[0].generated_column) <= (w[1].generated_line, w[1].generated_column)
})
}
use srcmap_scopes::ScopeInfo;
use std::io;
#[derive(Debug, Clone)]
pub struct SourceMapParts {
pub file: Option<String>,
pub mappings: String,
pub sources: Vec<String>,
pub names: Vec<String>,
pub sources_content: Vec<Option<String>>,
pub ignore_list: Vec<u32>,
pub debug_id: Option<String>,
pub source_root: Option<String>,
pub range_mappings: Option<String>,
}
#[derive(Debug, Clone)]
pub struct Mapping {
pub generated_line: u32,
pub generated_column: u32,
pub source: Option<u32>,
pub original_line: u32,
pub original_column: u32,
pub name: Option<u32>,
pub is_range_mapping: bool,
}
#[derive(Debug)]
pub struct SourceMapGenerator {
file: Option<String>,
source_root: Option<String>,
sources: Vec<String>,
sources_content: Vec<Option<String>>,
names: Vec<String>,
mappings: Vec<Mapping>,
ignore_list: Vec<u32>,
debug_id: Option<String>,
scopes: Option<ScopeInfo>,
assume_sorted: bool,
source_map: FxHashMap<String, u32>,
name_map: FxHashMap<String, u32>,
}
impl SourceMapGenerator {
pub fn new(file: Option<String>) -> Self {
Self {
file,
source_root: None,
sources: Vec::new(),
sources_content: Vec::new(),
names: Vec::new(),
mappings: Vec::new(),
ignore_list: Vec::new(),
debug_id: None,
scopes: None,
assume_sorted: false,
source_map: FxHashMap::default(),
name_map: FxHashMap::default(),
}
}
pub fn with_capacity(file: Option<String>, mapping_capacity: usize) -> Self {
Self {
file,
source_root: None,
sources: Vec::new(),
sources_content: Vec::new(),
names: Vec::new(),
mappings: Vec::with_capacity(mapping_capacity),
ignore_list: Vec::new(),
debug_id: None,
scopes: None,
assume_sorted: false,
source_map: FxHashMap::default(),
name_map: FxHashMap::default(),
}
}
pub fn set_source_root(&mut self, root: impl Into<String>) {
self.source_root = Some(root.into());
}
pub fn set_debug_id(&mut self, id: impl Into<String>) {
self.debug_id = Some(id.into());
}
pub fn set_scopes(&mut self, scopes: ScopeInfo) {
self.scopes = Some(scopes);
}
pub fn set_assume_sorted(&mut self, sorted: bool) {
self.assume_sorted = sorted;
}
#[inline]
pub fn add_source(&mut self, source: &str) -> u32 {
if let Some(&idx) = self.source_map.get(source) {
return idx;
}
let idx = self.sources.len() as u32;
self.sources.push(source.to_string());
self.sources_content.push(None);
self.source_map.insert(source.to_string(), idx);
idx
}
pub fn set_source_content(&mut self, source_idx: u32, content: impl Into<String>) {
if (source_idx as usize) < self.sources_content.len() {
self.sources_content[source_idx as usize] = Some(content.into());
}
}
#[inline]
pub fn add_name(&mut self, name: &str) -> u32 {
if let Some(&idx) = self.name_map.get(name) {
return idx;
}
let idx = self.names.len() as u32;
self.names.push(name.to_string());
self.name_map.insert(name.to_string(), idx);
idx
}
pub fn add_to_ignore_list(&mut self, source_idx: u32) {
if !self.ignore_list.contains(&source_idx) {
self.ignore_list.push(source_idx);
}
}
pub fn add_generated_mapping(&mut self, generated_line: u32, generated_column: u32) {
self.mappings.push(Mapping {
generated_line,
generated_column,
source: None,
original_line: 0,
original_column: 0,
name: None,
is_range_mapping: false,
});
}
pub fn add_mapping(
&mut self,
generated_line: u32,
generated_column: u32,
source: u32,
original_line: u32,
original_column: u32,
) {
self.mappings.push(Mapping {
generated_line,
generated_column,
source: Some(source),
original_line,
original_column,
name: None,
is_range_mapping: false,
});
}
pub fn add_named_mapping(
&mut self,
generated_line: u32,
generated_column: u32,
source: u32,
original_line: u32,
original_column: u32,
name: u32,
) {
self.mappings.push(Mapping {
generated_line,
generated_column,
source: Some(source),
original_line,
original_column,
name: Some(name),
is_range_mapping: false,
});
}
pub fn add_range_mapping(
&mut self,
generated_line: u32,
generated_column: u32,
source: u32,
original_line: u32,
original_column: u32,
) {
self.mappings.push(Mapping {
generated_line,
generated_column,
source: Some(source),
original_line,
original_column,
name: None,
is_range_mapping: true,
});
}
pub fn add_named_range_mapping(
&mut self,
generated_line: u32,
generated_column: u32,
source: u32,
original_line: u32,
original_column: u32,
name: u32,
) {
self.mappings.push(Mapping {
generated_line,
generated_column,
source: Some(source),
original_line,
original_column,
name: Some(name),
is_range_mapping: true,
});
}
pub fn maybe_add_mapping(
&mut self,
generated_line: u32,
generated_column: u32,
source: u32,
original_line: u32,
original_column: u32,
) -> bool {
if let Some(last) = self.mappings.last()
&& last.generated_line == generated_line
&& last.source == Some(source)
&& last.original_line == original_line
&& last.original_column == original_column
{
return false;
}
self.add_mapping(generated_line, generated_column, source, original_line, original_column);
true
}
fn encode_mappings_into(&self, out: &mut Vec<u8>) {
if self.mappings.is_empty() {
return;
}
if self.assume_sorted || is_sorted_by_position(&self.mappings) {
#[cfg(feature = "parallel")]
if self.mappings.len() >= 4096 {
let refs: Vec<&Mapping> = self.mappings.iter().collect();
let encoded = Self::encode_parallel_impl(&refs);
out.extend_from_slice(encoded.as_bytes());
return;
}
Self::encode_sequential_into(&self.mappings, out);
return;
}
let mut sorted: Vec<&Mapping> = self.mappings.iter().collect();
sorted.sort_unstable_by(|a, b| {
a.generated_line
.cmp(&b.generated_line)
.then(a.generated_column.cmp(&b.generated_column))
});
#[cfg(feature = "parallel")]
if sorted.len() >= 4096 {
let encoded = Self::encode_parallel_impl(&sorted);
out.extend_from_slice(encoded.as_bytes());
return;
}
Self::encode_sequential_into(&sorted, out);
}
#[allow(dead_code, reason = "used by tests and the parallel encoding path")]
fn encode_mappings(&self) -> String {
if self.mappings.is_empty() {
return String::new();
}
if self.assume_sorted || is_sorted_by_position(&self.mappings) {
#[cfg(feature = "parallel")]
if self.mappings.len() >= 4096 {
let refs: Vec<&Mapping> = self.mappings.iter().collect();
return Self::encode_parallel_impl(&refs);
}
return Self::encode_sequential_impl(&self.mappings);
}
let mut sorted: Vec<&Mapping> = self.mappings.iter().collect();
sorted.sort_unstable_by(|a, b| {
a.generated_line
.cmp(&b.generated_line)
.then(a.generated_column.cmp(&b.generated_column))
});
#[cfg(feature = "parallel")]
if sorted.len() >= 4096 {
return Self::encode_parallel_impl(&sorted);
}
Self::encode_sequential_impl(&sorted)
}
#[inline]
fn encode_sequential_into<T: std::borrow::Borrow<Mapping>>(sorted: &[T], out: &mut Vec<u8>) {
let max_line = sorted.last().map_or(0, |m| m.borrow().generated_line as usize);
out.reserve(sorted.len() * 36 + max_line + 1);
let mut prev_gen_col: i64 = 0;
let mut prev_source: i64 = 0;
let mut prev_orig_line: i64 = 0;
let mut prev_orig_col: i64 = 0;
let mut prev_name: i64 = 0;
let mut prev_gen_line: u32 = 0;
let mut first_in_line = true;
for item in sorted {
let m = item.borrow();
while prev_gen_line < m.generated_line {
out.push(b';');
prev_gen_line += 1;
prev_gen_col = 0;
first_in_line = true;
}
if !first_in_line {
out.push(b',');
}
first_in_line = false;
unsafe {
vlq_encode_unchecked(out, m.generated_column as i64 - prev_gen_col);
prev_gen_col = m.generated_column as i64;
if let Some(source) = m.source {
vlq_encode_unchecked(out, source as i64 - prev_source);
prev_source = source as i64;
vlq_encode_unchecked(out, m.original_line as i64 - prev_orig_line);
prev_orig_line = m.original_line as i64;
vlq_encode_unchecked(out, m.original_column as i64 - prev_orig_col);
prev_orig_col = m.original_column as i64;
if let Some(name) = m.name {
vlq_encode_unchecked(out, name as i64 - prev_name);
prev_name = name as i64;
}
}
}
}
}
#[inline]
#[allow(dead_code, reason = "used by tests and the parallel encoding path")]
fn encode_sequential_impl<T: std::borrow::Borrow<Mapping>>(sorted: &[T]) -> String {
let max_line = sorted.last().map_or(0, |m| m.borrow().generated_line as usize);
let mut out: Vec<u8> = Vec::with_capacity(sorted.len() * 36 + max_line + 1);
Self::encode_sequential_into(sorted, &mut out);
debug_assert!(out.is_ascii());
unsafe { String::from_utf8_unchecked(out) }
}
#[cfg(feature = "parallel")]
fn encode_parallel_impl(sorted: &[&Mapping]) -> String {
use rayon::prelude::*;
let max_line = sorted
.last()
.expect("encode_parallel_impl requires non-empty sorted slice")
.generated_line as usize;
let mut line_ranges: Vec<(usize, usize)> = vec![(0, 0); max_line + 1];
let mut i = 0;
while i < sorted.len() {
let line = sorted[i].generated_line as usize;
let start = i;
while i < sorted.len() && sorted[i].generated_line as usize == line {
i += 1;
}
line_ranges[line] = (start, i);
}
let mut states: Vec<(i64, i64, i64, i64)> = Vec::with_capacity(max_line + 1);
let mut prev_source: i64 = 0;
let mut prev_orig_line: i64 = 0;
let mut prev_orig_col: i64 = 0;
let mut prev_name: i64 = 0;
for &(start, end) in &line_ranges {
states.push((prev_source, prev_orig_line, prev_orig_col, prev_name));
for m in &sorted[start..end] {
if let Some(source) = m.source {
prev_source = source as i64;
prev_orig_line = m.original_line as i64;
prev_orig_col = m.original_column as i64;
if let Some(name) = m.name {
prev_name = name as i64;
}
}
}
}
let encoded_lines: Vec<Vec<u8>> = line_ranges
.par_iter()
.zip(states.par_iter())
.map(|(&(start, end), &(s, ol, oc, n))| {
if start == end {
return Vec::new();
}
encode_mapping_slice(&sorted[start..end], s, ol, oc, n)
})
.collect();
let total_len = encoded_lines.iter().map(|l| l.len()).sum::<usize>() + max_line;
let mut out: Vec<u8> = Vec::with_capacity(total_len);
for (i, bytes) in encoded_lines.iter().enumerate() {
if i > 0 {
out.push(b';');
}
out.extend_from_slice(bytes);
}
debug_assert!(out.is_ascii());
unsafe { String::from_utf8_unchecked(out) }
}
fn encode_range_mappings(&self) -> Option<String> {
if !self.mappings.iter().any(|m| m.is_range_mapping) {
return None;
}
let ordered: Vec<&Mapping> = if self.assume_sorted || is_sorted_by_position(&self.mappings)
{
self.mappings.iter().collect()
} else {
let mut sorted: Vec<&Mapping> = self.mappings.iter().collect();
sorted.sort_unstable_by(|a, b| {
a.generated_line
.cmp(&b.generated_line)
.then(a.generated_column.cmp(&b.generated_column))
});
sorted
};
let max_line = ordered.last().map_or(0, |m| m.generated_line);
let mut out: Vec<u8> = Vec::new();
let mut ordered_idx = 0;
for line in 0..=max_line {
if line > 0 {
out.push(b';');
}
let mut prev_offset: u64 = 0;
let mut first_on_line = true;
let mut line_local_idx: u64 = 0;
while ordered_idx < ordered.len() && ordered[ordered_idx].generated_line == line {
if ordered[ordered_idx].is_range_mapping {
if !first_on_line {
out.push(b',');
}
first_on_line = false;
let delta = line_local_idx - prev_offset;
vlq_encode_unsigned(&mut out, delta);
prev_offset = line_local_idx;
}
line_local_idx += 1;
ordered_idx += 1;
}
}
while out.last() == Some(&b';') {
out.pop();
}
if out.is_empty() {
return None;
}
debug_assert!(out.is_ascii());
Some(unsafe { String::from_utf8_unchecked(out) })
}
pub fn to_json(&self) -> String {
use std::io::Write;
let (scopes_str, names_for_json) = if let Some(ref scopes_info) = self.scopes {
let mut names = self.names.clone();
let s = srcmap_scopes::encode_scopes(scopes_info, &mut names);
(Some(s), std::borrow::Cow::Owned(names))
} else {
(None, std::borrow::Cow::Borrowed(&self.names))
};
let sources_size: usize = self.sources.iter().map(|s| s.len() + 4).sum();
let names_size: usize = names_for_json.iter().map(|n| n.len() + 4).sum();
let content_size: usize =
self.sources_content.iter().map(|c| c.as_ref().map_or(5, |s| s.len() + 4)).sum();
let mappings_estimate = self.mappings.len() * 6;
let capacity = 100 + sources_size + names_size + mappings_estimate + content_size;
let mut json: Vec<u8> = Vec::with_capacity(capacity);
json.extend_from_slice(br#"{"version":3"#);
if let Some(ref file) = self.file {
json.extend_from_slice(br#","file":"#);
json_quote_into(&mut json, file);
}
if let Some(ref root) = self.source_root {
json.extend_from_slice(br#","sourceRoot":"#);
json_quote_into(&mut json, root);
}
json.extend_from_slice(br#","sources":["#);
for (i, s) in self.sources.iter().enumerate() {
if i > 0 {
json.push(b',');
}
json_quote_into(&mut json, s);
}
json.push(b']');
if self.sources_content.iter().any(|c| c.is_some()) {
json.extend_from_slice(br#","sourcesContent":["#);
#[cfg(feature = "parallel")]
{
use rayon::prelude::*;
let total_content: usize =
self.sources_content.iter().map(|c| c.as_ref().map_or(0, |s| s.len())).sum();
if self.sources_content.len() >= 8 && total_content >= 8192 {
let quoted: Vec<String> = self
.sources_content
.par_iter()
.map(|c| match c {
Some(content) => json_quote(content),
None => "null".to_string(),
})
.collect();
for (i, q) in quoted.iter().enumerate() {
if i > 0 {
json.push(b',');
}
json.extend_from_slice(q.as_bytes());
}
} else {
for (i, c) in self.sources_content.iter().enumerate() {
if i > 0 {
json.push(b',');
}
match c {
Some(content) => json_quote_into(&mut json, content),
None => json.extend_from_slice(b"null"),
}
}
}
}
#[cfg(not(feature = "parallel"))]
for (i, c) in self.sources_content.iter().enumerate() {
if i > 0 {
json.push(b',');
}
match c {
Some(content) => json_quote_into(&mut json, content),
None => json.extend_from_slice(b"null"),
}
}
json.push(b']');
}
json.extend_from_slice(br#","names":["#);
for (i, n) in names_for_json.iter().enumerate() {
if i > 0 {
json.push(b',');
}
json_quote_into(&mut json, n);
}
json.push(b']');
json.extend_from_slice(br#","mappings":""#);
self.encode_mappings_into(&mut json);
json.push(b'"');
if !self.ignore_list.is_empty() {
json.extend_from_slice(br#","ignoreList":["#);
for (i, &idx) in self.ignore_list.iter().enumerate() {
if i > 0 {
json.push(b',');
}
let _ = write!(json, "{idx}");
}
json.push(b']');
}
if let Some(ref range_mappings) = self.encode_range_mappings() {
json.extend_from_slice(br#","rangeMappings":""#);
json.extend_from_slice(range_mappings.as_bytes());
json.push(b'"');
}
if let Some(ref id) = self.debug_id {
json.extend_from_slice(br#","debugId":"#);
json_quote_into(&mut json, id);
}
if let Some(ref s) = scopes_str {
json.extend_from_slice(br#","scopes":"#);
json_quote_into(&mut json, s);
}
json.push(b'}');
unsafe { String::from_utf8_unchecked(json) }
}
pub fn mapping_count(&self) -> usize {
self.mappings.len()
}
pub fn to_decoded_map(&self) -> srcmap_sourcemap::SourceMap {
let convert_mapping = |m: &Mapping| srcmap_sourcemap::Mapping {
generated_line: m.generated_line,
generated_column: m.generated_column,
source: m.source.unwrap_or(u32::MAX),
original_line: m.original_line,
original_column: m.original_column,
name: m.name.unwrap_or(u32::MAX),
is_range_mapping: m.is_range_mapping,
};
let sm_mappings: Vec<srcmap_sourcemap::Mapping> =
if self.assume_sorted || is_sorted_by_position(&self.mappings) {
self.mappings.iter().map(convert_mapping).collect()
} else {
let mut sorted: Vec<&Mapping> = self.mappings.iter().collect();
sorted.sort_unstable_by(|a, b| {
a.generated_line
.cmp(&b.generated_line)
.then(a.generated_column.cmp(&b.generated_column))
});
sorted.iter().map(|m| convert_mapping(m)).collect()
};
let sources_content: Vec<Option<String>> = self.sources_content.clone();
let sources: Vec<String> = match &self.source_root {
Some(root) if !root.is_empty() => {
self.sources.iter().map(|s| format!("{root}{s}")).collect()
}
_ => self.sources.clone(),
};
srcmap_sourcemap::SourceMap::from_parts(
self.file.clone(),
self.source_root.clone(),
sources,
sources_content,
self.names.clone(),
sm_mappings,
self.ignore_list.clone(),
self.debug_id.clone(),
None, )
}
pub fn into_parts(self) -> SourceMapParts {
let mappings = self.encode_mappings();
let range_mappings = self.encode_range_mappings();
SourceMapParts {
file: self.file,
mappings,
sources: self.sources,
names: self.names,
sources_content: self.sources_content,
ignore_list: self.ignore_list,
debug_id: self.debug_id,
source_root: self.source_root,
range_mappings,
}
}
pub fn to_writer(&self, writer: &mut impl io::Write) -> io::Result<()> {
let (scopes_str, names_for_json) = if let Some(ref scopes_info) = self.scopes {
let mut names = self.names.clone();
let s = srcmap_scopes::encode_scopes(scopes_info, &mut names);
(Some(s), std::borrow::Cow::Owned(names))
} else {
(None, std::borrow::Cow::Borrowed(&self.names))
};
writer.write_all(br#"{"version":3"#)?;
if let Some(ref file) = self.file {
writer.write_all(br#","file":"#)?;
write_json_quoted(writer, file)?;
}
if let Some(ref root) = self.source_root {
writer.write_all(br#","sourceRoot":"#)?;
write_json_quoted(writer, root)?;
}
writer.write_all(br#","sources":["#)?;
for (i, s) in self.sources.iter().enumerate() {
if i > 0 {
writer.write_all(b",")?;
}
write_json_quoted(writer, s)?;
}
writer.write_all(b"]")?;
if self.sources_content.iter().any(|c| c.is_some()) {
writer.write_all(br#","sourcesContent":["#)?;
for (i, c) in self.sources_content.iter().enumerate() {
if i > 0 {
writer.write_all(b",")?;
}
match c {
Some(content) => write_json_quoted(writer, content)?,
None => writer.write_all(b"null")?,
}
}
writer.write_all(b"]")?;
}
writer.write_all(br#","names":["#)?;
for (i, n) in names_for_json.iter().enumerate() {
if i > 0 {
writer.write_all(b",")?;
}
write_json_quoted(writer, n)?;
}
writer.write_all(b"]")?;
writer.write_all(br#","mappings":""#)?;
let mut vlq_buf: Vec<u8> = Vec::new();
self.encode_mappings_into(&mut vlq_buf);
writer.write_all(&vlq_buf)?;
writer.write_all(b"\"")?;
if !self.ignore_list.is_empty() {
writer.write_all(br#","ignoreList":["#)?;
for (i, &idx) in self.ignore_list.iter().enumerate() {
if i > 0 {
writer.write_all(b",")?;
}
write!(writer, "{idx}")?;
}
writer.write_all(b"]")?;
}
if let Some(ref range_mappings) = self.encode_range_mappings() {
writer.write_all(br#","rangeMappings":""#)?;
writer.write_all(range_mappings.as_bytes())?;
writer.write_all(b"\"")?;
}
if let Some(ref id) = self.debug_id {
writer.write_all(br#","debugId":"#)?;
write_json_quoted(writer, id)?;
}
if let Some(ref s) = scopes_str {
writer.write_all(br#","scopes":"#)?;
write_json_quoted(writer, s)?;
}
writer.write_all(b"}")?;
Ok(())
}
}
#[derive(Debug)]
pub struct StreamingGenerator {
file: Option<String>,
source_root: Option<String>,
sources: Vec<String>,
sources_content: Vec<Option<String>>,
names: Vec<String>,
ignore_list: Vec<u32>,
debug_id: Option<String>,
source_map: FxHashMap<String, u32>,
name_map: FxHashMap<String, u32>,
vlq_out: Vec<u8>,
prev_gen_line: u32,
prev_gen_col: i64,
prev_source: i64,
prev_orig_line: i64,
prev_orig_col: i64,
prev_name: i64,
first_in_line: bool,
mapping_count: usize,
line_local_index: u32,
range_entries: Vec<(u32, u32)>,
}
impl StreamingGenerator {
pub fn new(file: Option<String>) -> Self {
Self {
file,
source_root: None,
sources: Vec::new(),
sources_content: Vec::new(),
names: Vec::new(),
ignore_list: Vec::new(),
debug_id: None,
source_map: FxHashMap::default(),
name_map: FxHashMap::default(),
vlq_out: Vec::with_capacity(1024),
prev_gen_line: 0,
prev_gen_col: 0,
prev_source: 0,
prev_orig_line: 0,
prev_orig_col: 0,
prev_name: 0,
first_in_line: true,
mapping_count: 0,
line_local_index: 0,
range_entries: Vec::new(),
}
}
pub fn with_capacity(file: Option<String>, vlq_capacity: usize) -> Self {
Self {
file,
source_root: None,
sources: Vec::new(),
sources_content: Vec::new(),
names: Vec::new(),
ignore_list: Vec::new(),
debug_id: None,
source_map: FxHashMap::default(),
name_map: FxHashMap::default(),
vlq_out: Vec::with_capacity(vlq_capacity),
prev_gen_line: 0,
prev_gen_col: 0,
prev_source: 0,
prev_orig_line: 0,
prev_orig_col: 0,
prev_name: 0,
first_in_line: true,
mapping_count: 0,
line_local_index: 0,
range_entries: Vec::new(),
}
}
pub fn set_source_root(&mut self, root: impl Into<String>) {
self.source_root = Some(root.into());
}
pub fn set_debug_id(&mut self, id: impl Into<String>) {
self.debug_id = Some(id.into());
}
#[inline]
pub fn add_source(&mut self, source: &str) -> u32 {
if let Some(&idx) = self.source_map.get(source) {
return idx;
}
let idx = self.sources.len() as u32;
self.sources.push(source.to_string());
self.sources_content.push(None);
self.source_map.insert(source.to_string(), idx);
idx
}
pub fn set_source_content(&mut self, source_idx: u32, content: impl Into<String>) {
if (source_idx as usize) < self.sources_content.len() {
self.sources_content[source_idx as usize] = Some(content.into());
}
}
#[inline]
pub fn add_name(&mut self, name: &str) -> u32 {
if let Some(&idx) = self.name_map.get(name) {
return idx;
}
let idx = self.names.len() as u32;
self.names.push(name.to_string());
self.name_map.insert(name.to_string(), idx);
idx
}
pub fn add_to_ignore_list(&mut self, source_idx: u32) {
if !self.ignore_list.contains(&source_idx) {
self.ignore_list.push(source_idx);
}
}
#[inline]
pub fn add_generated_mapping(&mut self, generated_line: u32, generated_column: u32) {
self.advance_to_line(generated_line);
self.vlq_out.reserve(8);
if !self.first_in_line {
self.vlq_out.push(b',');
}
self.first_in_line = false;
unsafe {
vlq_encode_unchecked(&mut self.vlq_out, generated_column as i64 - self.prev_gen_col);
}
self.prev_gen_col = generated_column as i64;
self.line_local_index += 1;
self.mapping_count += 1;
}
#[inline]
pub fn add_mapping(
&mut self,
generated_line: u32,
generated_column: u32,
source: u32,
original_line: u32,
original_column: u32,
) {
self.advance_to_line(generated_line);
self.vlq_out.reserve(29);
if !self.first_in_line {
self.vlq_out.push(b',');
}
self.first_in_line = false;
unsafe {
vlq_encode_unchecked(&mut self.vlq_out, generated_column as i64 - self.prev_gen_col);
self.prev_gen_col = generated_column as i64;
vlq_encode_unchecked(&mut self.vlq_out, source as i64 - self.prev_source);
self.prev_source = source as i64;
vlq_encode_unchecked(&mut self.vlq_out, original_line as i64 - self.prev_orig_line);
self.prev_orig_line = original_line as i64;
vlq_encode_unchecked(&mut self.vlq_out, original_column as i64 - self.prev_orig_col);
self.prev_orig_col = original_column as i64;
}
self.line_local_index += 1;
self.mapping_count += 1;
}
#[inline]
pub fn add_range_mapping(
&mut self,
generated_line: u32,
generated_column: u32,
source: u32,
original_line: u32,
original_column: u32,
) {
self.advance_to_line(generated_line);
self.range_entries.push((self.prev_gen_line, self.line_local_index));
self.vlq_out.reserve(29);
if !self.first_in_line {
self.vlq_out.push(b',');
}
self.first_in_line = false;
unsafe {
vlq_encode_unchecked(&mut self.vlq_out, generated_column as i64 - self.prev_gen_col);
self.prev_gen_col = generated_column as i64;
vlq_encode_unchecked(&mut self.vlq_out, source as i64 - self.prev_source);
self.prev_source = source as i64;
vlq_encode_unchecked(&mut self.vlq_out, original_line as i64 - self.prev_orig_line);
self.prev_orig_line = original_line as i64;
vlq_encode_unchecked(&mut self.vlq_out, original_column as i64 - self.prev_orig_col);
self.prev_orig_col = original_column as i64;
}
self.line_local_index += 1;
self.mapping_count += 1;
}
#[inline]
pub fn add_named_mapping(
&mut self,
generated_line: u32,
generated_column: u32,
source: u32,
original_line: u32,
original_column: u32,
name: u32,
) {
self.advance_to_line(generated_line);
self.vlq_out.reserve(36);
if !self.first_in_line {
self.vlq_out.push(b',');
}
self.first_in_line = false;
unsafe {
vlq_encode_unchecked(&mut self.vlq_out, generated_column as i64 - self.prev_gen_col);
self.prev_gen_col = generated_column as i64;
vlq_encode_unchecked(&mut self.vlq_out, source as i64 - self.prev_source);
self.prev_source = source as i64;
vlq_encode_unchecked(&mut self.vlq_out, original_line as i64 - self.prev_orig_line);
self.prev_orig_line = original_line as i64;
vlq_encode_unchecked(&mut self.vlq_out, original_column as i64 - self.prev_orig_col);
self.prev_orig_col = original_column as i64;
vlq_encode_unchecked(&mut self.vlq_out, name as i64 - self.prev_name);
self.prev_name = name as i64;
}
self.line_local_index += 1;
self.mapping_count += 1;
}
#[inline]
pub fn add_named_range_mapping(
&mut self,
generated_line: u32,
generated_column: u32,
source: u32,
original_line: u32,
original_column: u32,
name: u32,
) {
self.advance_to_line(generated_line);
self.range_entries.push((self.prev_gen_line, self.line_local_index));
self.vlq_out.reserve(36);
if !self.first_in_line {
self.vlq_out.push(b',');
}
self.first_in_line = false;
unsafe {
vlq_encode_unchecked(&mut self.vlq_out, generated_column as i64 - self.prev_gen_col);
self.prev_gen_col = generated_column as i64;
vlq_encode_unchecked(&mut self.vlq_out, source as i64 - self.prev_source);
self.prev_source = source as i64;
vlq_encode_unchecked(&mut self.vlq_out, original_line as i64 - self.prev_orig_line);
self.prev_orig_line = original_line as i64;
vlq_encode_unchecked(&mut self.vlq_out, original_column as i64 - self.prev_orig_col);
self.prev_orig_col = original_column as i64;
vlq_encode_unchecked(&mut self.vlq_out, name as i64 - self.prev_name);
self.prev_name = name as i64;
}
self.line_local_index += 1;
self.mapping_count += 1;
}
pub fn mapping_count(&self) -> usize {
self.mapping_count
}
#[inline]
fn advance_to_line(&mut self, generated_line: u32) {
while self.prev_gen_line < generated_line {
self.vlq_out.push(b';');
self.prev_gen_line += 1;
self.prev_gen_col = 0;
self.first_in_line = true;
self.line_local_index = 0;
}
}
pub fn to_json(&self) -> String {
use std::io::Write;
let vlq = self.vlq_str();
let sources_size: usize = self.sources.iter().map(|s| s.len() + 4).sum();
let names_size: usize = self.names.iter().map(|n| n.len() + 4).sum();
let content_size: usize =
self.sources_content.iter().map(|c| c.as_ref().map_or(5, |s| s.len() + 4)).sum();
let capacity = 100 + sources_size + names_size + vlq.len() + content_size;
let mut json: Vec<u8> = Vec::with_capacity(capacity);
json.extend_from_slice(br#"{"version":3"#);
if let Some(ref file) = self.file {
json.extend_from_slice(br#","file":"#);
json_quote_into(&mut json, file);
}
if let Some(ref root) = self.source_root {
json.extend_from_slice(br#","sourceRoot":"#);
json_quote_into(&mut json, root);
}
json.extend_from_slice(br#","sources":["#);
for (i, s) in self.sources.iter().enumerate() {
if i > 0 {
json.push(b',');
}
json_quote_into(&mut json, s);
}
json.push(b']');
if self.sources_content.iter().any(|c| c.is_some()) {
json.extend_from_slice(br#","sourcesContent":["#);
for (i, c) in self.sources_content.iter().enumerate() {
if i > 0 {
json.push(b',');
}
match c {
Some(content) => json_quote_into(&mut json, content),
None => json.extend_from_slice(b"null"),
}
}
json.push(b']');
}
json.extend_from_slice(br#","names":["#);
for (i, n) in self.names.iter().enumerate() {
if i > 0 {
json.push(b',');
}
json_quote_into(&mut json, n);
}
json.push(b']');
json.extend_from_slice(br#","mappings":""#);
json.extend_from_slice(vlq.as_bytes());
json.push(b'"');
if !self.ignore_list.is_empty() {
json.extend_from_slice(br#","ignoreList":["#);
for (i, &idx) in self.ignore_list.iter().enumerate() {
if i > 0 {
json.push(b',');
}
let _ = write!(json, "{idx}");
}
json.push(b']');
}
if let Some(ref range_mappings) = self.encode_range_mappings() {
json.extend_from_slice(br#","rangeMappings":""#);
json.extend_from_slice(range_mappings.as_bytes());
json.push(b'"');
}
if let Some(ref id) = self.debug_id {
json.extend_from_slice(br#","debugId":"#);
json_quote_into(&mut json, id);
}
json.push(b'}');
unsafe { String::from_utf8_unchecked(json) }
}
pub fn to_decoded_map(
&self,
) -> Result<srcmap_sourcemap::SourceMap, srcmap_sourcemap::ParseError> {
let vlq = self.vlq_str();
let range_mappings = self.encode_range_mappings();
let sources: Vec<String> = match &self.source_root {
Some(root) if !root.is_empty() => {
self.sources.iter().map(|s| format!("{root}{s}")).collect()
}
_ => self.sources.clone(),
};
srcmap_sourcemap::SourceMap::from_vlq_with_range_mappings(
vlq,
sources,
self.names.clone(),
self.file.clone(),
self.source_root.clone(),
self.sources_content.clone(),
self.ignore_list.clone(),
self.debug_id.clone(),
range_mappings.as_deref(),
)
}
fn encode_range_mappings(&self) -> Option<String> {
if self.range_entries.is_empty() {
return None;
}
let max_line = self.range_entries.last().map_or(0, |&(line, _)| line);
let mut out: Vec<u8> = Vec::new();
let mut entry_idx = 0;
for line in 0..=max_line {
if line > 0 {
out.push(b';');
}
let mut prev_offset: u64 = 0;
let mut first_on_line = true;
while entry_idx < self.range_entries.len() && self.range_entries[entry_idx].0 == line {
if !first_on_line {
out.push(b',');
}
first_on_line = false;
let local_idx = self.range_entries[entry_idx].1 as u64;
let delta = local_idx - prev_offset;
vlq_encode_unsigned(&mut out, delta);
prev_offset = local_idx;
entry_idx += 1;
}
}
while out.last() == Some(&b';') {
out.pop();
}
if out.is_empty() {
return None;
}
Some(unsafe { String::from_utf8_unchecked(out) })
}
fn vlq_str(&self) -> &str {
let end = self.vlq_out.iter().rposition(|&b| b != b';').map_or(0, |i| i + 1);
unsafe { std::str::from_utf8_unchecked(&self.vlq_out[..end]) }
}
pub fn into_parts(self) -> SourceMapParts {
let range_mappings = self.encode_range_mappings();
let end = self.vlq_out.iter().rposition(|&b| b != b';').map_or(0, |i| i + 1);
let mappings = unsafe { String::from_utf8_unchecked(self.vlq_out[..end].to_vec()) };
SourceMapParts {
file: self.file,
mappings,
sources: self.sources,
names: self.names,
sources_content: self.sources_content,
ignore_list: self.ignore_list,
debug_id: self.debug_id,
source_root: self.source_root,
range_mappings,
}
}
pub fn to_writer(&self, writer: &mut impl io::Write) -> io::Result<()> {
let vlq = self.vlq_str();
writer.write_all(br#"{"version":3"#)?;
if let Some(ref file) = self.file {
writer.write_all(br#","file":"#)?;
write_json_quoted(writer, file)?;
}
if let Some(ref root) = self.source_root {
writer.write_all(br#","sourceRoot":"#)?;
write_json_quoted(writer, root)?;
}
writer.write_all(br#","sources":["#)?;
for (i, s) in self.sources.iter().enumerate() {
if i > 0 {
writer.write_all(b",")?;
}
write_json_quoted(writer, s)?;
}
writer.write_all(b"]")?;
if self.sources_content.iter().any(|c| c.is_some()) {
writer.write_all(br#","sourcesContent":["#)?;
for (i, c) in self.sources_content.iter().enumerate() {
if i > 0 {
writer.write_all(b",")?;
}
match c {
Some(content) => write_json_quoted(writer, content)?,
None => writer.write_all(b"null")?,
}
}
writer.write_all(b"]")?;
}
writer.write_all(br#","names":["#)?;
for (i, n) in self.names.iter().enumerate() {
if i > 0 {
writer.write_all(b",")?;
}
write_json_quoted(writer, n)?;
}
writer.write_all(b"]")?;
writer.write_all(br#","mappings":""#)?;
writer.write_all(vlq.as_bytes())?;
writer.write_all(b"\"")?;
if !self.ignore_list.is_empty() {
writer.write_all(br#","ignoreList":["#)?;
for (i, &idx) in self.ignore_list.iter().enumerate() {
if i > 0 {
writer.write_all(b",")?;
}
write!(writer, "{idx}")?;
}
writer.write_all(b"]")?;
}
if let Some(ref range_mappings) = self.encode_range_mappings() {
writer.write_all(br#","rangeMappings":""#)?;
writer.write_all(range_mappings.as_bytes())?;
writer.write_all(b"\"")?;
}
if let Some(ref id) = self.debug_id {
writer.write_all(br#","debugId":"#)?;
write_json_quoted(writer, id)?;
}
writer.write_all(b"}")?;
Ok(())
}
}
#[cfg(feature = "parallel")]
fn encode_mapping_slice(
mappings: &[&Mapping],
init_source: i64,
init_orig_line: i64,
init_orig_col: i64,
init_name: i64,
) -> Vec<u8> {
let mut buf = Vec::with_capacity(mappings.len() * 36);
let mut prev_gen_col: i64 = 0;
let mut prev_source = init_source;
let mut prev_orig_line = init_orig_line;
let mut prev_orig_col = init_orig_col;
let mut prev_name = init_name;
let mut first = true;
for m in mappings {
if !first {
buf.push(b',');
}
first = false;
unsafe {
vlq_encode_unchecked(&mut buf, m.generated_column as i64 - prev_gen_col);
prev_gen_col = m.generated_column as i64;
if let Some(source) = m.source {
vlq_encode_unchecked(&mut buf, source as i64 - prev_source);
prev_source = source as i64;
vlq_encode_unchecked(&mut buf, m.original_line as i64 - prev_orig_line);
prev_orig_line = m.original_line as i64;
vlq_encode_unchecked(&mut buf, m.original_column as i64 - prev_orig_col);
prev_orig_col = m.original_column as i64;
if let Some(name) = m.name {
vlq_encode_unchecked(&mut buf, name as i64 - prev_name);
prev_name = name as i64;
}
}
}
}
buf
}
fn json_quote_into(out: &mut Vec<u8>, s: &str) {
let bytes = s.as_bytes();
out.push(b'"');
let mut start = 0;
for (i, &b) in bytes.iter().enumerate() {
let escape: &[u8] = match b {
b'"' => b"\\\"",
b'\\' => b"\\\\",
b'\n' => b"\\n",
b'\r' => b"\\r",
b'\t' => b"\\t",
0x00..=0x1f => {
out.extend_from_slice(&bytes[start..i]);
let hex = b"0123456789abcdef";
out.extend_from_slice(&[
b'\\',
b'u',
b'0',
b'0',
hex[(b >> 4) as usize],
hex[(b & 0xf) as usize],
]);
start = i + 1;
continue;
}
_ => continue,
};
out.extend_from_slice(&bytes[start..i]);
out.extend_from_slice(escape);
start = i + 1;
}
out.extend_from_slice(&bytes[start..]);
out.push(b'"');
}
#[cfg(feature = "parallel")]
fn json_quote(s: &str) -> String {
let mut out = Vec::with_capacity(s.len() + 2);
json_quote_into(&mut out, s);
unsafe { String::from_utf8_unchecked(out) }
}
fn write_json_quoted(writer: &mut impl io::Write, s: &str) -> io::Result<()> {
let mut buf = Vec::with_capacity(s.len() + 2);
json_quote_into(&mut buf, s);
writer.write_all(&buf)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_generator() {
let builder = SourceMapGenerator::new(None);
let json = builder.to_json();
assert!(json.contains(r#""version":3"#));
assert!(json.contains(r#""mappings":"""#));
}
#[test]
fn simple_mapping() {
let mut builder = SourceMapGenerator::new(Some("output.js".to_string()));
let src = builder.add_source("input.js");
builder.add_mapping(0, 0, src, 0, 0);
let json = builder.to_json();
assert!(json.contains(r#""file":"output.js""#));
assert!(json.contains(r#""sources":["input.js"]"#));
let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
let loc = sm.original_position_for(0, 0).unwrap();
assert_eq!(sm.source(loc.source), "input.js");
assert_eq!(loc.line, 0);
assert_eq!(loc.column, 0);
}
#[test]
fn mapping_with_name() {
let mut builder = SourceMapGenerator::new(None);
let src = builder.add_source("input.js");
let name = builder.add_name("myFunction");
builder.add_named_mapping(0, 0, src, 0, 0, name);
let json = builder.to_json();
assert!(json.contains(r#""names":["myFunction"]"#));
let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
let loc = sm.original_position_for(0, 0).unwrap();
assert_eq!(loc.name, Some(0));
assert_eq!(sm.name(0), "myFunction");
}
#[test]
fn multiple_lines() {
let mut builder = SourceMapGenerator::new(None);
let src = builder.add_source("input.js");
builder.add_mapping(0, 0, src, 0, 0);
builder.add_mapping(1, 4, src, 1, 2);
builder.add_mapping(2, 0, src, 2, 0);
let json = builder.to_json();
let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
assert_eq!(sm.line_count(), 3);
let loc = sm.original_position_for(1, 4).unwrap();
assert_eq!(loc.line, 1);
assert_eq!(loc.column, 2);
}
#[test]
fn multiple_sources() {
let mut builder = SourceMapGenerator::new(None);
let a = builder.add_source("a.js");
let b = builder.add_source("b.js");
builder.add_mapping(0, 0, a, 0, 0);
builder.add_mapping(1, 0, b, 0, 0);
let json = builder.to_json();
let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
let loc0 = sm.original_position_for(0, 0).unwrap();
let loc1 = sm.original_position_for(1, 0).unwrap();
assert_eq!(sm.source(loc0.source), "a.js");
assert_eq!(sm.source(loc1.source), "b.js");
}
#[test]
fn source_content() {
let mut builder = SourceMapGenerator::new(None);
let src = builder.add_source("input.js");
builder.set_source_content(src, "var x = 1;".to_string());
builder.add_mapping(0, 0, src, 0, 0);
let json = builder.to_json();
assert!(json.contains(r#""sourcesContent":["var x = 1;"]"#));
let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
assert_eq!(sm.sources_content[0], Some("var x = 1;".to_string()));
}
#[test]
fn source_root() {
let mut builder = SourceMapGenerator::new(None);
builder.set_source_root("src/".to_string());
let src = builder.add_source("input.js");
builder.add_mapping(0, 0, src, 0, 0);
let json = builder.to_json();
assert!(json.contains(r#""sourceRoot":"src/""#));
}
#[test]
fn ignore_list() {
let mut builder = SourceMapGenerator::new(None);
let _app = builder.add_source("app.js");
let lib = builder.add_source("node_modules/lib.js");
builder.add_to_ignore_list(lib);
builder.add_mapping(0, 0, lib, 0, 0);
let json = builder.to_json();
assert!(json.contains(r#""ignoreList":[1]"#));
}
#[test]
fn generated_only_mapping() {
let mut builder = SourceMapGenerator::new(None);
builder.add_generated_mapping(0, 0);
let json = builder.to_json();
let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
assert!(sm.original_position_for(0, 0).is_none());
}
#[test]
fn dedup_sources_and_names() {
let mut builder = SourceMapGenerator::new(None);
let s1 = builder.add_source("input.js");
let s2 = builder.add_source("input.js"); assert_eq!(s1, s2);
let n1 = builder.add_name("foo");
let n2 = builder.add_name("foo"); assert_eq!(n1, n2);
assert_eq!(builder.sources.len(), 1);
assert_eq!(builder.names.len(), 1);
}
#[test]
fn large_roundtrip() {
let mut builder = SourceMapGenerator::new(Some("bundle.js".to_string()));
for i in 0..5 {
builder.add_source(&format!("src/file{i}.js"));
}
for i in 0..10 {
builder.add_name(&format!("var{i}"));
}
for line in 0..100u32 {
for col in 0..10u32 {
let src = (line * 10 + col) % 5;
let name = if col % 3 == 0 { Some(col % 10) } else { None };
match name {
Some(n) => builder.add_named_mapping(line, col * 10, src, line, col * 5, n),
None => builder.add_mapping(line, col * 10, src, line, col * 5),
}
}
}
let json = builder.to_json();
let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
assert_eq!(sm.mapping_count(), 1000);
assert_eq!(sm.line_count(), 100);
let loc = sm.original_position_for(50, 30).unwrap();
assert_eq!(loc.line, 50);
assert_eq!(loc.column, 15);
}
#[test]
fn json_escaping() {
let mut builder = SourceMapGenerator::new(None);
let src = builder.add_source("path/with\"quotes.js");
builder.set_source_content(src, "line1\nline2\ttab".to_string());
builder.add_mapping(0, 0, src, 0, 0);
let json = builder.to_json();
let _: serde_json::Value = serde_json::from_str(&json).unwrap();
}
#[test]
fn maybe_add_mapping_skips_redundant() {
let mut builder = SourceMapGenerator::new(None);
let src = builder.add_source("input.js");
assert!(builder.maybe_add_mapping(0, 0, src, 10, 0));
assert!(!builder.maybe_add_mapping(0, 5, src, 10, 0));
assert!(builder.maybe_add_mapping(0, 10, src, 11, 0));
assert!(builder.maybe_add_mapping(1, 0, src, 11, 0));
assert_eq!(builder.mapping_count(), 3);
let json = builder.to_json();
let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
assert_eq!(sm.mapping_count(), 3);
}
#[test]
fn maybe_add_mapping_different_source() {
let mut builder = SourceMapGenerator::new(None);
let a = builder.add_source("a.js");
let b = builder.add_source("b.js");
assert!(builder.maybe_add_mapping(0, 0, a, 0, 0));
assert!(builder.maybe_add_mapping(0, 5, b, 0, 0));
assert_eq!(builder.mapping_count(), 2);
}
#[test]
fn to_decoded_map_basic() {
let mut builder = SourceMapGenerator::new(Some("output.js".to_string()));
let src = builder.add_source("input.js");
builder.add_mapping(0, 0, src, 0, 0);
builder.add_mapping(1, 4, src, 1, 2);
let sm = builder.to_decoded_map();
assert_eq!(sm.mapping_count(), 2);
assert_eq!(sm.line_count(), 2);
let loc = sm.original_position_for(0, 0).unwrap();
assert_eq!(sm.source(loc.source), "input.js");
assert_eq!(loc.line, 0);
assert_eq!(loc.column, 0);
let loc = sm.original_position_for(1, 4).unwrap();
assert_eq!(loc.line, 1);
assert_eq!(loc.column, 2);
}
#[test]
fn to_decoded_map_with_names() {
let mut builder = SourceMapGenerator::new(None);
let src = builder.add_source("input.js");
let name = builder.add_name("myFunction");
builder.add_named_mapping(0, 0, src, 0, 0, name);
let sm = builder.to_decoded_map();
let loc = sm.original_position_for(0, 0).unwrap();
assert_eq!(loc.name, Some(0));
assert_eq!(sm.name(0), "myFunction");
}
#[test]
fn to_decoded_map_matches_json_roundtrip() {
let mut builder = SourceMapGenerator::new(Some("bundle.js".to_string()));
for i in 0..5 {
builder.add_source(&format!("src/file{i}.js"));
}
for i in 0..10 {
builder.add_name(&format!("var{i}"));
}
for line in 0..50u32 {
for col in 0..10u32 {
let src = (line * 10 + col) % 5;
let name = if col % 3 == 0 { Some(col % 10) } else { None };
match name {
Some(n) => builder.add_named_mapping(line, col * 10, src, line, col * 5, n),
None => builder.add_mapping(line, col * 10, src, line, col * 5),
}
}
}
let sm_decoded = builder.to_decoded_map();
let json = builder.to_json();
let sm_json = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
assert_eq!(sm_decoded.mapping_count(), sm_json.mapping_count());
assert_eq!(sm_decoded.line_count(), sm_json.line_count());
for m in sm_json.all_mappings() {
let a = sm_json.original_position_for(m.generated_line, m.generated_column);
let b = sm_decoded.original_position_for(m.generated_line, m.generated_column);
match (a, b) {
(Some(a), Some(b)) => {
assert_eq!(
a.source, b.source,
"source mismatch at ({}, {})",
m.generated_line, m.generated_column
);
assert_eq!(
a.line, b.line,
"line mismatch at ({}, {})",
m.generated_line, m.generated_column
);
assert_eq!(
a.column, b.column,
"column mismatch at ({}, {})",
m.generated_line, m.generated_column
);
assert_eq!(
a.name, b.name,
"name mismatch at ({}, {})",
m.generated_line, m.generated_column
);
}
(None, None) => {}
_ => panic!("lookup mismatch at ({}, {})", m.generated_line, m.generated_column),
}
}
}
#[test]
fn to_decoded_map_empty() {
let builder = SourceMapGenerator::new(None);
let sm = builder.to_decoded_map();
assert_eq!(sm.mapping_count(), 0);
assert_eq!(sm.line_count(), 0);
}
#[test]
fn to_decoded_map_generated_only() {
let mut builder = SourceMapGenerator::new(None);
builder.add_generated_mapping(0, 0);
let sm = builder.to_decoded_map();
assert_eq!(sm.mapping_count(), 1);
assert!(sm.original_position_for(0, 0).is_none());
}
#[test]
fn to_decoded_map_multiple_sources() {
let mut builder = SourceMapGenerator::new(None);
let a = builder.add_source("a.js");
let b = builder.add_source("b.js");
builder.add_mapping(0, 0, a, 0, 0);
builder.add_mapping(1, 0, b, 0, 0);
let sm = builder.to_decoded_map();
let loc0 = sm.original_position_for(0, 0).unwrap();
let loc1 = sm.original_position_for(1, 0).unwrap();
assert_eq!(sm.source(loc0.source), "a.js");
assert_eq!(sm.source(loc1.source), "b.js");
}
#[test]
fn to_decoded_map_with_source_content() {
let mut builder = SourceMapGenerator::new(None);
let src = builder.add_source("input.js");
builder.set_source_content(src, "var x = 1;".to_string());
builder.add_mapping(0, 0, src, 0, 0);
let sm = builder.to_decoded_map();
assert_eq!(sm.sources_content[0], Some("var x = 1;".to_string()));
}
#[test]
fn to_decoded_map_reverse_lookup() {
let mut builder = SourceMapGenerator::new(None);
let src = builder.add_source("input.js");
builder.add_mapping(0, 0, src, 10, 5);
let sm = builder.to_decoded_map();
let loc = sm.generated_position_for("input.js", 10, 5).unwrap();
assert_eq!(loc.line, 0);
assert_eq!(loc.column, 0);
}
#[test]
fn to_decoded_map_sparse_lines() {
let mut builder = SourceMapGenerator::new(None);
let src = builder.add_source("input.js");
builder.add_mapping(0, 0, src, 0, 0);
builder.add_mapping(5, 0, src, 5, 0);
let sm = builder.to_decoded_map();
assert_eq!(sm.line_count(), 6);
assert!(sm.original_position_for(0, 0).is_some());
assert!(sm.original_position_for(2, 0).is_none());
assert!(sm.original_position_for(5, 0).is_some());
}
#[test]
fn empty_lines_between_mappings() {
let mut builder = SourceMapGenerator::new(None);
let src = builder.add_source("input.js");
builder.add_mapping(0, 0, src, 0, 0);
builder.add_mapping(5, 0, src, 5, 0);
let json = builder.to_json();
let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
assert!(sm.original_position_for(0, 0).is_some());
assert!(sm.original_position_for(2, 0).is_none());
assert!(sm.original_position_for(5, 0).is_some());
}
#[test]
fn debug_id() {
let mut builder = SourceMapGenerator::new(None);
builder.set_debug_id("85314830-023f-4cf1-a267-535f4e37bb17".to_string());
let src = builder.add_source("input.js");
builder.add_mapping(0, 0, src, 0, 0);
let json = builder.to_json();
assert!(json.contains(r#""debugId":"85314830-023f-4cf1-a267-535f4e37bb17""#));
let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
assert_eq!(sm.debug_id.as_deref(), Some("85314830-023f-4cf1-a267-535f4e37bb17"));
}
#[test]
fn scopes_roundtrip() {
use srcmap_scopes::{Binding, GeneratedRange, OriginalScope, Position, ScopeInfo};
let mut builder = SourceMapGenerator::new(Some("bundle.js".to_string()));
let src = builder.add_source("input.js");
builder.set_source_content(
src,
"function hello(name) {\n return name;\n}\nhello('world');".to_string(),
);
let name_hello = builder.add_name("hello");
builder.add_named_mapping(0, 0, src, 0, 0, name_hello);
builder.add_mapping(1, 0, src, 1, 0);
builder.set_scopes(ScopeInfo {
scopes: vec![Some(OriginalScope {
start: Position { line: 0, column: 0 },
end: Position { line: 3, column: 14 },
name: None,
kind: Some("global".to_string()),
is_stack_frame: false,
variables: vec!["hello".to_string()],
children: vec![OriginalScope {
start: Position { line: 0, column: 9 },
end: Position { line: 2, column: 1 },
name: Some("hello".to_string()),
kind: Some("function".to_string()),
is_stack_frame: true,
variables: vec!["name".to_string()],
children: vec![],
}],
})],
ranges: vec![GeneratedRange {
start: Position { line: 0, column: 0 },
end: Position { line: 3, column: 14 },
is_stack_frame: false,
is_hidden: false,
definition: Some(0),
call_site: None,
bindings: vec![Binding::Expression("hello".to_string())],
children: vec![GeneratedRange {
start: Position { line: 0, column: 9 },
end: Position { line: 2, column: 1 },
is_stack_frame: true,
is_hidden: false,
definition: Some(1),
call_site: None,
bindings: vec![Binding::Expression("name".to_string())],
children: vec![],
}],
}],
});
let json = builder.to_json();
assert!(json.contains(r#""scopes":"#));
let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
assert!(sm.scopes.is_some());
let scopes_info = sm.scopes.unwrap();
assert_eq!(scopes_info.scopes.len(), 1);
let root_scope = scopes_info.scopes[0].as_ref().unwrap();
assert_eq!(root_scope.kind.as_deref(), Some("global"));
assert_eq!(root_scope.variables, vec!["hello"]);
assert_eq!(root_scope.children.len(), 1);
let fn_scope = &root_scope.children[0];
assert_eq!(fn_scope.name.as_deref(), Some("hello"));
assert_eq!(fn_scope.kind.as_deref(), Some("function"));
assert!(fn_scope.is_stack_frame);
assert_eq!(fn_scope.variables, vec!["name"]);
assert_eq!(scopes_info.ranges.len(), 1);
let outer = &scopes_info.ranges[0];
assert_eq!(outer.definition, Some(0));
assert_eq!(outer.bindings, vec![Binding::Expression("hello".to_string())]);
assert_eq!(outer.children.len(), 1);
let inner = &outer.children[0];
assert_eq!(inner.definition, Some(1));
assert!(inner.is_stack_frame);
assert_eq!(inner.bindings, vec![Binding::Expression("name".to_string())]);
}
#[test]
fn scopes_with_inlining_roundtrip() {
use srcmap_scopes::{
Binding, CallSite, GeneratedRange, OriginalScope, Position, ScopeInfo,
};
let mut builder = SourceMapGenerator::new(None);
let src = builder.add_source("input.js");
builder.add_mapping(0, 0, src, 0, 0);
builder.set_scopes(ScopeInfo {
scopes: vec![Some(OriginalScope {
start: Position { line: 0, column: 0 },
end: Position { line: 10, column: 0 },
name: None,
kind: None,
is_stack_frame: false,
variables: vec!["x".to_string()],
children: vec![OriginalScope {
start: Position { line: 1, column: 0 },
end: Position { line: 4, column: 1 },
name: Some("greet".to_string()),
kind: Some("function".to_string()),
is_stack_frame: true,
variables: vec!["msg".to_string()],
children: vec![],
}],
})],
ranges: vec![GeneratedRange {
start: Position { line: 0, column: 0 },
end: Position { line: 10, column: 0 },
is_stack_frame: false,
is_hidden: false,
definition: Some(0),
call_site: None,
bindings: vec![Binding::Expression("_x".to_string())],
children: vec![GeneratedRange {
start: Position { line: 6, column: 0 },
end: Position { line: 8, column: 0 },
is_stack_frame: true,
is_hidden: false,
definition: Some(1),
call_site: Some(CallSite { source_index: 0, line: 8, column: 0 }),
bindings: vec![Binding::Expression("\"Hello\"".to_string())],
children: vec![],
}],
}],
});
let json = builder.to_json();
let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
let info = sm.scopes.unwrap();
let inlined = &info.ranges[0].children[0];
assert_eq!(inlined.call_site, Some(CallSite { source_index: 0, line: 8, column: 0 }));
assert_eq!(inlined.bindings, vec![Binding::Expression("\"Hello\"".to_string())]);
}
#[test]
fn set_source_content_out_of_bounds() {
let mut builder = SourceMapGenerator::new(None);
builder.set_source_content(0, "content".to_string());
let json = builder.to_json();
assert!(!json.contains("content"));
}
#[test]
fn add_to_ignore_list_dedup() {
let mut builder = SourceMapGenerator::new(None);
let idx = builder.add_source("vendor.js");
builder.add_to_ignore_list(idx);
builder.add_to_ignore_list(idx); let json = builder.to_json();
let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
assert_eq!(sm.ignore_list, vec![0]);
}
#[test]
fn to_decoded_map_with_source_root() {
let mut builder = SourceMapGenerator::new(None);
builder.set_source_root("src/".to_string());
let src = builder.add_source("app.ts");
builder.add_mapping(0, 0, src, 0, 0);
let sm = builder.to_decoded_map();
assert_eq!(sm.sources, vec!["src/app.ts"]);
}
#[test]
fn json_escaping_special_chars() {
let mut builder = SourceMapGenerator::new(None);
let src = builder.add_source("a.js");
builder.set_source_content(src, "line1\nline2\r\ttab\\\"\x01end".to_string());
builder.add_mapping(0, 0, src, 0, 0);
let json = builder.to_json();
let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
assert_eq!(sm.sources_content, vec![Some("line1\nline2\r\ttab\\\"\x01end".to_string())]);
}
#[test]
fn json_escaping_in_names() {
let mut builder = SourceMapGenerator::new(None);
let src = builder.add_source("a.js");
let name = builder.add_name("func\"with\\special");
builder.add_named_mapping(0, 0, src, 0, 0, name);
let json = builder.to_json();
let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
assert_eq!(sm.names[0], "func\"with\\special");
}
#[test]
fn json_escaping_in_sources() {
let mut builder = SourceMapGenerator::new(None);
let src = builder.add_source("path/with\"quotes.js");
builder.add_mapping(0, 0, src, 0, 0);
let json = builder.to_json();
let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
assert_eq!(sm.sources[0], "path/with\"quotes.js");
}
#[cfg(feature = "parallel")]
mod parallel_tests {
use super::*;
fn build_large_generator(lines: u32, cols_per_line: u32) -> SourceMapGenerator {
let mut builder = SourceMapGenerator::new(Some("bundle.js".to_string()));
for i in 0..10 {
let src = builder.add_source(&format!("src/file{i}.js"));
builder.set_source_content(
src,
format!("// source file {i}\n{}", "x = 1;\n".repeat(100)),
);
}
for i in 0..20 {
builder.add_name(&format!("var{i}"));
}
for line in 0..lines {
for col in 0..cols_per_line {
let src = (line * cols_per_line + col) % 10;
let name = if col % 3 == 0 { Some(col % 20) } else { None };
match name {
Some(n) => builder.add_named_mapping(line, col * 10, src, line, col * 5, n),
None => builder.add_mapping(line, col * 10, src, line, col * 5),
}
}
}
builder
}
#[test]
fn parallel_large_roundtrip() {
let builder = build_large_generator(500, 20);
let json = builder.to_json();
let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
assert_eq!(sm.mapping_count(), 10000);
assert_eq!(sm.line_count(), 500);
let loc = sm.original_position_for(250, 50).unwrap();
assert_eq!(loc.line, 250);
assert_eq!(loc.column, 25);
}
#[test]
fn parallel_matches_sequential() {
let builder = build_large_generator(500, 20);
let mut sorted: Vec<&Mapping> = builder.mappings.iter().collect();
sorted.sort_unstable_by(|a, b| {
a.generated_line
.cmp(&b.generated_line)
.then(a.generated_column.cmp(&b.generated_column))
});
let sequential = SourceMapGenerator::encode_sequential_impl(&sorted);
let parallel = SourceMapGenerator::encode_parallel_impl(&sorted);
assert_eq!(sequential, parallel);
}
#[test]
fn parallel_with_sparse_lines() {
let mut builder = SourceMapGenerator::new(None);
let src = builder.add_source("input.js");
for i in 0..50 {
let line = i * 100;
for col in 0..100u32 {
builder.add_mapping(line, col * 10, src, line, col * 5);
}
}
let json = builder.to_json();
let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
assert_eq!(sm.mapping_count(), 5000);
assert!(sm.original_position_for(50, 0).is_none());
let loc = sm.original_position_for(200, 50).unwrap();
assert_eq!(loc.line, 200);
assert_eq!(loc.column, 25);
}
}
#[test]
fn streaming_basic() {
let mut sg = StreamingGenerator::new(Some("out.js".to_string()));
let src = sg.add_source("input.js");
sg.add_mapping(0, 0, src, 0, 0);
sg.add_mapping(1, 0, src, 1, 0);
let json = sg.to_json();
let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
assert_eq!(sm.sources, vec!["input.js"]);
assert_eq!(sm.mapping_count(), 2);
let loc0 = sm.original_position_for(0, 0).unwrap();
assert_eq!(sm.source(loc0.source), "input.js");
assert_eq!(loc0.line, 0);
let loc1 = sm.original_position_for(1, 0).unwrap();
assert_eq!(loc1.line, 1);
}
#[test]
fn streaming_with_names() {
let mut sg = StreamingGenerator::new(None);
let src = sg.add_source("a.js");
let name = sg.add_name("foo");
sg.add_named_mapping(0, 0, src, 0, 0, name);
let sm = srcmap_sourcemap::SourceMap::from_json(&sg.to_json()).unwrap();
let loc = sm.original_position_for(0, 0).unwrap();
assert_eq!(loc.name, Some(0));
assert_eq!(sm.name(0), "foo");
}
#[test]
fn streaming_generated_only() {
let mut sg = StreamingGenerator::new(None);
let src = sg.add_source("a.js");
sg.add_generated_mapping(0, 0);
sg.add_mapping(0, 5, src, 0, 0);
let sm = srcmap_sourcemap::SourceMap::from_json(&sg.to_json()).unwrap();
assert_eq!(sm.mapping_count(), 2);
assert!(sm.original_position_for(0, 0).is_none());
assert!(sm.original_position_for(0, 5).is_some());
}
#[test]
fn streaming_matches_regular_generator() {
let mut regular = SourceMapGenerator::new(Some("out.js".to_string()));
let mut streaming = StreamingGenerator::new(Some("out.js".to_string()));
let src_r = regular.add_source("a.js");
let src_s = streaming.add_source("a.js");
let name_r = regular.add_name("hello");
let name_s = streaming.add_name("hello");
regular.set_source_content(src_r, "var hello;".to_string());
streaming.set_source_content(src_s, "var hello;".to_string());
regular.add_named_mapping(0, 0, src_r, 0, 0, name_r);
streaming.add_named_mapping(0, 0, src_s, 0, 0, name_s);
regular.add_mapping(0, 10, src_r, 0, 4);
streaming.add_mapping(0, 10, src_s, 0, 4);
regular.add_mapping(1, 0, src_r, 1, 0);
streaming.add_mapping(1, 0, src_s, 1, 0);
let sm_r = srcmap_sourcemap::SourceMap::from_json(®ular.to_json()).unwrap();
let sm_s = srcmap_sourcemap::SourceMap::from_json(&streaming.to_json()).unwrap();
assert_eq!(sm_r.mapping_count(), sm_s.mapping_count());
assert_eq!(sm_r.sources, sm_s.sources);
assert_eq!(sm_r.names, sm_s.names);
assert_eq!(sm_r.sources_content, sm_s.sources_content);
for (a, b) in sm_r.all_mappings().iter().zip(sm_s.all_mappings().iter()) {
assert_eq!(a.generated_line, b.generated_line);
assert_eq!(a.generated_column, b.generated_column);
assert_eq!(a.source, b.source);
assert_eq!(a.original_line, b.original_line);
assert_eq!(a.original_column, b.original_column);
assert_eq!(a.name, b.name);
}
}
#[test]
fn streaming_to_decoded_map() {
let mut sg = StreamingGenerator::new(None);
let src = sg.add_source("test.js");
sg.add_mapping(0, 0, src, 0, 0);
sg.add_mapping(2, 5, src, 1, 3);
let sm = sg.to_decoded_map().unwrap();
assert_eq!(sm.mapping_count(), 2);
assert_eq!(sm.sources, vec!["test.js"]);
let loc = sm.original_position_for(2, 5).unwrap();
assert_eq!(loc.line, 1);
assert_eq!(loc.column, 3);
}
#[test]
fn streaming_source_dedup() {
let mut sg = StreamingGenerator::new(None);
let src1 = sg.add_source("a.js");
let src2 = sg.add_source("a.js");
assert_eq!(src1, src2);
assert_eq!(sg.sources.len(), 1);
}
#[test]
fn streaming_ignore_list() {
let mut sg = StreamingGenerator::new(None);
let src = sg.add_source("vendor.js");
sg.add_to_ignore_list(src);
sg.add_mapping(0, 0, src, 0, 0);
let sm = srcmap_sourcemap::SourceMap::from_json(&sg.to_json()).unwrap();
assert_eq!(sm.ignore_list, vec![0]);
}
#[test]
fn streaming_empty() {
let sg = StreamingGenerator::new(None);
let json = sg.to_json();
let sm = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
assert_eq!(sm.mapping_count(), 0);
}
#[test]
fn streaming_sparse_lines() {
let mut sg = StreamingGenerator::new(None);
let src = sg.add_source("a.js");
sg.add_mapping(0, 0, src, 0, 0);
sg.add_mapping(5, 0, src, 5, 0);
let sm = srcmap_sourcemap::SourceMap::from_json(&sg.to_json()).unwrap();
assert_eq!(sm.mapping_count(), 2);
assert!(sm.original_position_for(0, 0).is_some());
assert!(sm.original_position_for(5, 0).is_some());
}
#[test]
fn range_mapping_basic() {
let mut builder = SourceMapGenerator::new(None);
let src = builder.add_source("input.js");
builder.add_range_mapping(0, 0, src, 0, 0);
builder.add_mapping(0, 5, src, 0, 10);
let json = builder.to_json();
assert!(json.contains(r#""rangeMappings":"A""#));
}
#[test]
fn range_mapping_multiple_on_line() {
let mut builder = SourceMapGenerator::new(None);
let src = builder.add_source("input.js");
builder.add_range_mapping(0, 0, src, 0, 0);
builder.add_mapping(0, 5, src, 0, 10);
builder.add_range_mapping(0, 10, src, 0, 20);
let json = builder.to_json();
assert!(json.contains(r#""rangeMappings":"A,C""#));
}
#[test]
fn range_mapping_multi_line() {
let mut builder = SourceMapGenerator::new(None);
let src = builder.add_source("input.js");
builder.add_range_mapping(0, 0, src, 0, 0);
builder.add_range_mapping(1, 0, src, 1, 0);
let json = builder.to_json();
assert!(json.contains(r#""rangeMappings":"A;A""#));
}
#[test]
fn no_range_mappings_omits_field() {
let mut builder = SourceMapGenerator::new(None);
let src = builder.add_source("input.js");
builder.add_mapping(0, 0, src, 0, 0);
let json = builder.to_json();
assert!(!json.contains("rangeMappings"));
}
#[test]
fn named_range_mapping() {
let mut builder = SourceMapGenerator::new(None);
let src = builder.add_source("input.js");
let name = builder.add_name("foo");
builder.add_named_range_mapping(0, 0, src, 0, 0, name);
let json = builder.to_json();
assert!(json.contains(r#""rangeMappings":"A""#));
}
#[test]
fn to_decoded_map_preserves_range_mappings() {
let mut builder = SourceMapGenerator::new(None);
let src = builder.add_source("input.js");
builder.add_range_mapping(0, 0, src, 0, 0);
builder.add_mapping(0, 5, src, 0, 10);
let sm = builder.to_decoded_map();
assert!(sm.has_range_mappings());
let mappings = sm.all_mappings();
assert!(mappings[0].is_range_mapping);
assert!(!mappings[1].is_range_mapping);
}
#[test]
fn streaming_range_mapping_basic() {
let mut sg = StreamingGenerator::new(None);
let src = sg.add_source("input.js");
sg.add_range_mapping(0, 0, src, 0, 0);
sg.add_mapping(0, 5, src, 0, 10);
let json = sg.to_json();
assert!(json.contains(r#""rangeMappings":"A""#));
}
#[test]
fn streaming_range_mapping_roundtrip() {
let mut sg = StreamingGenerator::new(None);
let src = sg.add_source("input.js");
sg.add_range_mapping(0, 0, src, 0, 0);
sg.add_mapping(0, 5, src, 0, 10);
let sm = sg.to_decoded_map().unwrap();
assert!(sm.has_range_mappings());
let mappings = sm.all_mappings();
assert!(mappings[0].is_range_mapping);
assert!(!mappings[1].is_range_mapping);
}
#[test]
fn streaming_range_and_named_range() {
let mut sg = StreamingGenerator::new(None);
let src = sg.add_source("input.js");
let name = sg.add_name("foo");
sg.add_range_mapping(0, 0, src, 0, 0);
sg.add_named_range_mapping(0, 10, src, 0, 5, name);
let json = sg.to_json();
assert!(json.contains(r#""rangeMappings":"A,B""#));
let sm = sg.to_decoded_map().unwrap();
assert!(sm.has_range_mappings());
let mappings = sm.all_mappings();
assert!(mappings[0].is_range_mapping);
assert!(mappings[1].is_range_mapping);
}
#[test]
fn streaming_range_mapping_matches_regular() {
let mut regular = SourceMapGenerator::new(None);
let mut streaming = StreamingGenerator::new(None);
let src_r = regular.add_source("input.js");
let src_s = streaming.add_source("input.js");
regular.add_range_mapping(0, 0, src_r, 0, 0);
streaming.add_range_mapping(0, 0, src_s, 0, 0);
regular.add_mapping(0, 5, src_r, 0, 10);
streaming.add_mapping(0, 5, src_s, 0, 10);
regular.add_range_mapping(0, 10, src_r, 0, 20);
streaming.add_range_mapping(0, 10, src_s, 0, 20);
regular.add_range_mapping(1, 0, src_r, 1, 0);
streaming.add_range_mapping(1, 0, src_s, 1, 0);
let json_r = regular.to_json();
let json_s = streaming.to_json();
let sm_r = srcmap_sourcemap::SourceMap::from_json(&json_r).unwrap();
let sm_s = srcmap_sourcemap::SourceMap::from_json(&json_s).unwrap();
assert_eq!(sm_r.mapping_count(), sm_s.mapping_count());
for (a, b) in sm_r.all_mappings().iter().zip(sm_s.all_mappings().iter()) {
assert_eq!(a.generated_line, b.generated_line);
assert_eq!(a.generated_column, b.generated_column);
assert_eq!(a.source, b.source);
assert_eq!(a.original_line, b.original_line);
assert_eq!(a.original_column, b.original_column);
assert_eq!(a.name, b.name);
assert_eq!(a.is_range_mapping, b.is_range_mapping);
}
}
#[test]
fn into_parts_basic() {
let mut builder = SourceMapGenerator::new(Some("output.js".to_string()));
let src = builder.add_source("input.js");
builder.set_source_content(src, "var x = 1;".to_string());
let name = builder.add_name("x");
builder.add_named_mapping(0, 0, src, 0, 4, name);
builder.add_mapping(1, 0, src, 1, 0);
builder.set_debug_id("test-id");
let json = builder.to_json();
let sm_json = srcmap_sourcemap::SourceMap::from_json(&json).unwrap();
let mut builder2 = SourceMapGenerator::new(Some("output.js".to_string()));
let src2 = builder2.add_source("input.js");
builder2.set_source_content(src2, "var x = 1;".to_string());
let name2 = builder2.add_name("x");
builder2.add_named_mapping(0, 0, src2, 0, 4, name2);
builder2.add_mapping(1, 0, src2, 1, 0);
builder2.set_debug_id("test-id");
let parts = builder2.into_parts();
assert_eq!(parts.file, Some("output.js".to_string()));
assert_eq!(parts.sources, vec!["input.js"]);
assert_eq!(parts.names, vec!["x"]);
assert_eq!(parts.sources_content, vec![Some("var x = 1;".to_string())]);
assert_eq!(parts.debug_id, Some("test-id".to_string()));
assert!(!parts.mappings.is_empty());
let sm_parts = srcmap_sourcemap::SourceMap::from_vlq(
&parts.mappings,
parts.sources,
parts.names,
parts.file,
parts.source_root,
parts.sources_content,
parts.ignore_list,
parts.debug_id,
)
.unwrap();
assert_eq!(sm_parts.mapping_count(), sm_json.mapping_count());
}
#[test]
fn into_parts_empty() {
let builder = SourceMapGenerator::new(None);
let parts = builder.into_parts();
assert_eq!(parts.file, None);
assert!(parts.mappings.is_empty());
assert!(parts.sources.is_empty());
assert!(parts.names.is_empty());
}
#[test]
fn into_parts_with_ignore_list() {
let mut builder = SourceMapGenerator::new(None);
let src = builder.add_source("vendor.js");
builder.add_to_ignore_list(src);
builder.add_mapping(0, 0, src, 0, 0);
let parts = builder.into_parts();
assert_eq!(parts.ignore_list, vec![0]);
}
#[test]
fn into_parts_with_range_mappings() {
let mut builder = SourceMapGenerator::new(None);
let src = builder.add_source("input.js");
builder.add_range_mapping(0, 0, src, 0, 0);
builder.add_mapping(0, 5, src, 0, 10);
let parts = builder.into_parts();
assert!(parts.range_mappings.is_some());
}
#[test]
fn streaming_into_parts() {
let mut sg = StreamingGenerator::new(Some("out.js".to_string()));
let src = sg.add_source("input.js");
sg.set_source_content(src, "var x = 1;".to_string());
let name = sg.add_name("x");
sg.add_named_mapping(0, 0, src, 0, 4, name);
sg.add_mapping(1, 0, src, 1, 0);
let parts = sg.into_parts();
assert_eq!(parts.file, Some("out.js".to_string()));
assert_eq!(parts.sources, vec!["input.js"]);
assert_eq!(parts.names, vec!["x"]);
assert!(!parts.mappings.is_empty());
}
#[test]
fn to_writer_matches_to_json() {
let mut builder = SourceMapGenerator::new(Some("output.js".to_string()));
let src = builder.add_source("input.js");
builder.set_source_content(src, "var x = 1;".to_string());
let name = builder.add_name("x");
builder.add_named_mapping(0, 0, src, 0, 4, name);
builder.add_mapping(1, 0, src, 1, 0);
let json = builder.to_json();
let mut buf = Vec::new();
builder.to_writer(&mut buf).unwrap();
let writer_output = String::from_utf8(buf).unwrap();
assert_eq!(json, writer_output);
}
#[test]
fn to_writer_empty() {
let builder = SourceMapGenerator::new(None);
let mut buf = Vec::new();
builder.to_writer(&mut buf).unwrap();
let output = String::from_utf8(buf).unwrap();
assert!(output.contains(r#""version":3"#));
assert!(output.contains(r#""mappings":"""#));
}
#[test]
fn to_writer_with_all_fields() {
let mut builder = SourceMapGenerator::new(Some("bundle.js".to_string()));
builder.set_source_root("src/");
builder.set_debug_id("test-uuid");
let src = builder.add_source("app.ts");
builder.set_source_content(src, "const x = 1;".to_string());
builder.add_to_ignore_list(src);
let name = builder.add_name("x");
builder.add_named_mapping(0, 0, src, 0, 6, name);
let json = builder.to_json();
let mut buf = Vec::new();
builder.to_writer(&mut buf).unwrap();
let writer_output = String::from_utf8(buf).unwrap();
assert_eq!(json, writer_output);
let sm = srcmap_sourcemap::SourceMap::from_json(&writer_output).unwrap();
assert_eq!(sm.source(0), "src/app.ts");
assert_eq!(sm.name(0), "x");
}
#[test]
fn streaming_to_writer_matches_to_json() {
let mut sg = StreamingGenerator::new(Some("out.js".to_string()));
let src = sg.add_source("input.js");
sg.set_source_content(src, "var x = 1;".to_string());
let name = sg.add_name("x");
sg.add_named_mapping(0, 0, src, 0, 4, name);
sg.add_mapping(1, 0, src, 1, 0);
let json = sg.to_json();
let mut buf = Vec::new();
sg.to_writer(&mut buf).unwrap();
let writer_output = String::from_utf8(buf).unwrap();
assert_eq!(json, writer_output);
}
}