use crate::resolve_url;
pub use sourcemap::SourceMap;
use std::borrow::Cow;
use std::collections::HashMap;
use std::rc::Rc;
use std::str;
pub trait SourceMapGetter {
fn get_source_map(&self, file_name: &str) -> Option<Vec<u8>>;
fn get_source_line(
&self,
file_name: &str,
line_number: usize,
) -> Option<String>;
}
impl<T> SourceMapGetter for Rc<T>
where
T: SourceMapGetter,
{
fn get_source_map(&self, file_name: &str) -> Option<Vec<u8>> {
(**self).get_source_map(file_name)
}
fn get_source_line(
&self,
file_name: &str,
line_number: usize,
) -> Option<String> {
(**self).get_source_line(file_name, line_number)
}
}
#[derive(Debug, Default)]
pub struct SourceMapCache {
maps: HashMap<String, Option<SourceMap>>,
source_lines: HashMap<(String, i64), Option<String>>,
pub stashed_file_name: Option<String>,
}
pub enum SourceMapApplication {
Unchanged,
LineAndColumn {
line_number: u32,
column_number: u32,
},
LineAndColumnAndFileName {
file_name: String,
line_number: u32,
column_number: u32,
},
}
pub fn apply_source_map<G: SourceMapGetter + ?Sized>(
file_name: &str,
line_number: u32,
column_number: u32,
cache: &mut SourceMapCache,
getter: &G,
) -> SourceMapApplication {
let line_number = line_number - 1;
let column_number = column_number - 1;
let maybe_source_map_entry = cache.maps.get(file_name);
let maybe_source_map = maybe_source_map_entry
.map(Cow::Borrowed)
.unwrap_or_else(|| {
let maybe_source_map = getter
.get_source_map(file_name)
.and_then(|raw_source_map| SourceMap::from_slice(&raw_source_map).ok());
Cow::Owned(maybe_source_map)
});
let Some(source_map) = maybe_source_map.as_ref() else {
return SourceMapApplication::Unchanged;
};
let Some(token) = source_map.lookup_token(line_number, column_number) else {
return SourceMapApplication::Unchanged;
};
let new_line_number = token.get_src_line() + 1;
let new_column_number = token.get_src_col() + 1;
let new_file_name = match token.get_source() {
Some(source_file_name) => {
if source_file_name == file_name {
None
} else {
match resolve_url(source_file_name) {
Ok(m) if m.scheme() == "blob" => None,
Ok(m) => Some(m.to_string()),
Err(_) => None,
}
}
}
None => None,
};
match maybe_source_map {
Cow::Borrowed(_) => {}
Cow::Owned(maybe_source_map) => {
cache.maps.insert(file_name.to_owned(), maybe_source_map);
}
}
match new_file_name {
None => SourceMapApplication::LineAndColumn {
line_number: new_line_number,
column_number: new_column_number,
},
Some(file_name) => SourceMapApplication::LineAndColumnAndFileName {
file_name,
line_number: new_line_number,
column_number: new_column_number,
},
}
}
const MAX_SOURCE_LINE_LENGTH: usize = 150;
pub fn get_source_line<G: SourceMapGetter + ?Sized>(
file_name: &str,
line_number: i64,
cache: &mut SourceMapCache,
getter: &G,
) -> Option<String> {
cache
.source_lines
.entry((file_name.to_string(), line_number))
.or_insert_with(|| {
let s = getter.get_source_line(file_name, (line_number - 1) as usize);
s.filter(|s| s.len() <= MAX_SOURCE_LINE_LENGTH)
})
.clone()
}