Skip to main content

oxc_sourcemap/
sourcemap_builder.rs

1use std::sync::Arc;
2
3use rustc_hash::FxHashMap;
4
5use crate::{
6    SourceMap,
7    token::{Token, TokenChunk},
8};
9
10/// The `SourceMapBuilder` is a helper to generate sourcemap.
11#[derive(Debug, Default)]
12pub struct SourceMapBuilder {
13    pub(crate) file: Option<Arc<str>>,
14    pub(crate) names_map: FxHashMap<Arc<str>, u32>,
15    pub(crate) names: Vec<Arc<str>>,
16    pub(crate) sources: Vec<Arc<str>>,
17    pub(crate) sources_map: FxHashMap<Arc<str>, u32>,
18    pub(crate) source_contents: Vec<Option<Arc<str>>>,
19    pub(crate) tokens: Vec<Token>,
20    pub(crate) token_chunks: Option<Vec<TokenChunk>>,
21}
22
23impl SourceMapBuilder {
24    /// Add item to `SourceMap::name`.
25    pub fn add_name(&mut self, name: &str) -> u32 {
26        if let Some(&id) = self.names_map.get(name) {
27            return id;
28        }
29        let count = self.names.len() as u32;
30        let name = Arc::from(name);
31        self.names_map.insert(Arc::clone(&name), count);
32        self.names.push(name);
33        count
34    }
35
36    /// Add item to `SourceMap::sources` and `SourceMap::source_contents`.
37    /// If `source` maybe duplicate, please use it.
38    pub fn add_source_and_content(&mut self, source: &str, source_content: &str) -> u32 {
39        let count = self.sources.len() as u32;
40        let id = *self.sources_map.entry(source.into()).or_insert(count);
41        if id == count {
42            self.sources.push(source.into());
43            self.source_contents.push(Some(source_content.into()));
44        }
45        id
46    }
47
48    /// Add item to `SourceMap::sources` and `SourceMap::source_contents`.
49    /// If `source` hasn't duplicate,it will avoid extra hash calculation.
50    pub fn set_source_and_content(&mut self, source: &str, source_content: &str) -> u32 {
51        let count = self.sources.len() as u32;
52        self.sources.push(source.into());
53        self.source_contents.push(Some(source_content.into()));
54        count
55    }
56
57    /// Add item to `SourceMap::tokens`.
58    pub fn add_token(
59        &mut self,
60        dst_line: u32,
61        dst_col: u32,
62        src_line: u32,
63        src_col: u32,
64        src_id: Option<u32>,
65        name_id: Option<u32>,
66    ) {
67        self.tokens.push(Token::new(dst_line, dst_col, src_line, src_col, src_id, name_id));
68    }
69
70    pub fn set_file(&mut self, file: &str) {
71        self.file = Some(file.into());
72    }
73
74    /// Set the `SourceMap::token_chunks` to make the sourcemap to vlq mapping at parallel.
75    pub fn set_token_chunks(&mut self, token_chunks: Vec<TokenChunk>) {
76        self.token_chunks = Some(token_chunks);
77    }
78
79    pub fn into_sourcemap(mut self) -> SourceMap {
80        // Trade performance for memory.
81        // The tokens array take enormously large amount of data,
82        // which is not ideal for large applications.
83        self.names_map.shrink_to_fit();
84        self.names.shrink_to_fit();
85        self.sources.shrink_to_fit();
86        self.sources_map.shrink_to_fit();
87        // For checker.ts, capacity for `tokens` before and after are 262144 and 171174 respectively.
88        self.tokens.shrink_to_fit();
89        if let Some(c) = self.token_chunks.as_mut() {
90            c.shrink_to_fit()
91        }
92        SourceMap::new(
93            self.file,
94            self.names,
95            None,
96            self.sources,
97            self.source_contents,
98            self.tokens.into_boxed_slice(),
99            self.token_chunks,
100        )
101    }
102}
103
104#[test]
105fn test_sourcemap_builder() {
106    let mut builder = SourceMapBuilder::default();
107    builder.set_source_and_content("baz.js", "");
108    builder.add_name("x");
109    builder.set_file("file");
110
111    let sm = builder.into_sourcemap();
112    assert_eq!(sm.get_source(0).map(|s| s.as_ref()), Some("baz.js"));
113    assert_eq!(sm.get_name(0).map(|s| s.as_ref()), Some("x"));
114    assert_eq!(sm.get_file().map(|s| s.as_ref()), Some("file"));
115
116    let expected = r#"{"version":3,"file":"file","names":["x"],"sources":["baz.js"],"sourcesContent":[""],"mappings":""}"#;
117    assert_eq!(expected, sm.to_json_string());
118}