oak_source_map/
decoder.rs1use std::collections::BTreeMap;
4
5use crate::{BoundedMapping, Mapping, Result, SourceMap, SourceMapError};
6
7#[derive(Debug, Clone)]
11pub struct SourceMapDecoder {
12 source_map: SourceMap,
13 lines: BTreeMap<u32, Vec<BoundedMapping>>,
14 sources: Vec<String>,
15 names: Vec<String>,
16}
17
18impl SourceMapDecoder {
19 pub fn new(source_map: SourceMap) -> Result<Self> {
21 let mappings = source_map.parse_mappings()?;
22 let mut lines: BTreeMap<u32, Vec<BoundedMapping>> = BTreeMap::new();
23
24 for mapping in mappings {
25 let line = mapping.generated_line;
26 let col = mapping.generated_column;
27
28 let line_mappings = lines.entry(line).or_default();
29
30 if let Some(last) = line_mappings.last_mut() {
31 last.end_column = col;
32 }
33
34 line_mappings.push(BoundedMapping::new(mapping, col, u32::MAX));
35 }
36
37 Ok(Self { source_map, lines, sources: Vec::new(), names: Vec::new() })
38 }
39
40 pub fn lookup(&self, generated_line: u32, generated_column: u32) -> Option<&Mapping> {
42 let line_mappings = self.lines.get(&generated_line)?;
43
44 let idx = line_mappings
45 .binary_search_by(|m| {
46 if m.end_column <= generated_column {
47 std::cmp::Ordering::Less
48 }
49 else if m.start_column > generated_column {
50 std::cmp::Ordering::Greater
51 }
52 else {
53 std::cmp::Ordering::Equal
54 }
55 })
56 .ok()?;
57
58 Some(&line_mappings[idx].mapping)
59 }
60
61 pub fn lookup_full(&self, generated_line: u32, generated_column: u32) -> Option<OriginalPosition> {
63 let mapping = self.lookup(generated_line, generated_column)?;
64
65 let source = mapping.source_index.and_then(|idx| self.source_map.get_source(idx as usize));
66
67 let name = mapping.name_index.and_then(|idx| self.source_map.get_name(idx as usize));
68
69 Some(OriginalPosition { source: source.map(String::from), original_line: mapping.original_line, original_column: mapping.original_column, name: name.map(String::from) })
70 }
71
72 pub fn get_line_mappings(&self, line: u32) -> Option<&[BoundedMapping]> {
74 self.lines.get(&line).map(|v| v.as_slice())
75 }
76
77 pub fn generated_line_count(&self) -> usize {
79 self.lines.len()
80 }
81
82 pub fn source_map(&self) -> &SourceMap {
84 &self.source_map
85 }
86
87 pub fn iter_mappings(&self) -> impl Iterator<Item = &BoundedMapping> {
89 self.lines.values().flat_map(|v| v.iter())
90 }
91}
92
93#[derive(Debug, Clone, PartialEq, Eq)]
95pub struct OriginalPosition {
96 pub source: Option<String>,
98 pub original_line: Option<u32>,
100 pub original_column: Option<u32>,
102 pub name: Option<String>,
104}
105
106impl OriginalPosition {
107 pub fn new(source: Option<String>, original_line: Option<u32>, original_column: Option<u32>, name: Option<String>) -> Self {
109 Self { source, original_line, original_column, name }
110 }
111
112 pub fn has_source(&self) -> bool {
114 self.source.is_some()
115 }
116
117 pub fn has_name(&self) -> bool {
119 self.name.is_some()
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn test_decoder_lookup() {
129 let json = r#"{"version":3,"sources":["a.js"],"names":[],"mappings":"AAAA,SAASA"}"#;
130 let sm = SourceMap::parse(json).unwrap();
131 let decoder = SourceMapDecoder::new(sm).unwrap();
132
133 let pos = decoder.lookup(0, 0);
134 assert!(pos.is_some());
135 }
136
137 #[test]
138 fn test_decoder_lookup_full() {
139 let json = r#"{"version":3,"sources":["a.js"],"names":["foo"],"mappings":"AAAA,SAASA"}"#;
140 let sm = SourceMap::parse(json).unwrap();
141 let decoder = SourceMapDecoder::new(sm).unwrap();
142
143 let pos = decoder.lookup_full(0, 0);
144 assert!(pos.is_some());
145
146 let pos = pos.unwrap();
147 assert_eq!(pos.source, Some("a.js".to_string()));
148 }
149}