1use std::{
5 borrow::Cow,
6 collections::{BTreeSet, HashMap},
7};
8
9use bitvec::{order::Lsb0, vec::BitVec, view::BitView};
10use bytes_str::BytesStr;
11use serde::{Deserialize, Deserializer, Serialize};
12use serde_json::value::RawValue;
13
14use crate::{
15 decoder::{decode_rmi, strip_junk_header},
16 encoder::{encode_rmi, encode_vlq_diff},
17 types::adjust_mappings,
18 vlq::parse_vlq_segment_into,
19 Error, RawToken, Result,
20};
21
22#[derive(Serialize, Deserialize, Debug)]
23#[serde(rename_all = "camelCase")]
24pub struct RawSourceMap<'a> {
25 #[serde(default, skip_serializing_if = "Option::is_none")]
26 pub(crate) version: Option<u32>,
27 #[serde(default, borrow, skip_serializing_if = "Option::is_none")]
28 pub(crate) file: Option<MaybeRawValue<'a, Str>>,
29 #[serde(borrow)]
30 pub(crate) sources: MaybeRawValue<'a, Vec<StrValue<'a>>>,
31 #[serde(default, borrow, skip_serializing_if = "Option::is_none")]
32 pub(crate) source_root: Option<StrValue<'a>>,
33 #[serde(default, borrow, skip_serializing_if = "MaybeRawValue::is_empty")]
34 pub(crate) sources_content: MaybeRawValue<'a, Vec<Option<StrValue<'a>>>>,
35 #[serde(default, skip_serializing_if = "Option::is_none")]
36 pub(crate) sections: Option<Vec<RawSection<'a>>>,
37 #[serde(default, borrow)]
38 pub(crate) names: MaybeRawValue<'a, Vec<StrValue<'a>>>,
39 #[serde(default, borrow, skip_serializing_if = "Option::is_none")]
40 pub(crate) scopes: Option<StrValue<'a>>,
41 #[serde(default, skip_serializing_if = "Option::is_none")]
42 pub(crate) range_mappings: Option<String>,
43 #[serde(default, skip_serializing_if = "Option::is_none")]
44 pub(crate) mappings: Option<String>,
45 #[serde(default, borrow, skip_serializing_if = "Option::is_none")]
46 pub(crate) ignore_list: Option<MaybeRawValue<'a, BTreeSet<u32>>>,
47}
48
49#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Copy)]
50pub struct RawSectionOffset {
51 pub line: u32,
52 pub column: u32,
53}
54
55#[derive(Serialize, Deserialize, Debug)]
56pub struct RawSection<'a> {
57 pub offset: RawSectionOffset,
58 #[serde(borrow)]
59 pub url: Option<&'a RawValue>,
60 #[serde(borrow)]
61 pub map: Option<&'a RawValue>,
62}
63
64#[derive(Debug)]
65pub enum DecodedMap<'a> {
66 Regular(SourceMap<'a>),
67 Index(SourceMapIndex<'a>),
68}
69
70impl<'a> DecodedMap<'a> {
71 pub fn into_source_map(self) -> Result<SourceMap<'a>> {
72 match self {
73 DecodedMap::Regular(source_map) => Ok(source_map),
74 DecodedMap::Index(source_map_index) => source_map_index.flatten(),
75 }
76 }
77}
78
79#[derive(Debug, Clone, Copy, Serialize)]
80#[serde(untagged)]
81pub(crate) enum MaybeRawValue<'a, T> {
82 RawValue(#[serde(borrow)] &'a RawValue),
83 Data(T),
84}
85
86impl<T> MaybeRawValue<'_, Vec<T>> {
87 pub fn is_empty(&self) -> bool {
88 match self {
89 MaybeRawValue::Data(vec) => vec.is_empty(),
90 MaybeRawValue::RawValue(_) => false,
91 }
92 }
93}
94
95impl<'a, 'de, T> Deserialize<'de> for MaybeRawValue<'a, T>
96where
97 'de: 'a,
98 T: Deserialize<'de>,
99{
100 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
101 where
102 D: Deserializer<'de>,
103 {
104 let raw: &'de RawValue = Deserialize::deserialize(deserializer)?;
105 Ok(MaybeRawValue::RawValue(raw))
106 }
107}
108
109impl<'a, T> MaybeRawValue<'a, T>
110where
111 T: Deserialize<'a>,
112{
113 pub fn into_data(self) -> T {
114 match self {
115 MaybeRawValue::RawValue(s) => {
116 serde_json::from_str(s.get()).expect("Failed to convert RawValue to Data")
117 }
118 MaybeRawValue::Data(data) => data,
119 }
120 }
121
122 fn assert_raw_value(self) -> &'a RawValue {
123 match self {
124 MaybeRawValue::RawValue(s) => s,
125 MaybeRawValue::Data(_) => unreachable!("Expected RawValue, got Data"),
126 }
127 }
128}
129
130impl<T> Default for MaybeRawValue<'_, T>
131where
132 T: Default,
133{
134 fn default() -> Self {
135 MaybeRawValue::Data(T::default())
136 }
137}
138
139impl<'a, T> MaybeRawValue<'a, T>
140where
141 T: Deserialize<'a>,
142 T: Default,
143{
144 pub fn as_data(&mut self) -> &mut T {
145 match self {
146 MaybeRawValue::RawValue(s) => {
147 *self = MaybeRawValue::Data(
148 serde_json::from_str(s.get()).expect("Failed to convert RawValue to Data"),
149 );
150 if let MaybeRawValue::Data(data) = self {
151 data
152 } else {
153 unreachable!()
154 }
155 }
156 MaybeRawValue::Data(data) => data,
157 }
158 }
159}
160
161type Str = BytesStr;
162
163type StrValue<'a> = MaybeRawValue<'a, Str>;
164
165#[derive(Debug)]
166pub struct SourceMap<'a> {
167 pub(crate) file: Option<StrValue<'a>>,
168 pub(crate) tokens: Vec<RawToken>,
169 pub(crate) names: MaybeRawValue<'a, Vec<StrValue<'a>>>,
170 pub(crate) scopes: Option<StrValue<'a>>,
171 pub(crate) source_root: Option<StrValue<'a>>,
172 pub(crate) sources: MaybeRawValue<'a, Vec<StrValue<'a>>>,
173 pub(crate) sources_content: MaybeRawValue<'a, Vec<Option<StrValue<'a>>>>,
174 pub(crate) ignore_list: Option<MaybeRawValue<'a, BTreeSet<u32>>>,
175}
176
177#[derive(Debug)]
178pub(crate) struct SourceMapBuilder<'a> {
179 file: Option<StrValue<'a>>,
180 name_map: HashMap<&'a str, u32>,
181 names: Vec<StrValue<'a>>,
182 tokens: Vec<RawToken>,
183 source_map: HashMap<&'a str, u32>,
184 sources: Vec<StrValue<'a>>,
185 source_contents: Vec<Option<StrValue<'a>>>,
186 source_root: Option<StrValue<'a>>,
187 ignore_list: Option<BTreeSet<u32>>,
188}
189
190impl<'a> SourceMapBuilder<'a> {
191 pub fn new(file: Option<StrValue<'a>>) -> Self {
192 SourceMapBuilder {
193 file,
194 name_map: HashMap::new(),
195 names: Vec::new(),
196 tokens: Vec::new(),
197 source_map: HashMap::new(),
198 sources: Vec::new(),
199 source_contents: Vec::new(),
200 source_root: None,
201 ignore_list: None,
202 }
203 }
204
205 pub fn add_source(&mut self, src_raw: &'a RawValue) -> u32 {
206 let src_str = src_raw.get(); let count = self.sources.len() as u32;
208 let id = *self.source_map.entry(src_str).or_insert(count);
209 if id == count {
210 self.sources.push(MaybeRawValue::RawValue(src_raw));
212 self.source_contents.resize(self.sources.len(), None);
216 }
217 id
218 }
219
220 pub fn add_name(&mut self, name_raw: &'a RawValue) -> u32 {
221 let name_str = name_raw.get();
222 let count = self.names.len() as u32;
223 let id = *self.name_map.entry(name_str).or_insert(count);
224 if id == count {
225 self.names.push(MaybeRawValue::RawValue(name_raw));
227 }
228 id
229 }
230
231 pub fn set_source_contents(&mut self, src_id: u32, contents: Option<&'a RawValue>) {
232 if (src_id as usize) >= self.source_contents.len() {
234 self.source_contents.resize(src_id as usize + 1, None);
235 }
236 self.source_contents[src_id as usize] = contents.map(MaybeRawValue::RawValue);
237 }
238
239 pub fn add_to_ignore_list(&mut self, src_id: u32) {
240 self.ignore_list
241 .get_or_insert_with(BTreeSet::new)
242 .insert(src_id);
243 }
244
245 pub fn into_sourcemap(self) -> SourceMap<'a> {
246 SourceMap {
247 file: self.file,
248 tokens: self.tokens,
249 names: MaybeRawValue::Data(self.names),
250 scopes: None,
251 source_root: self.source_root,
252 sources: MaybeRawValue::Data(self.sources),
253 sources_content: MaybeRawValue::Data(self.source_contents),
254 ignore_list: self.ignore_list.map(MaybeRawValue::Data),
255 }
256 }
257
258 #[allow(clippy::too_many_arguments)]
260 pub fn add_raw(
261 &mut self,
262 dst_line: u32,
263 dst_col: u32,
264 src_line: u32,
265 src_col: u32,
266 source: Option<u32>,
267 name: Option<u32>,
268 is_range: bool,
269 ) -> RawToken {
270 let src_id = source.unwrap_or(!0);
271 let name_id = name.unwrap_or(!0);
272 let raw = RawToken {
273 dst_line,
274 dst_col,
275 src_line,
276 src_col,
277 src_id,
278 name_id,
279 is_range,
280 };
281 self.tokens.push(raw);
282 raw
283 }
284}
285
286#[derive(Debug)]
287pub(crate) struct SourceMapSection<'a> {
288 offset: (u32, u32),
289 url: Option<MaybeRawValue<'a, String>>,
290 map: Option<Box<MaybeRawValue<'a, RawSourceMap<'a>>>>,
291}
292
293impl<'a> SourceMapSection<'a> {
294 pub fn new(
300 offset: (u32, u32),
301 url: Option<MaybeRawValue<'a, String>>,
302 map: Option<MaybeRawValue<'a, RawSourceMap<'a>>>,
303 ) -> SourceMapSection<'a> {
304 SourceMapSection {
305 offset,
306 url,
307 map: map.map(Box::new),
308 }
309 }
310
311 pub fn get_offset(&self) -> (u32, u32) {
313 self.offset
314 }
315}
316
317#[derive(Debug)]
318pub struct SourceMapIndex<'a> {
319 file: Option<MaybeRawValue<'a, Str>>,
320 sections: Vec<SourceMapSection<'a>>,
321}
322
323pub fn decode(slice: &[u8]) -> Result<DecodedMap<'_>> {
324 let content = strip_junk_header(slice)?;
325 let rsm: RawSourceMap = serde_json::from_slice(content)?;
326
327 decode_common(rsm)
328}
329
330fn decode_common(rsm: RawSourceMap) -> Result<DecodedMap> {
331 if rsm.sections.is_some() {
332 decode_index(rsm).map(DecodedMap::Index)
333 } else {
334 decode_regular(rsm).map(DecodedMap::Regular)
335 }
336}
337
338fn decode_index(rsm: RawSourceMap) -> Result<SourceMapIndex> {
339 let mut sections = vec![];
340
341 for raw_section in rsm.sections.unwrap_or_default() {
342 sections.push(SourceMapSection::new(
343 (raw_section.offset.line, raw_section.offset.column),
344 raw_section.url.map(MaybeRawValue::RawValue),
345 raw_section.map.map(MaybeRawValue::RawValue),
346 ));
347 }
348
349 sections.sort_by_key(SourceMapSection::get_offset);
350
351 let file = rsm.file;
353
354 Ok(SourceMapIndex { file, sections })
355}
356
357pub fn decode_regular(rsm: RawSourceMap) -> Result<SourceMap> {
358 let mut dst_col;
359
360 let mut running_src_id = 0;
363 let mut running_src_line = 0;
364 let mut running_src_col = 0;
365 let mut running_name_id = 0;
366
367 let range_mappings = rsm.range_mappings.unwrap_or_default();
368 let mappings = rsm.mappings.unwrap_or_default();
369 let allocation_size = mappings.matches(&[',', ';'][..]).count() + 10;
370 let mut tokens = Vec::with_capacity(allocation_size);
371
372 let mut nums = Vec::with_capacity(6);
373 let mut rmi = BitVec::new();
374
375 for (dst_line, (line, rmi_str)) in mappings
376 .split(';')
377 .zip(range_mappings.split(';').chain(std::iter::repeat("")))
378 .enumerate()
379 {
380 if line.is_empty() {
381 continue;
382 }
383
384 dst_col = 0;
385
386 decode_rmi(rmi_str, &mut rmi)?;
387
388 for (line_index, segment) in line.split(',').enumerate() {
389 if segment.is_empty() {
390 continue;
391 }
392
393 nums.clear();
394 parse_vlq_segment_into(segment, &mut nums)?;
395 match nums.len() {
396 1 | 4 | 5 => {}
397 _ => return Err(Error::BadSegmentSize(nums.len() as u32)),
398 }
399
400 dst_col = (i64::from(dst_col) + nums[0]) as u32;
401
402 let mut current_src_id = !0;
406 let mut current_src_line = !0;
407 let mut current_src_col = !0;
408 let mut current_name_id = !0;
409
410 if nums.len() > 1 {
411 running_src_id = (i64::from(running_src_id) + nums[1]) as u32;
412
413 running_src_line = (i64::from(running_src_line) + nums[2]) as u32;
414 running_src_col = (i64::from(running_src_col) + nums[3]) as u32;
415
416 current_src_id = running_src_id;
417 current_src_line = running_src_line;
418 current_src_col = running_src_col;
419
420 if nums.len() > 4 {
421 running_name_id = (i64::from(running_name_id) + nums[4]) as u32;
422 current_name_id = running_name_id;
423 }
424 }
425
426 let is_range = rmi.get(line_index).map(|v| *v).unwrap_or_default();
427
428 tokens.push(RawToken {
429 dst_line: dst_line as u32,
430 dst_col,
431 src_line: current_src_line,
432 src_col: current_src_col,
433 src_id: current_src_id,
434 name_id: current_name_id,
435 is_range,
436 });
437 }
438 }
439
440 let sm = SourceMap {
441 file: rsm.file,
442 tokens,
443 names: rsm.names,
444 scopes: rsm.scopes,
445 source_root: rsm.source_root,
446 sources: rsm.sources,
447 sources_content: rsm.sources_content,
448 ignore_list: rsm.ignore_list,
449 };
450
451 Ok(sm)
452}
453
454impl<'a> SourceMap<'a> {
455 pub fn adjust_mappings(&mut self, adjustment: crate::SourceMap) {
457 self.tokens = adjust_mappings(
458 std::mem::take(&mut self.tokens),
459 Cow::Owned(adjustment.tokens),
460 );
461 self.scopes = None;
462 }
463
464 pub fn into_raw_sourcemap(self) -> RawSourceMap<'a> {
465 RawSourceMap {
466 version: Some(3),
467 range_mappings: serialize_range_mappings(&self),
468 mappings: Some(serialize_mappings(&self)),
469 file: self.file,
470 sources: self.sources,
471 source_root: self.source_root,
472 sources_content: self.sources_content,
473 sections: None,
474 names: self.names,
475 scopes: self.scopes,
476 ignore_list: self.ignore_list,
477 }
478 }
479
480 pub fn file(&mut self) -> Option<&BytesStr> {
481 self.file.as_mut().map(|f| &*f.as_data())
482 }
483
484 pub fn sources(&mut self) -> impl Iterator<Item = &'_ BytesStr> + use<'_, 'a> {
485 self.sources.as_data().iter_mut().map(|d| &*d.as_data())
486 }
487
488 pub fn get_source(&mut self, src_id: u32) -> Option<&BytesStr> {
489 self.sources
490 .as_data()
491 .get_mut(src_id as usize)
492 .map(|d| &*d.as_data())
493 }
494
495 pub fn get_name(&mut self, src_id: u32) -> Option<&BytesStr> {
496 self.names
497 .as_data()
498 .get_mut(src_id as usize)
499 .map(|d| &*d.as_data())
500 }
501
502 pub fn get_source_contents(&mut self, src_id: u32) -> Option<&BytesStr> {
503 self.sources_content
504 .as_data()
505 .get_mut(src_id as usize)
506 .and_then(|d| d.as_mut().map(|d| &*d.as_data()))
507 }
508}
509
510impl<'a> SourceMapIndex<'a> {
511 pub fn flatten(self) -> Result<SourceMap<'a>> {
512 let mut builder = SourceMapBuilder::new(self.file);
513
514 for section in self.sections {
515 let (off_line, off_col) = section.get_offset();
516
517 let map = match section.map {
518 Some(map) => match decode_common(map.into_data())? {
519 DecodedMap::Regular(sm) => sm,
520 DecodedMap::Index(idx) => idx.flatten()?,
521 },
522 None => {
523 return Err(Error::CannotFlatten(format!(
524 "Section has an unresolved sourcemap: {}",
525 section
526 .url
527 .map(|v| v.into_data())
528 .as_deref()
529 .unwrap_or("<unknown url>")
530 )));
531 }
532 };
533
534 let sources = map.sources.into_data();
535 let source_contents = map.sources_content.into_data();
536 let ignore_list = map.ignore_list.unwrap_or_default().into_data();
537
538 let mut src_id_map = Vec::<u32>::with_capacity(sources.len());
539
540 for (original_id, (source, contents)) in
541 sources.into_iter().zip(source_contents).enumerate()
542 {
543 debug_assert_eq!(original_id, src_id_map.len());
544 let src_id = builder.add_source(source.assert_raw_value());
545
546 src_id_map.push(src_id);
547
548 if let Some(contents) = contents {
549 builder.set_source_contents(src_id, Some(contents.assert_raw_value()));
550 }
551 }
552
553 let names = map.names.into_data();
554 let mut name_id_map = Vec::<u32>::with_capacity(names.len());
555
556 for (original_id, name) in names.into_iter().enumerate() {
557 debug_assert_eq!(original_id, name_id_map.len());
558 let name_id = builder.add_name(name.assert_raw_value());
559 name_id_map.push(name_id);
560 }
561
562 for token in map.tokens {
563 let dst_col = if token.dst_line == 0 {
564 token.dst_col + off_col
565 } else {
566 token.dst_col
567 };
568
569 let original_src_id = token.src_id;
572 let src_id = if original_src_id == !0 {
573 None
574 } else {
575 src_id_map.get(original_src_id as usize).copied()
576 };
577
578 let original_name_id = token.name_id;
579 let name_id = if original_name_id == !0 {
580 None
581 } else {
582 name_id_map.get(original_name_id as usize).copied()
583 };
584
585 let raw = builder.add_raw(
586 token.dst_line + off_line,
587 dst_col,
588 token.src_line,
589 token.src_col,
590 src_id,
591 name_id,
592 token.is_range,
593 );
594
595 if ignore_list.contains(&token.src_id) {
596 builder.add_to_ignore_list(raw.src_id);
597 }
598 }
599 }
600
601 Ok(builder.into_sourcemap())
602 }
603}
604
605fn serialize_range_mappings(sm: &SourceMap) -> Option<String> {
606 let mut buf = Vec::new();
607 let mut prev_line = 0;
608 let mut had_rmi = false;
609 let mut empty = true;
610
611 let mut idx_of_first_in_line = 0;
612
613 let mut rmi_data = Vec::<u8>::new();
614
615 for (idx, token) in sm.tokens.iter().enumerate() {
616 if token.is_range {
617 had_rmi = true;
618 empty = false;
619
620 let num = idx - idx_of_first_in_line;
621
622 rmi_data.resize(rmi_data.len() + 2, 0);
623
624 let rmi_bits = rmi_data.view_bits_mut::<Lsb0>();
625 rmi_bits.set(num, true);
626 }
627
628 while token.dst_line != prev_line {
629 if had_rmi {
630 encode_rmi(&mut buf, &rmi_data);
631 rmi_data.clear();
632 }
633
634 buf.push(b';');
635 prev_line += 1;
636 had_rmi = false;
637 idx_of_first_in_line = idx;
638 }
639 }
640 if empty {
641 return None;
642 }
643
644 if had_rmi {
645 encode_rmi(&mut buf, &rmi_data);
646 }
647
648 Some(String::from_utf8(buf).expect("invalid utf8"))
649}
650
651fn serialize_mappings(sm: &SourceMap) -> String {
652 let mut rv = String::new();
653 let mut prev_dst_line = 0;
655 let mut prev_dst_col = 0;
656 let mut prev_src_line = 0;
657 let mut prev_src_col = 0;
658 let mut prev_name_id = 0;
659 let mut prev_src_id = 0;
660
661 for (idx, token) in sm.tokens.iter().enumerate() {
662 if token.dst_line != prev_dst_line {
663 prev_dst_col = 0;
664 while token.dst_line != prev_dst_line {
665 rv.push(';');
666 prev_dst_line += 1;
667 }
668 } else if idx > 0 {
669 if Some(&token) == sm.tokens.get(idx - 1).as_ref() {
670 continue;
671 }
672 rv.push(',');
673 }
674
675 encode_vlq_diff(&mut rv, token.dst_col, prev_dst_col);
676 prev_dst_col = token.dst_col;
677
678 if token.src_id != !0 {
679 encode_vlq_diff(&mut rv, token.src_id, prev_src_id);
680 prev_src_id = token.src_id;
681 encode_vlq_diff(&mut rv, token.src_line, prev_src_line);
682 prev_src_line = token.src_line;
683 encode_vlq_diff(&mut rv, token.src_col, prev_src_col);
684 prev_src_col = token.src_col;
685 if token.name_id != !0 {
686 encode_vlq_diff(&mut rv, token.name_id, prev_name_id);
687 prev_name_id = token.name_id;
688 }
689 }
690 }
691
692 rv
693}
694
695#[cfg(test)]
696mod tests {
697 use super::*;
698
699 #[test]
700 fn test_scopes_roundtrip() {
701 let map = decode(
702 br#"{
703 "version": 3,
704 "sources": ["coolstuff.js"],
705 "names": [],
706 "mappings": "AAAA",
707 "scopes": "B,A,A,C,A,A"
708 }"#,
709 )
710 .unwrap()
711 .into_source_map()
712 .unwrap();
713
714 let raw = map.into_raw_sourcemap();
715 let scopes = raw.scopes.map(|v| v.into_data().to_string());
716 assert_eq!(scopes.as_deref(), Some("B,A,A,C,A,A"));
717 }
718
719 #[test]
720 fn test_adjust_mappings_drops_scopes() {
721 let mut map = decode(
722 br#"{
723 "version": 3,
724 "sources": ["coolstuff.js"],
725 "names": [],
726 "mappings": "AAAA",
727 "scopes": "B,A,A,C,A,A"
728 }"#,
729 )
730 .unwrap()
731 .into_source_map()
732 .unwrap();
733
734 let adjustment = crate::SourceMap::from_slice(
735 br#"{
736 "version": 3,
737 "sources": ["coolstuff.js"],
738 "names": [],
739 "mappings": "AAAA"
740 }"#,
741 )
742 .unwrap();
743
744 map.adjust_mappings(adjustment);
745
746 let raw = map.into_raw_sourcemap();
747 assert!(raw.scopes.is_none());
748 }
749}