1#![allow(dead_code)]
2
3use crate::array_set::ArraySet;
4use crate::base64_vlq::base64vlq_encode;
5use crate::mapping::Mapping;
6use crate::mapping_list::MappingList;
7use crate::source_map::SourceMapJson;
8use crate::util;
9use serde_json;
10use std::collections::hash_map::HashMap;
11
12pub struct SourceMapGenerator {
13 pub(crate) file: Option<String>,
14 pub(crate) source_root: Option<String>,
15 pub(crate) skip_validation: bool,
16
17 pub(crate) sources: ArraySet,
18 pub(crate) names: ArraySet,
19 pub(crate) mappings: MappingList,
20 pub(crate) source_contents: HashMap<String, String>,
21}
22
23impl SourceMapGenerator {
24 pub fn new(file: Option<String>, source_root: Option<String>, skip_validation: bool) -> Self {
25 SourceMapGenerator {
26 file,
27 source_root,
28 skip_validation,
29 sources: ArraySet::new(),
30 names: ArraySet::new(),
31 mappings: MappingList::new(),
32 source_contents: HashMap::new(),
33 }
34 }
35
36 pub fn from_source_map() {
37 unimplemented!()
38 }
39
40 pub fn add_mapping(&mut self, mapping: Mapping) {
41 let source: Option<String> = mapping.source.clone();
45 let name: Option<String> = mapping.name.clone();
46
47 if let Some(ref s) = source {
48 if !self.sources.has(s.clone()) {
49 self.sources.add(s.clone(), false)
50 }
51 }
52
53 if let Some(ref n) = name {
54 if !self.names.has(n.clone()) {
55 self.names.add(n.clone(), false);
56 }
57 }
58
59 self.mappings.add(mapping);
60 }
61
62 pub fn set_source_content(&mut self, source_file: String, source_content: Option<String>) {
63 let mut source = source_file;
64 source = match self.source_root {
65 Some(ref s) => util::relative(s.clone(), source.clone()),
66 None => source,
67 };
68
69 if let Some(content) = source_content {
70 self.source_contents.insert(source, content);
71 } else {
72 self.source_contents.remove(&source);
73 }
74 }
75
76 pub fn apply_sourcemap() {
77 unimplemented!();
78 }
79
80 fn validate_mapping(mapping: &Mapping) -> Result<(), ()> {
81 if (mapping.generated.column >= 0
82 && mapping.generated.line > 0
83 && mapping.original.is_none()
84 && mapping.source.is_none()
85 && mapping.name.is_none()) || (mapping.original.is_some()
87 && mapping.generated.line > 0
88 && mapping.generated.column >= 0
89 && mapping.original.as_ref().unwrap().line > 0
90 && mapping.original.as_ref().unwrap().column >= 0
91 && mapping.source.is_some())
92 {
94 Ok(())
96 } else {
97 Err(())
98 }
99 }
100
101 fn serialize_mappings(&mut self) -> String {
102 let mut previous_generated_column = 0;
103 let mut previous_generated_line = 1;
104 let mut previous_original_column = 0;
105 let mut previous_original_line = 0;
106 let mut previous_name = 0;
107 let mut previous_source = 0;
108 let mut result = "".to_string();
109 let mut name_idx;
110 let mut source_idx;
111
112 let mappings = self.mappings.to_array();
113
114 let mappings_len = mappings.len();
115 for i in 0..mappings_len {
116 let mapping = mappings[i].clone();
117 let mut next = "".to_string();
118
119 if mapping.generated.line != previous_generated_line {
120 previous_generated_column = 0;
121 while mapping.generated.line != previous_generated_line {
122 next += ";";
123 previous_generated_line += 1;
124 }
125 } else if i > 0 {
126 if util::compare_by_generated_pos_inflated(&mapping, &mappings[i - 1]) == 0 {
127 continue;
128 }
129 next += ",";
130 }
131
132 next += &base64vlq_encode(mapping.generated.column - previous_generated_column);
133 previous_generated_column = mapping.generated.column;
134
135 if let Some(mapping_source) = mapping.source {
136 source_idx = self.sources.index_of(mapping_source.clone()).unwrap() as i32;
137 next += &base64vlq_encode(source_idx - previous_source);
138 previous_source = source_idx;
139
140 next += &base64vlq_encode(
142 mapping.original.as_ref().unwrap().line - 1 - previous_original_line,
143 );
144 previous_original_line = mapping.original.as_ref().unwrap().line - 1;
145
146 next += &base64vlq_encode(
147 mapping.original.as_ref().unwrap().column - previous_original_column,
148 );
149 previous_original_column = mapping.original.as_ref().unwrap().column;
150
151 if let Some(mapping_name) = mapping.name {
152 name_idx = self.names.index_of(mapping_name).unwrap() as i32;
153 next += &base64vlq_encode(name_idx - previous_name);
154 previous_name = name_idx;
155 }
156 }
157 result += &next;
158 }
159
160 result
161 }
162
163 fn generate_sources_contents(
164 &self,
165 sources: Vec<String>,
166 source_root: Option<String>,
167 ) -> Vec<Option<String>> {
168 sources
169 .into_iter()
170 .map(|mut source| -> Option<String> {
171 if let Some(ref root) = source_root {
172 source = util::relative(root.clone(), source);
173 }
174
175 if self.source_contents.contains_key(&source) {
176 Some(self.source_contents[&source].clone())
177 } else {
178 None
179 }
180 })
181 .collect()
182 }
183
184 pub(crate) fn as_json(&mut self) -> SourceMapJson {
185 let sources_vec = self.sources.to_vec();
186 let mut sources_content: Option<Vec<String>> = None;
187 if !self.source_contents.is_empty() {
188 sources_content = Some(
189 self.generate_sources_contents(sources_vec.clone(), self.source_root.clone())
190 .into_iter()
191 .flatten()
192 .collect(),
193 );
194 }
195 SourceMapJson {
196 version: 3,
197 sources: Some(sources_vec),
198 names: Some(self.names.to_vec()),
199 mappings: Some(self.serialize_mappings()),
200 file: self.file.clone(),
201 source_root: self.source_root.clone(),
202 sources_content,
203 sections: None,
204 }
205 }
206
207 pub fn as_string(&mut self) -> String {
208 serde_json::to_string(&self.as_json()).unwrap()
209 }
210}
211
212#[cfg(test)]
213mod test {
214 use super::*;
215 use crate::array_set::ArraySet;
216 use crate::mapping_list::MappingList;
217 use crate::source_map::Position;
218
219 #[test]
220 fn simple() {
221 let map = SourceMapGenerator {
222 file: Some("foo.js".to_string()),
223 source_root: Some(".".to_string()),
224 skip_validation: false,
225 sources: ArraySet::new(),
226 names: ArraySet::new(),
227 mappings: MappingList::new(),
228 source_contents: Default::default(),
229 }
230 .as_json();
231 assert!(map.file.is_some());
232 assert!(map.source_root.is_some());
233 }
234
235 #[test]
236 fn simple_json() {
237 let map = SourceMapGenerator {
238 file: Some("foo.js".to_string()),
239 source_root: Some(".".to_string()),
240 skip_validation: false,
241 sources: ArraySet::new(),
242 names: ArraySet::new(),
243 mappings: MappingList::new(),
244 source_contents: Default::default(),
245 }
246 .as_string();
247 assert_eq!(map, r#"{"version":3,"sources":[],"names":[],"mappings":"","file":"foo.js","sourceRoot":"."}"#.to_string());
248 }
249
250 #[test]
251 fn mappings() {
252 let mut map = SourceMapGenerator {
253 file: Some("min.js".to_string()),
254 source_root: Some("/the/root".to_string()),
255 skip_validation: false,
256 sources: ArraySet::new(),
257 names: ArraySet::new(),
258 mappings: MappingList::new(),
259 source_contents: Default::default(),
260 };
261
262 map.add_mapping(Mapping {
263 generated: Position { line: 1, column: 1 },
264 original: Some(Position { line: 1, column: 1 }),
265 source: Some("one.js".to_string()),
266 name: None,
267 last_generated_column: None,
268 });
269
270 map.add_mapping(Mapping {
271 generated: Position { line: 1, column: 5 },
272 original: Some(Position { line: 1, column: 5 }),
273 source: Some("one.js".to_string()),
274 name: None,
275 last_generated_column: None,
276 });
277
278 map.add_mapping(Mapping {
279 generated: Position { line: 1, column: 9 },
280 original: Some(Position {
281 line: 1,
282 column: 11,
283 }),
284 source: Some("one.js".to_string()),
285 name: None,
286 last_generated_column: None,
287 });
288
289 map.add_mapping(Mapping {
290 generated: Position {
291 line: 1,
292 column: 18,
293 },
294 original: Some(Position {
295 line: 1,
296 column: 21,
297 }),
298 source: Some("one.js".to_string()),
299 name: Some("bar".to_string()),
300 last_generated_column: None,
301 });
302
303 map.add_mapping(Mapping {
304 generated: Position {
305 line: 1,
306 column: 21,
307 },
308 original: Some(Position { line: 2, column: 3 }),
309 source: Some("one.js".to_string()),
310 name: None,
311 last_generated_column: None,
312 });
313
314 map.add_mapping(Mapping {
315 generated: Position {
316 line: 1,
317 column: 28,
318 },
319 original: Some(Position {
320 line: 2,
321 column: 10,
322 }),
323 source: Some("one.js".to_string()),
324 name: Some("baz".to_string()),
325 last_generated_column: None,
326 });
327
328 map.add_mapping(Mapping {
329 generated: Position {
330 line: 1,
331 column: 32,
332 },
333 original: Some(Position {
334 line: 2,
335 column: 14,
336 }),
337 source: Some("one.js".to_string()),
338 name: Some("bar".to_string()),
339 last_generated_column: None,
340 });
341
342 map.add_mapping(Mapping {
343 generated: Position { line: 2, column: 1 },
344 original: Some(Position { line: 1, column: 1 }),
345 source: Some("two.js".to_string()),
346 name: None,
347 last_generated_column: None,
348 });
349
350 map.add_mapping(Mapping {
351 generated: Position { line: 2, column: 5 },
352 original: Some(Position { line: 1, column: 5 }),
353 source: Some("two.js".to_string()),
354 name: None,
355 last_generated_column: None,
356 });
357
358 map.add_mapping(Mapping {
359 generated: Position { line: 2, column: 9 },
360 original: Some(Position {
361 line: 1,
362 column: 11,
363 }),
364 source: Some("two.js".to_string()),
365 name: None,
366 last_generated_column: None,
367 });
368
369 map.add_mapping(Mapping {
370 generated: Position {
371 line: 2,
372 column: 18,
373 },
374 original: Some(Position {
375 line: 1,
376 column: 21,
377 }),
378 source: Some("two.js".to_string()),
379 name: Some("n".to_string()),
380 last_generated_column: None,
381 });
382
383 map.add_mapping(Mapping {
384 generated: Position {
385 line: 2,
386 column: 21,
387 },
388 original: Some(Position { line: 2, column: 3 }),
389 source: Some("two.js".to_string()),
390 name: None,
391 last_generated_column: None,
392 });
393
394 map.add_mapping(Mapping {
395 generated: Position {
396 line: 2,
397 column: 28,
398 },
399 original: Some(Position {
400 line: 2,
401 column: 10,
402 }),
403 source: Some("two.js".to_string()),
404 name: Some("n".to_string()),
405 last_generated_column: None,
406 });
407
408 assert_eq!(map.as_string(), r#"{"version":3,"sources":["one.js","two.js"],"names":["bar","baz","n"],"mappings":"CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA","file":"min.js","sourceRoot":"/the/root"}"#.to_string())
409 }
410}