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
17pub 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 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 pub fn set_debug_id(&mut self, debug_id: Option<DebugId>) {
73 self.debug_id = debug_id;
74 }
75
76 pub fn set_file<T: Into<Arc<str>>>(&mut self, value: Option<T>) {
78 self.file = value.map(Into::into);
79 }
80
81 pub fn get_file(&self) -> Option<&str> {
83 self.file.as_deref()
84 }
85
86 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 pub fn get_source_root(&self) -> Option<&str> {
93 self.source_root.as_deref()
94 }
95
96 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 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 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 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 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 pub fn has_source_contents(&self, src_id: u32) -> bool {
144 self.get_source_contents(src_id).is_some()
145 }
146
147 #[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 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 #[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 #[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 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 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 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}