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#[derive(Debug, Clone)]
94pub struct ScriptSection {
95 pub extraction: ExtractionResult,
97 pub source_type: oxc_span::SourceType,
99}
100
101#[must_use]
108pub fn embedded_script_sections(
109 path: &std::path::Path,
110 source: &str,
111) -> Option<Vec<ScriptSection>> {
112 if crate::sfc::is_sfc_file(path) {
113 return Some(
114 crate::sfc::extract_sfc_scripts(source)
115 .iter()
116 .filter(|script| script.src.is_none())
117 .map(|script| ScriptSection {
118 extraction: ExtractionResult::contiguous(&script.body, script.byte_offset),
119 source_type: crate::sfc::source_type_for_script(script),
120 })
121 .collect(),
122 );
123 }
124 if crate::astro::is_astro_file(path) {
125 return Some(
126 crate::astro::extract_astro_frontmatter(source)
127 .iter()
128 .map(|script| ScriptSection {
129 extraction: ExtractionResult::contiguous(&script.body, script.byte_offset),
130 source_type: crate::sfc::source_type_for_script(script),
131 })
132 .collect(),
133 );
134 }
135 if crate::mdx::is_mdx_file(path) {
136 return Some(vec![ScriptSection {
137 extraction: crate::mdx::extract_mdx_statements_mapped(source),
138 source_type: oxc_span::SourceType::tsx(),
139 }]);
140 }
141 None
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 fn two_fragments() -> ExtractionResult {
151 let mut result = ExtractionResult::default();
152 result.push_mapped("ab", 10);
153 result.push_mapped("cd", 100);
154 result
155 }
156
157 #[test]
158 fn start_biased_lookup_takes_the_fragment_beginning_at_the_offset() {
159 let result = two_fragments();
160 assert_eq!(result.original_offset_start_biased(0), Some(10));
161 assert_eq!(result.original_offset_start_biased(1), Some(11));
162 assert_eq!(result.original_offset_start_biased(2), Some(100));
164 }
165
166 #[test]
167 fn end_biased_lookup_takes_the_fragment_ending_at_the_offset() {
168 let result = two_fragments();
169 assert_eq!(result.original_offset_end_biased(2), Some(12));
171 assert_eq!(result.original_offset_end_biased(4), Some(102));
172 assert_eq!(result.original_offset_end_biased(0), None);
174 }
175
176 #[test]
177 fn a_fragment_end_maps_but_an_offset_past_it_does_not() {
178 let result = ExtractionResult::contiguous("abcde", 100);
179 assert_eq!(result.original_offset_start_biased(5), Some(105));
180 assert_eq!(result.original_offset_end_biased(5), Some(105));
181 assert_eq!(result.original_offset_start_biased(6), None);
182 assert_eq!(result.original_offset_end_biased(6), None);
183 }
184
185 #[test]
186 fn an_empty_result_maps_nothing() {
187 let result = ExtractionResult::default();
188 assert_eq!(result.original_offset_start_biased(0), None);
189 assert_eq!(result.original_offset_end_biased(0), None);
190 }
191
192 #[test]
193 fn remap_span_pairs_the_start_biased_and_end_biased_lookups() {
194 let result = two_fragments();
195 assert_eq!(result.remap_span(Span::new(1, 4)), Span::new(11, 102));
196 assert_eq!(result.remap_span(Span::new(0, 0)), Span::new(0, 0));
198 assert_eq!(result.remap_span(Span::new(1, 9)), Span::new(1, 9));
200 }
201}