boa/syntax/ast/
position.rs

1//! This module implements the `Pos` structure, which represents a position in the source code.
2
3use std::{cmp::Ordering, fmt, num::NonZeroU32};
4
5#[cfg(feature = "deser")]
6use serde::{Deserialize, Serialize};
7
8/// A position in the JavaScript source code.
9///
10/// Stores both the column number and the line number.
11///
12/// Note that spans are of the form [begining, end) i.e. that the begining position is inclusive and the end position is exclusive.
13/// See test check_positions from syntax/lexer/tests.rs for an example.
14///
15/// ## Similar Implementations
16/// [V8: Location](https://cs.chromium.org/chromium/src/v8/src/parsing/scanner.h?type=cs&q=isValid+Location&g=0&l=216)
17#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
18#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
19pub struct Position {
20    /// Line number.
21    line_number: NonZeroU32,
22    /// Column number.
23    column_number: NonZeroU32,
24}
25
26impl Position {
27    /// Creates a new `Position`.
28    #[inline]
29    #[track_caller]
30    pub fn new(line_number: u32, column_number: u32) -> Self {
31        Self {
32            line_number: NonZeroU32::new(line_number).expect("line number cannot be 0"),
33            column_number: NonZeroU32::new(column_number).expect("column number cannot be 0"),
34        }
35    }
36
37    /// Gets the line number of the position.
38    #[inline]
39    pub fn line_number(self) -> u32 {
40        self.line_number.get()
41    }
42
43    /// Gets the column number of the position.
44    #[inline]
45    pub fn column_number(self) -> u32 {
46        self.column_number.get()
47    }
48}
49
50impl fmt::Display for Position {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        write!(f, "{}:{}", self.line_number, self.column_number)
53    }
54}
55
56/// A span in the JavaScript source code.
57///
58/// Stores a start position and an end position.
59#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub struct Span {
62    start: Position,
63    end: Position,
64}
65
66impl Span {
67    /// Creates a new `Span`.
68    #[inline]
69    #[track_caller]
70    pub fn new(start: Position, end: Position) -> Self {
71        assert!(start <= end, "a span cannot start after its end");
72
73        Self { start, end }
74    }
75
76    /// Gets the starting position of the span.
77    #[inline]
78    pub fn start(self) -> Position {
79        self.start
80    }
81
82    /// Gets the final position of the span.
83    #[inline]
84    pub fn end(self) -> Position {
85        self.end
86    }
87
88    /// Checks if this span inclusively contains another span or position.
89    #[inline]
90    pub fn contains<S>(self, other: S) -> bool
91    where
92        S: Into<Self>,
93    {
94        let other = other.into();
95        self.start <= other.start && self.end >= other.end
96    }
97}
98
99impl From<Position> for Span {
100    fn from(pos: Position) -> Self {
101        Self {
102            start: pos,
103            end: pos,
104        }
105    }
106}
107
108impl PartialOrd for Span {
109    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
110        if self == other {
111            Some(Ordering::Equal)
112        } else if self.end < other.start {
113            Some(Ordering::Less)
114        } else if self.start > other.end {
115            Some(Ordering::Greater)
116        } else {
117            None
118        }
119    }
120}
121
122impl fmt::Display for Span {
123    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124        write!(f, "[{}..{}]", self.start, self.end)
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::{Position, Span};
131
132    /// Checks that we cannot create a position with 0 as the column.
133    #[test]
134    #[should_panic]
135    fn invalid_position_column() {
136        Position::new(10, 0);
137    }
138
139    /// Checks that we cannot create a position with 0 as the line.
140    #[test]
141    #[should_panic]
142    fn invalid_position_line() {
143        Position::new(0, 10);
144    }
145
146    /// Checks that the `PartialEq` implementation of `Position` is consistent.
147    #[test]
148    fn position_equality() {
149        assert_eq!(Position::new(10, 50), Position::new(10, 50));
150        assert_ne!(Position::new(10, 50), Position::new(10, 51));
151        assert_ne!(Position::new(10, 50), Position::new(11, 50));
152        assert_ne!(Position::new(10, 50), Position::new(11, 51));
153    }
154
155    /// Checks that the `PartialOrd` implementation of `Position` is consistent.
156    #[test]
157    fn position_order() {
158        assert!(Position::new(10, 50) < Position::new(10, 51));
159        assert!(Position::new(9, 50) < Position::new(10, 50));
160        assert!(Position::new(10, 50) < Position::new(11, 51));
161        assert!(Position::new(10, 50) < Position::new(11, 49));
162
163        assert!(Position::new(10, 51) > Position::new(10, 50));
164        assert!(Position::new(10, 50) > Position::new(9, 50));
165        assert!(Position::new(11, 51) > Position::new(10, 50));
166        assert!(Position::new(11, 49) > Position::new(10, 50));
167    }
168
169    /// Checks that the position getters actually retreive correct values.
170    #[test]
171    fn position_getters() {
172        let pos = Position::new(10, 50);
173        assert_eq!(pos.line_number(), 10);
174        assert_eq!(pos.column_number(), 50);
175    }
176
177    /// Checks that the string representation of a position is correct.
178    #[test]
179    fn position_to_string() {
180        let pos = Position::new(10, 50);
181
182        assert_eq!("10:50", pos.to_string());
183        assert_eq!("10:50", format!("{}", pos));
184    }
185
186    /// Checks that we cannot create an invalid span.
187    #[test]
188    #[should_panic]
189    fn invalid_span() {
190        let a = Position::new(10, 30);
191        let b = Position::new(10, 50);
192        Span::new(b, a);
193    }
194
195    /// Checks that we can create valid spans.
196    #[test]
197    fn span_creation() {
198        let a = Position::new(10, 30);
199        let b = Position::new(10, 50);
200
201        let _ = Span::new(a, b);
202        let _ = Span::new(a, a);
203        let _ = Span::from(a);
204    }
205
206    /// Checks that the `PartialEq` implementation of `Span` is consistent.
207    #[test]
208    fn span_equality() {
209        let a = Position::new(10, 50);
210        let b = Position::new(10, 52);
211        let c = Position::new(11, 20);
212
213        let span_ab = Span::new(a, b);
214        let span_ab_2 = Span::new(a, b);
215        let span_ac = Span::new(a, c);
216        let span_bc = Span::new(b, c);
217
218        assert_eq!(span_ab, span_ab_2);
219        assert_ne!(span_ab, span_ac);
220        assert_ne!(span_ab, span_bc);
221        assert_ne!(span_bc, span_ac);
222
223        let span_a = Span::from(a);
224        let span_aa = Span::new(a, a);
225
226        assert_eq!(span_a, span_aa);
227    }
228
229    /// Checks that the getters retrieve the correct value.
230    #[test]
231    fn span_getters() {
232        let a = Position::new(10, 50);
233        let b = Position::new(10, 52);
234
235        let span = Span::new(a, b);
236
237        assert_eq!(span.start(), a);
238        assert_eq!(span.end(), b);
239    }
240
241    /// Checks that the `Span::contains()` method works properly.
242    #[test]
243    fn span_contains() {
244        let a = Position::new(10, 50);
245        let b = Position::new(10, 52);
246        let c = Position::new(11, 20);
247        let d = Position::new(12, 5);
248
249        let span_ac = Span::new(a, c);
250        assert!(span_ac.contains(b));
251
252        let span_ab = Span::new(a, b);
253        let span_cd = Span::new(c, d);
254
255        assert!(!span_ab.contains(span_cd));
256        assert!(span_ab.contains(b));
257
258        let span_ad = Span::new(a, d);
259        let span_bc = Span::new(b, c);
260
261        assert!(span_ad.contains(span_bc));
262        assert!(!span_bc.contains(span_ad));
263
264        let span_ac = Span::new(a, c);
265        let span_bd = Span::new(b, d);
266
267        assert!(!span_ac.contains(span_bd));
268        assert!(!span_bd.contains(span_ac));
269    }
270
271    /// Checks that the string representation of a span is correct.
272    #[test]
273    fn span_to_string() {
274        let a = Position::new(10, 50);
275        let b = Position::new(11, 20);
276        let span = Span::new(a, b);
277
278        assert_eq!("[10:50..11:20]", span.to_string());
279        assert_eq!("[10:50..11:20]", format!("{}", span));
280    }
281
282    /// Checks that the ordering of spans is correct.
283    #[test]
284    fn span_ordering() {
285        let a = Position::new(10, 50);
286        let b = Position::new(10, 52);
287        let c = Position::new(11, 20);
288        let d = Position::new(12, 5);
289
290        let span_ab = Span::new(a, b);
291        let span_cd = Span::new(c, d);
292
293        assert!(span_ab < span_cd);
294        assert!(span_cd > span_ab);
295    }
296}