sourcemap/
builder.rs

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