Skip to main content

oak_source_map/
mapping.rs

1//! Mapping types for Source Maps.
2
3use serde::{Deserialize, Serialize};
4
5/// A single mapping entry in a source map.
6///
7/// Each mapping describes how a generated position maps back to
8/// an original position in a source file.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub struct Mapping {
11    /// Generated line (0-indexed).
12    pub generated_line: u32,
13    /// Generated column (0-indexed).
14    pub generated_column: u32,
15    /// Source file index (if this mapping has a source).
16    pub source_index: Option<u32>,
17    /// Original line (0-indexed, if this mapping has a source).
18    pub original_line: Option<u32>,
19    /// Original column (0-indexed, if this mapping has a source).
20    pub original_column: Option<u32>,
21    /// Name index (if this mapping has a name).
22    pub name_index: Option<u32>,
23}
24
25impl Mapping {
26    /// Creates a new mapping with only generated position.
27    pub fn generated_only(line: u32, column: u32) -> Self {
28        Self { generated_line: line, generated_column: column, source_index: None, original_line: None, original_column: None, name_index: None }
29    }
30
31    /// Creates a new mapping with full information.
32    pub fn full(generated_line: u32, generated_column: u32, source_index: u32, original_line: u32, original_column: u32, name_index: Option<u32>) -> Self {
33        Self { generated_line, generated_column, source_index: Some(source_index), original_line: Some(original_line), original_column: Some(original_column), name_index }
34    }
35
36    /// Checks if this mapping has source information.
37    pub fn has_source(&self) -> bool {
38        self.source_index.is_some()
39    }
40
41    /// Checks if this mapping has a name.
42    pub fn has_name(&self) -> bool {
43        self.name_index.is_some()
44    }
45
46    /// Returns the source index, or 0 if none.
47    pub fn source_index_or_zero(&self) -> u32 {
48        self.source_index.unwrap_or(0)
49    }
50
51    /// Returns the original line, or 0 if none.
52    pub fn original_line_or_zero(&self) -> u32 {
53        self.original_line.unwrap_or(0)
54    }
55
56    /// Returns the original column, or 0 if none.
57    pub fn original_column_or_zero(&self) -> u32 {
58        self.original_column.unwrap_or(0)
59    }
60
61    /// Returns the name index, or 0 if none.
62    pub fn name_index_or_zero(&self) -> u32 {
63        self.name_index.unwrap_or(0)
64    }
65}
66
67impl PartialOrd for Mapping {
68    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
69        Some(self.cmp(other))
70    }
71}
72
73impl Ord for Mapping {
74    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
75        self.generated_line.cmp(&other.generated_line).then_with(|| self.generated_column.cmp(&other.generated_column))
76    }
77}
78
79/// A segment in the mappings string.
80///
81/// Segments are separated by commas within a line.
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub struct Segment {
84    /// Generated column (relative to previous segment on same line).
85    pub generated_column: i32,
86    /// Source index (relative to previous segment, if present).
87    pub source_index: Option<i32>,
88    /// Original line (relative to previous segment, if present).
89    pub original_line: Option<i32>,
90    /// Original column (relative to previous segment, if present).
91    pub original_column: Option<i32>,
92    /// Name index (relative to previous segment, if present).
93    pub name_index: Option<i32>,
94}
95
96impl Segment {
97    /// Creates a new segment with only generated column.
98    pub fn generated_only(column: i32) -> Self {
99        Self { generated_column: column, source_index: None, original_line: None, original_column: None, name_index: None }
100    }
101
102    /// Creates a new full segment.
103    pub fn full(generated_column: i32, source_index: i32, original_line: i32, original_column: i32, name_index: Option<i32>) -> Self {
104        Self { generated_column, source_index: Some(source_index), original_line: Some(original_line), original_column: Some(original_column), name_index }
105    }
106
107    /// Checks if this segment has source information.
108    pub fn has_source(&self) -> bool {
109        self.source_index.is_some()
110    }
111
112    /// Checks if this segment has a name.
113    pub fn has_name(&self) -> bool {
114        self.name_index.is_some()
115    }
116}
117
118/// A mapping with bounds information for efficient lookup.
119#[derive(Debug, Clone, PartialEq, Eq)]
120pub struct BoundedMapping {
121    /// The mapping.
122    pub mapping: Mapping,
123    /// The start column of the generated range.
124    pub start_column: u32,
125    /// The end column of the generated range (exclusive).
126    pub end_column: u32,
127}
128
129impl BoundedMapping {
130    /// Creates a new bounded mapping.
131    pub fn new(mapping: Mapping, start_column: u32, end_column: u32) -> Self {
132        Self { mapping, start_column, end_column }
133    }
134
135    /// Checks if a column is within this mapping's range.
136    pub fn contains_column(&self, column: u32) -> bool {
137        column >= self.start_column && column < self.end_column
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn test_mapping_ordering() {
147        let m1 = Mapping::generated_only(0, 5);
148        let m2 = Mapping::generated_only(0, 10);
149        let m3 = Mapping::generated_only(1, 0);
150
151        assert!(m1 < m2);
152        assert!(m2 < m3);
153    }
154
155    #[test]
156    fn test_mapping_has_source() {
157        let m1 = Mapping::generated_only(0, 0);
158        assert!(!m1.has_source());
159
160        let m2 = Mapping::full(0, 0, 0, 0, 0, None);
161        assert!(m2.has_source());
162    }
163
164    #[test]
165    fn test_bounded_mapping() {
166        let mapping = Mapping::generated_only(0, 5);
167        let bounded = BoundedMapping::new(mapping, 5, 10);
168
169        assert!(bounded.contains_column(5));
170        assert!(bounded.contains_column(7));
171        assert!(!bounded.contains_column(10));
172        assert!(!bounded.contains_column(4));
173    }
174}