1#![warn(missing_docs)]
4
5use std::borrow::Cow;
6use std::fmt;
7use std::ops::Deref;
8
9#[cfg(test)]
10use similar_asserts::assert_eq;
11
12#[derive(Debug)]
14pub struct ParseSourceMapError(sourcemap::Error);
15
16impl fmt::Display for ParseSourceMapError {
17 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18 match self.0 {
19 sourcemap::Error::Io(..) => write!(f, "sourcemap parsing failed with io error"),
20 sourcemap::Error::Utf8(..) => write!(f, "sourcemap parsing failed due to bad utf-8"),
21 sourcemap::Error::BadJson(..) => write!(f, "invalid json data on sourcemap parsing"),
22 ref other => write!(f, "{}", other),
23 }
24 }
25}
26
27impl std::error::Error for ParseSourceMapError {
28 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
29 Some(match self.0 {
30 sourcemap::Error::Io(ref err) => err,
31 sourcemap::Error::Utf8(ref err) => err,
32 sourcemap::Error::BadJson(ref err) => err,
33 _ => return None,
34 })
35 }
36}
37
38impl From<sourcemap::Error> for ParseSourceMapError {
39 fn from(error: sourcemap::Error) -> ParseSourceMapError {
40 ParseSourceMapError(error)
41 }
42}
43
44pub struct SourceView<'a> {
46 sv: sourcemap::SourceView<'a>,
47}
48
49enum SourceMapType {
50 Regular(sourcemap::SourceMap),
51 Hermes(sourcemap::SourceMapHermes),
52}
53
54impl Deref for SourceMapType {
55 type Target = sourcemap::SourceMap;
56
57 fn deref(&self) -> &Self::Target {
58 match self {
59 SourceMapType::Regular(sm) => sm,
60 SourceMapType::Hermes(smh) => smh,
61 }
62 }
63}
64
65pub struct SourceMapView {
67 sm: SourceMapType,
68}
69
70#[derive(Debug, Default, PartialEq)]
72pub struct TokenMatch<'a> {
73 pub src_line: u32,
75 pub src_col: u32,
77 pub dst_line: u32,
79 pub dst_col: u32,
81 pub src_id: u32,
83 pub name: Option<&'a str>,
85 pub src: Option<&'a str>,
87 pub function_name: Option<String>,
89}
90
91impl<'a> SourceView<'a> {
92 pub fn new(source: &'a str) -> Self {
94 SourceView {
95 sv: sourcemap::SourceView::new(source),
96 }
97 }
98
99 pub fn from_string(source: String) -> Self {
101 SourceView {
102 sv: sourcemap::SourceView::from_string(source),
103 }
104 }
105
106 pub fn from_slice(source: &'a [u8]) -> Self {
108 match String::from_utf8_lossy(source) {
109 Cow::Owned(s) => SourceView::from_string(s),
110 Cow::Borrowed(s) => SourceView::new(s),
111 }
112 }
113
114 pub fn as_str(&self) -> &str {
116 self.sv.source()
117 }
118
119 pub fn get_line(&self, idx: u32) -> Option<&str> {
121 self.sv.get_line(idx)
122 }
123
124 pub fn line_count(&self) -> usize {
126 self.sv.line_count()
127 }
128}
129
130impl SourceMapView {
131 pub fn from_json_slice(buffer: &[u8]) -> Result<Self, ParseSourceMapError> {
136 Ok(SourceMapView {
137 sm: match sourcemap::decode_slice(buffer)? {
138 sourcemap::DecodedMap::Regular(sm) => SourceMapType::Regular(sm),
139 sourcemap::DecodedMap::Index(smi) => SourceMapType::Regular(smi.flatten()?),
140 sourcemap::DecodedMap::Hermes(smh) => SourceMapType::Hermes(smh),
141 },
142 })
143 }
144
145 pub fn lookup_token(&self, line: u32, col: u32) -> Option<TokenMatch<'_>> {
147 self.sm
148 .lookup_token(line, col)
149 .map(|tok| self.make_token_match(tok))
150 }
151
152 pub fn get_token(&self, idx: u32) -> Option<TokenMatch<'_>> {
154 self.sm.get_token(idx).map(|tok| self.make_token_match(tok))
155 }
156
157 pub fn get_token_count(&self) -> u32 {
159 self.sm.get_token_count()
160 }
161
162 pub fn get_source_view(&self, idx: u32) -> Option<&SourceView<'_>> {
164 self.sm
165 .get_source_view(idx)
166 .map(|s| unsafe { &*(s as *const _ as *const SourceView<'_>) })
167 }
168
169 pub fn get_source_name(&self, idx: u32) -> Option<&str> {
171 self.sm.get_source(idx)
172 }
173
174 pub fn get_source_count(&self) -> u32 {
176 self.sm.get_source_count()
177 }
178
179 pub fn lookup_token_with_function_name<'a, 'b>(
186 &'a self,
187 line: u32,
188 col: u32,
189 minified_name: &str,
190 source: &SourceView<'b>,
191 ) -> Option<TokenMatch<'a>> {
192 match &self.sm {
193 SourceMapType::Hermes(smh) if line == 0 => {
202 smh.lookup_token(line, col + 1).map(|token| {
206 let mut rv = self.make_token_match(token);
207 rv.function_name = smh.get_original_function_name(col + 1).map(str::to_owned);
208 rv
209 })
210 }
211 _ => self.sm.lookup_token(line, col).map(|token| {
212 let mut rv = self.make_token_match(token);
213 rv.function_name = source
214 .sv
215 .get_original_function_name(token, minified_name)
216 .map(str::to_owned);
217 rv
218 }),
219 }
220 }
221
222 fn make_token_match<'a>(&'a self, tok: sourcemap::Token<'a>) -> TokenMatch<'a> {
223 TokenMatch {
224 src_line: tok.get_src_line(),
225 src_col: tok.get_src_col(),
226 dst_line: tok.get_dst_line(),
227 dst_col: tok.get_dst_col(),
228 src_id: tok.get_src_id(),
229 name: tok.get_name(),
230 src: tok.get_source(),
231 function_name: None,
232 }
233 }
234}
235
236#[test]
237fn test_react_native_hermes() {
238 let bytes = include_bytes!("../tests/fixtures/react-native-hermes.map");
239 let smv = SourceMapView::from_json_slice(bytes).unwrap();
240 let sv = SourceView::new("");
241
242 assert_eq!(
244 smv.lookup_token_with_function_name(0, 11939, "", &sv),
245 Some(TokenMatch {
246 src_line: 1,
247 src_col: 10,
248 dst_line: 0,
249 dst_col: 11939,
250 src_id: 5,
251 name: None,
252 src: Some("module.js"),
253 function_name: Some("foo".into())
254 })
255 );
256
257 assert_eq!(
259 smv.lookup_token_with_function_name(0, 11857, "", &sv),
260 Some(TokenMatch {
261 src_line: 2,
262 src_col: 0,
263 dst_line: 0,
264 dst_col: 11857,
265 src_id: 4,
266 name: None,
267 src: Some("input.js"),
268 function_name: Some("<global>".into())
269 })
270 );
271}
272
273#[test]
274fn test_react_native_metro() {
275 let source = include_str!("../tests/fixtures/react-native-metro.js");
276 let bytes = include_bytes!("../tests/fixtures/react-native-metro.js.map");
277 let smv = SourceMapView::from_json_slice(bytes).unwrap();
278 let sv = SourceView::new(source);
279
280 assert_eq!(
282 smv.lookup_token_with_function_name(6, 100, "e.foo", &sv),
283 Some(TokenMatch {
284 src_line: 1,
285 src_col: 10,
286 dst_line: 6,
287 dst_col: 100,
288 src_id: 6,
289 name: None,
290 src: Some("module.js"),
291 function_name: None,
292 })
293 );
294
295 assert_eq!(
297 smv.lookup_token_with_function_name(5, 43, "", &sv),
298 Some(TokenMatch {
299 src_line: 2,
300 src_col: 0,
301 dst_line: 5,
302 dst_col: 39,
303 src_id: 5,
304 name: Some("foo"),
305 src: Some("input.js"),
306 function_name: None,
307 })
308 );
309
310 assert_eq!(smv.lookup_token_with_function_name(0, 11857, "", &sv), None);
313}