rusty_source_map/
generator.rs

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        // if (!this._skipValidation) {
42        // 	this._validateMapping(generated, original, source, name);
43        // }
44        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()) // case 1
86            || (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        // case 2, 3
93        {
94            // case 1 2 3
95            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                // lines are stored 0-based in SourceMap spec version 3
141                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}