1#![allow(clippy::useless_conversion)]
2#![doc = include_str!("../README.md")]
3
4pub mod encodings;
5mod filesystem;
6mod lines_columns_indexes;
7mod source_id;
8mod span;
9mod to_string;
10
11use std::{
12 collections::{HashMap, HashSet},
13 convert::TryInto,
14};
15
16pub use filesystem::*;
17pub use lines_columns_indexes::LineStarts;
18pub use source_id::SourceId;
19pub use span::*;
20pub use to_string::*;
21
22const BASE64_ALPHABET: &[u8; 64] =
23 b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
24
25fn vlq_encode_integer_to_buffer(buf: &mut String, mut value: isize) {
27 if value.is_negative() {
28 value = (-value << 1) | 1;
29 } else {
30 value <<= 1;
31 };
32
33 loop {
34 let mut clamped = value & 31;
35 value >>= 5;
36 if value > 0 {
37 clamped |= 32;
38 }
39 buf.push(BASE64_ALPHABET[clamped as usize] as char);
40 if value <= 0 {
41 break;
42 }
43 }
44}
45
46#[derive(Debug)]
47struct SourceMapping {
48 pub(crate) on_output_column: u32,
49 pub(crate) source_byte_start: u32,
50 pub(crate) from_source: SourceId,
51 }
55
56#[derive(Debug)]
57enum MappingOrBreak {
58 Mapping(SourceMapping),
59 Break,
61}
62
63#[derive(Default)]
65pub struct SourceMapBuilder {
66 current_output_line: u32,
67 current_output_column: u32,
68 #[allow(dead_code)]
69 last_output_line: Option<u32>,
70 mappings: Vec<MappingOrBreak>,
72 used_sources: HashSet<SourceId>,
73}
74
75impl SourceMapBuilder {
76 pub fn new() -> SourceMapBuilder {
77 SourceMapBuilder::default()
78 }
79
80 pub fn add_new_line(&mut self) {
82 self.current_output_line += 1;
83 self.mappings.push(MappingOrBreak::Break);
84 }
85
86 pub fn add_to_column(&mut self, length: usize) {
88 self.current_output_column += length as u32;
89 }
90
91 pub fn add_mapping(&mut self, source_position: &SpanWithSource, current_column: u32) {
93 let SpanWithSource {
94 start: source_byte_start,
95 end: _source_byte_end,
97 source: from_source,
98 } = source_position;
99
100 self.used_sources.insert(*from_source);
101
102 self.mappings.push(MappingOrBreak::Mapping(SourceMapping {
103 from_source: *from_source,
104 source_byte_start: (*source_byte_start).try_into().unwrap(),
105 on_output_column: current_column,
106 }));
109 }
110
111 pub fn build(self, fs: &impl FileSystem) -> SourceMap {
117 let mut source_line_splits = HashMap::<SourceId, LineStarts>::new();
119 let mut sources = Vec::<SourceId>::new();
120
121 for source_id in self.used_sources.into_iter().filter(|id| !id.is_null()) {
122 source_line_splits.insert(
123 source_id,
124 fs.get_source_by_id(source_id, |source| source.line_starts.clone()),
125 );
126 sources.push(source_id);
127 }
128
129 let mut mappings = String::new();
130
131 let mut last_was_break = None::<bool>;
132 let mut last_mapped_source_line = 0;
133 let mut last_mapped_source_column = 0;
134 let mut last_mapped_output_column = 0;
135
136 for mapping in self.mappings {
137 match mapping {
138 MappingOrBreak::Mapping(mapping) => {
139 let SourceMapping {
140 on_output_column,
141 source_byte_start,
142 from_source,
146 } = mapping;
147
148 if from_source.is_null() {
149 continue;
150 }
151
152 if let Some(false) = last_was_break {
153 mappings.push(',');
154 }
155
156 let output_column =
157 on_output_column as isize - last_mapped_output_column as isize;
158
159 vlq_encode_integer_to_buffer(&mut mappings, output_column);
160 last_mapped_output_column = on_output_column;
161
162 let idx = sources.iter().position(|sid| *sid == from_source).unwrap();
165
166 vlq_encode_integer_to_buffer(&mut mappings, idx as isize);
168
169 let line_splits_for_this_file = source_line_splits.get(&from_source).unwrap();
170
171 let (source_line, source_column) = line_splits_for_this_file
172 .get_line_and_column_pos_is_on(source_byte_start as usize);
173
174 let source_line_diff = source_line as isize - last_mapped_source_line as isize;
175 vlq_encode_integer_to_buffer(&mut mappings, source_line_diff);
176
177 last_mapped_source_line = source_line;
178
179 let source_column_diff =
180 source_column as isize - last_mapped_source_column as isize;
181 vlq_encode_integer_to_buffer(&mut mappings, source_column_diff);
182
183 last_mapped_source_column = source_column;
184
185 last_was_break = Some(false);
188 }
189 MappingOrBreak::Break => {
190 mappings.push(';');
191 last_was_break = Some(true);
192 last_mapped_output_column = 0;
193 }
194 }
195 }
196
197 SourceMap { mappings, sources }
198 }
199}
200
201fn count_characters_on_last_line(s: &str) -> u32 {
202 let mut count = 0u32;
203 for b in s.as_bytes().iter().rev() {
204 if *b == b'\n' {
205 return count;
206 }
207 count += 1;
209 }
210 count
211}
212
213#[derive(Clone)]
214pub struct SourceMap {
215 pub mappings: String,
216 pub sources: Vec<SourceId>,
217}
218
219impl SourceMap {
220 pub fn to_json(self, filesystem: &impl FileSystem) -> String {
221 use std::fmt::Write;
222
223 let Self {
224 mappings,
225 sources: sources_used,
226 } = self;
227
228 let (mut sources, mut sources_content) = (String::new(), String::new());
229 for (idx, (path, content)) in sources_used
230 .into_iter()
231 .map(|source_id| filesystem.get_file_path_and_content(source_id))
232 .enumerate()
233 {
234 if idx != 0 {
235 sources.push(',');
236 sources_content.push(',');
237 }
238 write!(
239 sources,
240 "\"{}\"",
241 path.display().to_string().replace('\\', "/")
242 )
243 .unwrap();
244 write!(
245 sources_content,
246 "\"{}\"",
247 content
248 .replace('\n', "\\n")
249 .replace('\r', "\\r")
250 .replace('"', "\\\"")
251 )
252 .unwrap();
253 }
254
255 format!(
256 r#"{{"version":3,"sourceRoot":"","sources":[{sources}],"sourcesContent":[{sources_content}],"names":[],"mappings":"{mappings}"}}"#,
257 )
258 }
259}
260
261#[cfg(test)]
262mod source_map_tests {
263 use super::vlq_encode_integer_to_buffer;
264
265 fn vlq_encode_integer(value: isize) -> String {
266 let mut buf = String::new();
267 vlq_encode_integer_to_buffer(&mut buf, value);
268 buf
269 }
270
271 #[test]
272 fn vlq_encoder() {
273 assert_eq!(vlq_encode_integer(0), "A");
274 assert_eq!(vlq_encode_integer(1), "C");
275 assert_eq!(vlq_encode_integer(-1), "D");
276 assert_eq!(vlq_encode_integer(123), "2H");
277 assert_eq!(vlq_encode_integer(123456789), "qxmvrH");
278 }
279}