use std::borrow::Cow;
use std::cmp::Ordering;
use std::fmt;
use std::io::{Read, Write};
use std::path::Path;
use crate::builder::SourceMapBuilder;
use crate::decoder::{decode, decode_slice};
use crate::encoder::encode;
use crate::errors::{Error, Result};
use crate::sourceview::SourceView;
use crate::utils::find_common_prefix;
pub struct RewriteOptions<'a> {
pub with_names: bool,
pub with_source_contents: bool,
pub load_local_source_contents: bool,
pub base_path: Option<&'a Path>,
pub strip_prefixes: &'a [&'a str],
}
impl<'a> Default for RewriteOptions<'a> {
fn default() -> RewriteOptions<'a> {
RewriteOptions {
with_names: true,
with_source_contents: true,
load_local_source_contents: false,
base_path: None,
strip_prefixes: &[][..],
}
}
}
pub enum DecodedMap {
Regular(SourceMap),
Index(SourceMapIndex),
}
impl DecodedMap {
pub fn from_reader<R: Read>(rdr: R) -> Result<DecodedMap> {
decode(rdr)
}
pub fn to_writer<W: Write>(&self, w: W) -> Result<()> {
match *self {
DecodedMap::Regular(ref sm) => encode(sm, w),
DecodedMap::Index(ref smi) => encode(smi, w),
}
}
pub fn lookup_token(&self, line: u32, col: u32) -> Option<Token<'_>> {
match *self {
DecodedMap::Regular(ref sm) => sm.lookup_token(line, col),
DecodedMap::Index(ref smi) => smi.lookup_token(line, col),
}
}
}
#[derive(PartialEq, Copy, Clone, Debug)]
pub struct RawToken {
pub dst_line: u32,
pub dst_col: u32,
pub src_line: u32,
pub src_col: u32,
pub src_id: u32,
pub name_id: u32,
}
#[derive(Copy, Clone)]
pub struct Token<'a> {
raw: &'a RawToken,
i: &'a SourceMap,
idx: u32,
}
impl<'a> PartialEq for Token<'a> {
fn eq(&self, other: &Token<'_>) -> bool {
self.raw == other.raw
}
}
impl<'a> Eq for Token<'a> {}
impl<'a> PartialOrd for Token<'a> {
fn partial_cmp(&self, other: &Token<'_>) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<'a> Ord for Token<'a> {
fn cmp(&self, other: &Token<'_>) -> Ordering {
macro_rules! try_cmp {
($a:expr, $b:expr) => {
match $a.cmp(&$b) {
Ordering::Equal => {}
x => {
return x;
}
}
};
}
try_cmp!(self.get_dst_line(), other.get_dst_line());
try_cmp!(self.get_dst_col(), other.get_dst_col());
try_cmp!(self.get_source(), other.get_source());
try_cmp!(self.get_src_line(), other.get_src_line());
try_cmp!(self.get_src_col(), other.get_src_col());
try_cmp!(self.get_name(), other.get_name());
Ordering::Equal
}
}
impl<'a> Token<'a> {
pub fn get_dst_line(&self) -> u32 {
self.raw.dst_line
}
pub fn get_dst_col(&self) -> u32 {
self.raw.dst_col
}
pub fn get_dst(&self) -> (u32, u32) {
(self.get_dst_line(), self.get_dst_col())
}
pub fn get_src_line(&self) -> u32 {
self.raw.src_line
}
pub fn get_src_col(&self) -> u32 {
self.raw.src_col
}
pub fn get_src(&self) -> (u32, u32) {
(self.get_src_line(), self.get_src_col())
}
pub fn get_src_id(&self) -> u32 {
self.raw.src_id
}
pub fn get_source(&self) -> Option<&'a str> {
if self.raw.src_id == !0 {
None
} else {
self.i.get_source(self.raw.src_id)
}
}
pub fn has_source(&self) -> bool {
self.raw.src_id != !0
}
pub fn get_name(&self) -> Option<&'a str> {
if self.raw.name_id == !0 {
None
} else {
self.i.get_name(self.raw.name_id)
}
}
pub fn has_name(&self) -> bool {
self.get_name().is_some()
}
pub fn get_name_id(&self) -> u32 {
self.raw.name_id
}
pub fn to_tuple(&self) -> (&'a str, u32, u32, Option<&'a str>) {
(
self.get_source().unwrap_or(""),
self.get_src_line(),
self.get_src_col(),
self.get_name(),
)
}
pub fn get_raw_token(&self) -> RawToken {
*self.raw
}
}
pub fn idx_from_token(token: &Token<'_>) -> u32 {
token.idx
}
pub fn sourcemap_from_token<'a>(token: &Token<'a>) -> &'a SourceMap {
token.i
}
pub struct TokenIter<'a> {
i: &'a SourceMap,
next_idx: u32,
}
impl<'a> TokenIter<'a> {
pub fn seek(&mut self, line: u32, col: u32) -> bool {
let token = self.i.lookup_token(line, col);
match token {
Some(token) => {
self.next_idx = token.idx + 1;
true
}
None => false,
}
}
}
impl<'a> Iterator for TokenIter<'a> {
type Item = Token<'a>;
fn next(&mut self) -> Option<Token<'a>> {
self.i.get_token(self.next_idx).map(|tok| {
self.next_idx += 1;
tok
})
}
}
pub struct SourceIter<'a> {
i: &'a SourceMap,
next_idx: u32,
}
impl<'a> Iterator for SourceIter<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<&'a str> {
self.i.get_source(self.next_idx).map(|source| {
self.next_idx += 1;
source
})
}
}
pub struct SourceContentsIter<'a> {
i: &'a SourceMap,
next_idx: u32,
}
impl<'a> Iterator for SourceContentsIter<'a> {
type Item = Option<&'a str>;
fn next(&mut self) -> Option<Option<&'a str>> {
if self.next_idx >= self.i.get_source_count() {
None
} else {
let rv = Some(self.i.get_source_contents(self.next_idx));
self.next_idx += 1;
rv
}
}
}
pub struct NameIter<'a> {
i: &'a SourceMap,
next_idx: u32,
}
impl<'a> Iterator for NameIter<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<&'a str> {
self.i.get_name(self.next_idx).map(|name| {
self.next_idx += 1;
name
})
}
}
pub struct IndexIter<'a> {
i: &'a SourceMap,
next_idx: usize,
}
impl<'a> Iterator for IndexIter<'a> {
type Item = (u32, u32, u32);
fn next(&mut self) -> Option<(u32, u32, u32)> {
self.i.index.get(self.next_idx).map(|idx| {
self.next_idx += 1;
*idx
})
}
}
impl<'a> fmt::Debug for Token<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "<Token {:#}>", self)
}
}
impl<'a> fmt::Display for Token<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}:{}:{}{}",
self.get_source().unwrap_or("<unknown>"),
self.get_src_line(),
self.get_src_col(),
self.get_name()
.map(|x| format!(" name={}", x))
.unwrap_or_default()
)?;
if f.alternate() {
write!(f, " ({}:{})", self.get_dst_line(), self.get_dst_col())?;
}
Ok(())
}
}
pub struct SourceMapSection {
offset: (u32, u32),
url: Option<String>,
map: Option<Box<DecodedMap>>,
}
pub struct SourceMapSectionIter<'a> {
i: &'a SourceMapIndex,
next_idx: u32,
}
impl<'a> Iterator for SourceMapSectionIter<'a> {
type Item = &'a SourceMapSection;
fn next(&mut self) -> Option<&'a SourceMapSection> {
self.i.get_section(self.next_idx).map(|sec| {
self.next_idx += 1;
sec
})
}
}
pub struct SourceMapIndex {
file: Option<String>,
sections: Vec<SourceMapSection>,
x_facebook_offsets: Option<Vec<Option<u32>>>,
x_metro_module_paths: Option<Vec<String>>,
}
#[derive(Clone, Debug)]
pub struct SourceMap {
file: Option<String>,
tokens: Vec<RawToken>,
index: Vec<(u32, u32, u32)>,
names: Vec<String>,
sources: Vec<String>,
sources_content: Vec<Option<SourceView<'static>>>,
}
impl SourceMap {
pub fn from_reader<R: Read>(rdr: R) -> Result<SourceMap> {
match decode(rdr)? {
DecodedMap::Regular(sm) => Ok(sm),
DecodedMap::Index(_) => Err(Error::IndexedSourcemap),
}
}
pub fn to_writer<W: Write>(&self, w: W) -> Result<()> {
encode(self, w)
}
pub fn from_slice(slice: &[u8]) -> Result<SourceMap> {
match decode_slice(slice)? {
DecodedMap::Regular(sm) => Ok(sm),
DecodedMap::Index(_) => Err(Error::IndexedSourcemap),
}
}
pub fn new(
file: Option<String>,
tokens: Vec<RawToken>,
names: Vec<String>,
sources: Vec<String>,
sources_content: Option<Vec<Option<String>>>,
) -> SourceMap {
let mut index: Vec<_> = tokens
.iter()
.enumerate()
.map(|(idx, token)| (token.dst_line, token.dst_col, idx as u32))
.collect();
index.sort();
SourceMap {
file,
tokens,
index,
names,
sources,
sources_content: sources_content
.unwrap_or_default()
.into_iter()
.map(|opt| opt.map(SourceView::from_string))
.collect(),
}
}
pub fn get_file(&self) -> Option<&str> {
self.file.as_ref().map(|x| &x[..])
}
pub fn set_file(&mut self, value: Option<&str>) {
self.file = value.map(str::to_owned);
}
pub fn get_token(&self, idx: u32) -> Option<Token<'_>> {
self.tokens
.get(idx as usize)
.map(|raw| Token { raw, i: self, idx })
}
pub fn get_token_count(&self) -> u32 {
self.tokens.len() as u32
}
pub fn tokens(&self) -> TokenIter<'_> {
TokenIter {
i: self,
next_idx: 0,
}
}
pub fn lookup_token(&self, line: u32, col: u32) -> Option<Token<'_>> {
let mut low = 0;
let mut high = self.index.len();
while low < high {
let mid = (low + high) / 2;
let ii = &self.index[mid as usize];
if (line, col) < (ii.0, ii.1) {
high = mid;
} else {
low = mid + 1;
}
}
if low > 0 && low <= self.index.len() {
self.get_token(self.index[low as usize - 1].2)
} else {
None
}
}
pub fn get_original_function_name<'a>(
&self,
line: u32,
col: u32,
minified_name: &str,
sv: &'a SourceView<'a>,
) -> Option<&str> {
self.lookup_token(line, col)
.and_then(|token| sv.get_original_function_name(token, minified_name))
}
pub fn get_source_count(&self) -> u32 {
self.sources.len() as u32
}
pub fn get_source(&self, idx: u32) -> Option<&str> {
self.sources.get(idx as usize).map(|x| &x[..])
}
pub fn set_source(&mut self, idx: u32, value: &str) {
self.sources[idx as usize] = value.to_string();
}
pub fn sources(&self) -> SourceIter<'_> {
SourceIter {
i: self,
next_idx: 0,
}
}
pub fn get_source_view(&self, idx: u32) -> Option<&SourceView<'_>> {
self.sources_content
.get(idx as usize)
.and_then(Option::as_ref)
}
pub fn get_source_contents(&self, idx: u32) -> Option<&str> {
self.sources_content
.get(idx as usize)
.and_then(Option::as_ref)
.map(SourceView::source)
}
pub fn set_source_contents(&mut self, idx: u32, value: Option<&str>) {
if self.sources_content.len() != self.sources.len() {
self.sources_content.resize(self.sources.len(), None);
}
self.sources_content[idx as usize] = value.map(|x| SourceView::from_string(x.to_string()));
}
pub fn source_contents(&self) -> SourceContentsIter<'_> {
SourceContentsIter {
i: self,
next_idx: 0,
}
}
pub fn names(&self) -> NameIter<'_> {
NameIter {
i: self,
next_idx: 0,
}
}
pub fn get_name_count(&self) -> u32 {
self.names.len() as u32
}
pub fn has_names(&self) -> bool {
!self.names.is_empty()
}
pub fn get_name(&self, idx: u32) -> Option<&str> {
self.names.get(idx as usize).map(|x| &x[..])
}
pub fn remove_names(&mut self) {
self.names.clear();
}
pub fn get_index_size(&self) -> usize {
self.index.len()
}
pub fn index_iter(&self) -> IndexIter<'_> {
IndexIter {
i: self,
next_idx: 0,
}
}
pub fn rewrite(self, options: &RewriteOptions<'_>) -> Result<SourceMap> {
let mut builder = SourceMapBuilder::new(self.get_file());
for token in self.tokens() {
let raw = builder.add_token(&token, options.with_names);
if raw.src_id != !0
&& options.with_source_contents
&& !builder.has_source_contents(raw.src_id)
{
builder
.set_source_contents(raw.src_id, self.get_source_contents(token.get_src_id()));
}
}
if options.load_local_source_contents {
builder.load_local_source_contents(options.base_path)?;
}
let mut prefixes = vec![];
let mut need_common_prefix = false;
for &prefix in options.strip_prefixes.iter() {
if prefix == "~" {
need_common_prefix = true;
} else {
prefixes.push(prefix.to_string());
}
}
if need_common_prefix {
if let Some(prefix) = find_common_prefix(self.sources.iter().map(String::as_str)) {
prefixes.push(prefix);
}
}
if !prefixes.is_empty() {
builder.strip_prefixes(&prefixes);
}
Ok(builder.into_sourcemap())
}
}
impl SourceMapIndex {
pub fn from_reader<R: Read>(rdr: R) -> Result<SourceMapIndex> {
match decode(rdr)? {
DecodedMap::Regular(_) => Err(Error::RegularSourcemap),
DecodedMap::Index(smi) => Ok(smi),
}
}
pub fn to_writer<W: Write>(&self, w: W) -> Result<()> {
encode(self, w)
}
pub fn from_slice(slice: &[u8]) -> Result<SourceMapIndex> {
match decode_slice(slice)? {
DecodedMap::Regular(_) => Err(Error::RegularSourcemap),
DecodedMap::Index(smi) => Ok(smi),
}
}
pub fn new(
file: Option<String>,
sections: Vec<SourceMapSection>,
x_facebook_offsets: Option<Vec<Option<u32>>>,
x_metro_module_paths: Option<Vec<String>>,
) -> SourceMapIndex {
SourceMapIndex {
file,
sections,
x_facebook_offsets,
x_metro_module_paths,
}
}
pub fn get_file(&self) -> Option<&str> {
self.file.as_ref().map(|x| &x[..])
}
pub fn set_file(&mut self, value: Option<&str>) {
self.file = value.map(str::to_owned);
}
pub fn get_section_count(&self) -> u32 {
self.sections.len() as u32
}
pub fn get_section(&self, idx: u32) -> Option<&SourceMapSection> {
self.sections.get(idx as usize)
}
pub fn get_section_mut(&mut self, idx: u32) -> Option<&mut SourceMapSection> {
self.sections.get_mut(idx as usize)
}
pub fn sections(&self) -> SourceMapSectionIter<'_> {
SourceMapSectionIter {
i: self,
next_idx: 0,
}
}
pub fn lookup_token(&self, line: u32, col: u32) -> Option<Token<'_>> {
for section in self.sections() {
let (off_line, off_col) = section.get_offset();
println!("off_line: {}, off_col: {}", off_line, off_col);
if off_line < line || off_col < col {
continue;
}
if let Some(map) = section.get_sourcemap() {
if let Some(tok) = map.lookup_token(line - off_line, col - off_col) {
return Some(tok);
}
}
}
None
}
pub fn flatten(&self) -> Result<SourceMap> {
let mut builder = SourceMapBuilder::new(self.get_file());
for section in self.sections() {
let (off_line, off_col) = section.get_offset();
let map = match section.get_sourcemap() {
Some(map) => match map {
DecodedMap::Regular(sm) => Cow::Borrowed(sm),
DecodedMap::Index(idx) => Cow::Owned(idx.flatten()?),
},
None => {
return Err(Error::CannotFlatten(format!(
"Section has an unresolved \
sourcemap: {}",
section.get_url().unwrap_or("<unknown url>")
)));
}
};
for token in map.tokens() {
let raw = builder.add(
token.get_dst_line() + off_line,
token.get_dst_col() + off_col,
token.get_src_line(),
token.get_src_col(),
token.get_source(),
token.get_name(),
);
if token.get_source().is_some() && !builder.has_source_contents(raw.src_id) {
builder.set_source_contents(
raw.src_id,
map.get_source_contents(token.get_src_id()),
);
}
}
}
Ok(builder.into_sourcemap())
}
pub fn flatten_and_rewrite(self, options: &RewriteOptions<'_>) -> Result<SourceMap> {
self.flatten()?.rewrite(options)
}
pub fn is_for_react_native(&self) -> bool {
self.x_facebook_offsets.is_some() && self.x_metro_module_paths.is_some()
}
pub fn x_facebook_offsets(&self) -> Option<&Vec<Option<u32>>> {
self.x_facebook_offsets.as_ref()
}
pub fn x_metro_module_paths(&self) -> Option<&Vec<String>> {
self.x_metro_module_paths.as_ref()
}
}
impl SourceMapSection {
pub fn new(
offset: (u32, u32),
url: Option<String>,
map: Option<DecodedMap>,
) -> SourceMapSection {
SourceMapSection {
offset,
url,
map: map.map(Box::new),
}
}
pub fn get_offset_line(&self) -> u32 {
self.offset.0
}
pub fn get_offset_col(&self) -> u32 {
self.offset.1
}
pub fn get_offset(&self) -> (u32, u32) {
self.offset
}
pub fn get_url(&self) -> Option<&str> {
self.url.as_ref().map(|x| &**x)
}
pub fn set_url(&mut self, value: Option<&str>) {
self.url = value.map(str::to_owned);
}
pub fn get_sourcemap(&self) -> Option<&DecodedMap> {
self.map.as_ref().map(Box::as_ref)
}
pub fn get_sourcemap_mut(&mut self) -> Option<&mut DecodedMap> {
self.map.as_mut().map(Box::as_mut)
}
pub fn set_sourcemap(&mut self, sm: Option<DecodedMap>) {
self.map = sm.map(Box::new);
}
}