1use std::fmt::Write;
9
10use memchr;
11use serde::Serialize;
12
13#[derive(Debug, Clone)]
15pub struct Mapping {
16 pub generated_line: u32,
18 pub generated_column: u32,
20 pub source_index: u32,
22 pub original_line: u32,
24 pub original_column: u32,
26 pub name_index: Option<u32>,
28}
29
30#[derive(Debug, Serialize)]
32pub struct SourceMap {
33 pub version: u32,
34 pub file: String,
35 #[serde(rename = "sourceRoot")]
36 pub source_root: String,
37 pub sources: Vec<String>,
38 #[serde(rename = "sourcesContent")]
39 #[serde(skip_serializing_if = "Option::is_none")]
40 pub sources_content: Option<Vec<String>>,
41 pub names: Vec<String>,
42 pub mappings: String,
43}
44
45pub struct SourceMapGenerator {
47 file: String,
48 source_root: String,
49 sources: Vec<String>,
50 sources_content: Vec<Option<String>>,
51 names: Vec<String>,
52 mappings: Vec<Mapping>,
53
54 prev_generated_column: i32,
56 prev_original_line: i32,
57 prev_original_column: i32,
58 prev_source_index: i32,
59 prev_name_index: i32,
60}
61
62impl SourceMapGenerator {
63 #[must_use]
64 pub const fn new(file: String) -> Self {
65 Self {
66 file,
67 source_root: String::new(),
68 sources: Vec::new(),
69 sources_content: Vec::new(),
70 names: Vec::new(),
71 mappings: Vec::new(),
72 prev_generated_column: 0,
73 prev_original_line: 0,
74 prev_original_column: 0,
75 prev_source_index: 0,
76 prev_name_index: 0,
77 }
78 }
79
80 pub fn set_source_root(&mut self, root: String) {
82 self.source_root = root;
83 }
84
85 #[must_use]
87 pub fn add_source(&mut self, source: String) -> u32 {
88 let index = u32::try_from(self.sources.len()).unwrap_or(u32::MAX);
89 self.sources.push(source);
90 self.sources_content.push(None);
91 index
92 }
93
94 #[must_use]
96 pub fn add_source_with_content(&mut self, source: String, content: String) -> u32 {
97 let index = u32::try_from(self.sources.len()).unwrap_or(u32::MAX);
98 self.sources.push(source);
99 self.sources_content.push(Some(content));
100 index
101 }
102
103 #[must_use]
105 pub fn add_name(&mut self, name: String) -> u32 {
106 for (i, n) in self.names.iter().enumerate() {
108 if n == &name {
109 return u32::try_from(i).unwrap_or(u32::MAX);
110 }
111 }
112 let index = u32::try_from(self.names.len()).unwrap_or(u32::MAX);
113 self.names.push(name);
114 index
115 }
116
117 pub fn add_mapping(
119 &mut self,
120 generated_line: u32,
121 generated_column: u32,
122 source_index: u32,
123 original_line: u32,
124 original_column: u32,
125 name_index: Option<u32>,
126 ) {
127 self.mappings.push(Mapping {
128 generated_line,
129 generated_column,
130 source_index,
131 original_line,
132 original_column,
133 name_index,
134 });
135 }
136
137 pub fn add_simple_mapping(
139 &mut self,
140 generated_line: u32,
141 generated_column: u32,
142 source_index: u32,
143 original_line: u32,
144 original_column: u32,
145 ) {
146 self.add_mapping(
147 generated_line,
148 generated_column,
149 source_index,
150 original_line,
151 original_column,
152 None,
153 );
154 }
155
156 pub fn shift_generated_lines(&mut self, from_line: u32, delta: u32) {
160 for mapping in &mut self.mappings {
161 if mapping.generated_line >= from_line {
162 mapping.generated_line += delta;
163 }
164 }
165 }
166
167 pub fn generate(&mut self) -> SourceMap {
169 self.mappings.sort_by(|a, b| {
171 if a.generated_line == b.generated_line {
172 a.generated_column.cmp(&b.generated_column)
173 } else {
174 a.generated_line.cmp(&b.generated_line)
175 }
176 });
177
178 let mappings_str = self.encode_mappings();
180
181 let sources_content = self.sources_content.iter().any(Option::is_some).then(|| {
183 self.sources_content
184 .iter()
185 .map(|c| c.as_deref().unwrap_or_default().to_string())
186 .collect()
187 });
188
189 SourceMap {
190 version: 3,
191 file: self.file.clone(),
192 source_root: self.source_root.clone(),
193 sources: self.sources.clone(),
194 sources_content,
195 names: self.names.clone(),
196 mappings: mappings_str,
197 }
198 }
199
200 #[must_use]
202 pub fn generate_json(&mut self) -> String {
203 let map = self.generate();
204 serde_json::to_string(&map).unwrap_or_default()
205 }
206
207 #[must_use]
209 pub fn to_json(&mut self) -> String {
210 self.generate_json()
211 }
212
213 pub fn generate_inline(&mut self) -> String {
215 let json = self.generate_json();
216 let base64 = base64_encode(json.as_bytes());
217 format!("//# sourceMappingURL=data:application/json;base64,{base64}")
218 }
219
220 #[must_use]
222 pub fn to_inline_comment(&mut self) -> String {
223 self.generate_inline()
224 }
225
226 pub fn add_named_mapping(
228 &mut self,
229 generated_line: u32,
230 generated_column: u32,
231 source_index: u32,
232 original_line: u32,
233 original_column: u32,
234 name_index: u32,
235 ) {
236 self.add_mapping(
237 generated_line,
238 generated_column,
239 source_index,
240 original_line,
241 original_column,
242 Some(name_index),
243 );
244 }
245
246 fn encode_mappings(&mut self) -> String {
247 let mut result = String::new();
248
249 self.prev_generated_column = 0;
251 self.prev_original_line = 0;
252 self.prev_original_column = 0;
253 self.prev_source_index = 0;
254 self.prev_name_index = 0;
255
256 let mut current_line: u32 = 0;
257 let mut first_in_line = true;
258
259 let mappings = self.mappings.clone();
261 for mapping in &mappings {
262 while current_line < mapping.generated_line {
264 result.push(';');
265 current_line += 1;
266 self.prev_generated_column = 0;
267 first_in_line = true;
268 }
269
270 if !first_in_line {
271 result.push(',');
272 }
273 first_in_line = false;
274
275 let segment = self.encode_segment(mapping);
277 result.push_str(&segment);
278 }
279
280 result
281 }
282
283 fn encode_segment(&mut self, mapping: &Mapping) -> String {
284 let mut segment = String::with_capacity(16);
286
287 let gen_col = i32::try_from(mapping.generated_column).unwrap_or(i32::MAX);
289 vlq::encode_to(gen_col - self.prev_generated_column, &mut segment);
290 self.prev_generated_column = gen_col;
291
292 let src_idx = i32::try_from(mapping.source_index).unwrap_or(i32::MAX);
294 vlq::encode_to(src_idx - self.prev_source_index, &mut segment);
295 self.prev_source_index = src_idx;
296
297 let orig_line = i32::try_from(mapping.original_line).unwrap_or(i32::MAX);
299 vlq::encode_to(orig_line - self.prev_original_line, &mut segment);
300 self.prev_original_line = orig_line;
301
302 let orig_col = i32::try_from(mapping.original_column).unwrap_or(i32::MAX);
304 vlq::encode_to(orig_col - self.prev_original_column, &mut segment);
305 self.prev_original_column = orig_col;
306
307 if let Some(name_idx) = mapping.name_index {
309 let name_idx = i32::try_from(name_idx).unwrap_or(i32::MAX);
310 vlq::encode_to(name_idx - self.prev_name_index, &mut segment);
311 self.prev_name_index = name_idx;
312 }
313
314 segment
315 }
316}
317
318pub mod vlq {
320 const VLQ_BASE_SHIFT: i32 = 5;
321 const VLQ_BASE: i32 = 1 << VLQ_BASE_SHIFT;
322 const VLQ_BASE_MASK: i32 = VLQ_BASE - 1;
323 const VLQ_CONTINUATION_BIT: i32 = VLQ_BASE;
324
325 const BASE64_CHARS: &[u8; 64] =
326 b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
327
328 #[must_use]
330 pub fn encode(value: i32) -> String {
331 let mut result = String::with_capacity(8);
332 encode_to(value, &mut result);
333 result
334 }
335
336 #[inline]
339 pub fn encode_to(value: i32, buf: &mut String) {
340 let mut vlq = if value < 0 {
342 ((-value) << 1) + 1
343 } else {
344 value << 1
345 };
346
347 loop {
348 let mut digit = vlq & VLQ_BASE_MASK;
349 vlq >>= VLQ_BASE_SHIFT;
350
351 if vlq > 0 {
352 digit |= VLQ_CONTINUATION_BIT;
353 }
354
355 let digit_idx = usize::try_from(digit).unwrap_or(0);
357 buf.push(BASE64_CHARS[digit_idx].into());
358
359 if vlq == 0 {
360 break;
361 }
362 }
363 }
364
365 #[must_use]
367 pub fn decode(s: &str) -> Option<(i32, usize)> {
368 let bytes = s.as_bytes();
369 let mut result: i32 = 0;
370 let mut shift = 0;
371 let mut consumed = 0;
372
373 for &byte in bytes {
374 let char_idx = BASE64_CHARS.iter().position(|&c| c == byte)?;
375 let digit = i32::try_from(char_idx).unwrap_or(i32::MAX);
376
377 result |= (digit & VLQ_BASE_MASK) << shift;
378 consumed += 1;
379
380 if (digit & VLQ_CONTINUATION_BIT) == 0 {
381 let is_negative = (result & 1) == 1;
383 result >>= 1;
384 if is_negative {
385 result = -result;
386 }
387 return Some((result, consumed));
388 }
389
390 shift += VLQ_BASE_SHIFT;
391 }
392
393 None
394 }
395}
396
397const BASE64_CHARS: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
398
399#[must_use]
404pub fn escape_json(s: &str) -> String {
405 let bytes = s.as_bytes();
406
407 if memchr::memchr3(b'"', b'\\', b'\n', bytes).is_none()
410 && memchr::memchr2(b'\r', b'\t', bytes).is_none()
411 {
412 return s.to_string();
413 }
414
415 let mut result = String::with_capacity(s.len() + 16);
417 let mut start = 0;
418
419 for (i, &byte) in bytes.iter().enumerate() {
420 let escape = match byte {
421 b'"' => Some("\\\""),
422 b'\\' => Some("\\\\"),
423 b'\n' => Some("\\n"),
424 b'\r' => Some("\\r"),
425 b'\t' => Some("\\t"),
426 0..=0x1f => {
428 if i > start {
430 result.push_str(&s[start..i]);
431 }
432 let _ = write!(result, "\\u{byte:04x}");
433 start = i + 1;
434 continue;
435 }
436 _ => None,
437 };
438
439 if let Some(escaped) = escape {
440 if i > start {
442 result.push_str(&s[start..i]);
443 }
444 result.push_str(escaped);
445 start = i + 1;
446 }
447 }
448
449 if start < s.len() {
451 result.push_str(&s[start..]);
452 }
453
454 result
455}
456
457#[must_use]
460pub fn escape_js_string(s: &str, quote: char) -> String {
461 let bytes = s.as_bytes();
462 let quote_byte = quote as u8;
463
464 let has_backslash = memchr::memchr(b'\\', bytes).is_some();
466 let has_quote = memchr::memchr(quote_byte, bytes).is_some();
467 let has_newline = memchr::memchr2(b'\n', b'\r', bytes).is_some();
468
469 if !has_backslash && !has_quote && !has_newline {
470 return s.to_string();
471 }
472
473 let mut result = String::with_capacity(s.len() + 16);
474 let mut start = 0;
475
476 for (i, &byte) in bytes.iter().enumerate() {
477 let escape = match byte {
478 b'\\' => Some("\\\\"),
479 b'\n' => Some("\\n"),
480 b'\r' => Some("\\r"),
481 b'\t' => Some("\\t"),
482 b'\0' => Some("\\0"),
483 b if b == quote_byte => {
484 if i > start {
485 result.push_str(&s[start..i]);
486 }
487 result.push('\\');
488 result.push(quote);
489 start = i + 1;
490 continue;
491 }
492 _ => None,
493 };
494
495 if let Some(escaped) = escape {
496 if i > start {
497 result.push_str(&s[start..i]);
498 }
499 result.push_str(escaped);
500 start = i + 1;
501 }
502 }
503
504 if start < s.len() {
505 result.push_str(&s[start..]);
506 }
507
508 result
509}
510
511#[must_use]
513pub fn base64_encode(input: &[u8]) -> String {
514 let bytes = input;
515 let mut result = String::with_capacity(bytes.len().div_ceil(3) * 4);
516
517 for chunk in bytes.chunks(3) {
518 let b0 = u32::from(chunk[0]);
519 let b1 = u32::from(chunk.get(1).copied().unwrap_or(0));
520 let b2 = u32::from(chunk.get(2).copied().unwrap_or(0));
521
522 let n = (b0 << 16) | (b1 << 8) | b2;
523
524 result.push(BASE64_CHARS[((n >> 18) & 63) as usize] as char);
525 result.push(BASE64_CHARS[((n >> 12) & 63) as usize] as char);
526
527 if chunk.len() > 1 {
528 result.push(BASE64_CHARS[((n >> 6) & 63) as usize] as char);
529 } else {
530 result.push('=');
531 }
532
533 if chunk.len() > 2 {
534 result.push(BASE64_CHARS[(n & 63) as usize] as char);
535 } else {
536 result.push('=');
537 }
538 }
539
540 result
541}
542
543#[cfg(test)]
544#[path = "../tests/source_map.rs"]
545mod tests;