sourcemap/
sourceview.rs

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
16/// An iterator that iterates over tokens in reverse.
17pub 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        // if we are going to the same line as we did last iteration, we don't have to scan
35        // up to it again.  For normal sourcemaps this should mean we only ever go to the
36        // line once.
37        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                    // if we can't find the line, return am empty one
49                    ("", !0, !0)
50                }
51            }
52        };
53
54        // find the byte offset where our token starts
55        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        // remember where we were
86        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        // in case we run out of bounds here we reset the cache
94        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
126/// Provides efficient access to minified sources.
127///
128/// This type is used to implement fairly efficient source mapping
129/// operations.
130pub 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    /// Creates an optimized view of a given source.
162    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    /// Creates an optimized view from a given source string
171    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    /// Returns a requested minified line.
180    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        // fetched everything
190        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    /// Returns a line slice.
226    ///
227    /// Note that columns are indexed as JavaScript WTF-16 columns.
228    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    /// Returns an iterator over all lines.
261    pub fn lines(&self) -> Lines {
262        Lines { sv: self, idx: 0 }
263    }
264
265    /// Returns the source.
266    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    /// Given a token and minified function name this attemps to resolve the
279    /// name to an original function name.
280    ///
281    /// This invokes some guesswork and requires access to the original minified
282    /// source.  This will not yield proper results for anonymous functions or
283    /// functions that do not have clear function names.  (For instance it's
284    /// recommended that dotted function names are not passed to this
285    /// function).
286    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    /// Returns the number of lines.
312    pub fn line_count(&self) -> usize {
313        self.get_line(!0);
314        self.lines.lock().unwrap().len()
315    }
316
317    /// Returns the source map reference in the source view.
318    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}