1use std::sync::Arc;
2
3use swc_common::input::StringInput;
4pub use text_lines::LineAndColumnIndex;
5pub use text_lines::TextLines;
6
7use super::pos::*;
8use super::text_encoding::strip_bom_mut;
9use super::text_encoding::BOM_CHAR;
10use super::LineAndColumnDisplay;
11
12#[derive(Clone)]
17pub struct SourceTextInfo {
18 start_pos: StartSourcePos,
20 text: Arc<str>,
21 text_lines: Arc<TextLines>,
22}
23
24impl SourceTextInfo {
25 pub fn new(text: Arc<str>) -> Self {
27 Self::new_with_pos(StartSourcePos::START_SOURCE_POS.as_source_pos(), text)
28 }
29
30 pub fn new_with_pos(start_pos: SourcePos, text: Arc<str>) -> Self {
36 let text = if text.starts_with(BOM_CHAR) {
44 let mut text = text.to_string();
45 strip_bom_mut(&mut text);
46 text.into()
47 } else {
48 text
49 };
50
51 Self::new_with_indent_width(start_pos, text, 2)
52 }
53
54 pub fn new_with_indent_width(start_pos: SourcePos, text: Arc<str>, indent_width: usize) -> Self {
63 Self {
64 start_pos: StartSourcePos(start_pos),
65 text_lines: Arc::new(TextLines::with_indent_width(&text, indent_width)),
66 text,
67 }
68 }
69
70 pub fn from_string(text: String) -> Self {
75 Self::new(text.into())
76 }
77
78 pub fn as_string_input(&self) -> StringInput {
81 let range = self.range();
82 StringInput::new(self.text_str(), range.start.as_byte_pos(), range.end.as_byte_pos())
83 }
84
85 pub fn text(&self) -> Arc<str> {
87 self.text.clone()
88 }
89
90 pub fn text_str(&self) -> &str {
92 &self.text
93 }
94
95 pub fn range(&self) -> SourceRange<StartSourcePos> {
97 SourceRange::new(self.start_pos, self.start_pos + self.text.len())
98 }
99
100 pub fn lines_count(&self) -> usize {
102 self.text_lines.lines_count()
103 }
104
105 pub fn line_index(&self, pos: SourcePos) -> usize {
110 self.assert_pos(pos);
111 self.text_lines.line_index(self.get_relative_index_from_pos(pos))
112 }
113
114 pub fn line_start(&self, line_index: usize) -> SourcePos {
119 self.assert_line_index(line_index);
120 self.get_pos_from_relative_index(self.text_lines.line_start(line_index))
121 }
122
123 pub fn line_end(&self, line_index: usize) -> SourcePos {
128 self.assert_line_index(line_index);
129 self.get_pos_from_relative_index(self.text_lines.line_end(line_index))
130 }
131
132 pub fn line_and_column_index(&self, pos: SourcePos) -> LineAndColumnIndex {
137 self.assert_pos(pos);
138 self.text_lines.line_and_column_index(self.get_relative_index_from_pos(pos))
139 }
140
141 pub fn line_and_column_display(&self, pos: SourcePos) -> LineAndColumnDisplay {
147 self.assert_pos(pos);
148 self.text_lines.line_and_column_display(self.get_relative_index_from_pos(pos))
149 }
150
151 pub fn line_and_column_display_with_indent_width(&self, pos: SourcePos, indent_width: usize) -> LineAndColumnDisplay {
157 self.assert_pos(pos);
158 self
159 .text_lines
160 .line_and_column_display_with_indent_width(self.get_relative_index_from_pos(pos), indent_width)
161 }
162
163 pub fn loc_to_source_pos(&self, line_and_column_index: LineAndColumnIndex) -> SourcePos {
169 self.assert_line_index(line_and_column_index.line_index);
170 self.get_pos_from_relative_index(self.text_lines.byte_index(line_and_column_index))
171 }
172
173 pub fn line_text(&self, line_index: usize) -> &str {
179 let range = SourceRange {
180 start: self.line_start(line_index),
181 end: self.line_end(line_index),
182 };
183 self.range_text(&range)
184 }
185
186 pub fn range_text(&self, range: &SourceRange) -> &str {
188 let start = self.get_relative_index_from_pos(range.start);
189 let end = self.get_relative_index_from_pos(range.end);
190 &self.text_str()[start..end]
191 }
192
193 fn assert_pos(&self, pos: SourcePos) {
194 let range = self.range();
195 if pos < range.start {
196 panic!("The provided position {} was less than the start position {}.", pos, range.start,);
197 } else if pos > range.end {
198 panic!("The provided position {} was greater than the end position {}.", pos, range.end,);
199 }
200 }
201
202 fn assert_line_index(&self, line_index: usize) {
203 if line_index >= self.lines_count() {
204 panic!(
205 "The specified line index {} was greater or equal to the number of lines of {}.",
206 line_index,
207 self.lines_count()
208 );
209 }
210 }
211
212 fn get_relative_index_from_pos(&self, pos: SourcePos) -> usize {
213 pos - self.start_pos
214 }
215
216 fn get_pos_from_relative_index(&self, relative_index: usize) -> SourcePos {
217 self.start_pos + relative_index
218 }
219}
220
221impl std::fmt::Debug for SourceTextInfo {
222 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
223 f.debug_struct("SourceTextInfo")
224 .field("start_pos", &self.start_pos)
225 .field("text", &self.text)
226 .finish()
227 }
228}
229
230pub trait SourceTextInfoProvider<'a> {
231 fn text_info(&self) -> &'a SourceTextInfo;
232}
233
234impl<'a> SourceTextInfoProvider<'a> for &'a SourceTextInfo {
235 fn text_info(&self) -> &'a SourceTextInfo {
236 self
237 }
238}
239
240pub trait SourceTextProvider<'a> {
241 fn text(&self) -> &'a Arc<str>;
242 fn start_pos(&self) -> StartSourcePos;
243}
244
245impl<'a, T> SourceTextProvider<'a> for T
246where
247 T: SourceTextInfoProvider<'a>,
248{
249 fn text(&self) -> &'a Arc<str> {
250 &self.text_info().text
251 }
252
253 fn start_pos(&self) -> StartSourcePos {
254 self.text_info().start_pos
255 }
256}
257
258#[cfg(test)]
259mod test {
260 use super::*;
261
262 #[test]
263 fn line_and_column_index() {
264 let text = "12\n3\r\nβ\n5";
265 for i in 0..10 {
266 run_with_text_info(SourceTextInfo::new_with_pos(SourcePos::new(i), text.to_string().into()), i);
267 }
268
269 fn run_with_text_info(text_info: SourceTextInfo, i: usize) {
270 assert_pos_line_and_col(&text_info, i, 0, 0); assert_pos_line_and_col(&text_info, 1 + i, 0, 1); assert_pos_line_and_col(&text_info, 2 + i, 0, 2); assert_pos_line_and_col(&text_info, 3 + i, 1, 0); assert_pos_line_and_col(&text_info, 4 + i, 1, 1); assert_pos_line_and_col(&text_info, 5 + i, 1, 2); assert_pos_line_and_col(&text_info, 6 + i, 2, 0); assert_pos_line_and_col(&text_info, 7 + i, 2, 0); assert_pos_line_and_col(&text_info, 8 + i, 2, 1); assert_pos_line_and_col(&text_info, 9 + i, 3, 0); assert_pos_line_and_col(&text_info, 10 + i, 3, 1); }
282 }
283
284 fn assert_pos_line_and_col(text_info: &SourceTextInfo, pos: usize, line_index: usize, column_index: usize) {
285 assert_eq!(
286 text_info.line_and_column_index(SourcePos::new(pos)),
287 LineAndColumnIndex { line_index, column_index }
288 );
289 }
290
291 #[test]
292 #[should_panic(expected = "The provided position 0 was less than the start position 1.")]
293 fn line_and_column_index_panic_less_than() {
294 let info = SourceTextInfo::new_with_pos(SourcePos::new(1), "test".to_string().into());
295 info.line_and_column_index(SourcePos::new(0));
296 }
297
298 #[test]
299 #[should_panic(expected = "The provided position 6 was greater than the end position 5.")]
300 fn line_and_column_index_panic_greater_than() {
301 let info = SourceTextInfo::new_with_pos(SourcePos::new(1), "test".to_string().into());
302 info.line_and_column_index(SourcePos::new(6));
303 }
304
305 #[test]
306 fn line_start() {
307 let text = "12\n3\r\n4\n5";
308 for i in 0..10 {
309 run_with_text_info(SourceTextInfo::new_with_pos(SourcePos::new(i), text.to_string().into()), i);
310 }
311
312 fn run_with_text_info(text_info: SourceTextInfo, i: usize) {
313 assert_line_start(&text_info, 0, i);
314 assert_line_start(&text_info, 1, 3 + i);
315 assert_line_start(&text_info, 2, 6 + i);
316 assert_line_start(&text_info, 3, 8 + i);
317 }
318 }
319
320 fn assert_line_start(text_info: &SourceTextInfo, line_index: usize, line_end: usize) {
321 assert_eq!(text_info.line_start(line_index), SourcePos::new(line_end));
322 }
323
324 #[test]
325 #[should_panic(expected = "The specified line index 1 was greater or equal to the number of lines of 1.")]
326 fn line_start_equal_number_lines() {
327 let info = SourceTextInfo::new_with_pos(SourcePos::new(1), "test".to_string().into());
328 info.line_start(1);
329 }
330
331 #[test]
332 fn line_end() {
333 let text = "12\n3\r\n4\n5";
334 for i in 0..10 {
335 run_with_text_info(SourceTextInfo::new_with_pos(SourcePos::new(i), text.to_string().into()), i);
336 }
337
338 fn run_with_text_info(text_info: SourceTextInfo, i: usize) {
339 assert_line_end(&text_info, 0, 2 + i);
340 assert_line_end(&text_info, 1, 4 + i);
341 assert_line_end(&text_info, 2, 7 + i);
342 assert_line_end(&text_info, 3, 9 + i);
343 }
344 }
345
346 fn assert_line_end(text_info: &SourceTextInfo, line_index: usize, line_end: usize) {
347 assert_eq!(text_info.line_end(line_index), SourcePos::new(line_end));
348 }
349
350 #[test]
351 #[should_panic(expected = "The specified line index 1 was greater or equal to the number of lines of 1.")]
352 fn line_end_equal_number_lines() {
353 let info = SourceTextInfo::new_with_pos(SourcePos::new(1), "test".to_string().into());
354 info.line_end(1);
355 }
356}