1use std::fmt;
2use std::slice;
3use std::str;
4use std::sync::atomic::AtomicUsize;
5use std::sync::atomic::Ordering;
6use std::sync::Arc;
7use std::sync::Mutex;
8
9use if_chain::if_chain;
10
11use crate::detector::{locate_sourcemap_reference_slice, SourceMapRef};
12use crate::errors::Result;
13use crate::js_identifiers::{get_javascript_token, is_valid_javascript_identifier};
14use crate::types::Token;
15
16pub struct RevTokenIter<'view, 'map> {
18 sv: &'view SourceView,
19 token: Option<Token<'map>>,
20 source_line: Option<(&'view str, usize, usize, usize)>,
21}
22
23impl<'view, 'map> Iterator for RevTokenIter<'view, 'map> {
24 type Item = (Token<'map>, Option<&'view str>);
25
26 fn next(&mut self) -> Option<(Token<'map>, Option<&'view str>)> {
27 let token = self.token.take()?;
28 let idx = token.idx;
29
30 if idx > 0 {
31 self.token = token.sm.get_token(idx - 1);
32 }
33
34 let (source_line, last_char_offset, last_byte_offset) = if_chain! {
38 if let Some((source_line, dst_line, last_char_offset,
39 last_byte_offset)) = self.source_line;
40
41 if dst_line == token.get_dst_line() as usize;
42 then {
43 (source_line, last_char_offset, last_byte_offset)
44 } else {
45 if let Some(source_line) = self.sv.get_line(token.get_dst_line()) {
46 (source_line, !0, !0)
47 } else {
48 ("", !0, !0)
50 }
51 }
52 };
53
54 let byte_offset = if last_byte_offset == !0 {
56 let mut off = 0;
57 let mut idx = 0;
58 for c in source_line.chars() {
59 if idx >= token.get_dst_col() as usize {
60 break;
61 }
62 off += c.len_utf8();
63 idx += c.len_utf16();
64 }
65 off
66 } else {
67 let chars_to_move = last_char_offset - token.get_dst_col() as usize;
68 let mut new_offset = last_byte_offset;
69 let mut idx = 0;
70 for c in source_line
71 .get(..last_byte_offset)
72 .unwrap_or("")
73 .chars()
74 .rev()
75 {
76 if idx >= chars_to_move {
77 break;
78 }
79 new_offset -= c.len_utf8();
80 idx += c.len_utf16();
81 }
82 new_offset
83 };
84
85 self.source_line = Some((
87 source_line,
88 token.get_dst_line() as usize,
89 token.get_dst_col() as usize,
90 byte_offset,
91 ));
92
93 if byte_offset >= source_line.len() {
95 self.source_line = None;
96 Some((token, None))
97 } else {
98 Some((
99 token,
100 source_line
101 .get(byte_offset..)
102 .and_then(get_javascript_token),
103 ))
104 }
105 }
106}
107
108pub struct Lines<'a> {
109 sv: &'a SourceView,
110 idx: u32,
111}
112
113impl<'a> Iterator for Lines<'a> {
114 type Item = &'a str;
115
116 fn next(&mut self) -> Option<&'a str> {
117 if let Some(line) = self.sv.get_line(self.idx) {
118 self.idx += 1;
119 Some(line)
120 } else {
121 None
122 }
123 }
124}
125
126pub struct SourceView {
131 source: Arc<str>,
132 processed_until: AtomicUsize,
133 lines: Mutex<Vec<&'static str>>,
134}
135
136impl Clone for SourceView {
137 fn clone(&self) -> SourceView {
138 SourceView {
139 source: self.source.clone(),
140 processed_until: AtomicUsize::new(0),
141 lines: Mutex::new(vec![]),
142 }
143 }
144}
145
146impl fmt::Debug for SourceView {
147 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
148 f.debug_struct("SourceView")
149 .field("source", &self.source())
150 .finish()
151 }
152}
153
154impl PartialEq for SourceView {
155 fn eq(&self, other: &Self) -> bool {
156 self.source == other.source
157 }
158}
159
160impl SourceView {
161 pub fn new(source: Arc<str>) -> SourceView {
163 SourceView {
164 source,
165 processed_until: AtomicUsize::new(0),
166 lines: Mutex::new(vec![]),
167 }
168 }
169
170 pub fn from_string(source: String) -> SourceView {
172 SourceView {
173 source: source.into(),
174 processed_until: AtomicUsize::new(0),
175 lines: Mutex::new(vec![]),
176 }
177 }
178
179 pub fn get_line(&self, idx: u32) -> Option<&str> {
181 let idx = idx as usize;
182 {
183 let lines = self.lines.lock().unwrap();
184 if idx < lines.len() {
185 return Some(lines[idx]);
186 }
187 }
188
189 if self.processed_until.load(Ordering::Relaxed) > self.source.len() {
191 return None;
192 }
193
194 let mut lines = self.lines.lock().unwrap();
195 let mut done = false;
196
197 while !done {
198 let rest = &self.source.as_bytes()[self.processed_until.load(Ordering::Relaxed)..];
199
200 let rv = if let Some(mut idx) = rest.iter().position(|&x| x == b'\n' || x == b'\r') {
201 let rv = &rest[..idx];
202 if rest[idx] == b'\r' && rest.get(idx + 1) == Some(&b'\n') {
203 idx += 1;
204 }
205 self.processed_until.fetch_add(idx + 1, Ordering::Relaxed);
206 rv
207 } else {
208 self.processed_until
209 .fetch_add(rest.len() + 1, Ordering::Relaxed);
210 done = true;
211 rest
212 };
213
214 lines.push(unsafe {
215 str::from_utf8_unchecked(slice::from_raw_parts(rv.as_ptr(), rv.len()))
216 });
217 if let Some(&line) = lines.get(idx) {
218 return Some(line);
219 }
220 }
221
222 None
223 }
224
225 pub fn get_line_slice(&self, line: u32, col: u32, span: u32) -> Option<&str> {
229 self.get_line(line).and_then(|line| {
230 let mut off = 0;
231 let mut idx = 0;
232 let mut char_iter = line.chars().peekable();
233
234 while let Some(&c) = char_iter.peek() {
235 if idx >= col as usize {
236 break;
237 }
238 char_iter.next();
239 off += c.len_utf8();
240 idx += c.len_utf16();
241 }
242
243 let mut off_end = off;
244 for c in char_iter {
245 if idx >= (col + span) as usize {
246 break;
247 }
248 off_end += c.len_utf8();
249 idx += c.len_utf16();
250 }
251
252 if idx < ((col + span) as usize) {
253 None
254 } else {
255 line.get(off..off_end)
256 }
257 })
258 }
259
260 pub fn lines(&self) -> Lines {
262 Lines { sv: self, idx: 0 }
263 }
264
265 pub fn source(&self) -> &str {
267 &self.source
268 }
269
270 fn rev_token_iter<'this, 'map>(&'this self, token: Token<'map>) -> RevTokenIter<'this, 'map> {
271 RevTokenIter {
272 sv: self,
273 token: Some(token),
274 source_line: None,
275 }
276 }
277
278 pub fn get_original_function_name<'map>(
287 &self,
288 token: Token<'map>,
289 minified_name: &str,
290 ) -> Option<&'map str> {
291 if !is_valid_javascript_identifier(minified_name) {
292 return None;
293 }
294
295 let mut iter = self.rev_token_iter(token).take(128).peekable();
296
297 while let Some((token, original_identifier)) = iter.next() {
298 if_chain! {
299 if original_identifier == Some(minified_name);
300 if let Some(item) = iter.peek();
301 if item.1 == Some("function");
302 then {
303 return token.get_name();
304 }
305 }
306 }
307
308 None
309 }
310
311 pub fn line_count(&self) -> usize {
313 self.get_line(!0);
314 self.lines.lock().unwrap().len()
315 }
316
317 pub fn sourcemap_reference(&self) -> Result<Option<SourceMapRef>> {
319 locate_sourcemap_reference_slice(self.source.as_bytes())
320 }
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326
327 #[test]
328 #[allow(clippy::cognitive_complexity)]
329 fn test_minified_source_view() {
330 let view = SourceView::new("a\nb\nc".into());
331 assert_eq!(view.get_line(0), Some("a"));
332 assert_eq!(view.get_line(0), Some("a"));
333 assert_eq!(view.get_line(2), Some("c"));
334 assert_eq!(view.get_line(1), Some("b"));
335 assert_eq!(view.get_line(3), None);
336
337 assert_eq!(view.line_count(), 3);
338
339 let view = SourceView::new("a\r\nb\r\nc".into());
340 assert_eq!(view.get_line(0), Some("a"));
341 assert_eq!(view.get_line(0), Some("a"));
342 assert_eq!(view.get_line(2), Some("c"));
343 assert_eq!(view.get_line(1), Some("b"));
344 assert_eq!(view.get_line(3), None);
345
346 assert_eq!(view.line_count(), 3);
347
348 let view = SourceView::new("abc👌def\nblah".into());
349 assert_eq!(view.get_line_slice(0, 0, 3), Some("abc"));
350 assert_eq!(view.get_line_slice(0, 3, 1), Some("👌"));
351 assert_eq!(view.get_line_slice(0, 3, 2), Some("👌"));
352 assert_eq!(view.get_line_slice(0, 3, 3), Some("👌d"));
353 assert_eq!(view.get_line_slice(0, 0, 4), Some("abc👌"));
354 assert_eq!(view.get_line_slice(0, 0, 5), Some("abc👌"));
355 assert_eq!(view.get_line_slice(0, 0, 6), Some("abc👌d"));
356 assert_eq!(view.get_line_slice(1, 0, 4), Some("blah"));
357 assert_eq!(view.get_line_slice(1, 0, 5), None);
358 assert_eq!(view.get_line_slice(1, 0, 12), None);
359
360 let view = SourceView::new("a\nb\nc\n".into());
361 assert_eq!(view.get_line(0), Some("a"));
362 assert_eq!(view.get_line(1), Some("b"));
363 assert_eq!(view.get_line(2), Some("c"));
364 assert_eq!(view.get_line(3), Some(""));
365 assert_eq!(view.get_line(4), None);
366
367 fn is_send<T: Send>() {}
368 fn is_sync<T: Sync>() {}
369 is_send::<SourceView>();
370 is_sync::<SourceView>();
371 }
372}