Skip to main content

swc_sourcemap/
builder.rs

1#![cfg_attr(not(any(unix, windows, target_os = "redox")), allow(unused_imports))]
2
3use std::{
4    collections::BTreeSet,
5    env, fs,
6    io::Read,
7    path::{Path, PathBuf},
8};
9
10use bytes_str::BytesStr;
11use debugid::DebugId;
12use rustc_hash::FxHashMap;
13use url::Url;
14
15use crate::{
16    errors::Result,
17    types::{RawToken, SourceMap, Token},
18};
19
20/// Helper for sourcemap generation
21///
22/// This helper exists because generating and modifying `SourceMap`
23/// objects is generally not very comfortable.  As a general aid this
24/// type can help.
25pub struct SourceMapBuilder {
26    file: Option<BytesStr>,
27    name_map: FxHashMap<BytesStr, u32>,
28    names: Vec<BytesStr>,
29    tokens: Vec<RawToken>,
30    source_map: FxHashMap<BytesStr, u32>,
31    source_root: Option<BytesStr>,
32    sources: Vec<BytesStr>,
33    source_contents: Vec<Option<BytesStr>>,
34    sources_mapping: Vec<u32>,
35    ignore_list: BTreeSet<u32>,
36    debug_id: Option<DebugId>,
37}
38
39#[cfg(any(unix, windows, target_os = "redox"))]
40fn resolve_local_reference(base: &Url, reference: &str) -> Option<PathBuf> {
41    let url = match base.join(reference) {
42        Ok(url) => {
43            if url.scheme() != "file" {
44                return None;
45            }
46            url
47        }
48        Err(_) => {
49            return None;
50        }
51    };
52
53    url.to_file_path().ok()
54}
55
56impl SourceMapBuilder {
57    /// Creates a new source map builder and sets the file.
58    pub fn new(file: Option<BytesStr>) -> SourceMapBuilder {
59        SourceMapBuilder {
60            file,
61            name_map: FxHashMap::default(),
62            names: vec![],
63            tokens: vec![],
64            source_map: FxHashMap::default(),
65            source_root: None,
66            sources: vec![],
67            source_contents: vec![],
68            sources_mapping: vec![],
69            ignore_list: BTreeSet::default(),
70            debug_id: None,
71        }
72    }
73
74    /// Sets the debug id for the sourcemap (optional)
75    pub fn set_debug_id(&mut self, debug_id: Option<DebugId>) {
76        self.debug_id = debug_id;
77    }
78
79    /// Sets the file for the sourcemap (optional)
80    pub fn set_file<T: Into<BytesStr>>(&mut self, value: Option<T>) {
81        self.file = value.map(Into::into);
82    }
83
84    /// Returns the currently set file.
85    pub fn get_file(&self) -> Option<&str> {
86        self.file.as_deref()
87    }
88
89    /// Sets a new value for the source_root.
90    pub fn set_source_root<T: Into<BytesStr>>(&mut self, value: Option<T>) {
91        self.source_root = value.map(Into::into);
92    }
93
94    /// Returns the embedded source_root in case there is one.
95    pub fn get_source_root(&self) -> Option<&str> {
96        self.source_root.as_deref()
97    }
98
99    /// Registers a new source with the builder and returns the source ID.
100    pub fn add_source(&mut self, src: BytesStr) -> u32 {
101        self.add_source_with_id(src, !0)
102    }
103
104    fn add_source_with_id(&mut self, src: BytesStr, old_id: u32) -> u32 {
105        let count = self.sources.len() as u32;
106        let id = *self.source_map.entry(src.clone()).or_insert(count);
107        if id == count {
108            self.sources.push(src);
109            self.sources_mapping.push(old_id);
110        }
111        id
112    }
113
114    /// Changes the source name for an already set source.
115    pub fn set_source(&mut self, src_id: u32, src: BytesStr) {
116        assert!(src_id != !0, "Cannot set sources for tombstone source id");
117        self.sources[src_id as usize] = src;
118    }
119
120    /// Looks up a source name for an ID.
121    pub fn get_source(&self, src_id: u32) -> Option<&str> {
122        self.sources.get(src_id as usize).map(|x| &x[..])
123    }
124
125    pub fn add_to_ignore_list(&mut self, src_id: u32) {
126        self.ignore_list.insert(src_id);
127    }
128
129    /// Sets the source contents for an already existing source.
130    pub fn set_source_contents(&mut self, src_id: u32, contents: Option<BytesStr>) {
131        assert!(src_id != !0, "Cannot set sources for tombstone source id");
132        if self.sources.len() > self.source_contents.len() {
133            self.source_contents.resize(self.sources.len(), None);
134        }
135        self.source_contents[src_id as usize] = contents;
136    }
137
138    /// Returns the current source contents for a source.
139    pub fn get_source_contents(&self, src_id: u32) -> Option<&str> {
140        self.source_contents
141            .get(src_id as usize)
142            .and_then(|x| x.as_ref().map(|x| &x[..]))
143    }
144
145    /// Checks if a given source ID has source contents available.
146    pub fn has_source_contents(&self, src_id: u32) -> bool {
147        self.get_source_contents(src_id).is_some()
148    }
149
150    /// Loads source contents from locally accessible files if referenced
151    /// accordingly.  Returns the number of loaded source contents
152    #[cfg(any(unix, windows, target_os = "redox"))]
153    pub fn load_local_source_contents(&mut self, base_path: Option<&Path>) -> Result<usize> {
154        let mut abs_path = env::current_dir()?;
155        if let Some(path) = base_path {
156            abs_path.push(path);
157        }
158        let base_url = Url::from_directory_path(&abs_path).unwrap();
159
160        let mut to_read = vec![];
161        for (source, &src_id) in self.source_map.iter() {
162            if self.has_source_contents(src_id) {
163                continue;
164            }
165            if let Some(path) = resolve_local_reference(&base_url, source) {
166                to_read.push((src_id, path));
167            }
168        }
169
170        let rv = to_read.len();
171        for (src_id, path) in to_read {
172            if let Ok(mut f) = fs::File::open(path) {
173                let mut contents = String::new();
174                if f.read_to_string(&mut contents).is_ok() {
175                    self.set_source_contents(src_id, Some(contents.into()));
176                }
177            }
178        }
179
180        Ok(rv)
181    }
182
183    /// Registers a name with the builder and returns the name ID.
184    pub fn add_name(&mut self, name: BytesStr) -> u32 {
185        let count = self.names.len() as u32;
186        let id = *self.name_map.entry(name.clone()).or_insert(count);
187        if id == count {
188            self.names.push(name);
189        }
190        id
191    }
192
193    /// Adds a new mapping to the builder.
194    #[allow(clippy::too_many_arguments)]
195    pub fn add(
196        &mut self,
197        dst_line: u32,
198        dst_col: u32,
199        src_line: u32,
200        src_col: u32,
201        source: Option<BytesStr>,
202        name: Option<BytesStr>,
203        is_range: bool,
204    ) -> RawToken {
205        self.add_with_id(
206            dst_line, dst_col, src_line, src_col, source, !0, name, is_range,
207        )
208    }
209
210    #[allow(clippy::too_many_arguments)]
211    fn add_with_id(
212        &mut self,
213        dst_line: u32,
214        dst_col: u32,
215        src_line: u32,
216        src_col: u32,
217        source: Option<BytesStr>,
218        source_id: u32,
219        name: Option<BytesStr>,
220        is_range: bool,
221    ) -> RawToken {
222        let src_id = match source {
223            Some(source) => self.add_source_with_id(source, source_id),
224            None => !0,
225        };
226        let name_id = match name {
227            Some(name) => self.add_name(name),
228            None => !0,
229        };
230        let raw = RawToken {
231            dst_line,
232            dst_col,
233            src_line,
234            src_col,
235            src_id,
236            name_id,
237            is_range,
238        };
239        self.tokens.push(raw);
240        raw
241    }
242
243    /// Adds a new mapping to the builder.
244    #[allow(clippy::too_many_arguments)]
245    pub fn add_raw(
246        &mut self,
247        dst_line: u32,
248        dst_col: u32,
249        src_line: u32,
250        src_col: u32,
251        source: Option<u32>,
252        name: Option<u32>,
253        is_range: bool,
254    ) -> RawToken {
255        let src_id = source.unwrap_or(!0);
256        let name_id = name.unwrap_or(!0);
257        let raw = RawToken {
258            dst_line,
259            dst_col,
260            src_line,
261            src_col,
262            src_id,
263            name_id,
264            is_range,
265        };
266        self.tokens.push(raw);
267        raw
268    }
269
270    /// Shortcut for adding a new mapping based of an already existing token,
271    /// optionally removing the name.
272    pub fn add_token(&mut self, token: &Token<'_>, with_name: bool) -> RawToken {
273        let name = if with_name { token.get_name() } else { None };
274        self.add_with_id(
275            token.get_dst_line(),
276            token.get_dst_col(),
277            token.get_src_line(),
278            token.get_src_col(),
279            token.get_source().cloned(),
280            token.get_src_id(),
281            name.cloned(),
282            token.is_range(),
283        )
284    }
285
286    /// Strips common prefixes from the sources in the builder
287    pub fn strip_prefixes<S: AsRef<str>>(&mut self, prefixes: &[S]) {
288        for source in self.sources.iter_mut() {
289            for prefix in prefixes {
290                let mut prefix = prefix.as_ref().to_string();
291                if !prefix.ends_with('/') {
292                    prefix.push('/');
293                }
294                if source.starts_with(&prefix) {
295                    source.advance(prefix.len());
296                    break;
297                }
298            }
299        }
300    }
301
302    pub(crate) fn take_mapping(&mut self) -> Vec<u32> {
303        std::mem::take(&mut self.sources_mapping)
304    }
305
306    /// Converts the builder into a sourcemap.
307    pub fn into_sourcemap(self) -> SourceMap {
308        let contents = if !self.source_contents.is_empty() {
309            Some(self.source_contents)
310        } else {
311            None
312        };
313
314        let mut sm = SourceMap::new(self.file, self.tokens, self.names, self.sources, contents);
315        sm.set_source_root(self.source_root);
316        sm.set_debug_id(self.debug_id);
317        for ignored_src_id in self.ignore_list {
318            sm.add_to_ignore_list(ignored_src_id);
319        }
320
321        sm
322    }
323}