1use crate::{Mapping, SOURCE_MAP_VERSION, SourceMap, vlq::vlq_encode};
4
5#[derive(Debug, Default)]
25pub struct SourceMapBuilder {
26 sources: Vec<String>,
27 sources_content: Vec<Option<String>>,
28 names: Vec<String>,
29 mappings: Vec<Vec<Mapping>>,
30 file: Option<String>,
31 source_root: Option<String>,
32 last_generated_column: u32,
33 last_source_index: u32,
34 last_original_line: u32,
35 last_original_column: u32,
36 last_name_index: u32,
37}
38
39impl SourceMapBuilder {
40 pub fn new() -> Self {
42 Self::default()
43 }
44
45 pub fn file(mut self, file: impl Into<String>) -> Self {
47 self.file = Some(file.into());
48 self
49 }
50
51 pub fn source_root(mut self, root: impl Into<String>) -> Self {
53 self.source_root = Some(root.into());
54 self
55 }
56
57 pub fn add_source(&mut self, source: impl Into<String>) -> u32 {
59 let source_str = source.into();
60 if let Some(idx) = self.sources.iter().position(|s| s == &source_str) {
61 idx as u32
62 }
63 else {
64 self.sources.push(source_str);
65 self.sources_content.push(None);
66 (self.sources.len() - 1) as u32
67 }
68 }
69
70 pub fn set_source_content(&mut self, index: u32, content: impl Into<String>) {
72 let idx = index as usize;
73 if idx < self.sources_content.len() {
74 self.sources_content[idx] = Some(content.into());
75 }
76 }
77
78 pub fn add_name(&mut self, name: impl Into<String>) -> u32 {
80 let name_str = name.into();
81 if let Some(idx) = self.names.iter().position(|n| n == &name_str) {
82 idx as u32
83 }
84 else {
85 self.names.push(name_str);
86 (self.names.len() - 1) as u32
87 }
88 }
89
90 pub fn add_mapping(&mut self, generated_line: u32, generated_column: u32, source_index: Option<u32>, original_line: Option<u32>, original_column: Option<u32>, name_index: Option<u32>) {
94 while self.mappings.len() <= generated_line as usize {
95 self.mappings.push(Vec::new());
96 }
97
98 self.mappings[generated_line as usize].push(Mapping { generated_line, generated_column, source_index, original_line, original_column, name_index });
99 }
100
101 pub fn add_segment(&mut self, generated_line: u32, generated_column: u32, source: Option<&str>, original_line: Option<u32>, original_column: Option<u32>, name: Option<&str>) {
103 let source_index = source.map(|s| self.add_source(s));
104 let name_index = name.map(|n| self.add_name(n));
105
106 self.add_mapping(generated_line, generated_column, source_index, original_line, original_column, name_index);
107 }
108
109 pub fn build(self) -> SourceMap {
111 let mappings = self.encode_mappings();
112
113 SourceMap { version: SOURCE_MAP_VERSION, sources: self.sources, sources_content: self.sources_content, names: self.names, mappings, file: self.file, source_root: self.source_root, sections: Vec::new() }
114 }
115
116 fn encode_mappings(&self) -> String {
117 let mut result = String::new();
118
119 for (line_idx, line_mappings) in self.mappings.iter().enumerate() {
120 if line_idx > 0 {
121 result.push(';');
122 }
123
124 let mut last_col = 0u32;
125 let mut last_source = 0u32;
126 let mut last_orig_line = 0u32;
127 let mut last_orig_col = 0u32;
128 let mut last_name = 0u32;
129
130 for (seg_idx, mapping) in line_mappings.iter().enumerate() {
131 if seg_idx > 0 {
132 result.push(',');
133 }
134
135 result.push_str(&vlq_encode(mapping.generated_column as i32 - last_col as i32));
136 last_col = mapping.generated_column;
137
138 if let Some(si) = mapping.source_index {
139 result.push_str(&vlq_encode(si as i32 - last_source as i32));
140 last_source = si;
141
142 if let Some(ol) = mapping.original_line {
143 result.push_str(&vlq_encode(ol as i32 - last_orig_line as i32));
144 last_orig_line = ol;
145 }
146
147 if let Some(oc) = mapping.original_column {
148 result.push_str(&vlq_encode(oc as i32 - last_orig_col as i32));
149 last_orig_col = oc;
150 }
151
152 if let Some(ni) = mapping.name_index {
153 result.push_str(&vlq_encode(ni as i32 - last_name as i32));
154 last_name = ni;
155 }
156 }
157 }
158 }
159
160 result
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167
168 #[test]
169 fn test_builder_basic() {
170 let mut builder = SourceMapBuilder::new();
171 let source_idx = builder.add_source("test.ts");
172 builder.add_mapping(0, 0, Some(source_idx), Some(0), Some(0), None);
173
174 let sm = builder.build();
175 assert_eq!(sm.version, 3);
176 assert_eq!(sm.sources.len(), 1);
177 assert!(!sm.mappings.is_empty());
178 }
179
180 #[test]
181 fn test_builder_multiple_mappings() {
182 let mut builder = SourceMapBuilder::new();
183 let source_idx = builder.add_source("test.ts");
184
185 builder.add_mapping(0, 0, Some(source_idx), Some(0), Some(0), None);
186 builder.add_mapping(0, 10, Some(source_idx), Some(0), Some(10), None);
187 builder.add_mapping(1, 0, Some(source_idx), Some(1), Some(0), None);
188
189 let sm = builder.build();
190 let mappings = sm.parse_mappings().unwrap();
191 assert_eq!(mappings.len(), 3);
192 }
193
194 #[test]
195 fn test_builder_with_names() {
196 let mut builder = SourceMapBuilder::new();
197 let source_idx = builder.add_source("test.ts");
198 let name_idx = builder.add_name("foo");
199
200 builder.add_mapping(0, 0, Some(source_idx), Some(0), Some(0), Some(name_idx));
201
202 let sm = builder.build();
203 assert_eq!(sm.names.len(), 1);
204 assert_eq!(sm.names[0], "foo");
205 }
206
207 #[test]
208 fn test_builder_source_content() {
209 let mut builder = SourceMapBuilder::new();
210 let source_idx = builder.add_source("test.ts");
211 builder.set_source_content(source_idx, "const x = 1;");
212
213 let sm = builder.build();
214 assert_eq!(sm.sources_content.len(), 1);
215 assert_eq!(sm.sources_content[0], Some("const x = 1;".to_string()));
216 }
217}