Skip to main content

swc_sourcemap/
sourceview.rs

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
19/// An iterator that iterates over tokens in reverse.
20pub 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        // if we are going to the same line as we did last iteration, we don't have to
38        // scan up to it again.  For normal sourcemaps this should mean we only
39        // ever go to the line once.
40        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                    // if we can't find the line, return am empty one
52                    ("", !0, !0)
53                }
54            }
55        };
56
57        // find the byte offset where our token starts
58        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        // remember where we were
89        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        // in case we run out of bounds here we reset the cache
97        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
129/// Provides efficient access to minified sources.
130///
131/// This type is used to implement fairly efficient source mapping
132/// operations.
133pub 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    /// Creates an optimized view of a given source.
165    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    /// Creates an optimized view from a given source string
174    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    /// Returns a requested minified line.
183    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        // fetched everything
193        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    /// Returns a line slice.
229    ///
230    /// Note that columns are indexed as JavaScript WTF-16 columns.
231    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    /// Returns an iterator over all lines.
264    pub fn lines(&self) -> Lines<'_> {
265        Lines { sv: self, idx: 0 }
266    }
267
268    /// Returns the source.
269    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    /// Given a token and minified function name this attemps to resolve the
282    /// name to an original function name.
283    ///
284    /// This invokes some guesswork and requires access to the original minified
285    /// source.  This will not yield proper results for anonymous functions or
286    /// functions that do not have clear function names.  (For instance it's
287    /// recommended that dotted function names are not passed to this
288    /// function).
289    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    /// Returns the number of lines.
315    pub fn line_count(&self) -> usize {
316        self.get_line(!0);
317        self.lines.lock().unwrap().len()
318    }
319
320    /// Returns the source map reference in the source view.
321    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}