enhanced_magic_string/collapse_sourcemap/
mod.rsuse std::{
cell::{RefCell, RefMut},
path::PathBuf,
};
use farmfe_utils::file_url_to_path;
use sourcemap::{SourceMap, SourceMapBuilder, Token};
pub struct CollapseSourcemapOptions {
pub inline_content: bool,
pub remap_source: Option<Box<dyn Fn(&str) -> String>>,
}
impl Default for CollapseSourcemapOptions {
fn default() -> Self {
Self {
inline_content: true,
remap_source: None,
}
}
}
pub fn collapse_sourcemap_chain(
mut chain: Vec<SourceMap>,
opts: CollapseSourcemapOptions,
) -> SourceMap {
chain.reverse();
chain = chain
.into_iter()
.filter(|map| map.get_token_count() > 0)
.collect();
if chain.is_empty() {
let builder = SourceMapBuilder::new(None);
return builder.into_sourcemap();
}
let dest_map = &chain[0];
let mut builder = SourceMapBuilder::new(None);
let mut mapped_src_cache = std::collections::HashMap::new();
for token in dest_map.tokens() {
let mut last_map_token = token;
let mut completed_trace = true;
if chain.len() > 1 {
for map in &chain[1..] {
if let Some(map_token) = lookup_token(
map,
last_map_token.get_src_line(),
last_map_token.get_src_col(),
) {
last_map_token = map_token;
} else {
completed_trace = false;
break;
}
}
}
if !completed_trace {
continue;
}
let source = last_map_token.get_source();
let mut srd_id = None;
if let Some(src) = source {
let remapped_src = if let Some(remap_source) = &opts.remap_source {
mapped_src_cache
.entry(src)
.or_insert_with(|| remap_source(src))
.to_string()
} else {
src.to_string()
};
srd_id = Some(builder.add_source(&remapped_src));
}
let mut name_id = None;
if let Some(name) = last_map_token.get_name().or(token.get_name()) {
name_id = Some(builder.add_name(name));
}
let added_token = builder.add_raw(
token.get_dst_line(),
token.get_dst_col(),
last_map_token.get_src_line(),
last_map_token.get_src_col(),
srd_id,
name_id,
false,
);
if opts.inline_content && srd_id.is_some() && !builder.has_source_contents(srd_id.unwrap()) {
let src_content = read_source_content(last_map_token, chain.last().unwrap());
if let Some(src_content) = src_content {
builder.set_source_contents(added_token.src_id, Some(&src_content));
}
}
}
builder.into_sourcemap()
}
pub fn lookup_token<'a>(map: &'a SourceMap, line: u32, col: u32) -> Option<Token<'a>> {
let token = map.lookup_token(line, col);
if let Some(token) = token {
if line > 0 && token.get_dst_line() == line - 1 && token.get_dst_col() > 0 {
let next_token = map.lookup_token(line + 1, 0);
if let Some(next_token) = next_token {
if next_token.get_dst_line() == line {
return Some(next_token);
}
}
}
}
token
}
pub fn read_source_content(token: Token<'_>, map: &SourceMap) -> Option<String> {
if let Some(view) = token.get_source_view() {
Some(view.source().to_string())
} else if let Some(src) = token.get_source() {
let src = &file_url_to_path(src);
let map_file = map.get_file();
if PathBuf::from(src).is_absolute() || map_file.is_none() {
std::fs::read_to_string(src).ok()
} else if let Some(map_file) = map_file {
let src_file = PathBuf::from(map_file).parent().unwrap().join(src);
let src_content = std::fs::read_to_string(src_file).ok();
src_content
} else {
None
}
} else {
None
}
}
pub struct CollapsedSourceMap<'a> {
pub tokens: RefCell<Vec<Token<'a>>>,
pub map: SourceMap,
}
impl<'a> CollapsedSourceMap<'a> {
pub fn new(map: SourceMap) -> Self {
Self {
tokens: RefCell::new(vec![]),
map,
}
}
pub fn tokens(&'a self) -> RefMut<Vec<Token<'a>>> {
let mut tokens = self.tokens.borrow_mut();
if tokens.is_empty() {
*tokens = self.map.tokens().collect::<Vec<_>>();
}
tokens
}
}