fallow_extract/
source_map.rs1use oxc_span::Span;
4
5#[derive(Debug, Clone)]
6struct FragmentMap {
7 generated_start: u32,
8 original_start: u32,
9 len: u32,
10}
11
12#[derive(Debug, Clone, Default)]
14pub struct ExtractionResult {
15 pub body: String,
17 fragments: Vec<FragmentMap>,
18}
19
20impl ExtractionResult {
21 #[must_use]
23 pub fn contiguous(body: &str, original_start: usize) -> Self {
24 let mut result = Self::default();
25 result.push_mapped(body, original_start);
26 result
27 }
28
29 pub fn push_mapped(&mut self, text: &str, original_start: usize) {
31 if text.is_empty() {
32 return;
33 }
34 let generated_start = self.body.len();
35 self.body.push_str(text);
36 self.fragments.push(FragmentMap {
37 generated_start: generated_start as u32,
38 original_start: original_start as u32,
39 len: text.len() as u32,
40 });
41 }
42
43 fn original_offset_start_biased(&self, offset: u32) -> Option<u32> {
46 let idx = self
47 .fragments
48 .partition_point(|fragment| fragment.generated_start <= offset)
49 .checked_sub(1)?;
50 let fragment = &self.fragments[idx];
51 let delta = offset.checked_sub(fragment.generated_start)?;
52 if delta <= fragment.len {
53 Some(fragment.original_start + delta)
54 } else {
55 None
56 }
57 }
58
59 fn original_offset_end_biased(&self, offset: u32) -> Option<u32> {
62 let idx = self
63 .fragments
64 .partition_point(|fragment| fragment.generated_start < offset)
65 .checked_sub(1)?;
66 let fragment = &self.fragments[idx];
67 let delta = offset.checked_sub(fragment.generated_start)?;
68 if delta <= fragment.len {
69 Some(fragment.original_start + delta)
70 } else {
71 None
72 }
73 }
74
75 #[must_use]
77 pub fn remap_span(&self, span: Span) -> Span {
78 if span.start == 0 && span.end == 0 {
79 return span;
80 }
81 let Some(start) = self.original_offset_start_biased(span.start) else {
82 return span;
83 };
84 let Some(end) = self.original_offset_end_biased(span.end) else {
85 return span;
86 };
87 Span::new(start, end)
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 fn two_fragments() -> ExtractionResult {
98 let mut result = ExtractionResult::default();
99 result.push_mapped("ab", 10);
100 result.push_mapped("cd", 100);
101 result
102 }
103
104 #[test]
105 fn start_biased_lookup_takes_the_fragment_beginning_at_the_offset() {
106 let result = two_fragments();
107 assert_eq!(result.original_offset_start_biased(0), Some(10));
108 assert_eq!(result.original_offset_start_biased(1), Some(11));
109 assert_eq!(result.original_offset_start_biased(2), Some(100));
111 }
112
113 #[test]
114 fn end_biased_lookup_takes_the_fragment_ending_at_the_offset() {
115 let result = two_fragments();
116 assert_eq!(result.original_offset_end_biased(2), Some(12));
118 assert_eq!(result.original_offset_end_biased(4), Some(102));
119 assert_eq!(result.original_offset_end_biased(0), None);
121 }
122
123 #[test]
124 fn a_fragment_end_maps_but_an_offset_past_it_does_not() {
125 let result = ExtractionResult::contiguous("abcde", 100);
126 assert_eq!(result.original_offset_start_biased(5), Some(105));
127 assert_eq!(result.original_offset_end_biased(5), Some(105));
128 assert_eq!(result.original_offset_start_biased(6), None);
129 assert_eq!(result.original_offset_end_biased(6), None);
130 }
131
132 #[test]
133 fn an_empty_result_maps_nothing() {
134 let result = ExtractionResult::default();
135 assert_eq!(result.original_offset_start_biased(0), None);
136 assert_eq!(result.original_offset_end_biased(0), None);
137 }
138
139 #[test]
140 fn remap_span_pairs_the_start_biased_and_end_biased_lookups() {
141 let result = two_fragments();
142 assert_eq!(result.remap_span(Span::new(1, 4)), Span::new(11, 102));
143 assert_eq!(result.remap_span(Span::new(0, 0)), Span::new(0, 0));
145 assert_eq!(result.remap_span(Span::new(1, 9)), Span::new(1, 9));
147 }
148}