#![warn(missing_docs)]
use std::borrow::Cow;
use std::fmt;
use std::ops::Deref;
use failure::Fail;
#[derive(Debug)]
pub struct ParseSourceMapError(sourcemap::Error);
impl fmt::Display for ParseSourceMapError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
sourcemap::Error::Io(..) => write!(f, "sourcemap parsing failed with io error"),
sourcemap::Error::Utf8(..) => write!(f, "sourcemap parsing failed due to bad utf-8"),
sourcemap::Error::BadJson(..) => write!(f, "invalid json data on sourcemap parsing"),
ref other => write!(f, "{}", other),
}
}
}
impl Fail for ParseSourceMapError {
fn cause(&self) -> Option<&dyn Fail> {
Some(match self.0 {
sourcemap::Error::Io(ref err) => err,
sourcemap::Error::Utf8(ref err) => err,
sourcemap::Error::BadJson(ref err) => err,
_ => return None,
})
}
}
impl From<sourcemap::Error> for ParseSourceMapError {
fn from(error: sourcemap::Error) -> ParseSourceMapError {
ParseSourceMapError(error)
}
}
pub struct SourceView<'a> {
sv: sourcemap::SourceView<'a>,
}
enum SourceMapType {
Regular(sourcemap::SourceMap),
Hermes(sourcemap::SourceMapHermes),
}
impl Deref for SourceMapType {
type Target = sourcemap::SourceMap;
fn deref(&self) -> &Self::Target {
match self {
SourceMapType::Regular(sm) => sm,
SourceMapType::Hermes(smh) => smh,
}
}
}
pub struct SourceMapView {
sm: SourceMapType,
}
#[derive(Debug, Default, PartialEq)]
pub struct TokenMatch<'a> {
pub src_line: u32,
pub src_col: u32,
pub dst_line: u32,
pub dst_col: u32,
pub src_id: u32,
pub name: Option<&'a str>,
pub src: Option<&'a str>,
pub function_name: Option<String>,
}
impl<'a> SourceView<'a> {
pub fn new(source: &'a str) -> Self {
SourceView {
sv: sourcemap::SourceView::new(source),
}
}
pub fn from_string(source: String) -> Self {
SourceView {
sv: sourcemap::SourceView::from_string(source),
}
}
pub fn from_slice(source: &'a [u8]) -> Self {
match String::from_utf8_lossy(source) {
Cow::Owned(s) => SourceView::from_string(s),
Cow::Borrowed(s) => SourceView::new(s),
}
}
pub fn as_str(&self) -> &str {
self.sv.source()
}
pub fn get_line(&self, idx: u32) -> Option<&str> {
self.sv.get_line(idx)
}
pub fn line_count(&self) -> usize {
self.sv.line_count()
}
}
impl SourceMapView {
pub fn from_json_slice(buffer: &[u8]) -> Result<Self, ParseSourceMapError> {
Ok(SourceMapView {
sm: match sourcemap::decode_slice(buffer)? {
sourcemap::DecodedMap::Regular(sm) => SourceMapType::Regular(sm),
sourcemap::DecodedMap::Index(smi) => SourceMapType::Regular(smi.flatten()?),
sourcemap::DecodedMap::Hermes(smh) => SourceMapType::Hermes(smh),
},
})
}
pub fn lookup_token(&self, line: u32, col: u32) -> Option<TokenMatch<'_>> {
self.sm
.lookup_token(line, col)
.map(|tok| self.make_token_match(tok))
}
pub fn get_token(&self, idx: u32) -> Option<TokenMatch<'_>> {
self.sm.get_token(idx).map(|tok| self.make_token_match(tok))
}
pub fn get_token_count(&self) -> u32 {
self.sm.get_token_count()
}
pub fn get_source_view(&self, idx: u32) -> Option<&SourceView<'_>> {
self.sm
.get_source_view(idx)
.map(|s| unsafe { &*(s as *const _ as *const SourceView<'_>) })
}
pub fn get_source_name(&self, idx: u32) -> Option<&str> {
self.sm.get_source(idx)
}
pub fn get_source_count(&self) -> u32 {
self.sm.get_source_count()
}
pub fn lookup_token_with_function_name<'a, 'b>(
&'a self,
line: u32,
col: u32,
minified_name: &str,
source: &SourceView<'b>,
) -> Option<TokenMatch<'a>> {
match &self.sm {
SourceMapType::Hermes(smh) if line == 0 => {
smh.lookup_token(line, col + 1).map(|token| {
let mut rv = self.make_token_match(token);
rv.function_name = smh.get_original_function_name(col + 1).map(str::to_owned);
rv
})
}
_ => self.sm.lookup_token(line, col).map(|token| {
let mut rv = self.make_token_match(token);
rv.function_name = source
.sv
.get_original_function_name(token, minified_name)
.map(str::to_owned);
rv
}),
}
}
fn make_token_match<'a>(&'a self, tok: sourcemap::Token<'a>) -> TokenMatch<'a> {
TokenMatch {
src_line: tok.get_src_line(),
src_col: tok.get_src_col(),
dst_line: tok.get_dst_line(),
dst_col: tok.get_dst_col(),
src_id: tok.get_src_id(),
name: tok.get_name(),
src: tok.get_source(),
function_name: None,
}
}
}
#[test]
fn test_react_native_hermes() {
let bytes = include_bytes!("../tests/fixtures/react-native-hermes.map");
let smv = SourceMapView::from_json_slice(bytes).unwrap();
let sv = SourceView::new("");
assert_eq!(
smv.lookup_token_with_function_name(0, 11939, "", &sv),
Some(TokenMatch {
src_line: 1,
src_col: 10,
dst_line: 0,
dst_col: 11939,
src_id: 5,
name: None,
src: Some("module.js"),
function_name: Some("foo".into())
})
);
assert_eq!(
smv.lookup_token_with_function_name(0, 11857, "", &sv),
Some(TokenMatch {
src_line: 2,
src_col: 0,
dst_line: 0,
dst_col: 11857,
src_id: 4,
name: None,
src: Some("input.js"),
function_name: Some("<global>".into())
})
);
}
#[test]
fn test_react_native_metro() {
let source = include_str!("../tests/fixtures/react-native-metro.js");
let bytes = include_bytes!("../tests/fixtures/react-native-metro.js.map");
let smv = SourceMapView::from_json_slice(bytes).unwrap();
let sv = SourceView::new(source);
assert_eq!(
smv.lookup_token_with_function_name(6, 100, "e.foo", &sv),
Some(TokenMatch {
src_line: 1,
src_col: 10,
dst_line: 6,
dst_col: 100,
src_id: 6,
name: None,
src: Some("module.js"),
function_name: None,
})
);
assert_eq!(
smv.lookup_token_with_function_name(5, 43, "", &sv),
Some(TokenMatch {
src_line: 2,
src_col: 0,
dst_line: 5,
dst_col: 39,
src_id: 5,
name: Some("foo"),
src: Some("input.js"),
function_name: None,
})
);
assert_eq!(smv.lookup_token_with_function_name(0, 11857, "", &sv), None);
}