dprint_swc_ext/common/
pos.rs

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/// Swc unfortunately uses `BytePos(0)` as a magic value. This means
10/// that we can't have byte positions of nodes line up with the text.
11/// To get around this, we have created our own `SourcePos` wrapper
12/// that hides the underlying swc byte position so it can't be used
13/// incorrectly.
14#[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  /// Do not use this except when receiving an swc byte position
32  /// from swc and needing to convert it to a source position.
33  /// If you need to create a `SourcePos` then you should get
34  /// the text info's start position and add to it in order to
35  /// get a new source position.
36  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/// A special source pos that indicates the source start
99/// which functions can use as a parameter type in order
100/// to ensure someone doesn't provide the wrong position.
101#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
102pub struct StartSourcePos(pub(crate) SourcePos);
103
104impl StartSourcePos {
105  /// Use this value as the start byte position when parsing.
106  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// Only want Into and not From in order to prevent
122// people from creating one of these easily.
123#[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  /// Gets if the source range contains the other source range inclusive.
197  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  /// Gets the relative byte range based on the source text's start position.
206  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  /// Do not use this except when receiving an swc span
213  /// from swc and needing to convert it to a source position.
214  /// Generally, prefer using the `.range()` method.
215  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  /// Gets the relative byte range based on the source text's start position.
222  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// Only want Into and not From in order to prevent
239// people from creating one of these easily.
240#[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        // fallback
327        .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        // fallback
337        .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
364/// Adds source position helper methods for swc types that implement
365/// `swc_common::Spanned`.
366///
367/// There were conflicts with implementing `SourceRanged` for `&SourceRanged`
368/// with swc's Spanned implementation, so this needed to be a separate trait
369/// unfortunately and I couldn't figure out how to combine it with `SourceRanged`
370pub 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}