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