1use crate::swc::common::BytePos;
2use crate::swc::common::Span;
3use crate::swc::parser::token::TokenAndSpan;
4
5use super::comments::*;
6use super::text_info::*;
7use super::types::*;
8
9#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
15pub struct SourcePos(BytePos);
16
17impl SourcePos {
18 #[cfg(test)]
19 pub fn new(index: usize) -> Self {
20 Self(StartSourcePos::START_SOURCE_POS.as_byte_pos() + BytePos(index as u32))
21 }
22
23 pub fn as_byte_pos(&self) -> BytePos {
24 self.0
25 }
26
27 pub fn as_byte_index(&self, start_pos: StartSourcePos) -> usize {
28 *self - start_pos
29 }
30
31 pub fn unsafely_from_byte_pos(byte_pos: BytePos) -> Self {
37 #[cfg(debug_assertions)]
38 if byte_pos < StartSourcePos::START_SOURCE_POS.as_byte_pos() {
39 panic!(concat!(
40 "The provided byte position was less than the start byte position. ",
41 "Ensure the source file is parsed starting at SourcePos::START_SOURCE_POS."
42 ))
43 }
44 Self(byte_pos)
45 }
46
47 pub(crate) fn as_usize(&self) -> usize {
48 (self.as_byte_pos() - StartSourcePos::START_SOURCE_POS.as_byte_pos()).0 as usize
49 }
50}
51
52impl std::fmt::Debug for SourcePos {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 f.debug_tuple("SourcePos").field(&self.as_usize()).finish()
55 }
56}
57
58impl std::fmt::Display for SourcePos {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 f.write_str(&self.as_usize().to_string())
61 }
62}
63
64impl std::ops::Add<usize> for SourcePos {
65 type Output = SourcePos;
66
67 fn add(self, rhs: usize) -> Self::Output {
68 SourcePos(BytePos(self.0 .0 + rhs as u32))
69 }
70}
71
72impl std::ops::Sub<usize> for SourcePos {
73 type Output = SourcePos;
74
75 fn sub(self, rhs: usize) -> Self::Output {
76 SourcePos(BytePos(self.0 .0 - rhs as u32))
77 }
78}
79
80impl std::ops::Sub<SourcePos> for SourcePos {
81 type Output = usize;
82
83 fn sub(self, rhs: SourcePos) -> Self::Output {
84 (self.0 - rhs.0).0 as usize
85 }
86}
87
88impl SourceRanged for SourcePos {
89 fn start(&self) -> SourcePos {
90 *self
91 }
92
93 fn end(&self) -> SourcePos {
94 *self
95 }
96}
97
98#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
102pub struct StartSourcePos(pub(crate) SourcePos);
103
104impl StartSourcePos {
105 pub const START_SOURCE_POS: StartSourcePos = StartSourcePos(SourcePos(BytePos(1)));
107
108 pub fn as_byte_pos(&self) -> BytePos {
109 self.0.as_byte_pos()
110 }
111
112 pub fn as_source_pos(&self) -> SourcePos {
113 self.0
114 }
115
116 pub(crate) fn as_usize(&self) -> usize {
117 (self.as_byte_pos() - StartSourcePos::START_SOURCE_POS.as_byte_pos()).0 as usize
118 }
119}
120
121#[allow(clippy::from_over_into)]
124impl Into<SourcePos> for StartSourcePos {
125 fn into(self) -> SourcePos {
126 self.as_source_pos()
127 }
128}
129
130impl std::fmt::Debug for StartSourcePos {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 f.debug_tuple("StartSourcePos").field(&self.as_usize()).finish()
133 }
134}
135
136impl std::fmt::Display for StartSourcePos {
137 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138 f.write_str(&self.as_usize().to_string())
139 }
140}
141
142impl std::ops::Add<usize> for StartSourcePos {
143 type Output = SourcePos;
144
145 fn add(self, rhs: usize) -> Self::Output {
146 SourcePos(BytePos(self.0 .0 .0 + rhs as u32))
147 }
148}
149
150impl std::ops::Sub<StartSourcePos> for SourcePos {
151 type Output = usize;
152
153 fn sub(self, rhs: StartSourcePos) -> Self::Output {
154 (self.0 - rhs.0 .0).0 as usize
155 }
156}
157
158impl std::cmp::PartialEq<SourcePos> for StartSourcePos {
159 fn eq(&self, other: &SourcePos) -> bool {
160 self.0 == *other
161 }
162}
163
164impl std::cmp::PartialOrd<SourcePos> for StartSourcePos {
165 fn partial_cmp(&self, other: &SourcePos) -> Option<std::cmp::Ordering> {
166 self.0.partial_cmp(other)
167 }
168}
169
170impl std::cmp::PartialEq<StartSourcePos> for SourcePos {
171 fn eq(&self, other: &StartSourcePos) -> bool {
172 *self == other.0
173 }
174}
175
176impl std::cmp::PartialOrd<StartSourcePos> for SourcePos {
177 fn partial_cmp(&self, other: &StartSourcePos) -> Option<std::cmp::Ordering> {
178 self.partial_cmp(&other.0)
179 }
180}
181
182#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
183pub struct SourceRange<T = SourcePos>
184where
185 T: Into<SourcePos> + Clone + Copy,
186{
187 pub start: T,
188 pub end: SourcePos,
189}
190
191impl<T: Into<SourcePos> + Clone + Copy> SourceRange<T> {
192 pub fn new(start: T, end: SourcePos) -> Self {
193 Self { start, end }
194 }
195
196 pub fn contains<U: Into<SourcePos> + Clone + Copy>(&self, other: &SourceRange<U>) -> bool {
198 let start: SourcePos = self.start.into();
199 let other_start: SourcePos = other.start.into();
200 start <= other_start && self.end >= other.end
201 }
202}
203
204impl SourceRange<SourcePos> {
205 pub fn as_byte_range(&self, source_start: StartSourcePos) -> std::ops::Range<usize> {
207 let start = self.start - source_start;
208 let end = self.end - source_start;
209 start..end
210 }
211
212 pub fn unsafely_from_span(span: Span) -> Self {
216 SourceRange::new(SourcePos::unsafely_from_byte_pos(span.lo), SourcePos::unsafely_from_byte_pos(span.hi))
217 }
218}
219
220impl SourceRange<StartSourcePos> {
221 pub fn as_byte_range(&self) -> std::ops::Range<usize> {
223 let end = self.end - self.start;
224 0..end
225 }
226}
227
228impl<T: Into<SourcePos> + Clone + Copy> SourceRanged for SourceRange<T> {
229 fn start(&self) -> SourcePos {
230 self.start.into()
231 }
232
233 fn end(&self) -> SourcePos {
234 self.end
235 }
236}
237
238#[allow(clippy::from_over_into)]
241impl Into<Span> for SourceRange {
242 fn into(self) -> Span {
243 Span::new(self.start.as_byte_pos(), self.end.as_byte_pos())
244 }
245}
246
247macro_rules! source_ranged_trait {
248 () => {
249 fn range(&self) -> SourceRange {
250 SourceRange {
251 start: self.start(),
252 end: self.end(),
253 }
254 }
255
256 fn byte_width(&self) -> usize {
257 self.end() - self.start()
258 }
259
260 fn start_line_fast<'a, P: SourceTextInfoProvider<'a>>(&self, source: P) -> usize {
261 source.text_info().line_index(self.start())
262 }
263
264 fn end_line_fast<'a, P: SourceTextInfoProvider<'a>>(&self, source: P) -> usize {
265 source.text_info().line_index(self.end())
266 }
267
268 fn start_column_fast<'a, P: SourceTextInfoProvider<'a>>(&self, source: P) -> usize {
269 self.column_at_pos(source, self.start())
270 }
271
272 fn end_column_fast<'a, P: SourceTextInfoProvider<'a>>(&self, source: P) -> usize {
273 self.column_at_pos(source, self.end())
274 }
275
276 fn column_at_pos<'a, P: SourceTextInfoProvider<'a>>(&self, source: P, pos: SourcePos) -> usize {
277 let text_info = source.text_info();
278 let text_bytes = text_info.text_str().as_bytes();
279 let pos = pos - text_info.range().start;
280 let mut line_start = 0;
281 for i in (0..pos).rev() {
282 if text_bytes[i] == b'\n' {
283 line_start = i + 1;
284 break;
285 }
286 }
287 let text_slice = &text_info.text_str()[line_start..pos];
288 text_slice.chars().count()
289 }
290
291 fn char_width_fast<'a, P: SourceTextProvider<'a>>(&self, source: P) -> usize {
292 self.text_fast(source).chars().count()
293 }
294
295 fn text_fast<'a, P: SourceTextProvider<'a>>(&self, source: P) -> &'a str {
296 let text = source.text();
297 let byte_range = self.range().as_byte_range(source.start_pos());
298 &text[byte_range]
299 }
300
301 fn tokens_fast<'a>(&self, program: impl RootNode<'a>) -> &'a [TokenAndSpan] {
302 let token_container = program.token_container();
303 token_container.get_tokens_in_range(self.start(), self.end())
304 }
305
306 fn leading_comments_fast<'a>(&self, program: impl RootNode<'a>) -> CommentsIterator<'a> {
307 program.comment_container().leading_comments(self.start())
308 }
309
310 fn trailing_comments_fast<'a>(&self, program: impl RootNode<'a>) -> CommentsIterator<'a> {
311 program.comment_container().trailing_comments(self.end())
312 }
313
314 fn previous_token_fast<'a>(&self, program: impl RootNode<'a>) -> Option<&'a TokenAndSpan> {
315 program.token_container().get_previous_token(self.start())
316 }
317
318 fn next_token_fast<'a>(&self, program: impl RootNode<'a>) -> Option<&'a TokenAndSpan> {
319 program.token_container().get_next_token(self.end())
320 }
321
322 fn previous_tokens_fast<'a>(&self, program: impl RootNode<'a>) -> &'a [TokenAndSpan] {
323 let token_container = program.token_container();
324 let index = token_container
325 .get_token_index_at_start(self.start())
326 .or_else(|| token_container.get_token_index_at_end(self.start()))
328 .unwrap_or_else(|| panic!("The specified start position ({}) did not have a token index.", self.start()));
329 &token_container.tokens[0..index]
330 }
331
332 fn next_tokens_fast<'a>(&self, program: impl RootNode<'a>) -> &'a [TokenAndSpan] {
333 let token_container = program.token_container();
334 let index = token_container
335 .get_token_index_at_end(self.end())
336 .or_else(|| token_container.get_token_index_at_start(self.end()))
338 .unwrap_or_else(|| panic!("The specified end position ({}) did not have a token index.", self.end()));
339 &token_container.tokens[index + 1..]
340 }
341 };
342}
343
344pub trait SourceRanged {
345 fn start(&self) -> SourcePos;
346 fn end(&self) -> SourcePos;
347
348 source_ranged_trait!();
349}
350
351impl<'a, S> SourceRanged for &'a S
352where
353 S: ?Sized + SourceRanged + 'a,
354{
355 fn start(&self) -> SourcePos {
356 <S as SourceRanged>::start(*self)
357 }
358
359 fn end(&self) -> SourcePos {
360 <S as SourceRanged>::end(*self)
361 }
362}
363
364pub trait SourceRangedForSpanned {
371 fn start(&self) -> SourcePos;
372 fn end(&self) -> SourcePos;
373
374 source_ranged_trait!();
375}
376
377impl<T> SourceRangedForSpanned for T
378where
379 T: swc_common::Spanned,
380{
381 fn start(&self) -> SourcePos {
382 SourcePos::unsafely_from_byte_pos(self.span().lo)
383 }
384
385 fn end(&self) -> SourcePos {
386 SourcePos::unsafely_from_byte_pos(self.span().hi)
387 }
388}
389
390#[cfg(test)]
391mod test {
392 use super::*;
393
394 #[test]
395 fn source_range_contains() {
396 let start_pos = StartSourcePos::START_SOURCE_POS;
397 assert!(SourceRange::new(start_pos, start_pos + 5).contains(&SourceRange::new(start_pos + 1, start_pos + 2)));
398 assert!(SourceRange::new(start_pos + 1, start_pos + 5).contains(&SourceRange::new(start_pos + 1, start_pos + 2)));
399 assert!(!SourceRange::new(start_pos + 2, start_pos + 5).contains(&SourceRange::new(start_pos + 1, start_pos + 2)));
400
401 assert!(SourceRange::new(start_pos + 1, start_pos + 3).contains(&SourceRange::new(start_pos + 1, start_pos + 2)));
402 assert!(SourceRange::new(start_pos + 1, start_pos + 2).contains(&SourceRange::new(start_pos + 1, start_pos + 2)));
403 assert!(!SourceRange::new(start_pos + 1, start_pos + 1).contains(&SourceRange::new(start_pos + 1, start_pos + 2)));
404 }
405}