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
20pub 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 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 pub fn set_debug_id(&mut self, debug_id: Option<DebugId>) {
76 self.debug_id = debug_id;
77 }
78
79 pub fn set_file<T: Into<BytesStr>>(&mut self, value: Option<T>) {
81 self.file = value.map(Into::into);
82 }
83
84 pub fn get_file(&self) -> Option<&str> {
86 self.file.as_deref()
87 }
88
89 pub fn set_source_root<T: Into<BytesStr>>(&mut self, value: Option<T>) {
91 self.source_root = value.map(Into::into);
92 }
93
94 pub fn get_source_root(&self) -> Option<&str> {
96 self.source_root.as_deref()
97 }
98
99 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 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 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 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 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 pub fn has_source_contents(&self, src_id: u32) -> bool {
147 self.get_source_contents(src_id).is_some()
148 }
149
150 #[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 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 #[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 #[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 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 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 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}