Skip to main content

ruff_python_ast/token/
tokens.rs

1use std::{iter::FusedIterator, ops::Deref};
2
3use super::{Token, TokenKind};
4use ruff_python_trivia::{CommentRanges, ParenthesizedExpressions, TriviaRanges};
5use ruff_text_size::{Ranged as _, TextRange, TextSize};
6use rustc_hash::FxHashSet;
7
8/// Tokens represents a vector of lexed [`Token`].
9#[derive(Debug, Clone, PartialEq, Eq)]
10#[cfg_attr(feature = "get-size", derive(get_size2::GetSize))]
11pub struct Tokens {
12    raw: Vec<Token>,
13}
14
15impl Tokens {
16    pub fn new(tokens: Vec<Token>) -> Tokens {
17        Tokens { raw: tokens }
18    }
19
20    /// Returns an iterator over all the tokens that provides context.
21    pub fn iter_with_context(&self) -> TokenIterWithContext<'_> {
22        TokenIterWithContext::new(&self.raw)
23    }
24
25    /// Performs a binary search to find the index of the **first** token that starts at the given `offset`.
26    ///
27    /// Unlike `binary_search_by_key`, this method ensures that if multiple tokens start at the same offset,
28    /// it returns the index of the first one. Multiple tokens can start at the same offset in cases where
29    /// zero-length tokens are involved (like `Dedent` or `Newline` at the end of the file).
30    fn binary_search_by_start(&self, offset: TextSize) -> Result<usize, usize> {
31        let partition_point = self.partition_point(|token| token.start() < offset);
32
33        let after = &self[partition_point..];
34
35        if after.first().is_some_and(|first| first.start() == offset) {
36            Ok(partition_point)
37        } else {
38            Err(partition_point)
39        }
40    }
41
42    /// Returns a slice of [`Token`] that are within the given `range`.
43    ///
44    /// The start and end offset of the given range should be either:
45    /// 1. Token boundary
46    /// 2. Gap between the tokens
47    ///
48    /// For example, considering the following tokens and their corresponding range:
49    ///
50    /// | Token               | Range     |
51    /// |---------------------|-----------|
52    /// | `Def`               | `0..3`    |
53    /// | `Identifier`        | `4..7`    |
54    /// | `Lpar`              | `7..8`    |
55    /// | `Rpar`              | `8..9`    |
56    /// | `Colon`             | `9..10`   |
57    /// | `Newline`           | `10..11`  |
58    /// | `Comment`           | `15..24`  |
59    /// | `NonLogicalNewline` | `24..25`  |
60    /// | `Indent`            | `25..29`  |
61    /// | `Pass`              | `29..33`  |
62    ///
63    /// Here, for (1) a token boundary is considered either the start or end offset of any of the
64    /// above tokens. For (2), the gap would be any offset between the `Newline` and `Comment`
65    /// token which are 12, 13, and 14.
66    ///
67    /// Examples:
68    /// 1) `4..10` would give `Identifier`, `Lpar`, `Rpar`, `Colon`
69    /// 2) `11..25` would give `Comment`, `NonLogicalNewline`
70    /// 3) `12..25` would give same as (2) and offset 12 is in the "gap"
71    /// 4) `9..12` would give `Colon`, `Newline` and offset 12 is in the "gap"
72    /// 5) `18..27` would panic because both the start and end offset is within a token
73    ///
74    /// ## Note
75    ///
76    /// The returned slice can contain the [`TokenKind::Unknown`] token if there was a lexical
77    /// error encountered within the given range.
78    ///
79    /// # Panics
80    ///
81    /// If either the start or end offset of the given range is within a token range.
82    pub fn in_range(&self, range: TextRange) -> &[Token] {
83        let tokens_after_start = self.after(range.start());
84
85        Self::before_impl(tokens_after_start, range.end())
86    }
87
88    /// Searches the token(s) at `offset`.
89    ///
90    /// Returns [`TokenAt::Between`] if `offset` points directly inbetween two tokens
91    /// (the left token ends at `offset` and the right token starts at `offset`).
92    pub fn at_offset(&self, offset: TextSize) -> TokenAt {
93        match self.binary_search_by_start(offset) {
94            // The token at `index` starts exactly at `offset.
95            // ```python
96            // object.attribute
97            //        ^ OFFSET
98            // ```
99            Ok(index) => {
100                let token = self[index];
101                // `token` starts exactly at `offset`. Test if the offset is right between
102                // `token` and the previous token (if there's any)
103                if let Some(previous) = index.checked_sub(1).map(|idx| self[idx]) {
104                    if previous.end() == offset {
105                        return TokenAt::Between(previous, token);
106                    }
107                }
108
109                TokenAt::Single(token)
110            }
111
112            // No token found that starts exactly at the given offset. But it's possible that
113            // the token starting before `offset` fully encloses `offset` (it's end range ends after `offset`).
114            // ```python
115            // object.attribute
116            //   ^ OFFSET
117            // # or
118            // if True:
119            //     print("test")
120            //  ^ OFFSET
121            // ```
122            Err(index) => {
123                if let Some(previous) = index.checked_sub(1).map(|idx| self[idx]) {
124                    if previous.range().contains_inclusive(offset) {
125                        return TokenAt::Single(previous);
126                    }
127                }
128
129                TokenAt::None
130            }
131        }
132    }
133
134    /// Returns a slice of tokens before the given [`TextSize`] offset.
135    ///
136    /// If the given offset is between two tokens, the returned slice will end just before the
137    /// following token. In other words, if the offset is between the end of previous token and
138    /// start of next token, the returned slice will end just before the next token.
139    ///
140    /// # Panics
141    ///
142    /// If the given offset is inside a token range at any point
143    /// other than the start of the range.
144    pub fn before(&self, offset: TextSize) -> &[Token] {
145        Self::before_impl(&self.raw, offset)
146    }
147
148    fn before_impl(tokens: &[Token], offset: TextSize) -> &[Token] {
149        let partition_point = tokens.partition_point(|token| token.start() < offset);
150        let before = &tokens[..partition_point];
151
152        if let Some(last) = before.last() {
153            // If it's equal to the end offset, then it's at a token boundary which is
154            // valid. If it's greater than the end offset, then it's in the gap between
155            // the tokens which is valid as well.
156            assert!(
157                offset >= last.end(),
158                "Offset {offset:?} is inside token `{last:?}`",
159            );
160        }
161        before
162    }
163
164    /// Returns a slice of tokens after the given [`TextSize`] offset.
165    ///
166    /// If the given offset is between two tokens, the returned slice will start from the following
167    /// token. In other words, if the offset is between the end of previous token and start of next
168    /// token, the returned slice will start from the next token.
169    ///
170    /// # Panics
171    ///
172    /// If the given offset is inside a token range at any point
173    /// other than the start of the range.
174    pub fn after(&self, offset: TextSize) -> &[Token] {
175        let partition_point = self.partition_point(|token| token.end() <= offset);
176        let after = &self[partition_point..];
177
178        if let Some(first) = after.first() {
179            // valid. If it's greater than the end offset, then it's in the gap between
180            // the tokens which is valid as well.
181            assert!(
182                offset <= first.start(),
183                "Offset {offset:?} is inside token `{first:?}`",
184            );
185        }
186
187        after
188    }
189
190    /// Returns a pair of token slices from both before and after the given [`TextSize`] offset.
191    ///
192    /// If the given offset is between two tokens, the "before" slice will end just before the
193    /// following token. In other words, if the offset is between the end of previous token and
194    /// start of next token, the "before" slice will end just before the next token. The "after"
195    /// slice will contain the rest of the tokens.
196    ///
197    /// Note that the contents of the "after" slice may differ from the results of calling `after()`
198    /// directly, particularly when the given offset occurs on zero-width tokens like `Dedent`.
199    ///
200    /// # Panics
201    ///
202    /// If the given offset is inside a token range at any point
203    /// other than the start of the range.
204    pub fn split_at(&self, offset: TextSize) -> (&[Token], &[Token]) {
205        let partition_point = self.partition_point(|token| token.start() < offset);
206        let (before, after) = &self.raw.split_at(partition_point);
207
208        if let Some(last) = before.last() {
209            assert!(
210                offset >= last.end(),
211                "Offset {offset:?} is inside token `{last:?}`"
212            );
213        }
214        (before, after)
215    }
216
217    /// Return the range of the token at the given offset.
218    ///
219    /// Returns an empty range at the given offset if there's no token at the offset,
220    /// or if the offset is between two tokens.
221    pub fn token_range(&self, offset: TextSize) -> TextRange {
222        match self.at_offset(offset) {
223            TokenAt::Single(token) => token.range(),
224            TokenAt::None | TokenAt::Between(..) => TextRange::empty(offset),
225        }
226    }
227}
228
229impl IntoIterator for Tokens {
230    type Item = Token;
231    type IntoIter = std::vec::IntoIter<Token>;
232
233    fn into_iter(self) -> Self::IntoIter {
234        self.raw.into_iter()
235    }
236}
237
238impl<'a> IntoIterator for &'a Tokens {
239    type Item = &'a Token;
240    type IntoIter = std::slice::Iter<'a, Token>;
241
242    fn into_iter(self) -> Self::IntoIter {
243        self.iter()
244    }
245}
246
247impl Deref for Tokens {
248    type Target = [Token];
249
250    fn deref(&self) -> &Self::Target {
251        &self.raw
252    }
253}
254
255/// A token that encloses a given offset or ends exactly at it.
256#[derive(Debug, Clone)]
257pub enum TokenAt {
258    /// There's no token at the given offset
259    None,
260
261    /// There's a single token at the given offset.
262    Single(Token),
263
264    /// The offset falls exactly between two tokens. E.g. `CURSOR` in `call<CURSOR>(arguments)` is
265    /// positioned exactly between the `call` and `(` tokens.
266    Between(Token, Token),
267}
268
269impl Iterator for TokenAt {
270    type Item = Token;
271
272    fn next(&mut self) -> Option<Self::Item> {
273        match *self {
274            TokenAt::None => None,
275            TokenAt::Single(token) => {
276                *self = TokenAt::None;
277                Some(token)
278            }
279            TokenAt::Between(first, second) => {
280                *self = TokenAt::Single(second);
281                Some(first)
282            }
283        }
284    }
285}
286
287impl FusedIterator for TokenAt {}
288
289impl From<&Tokens> for CommentRanges {
290    fn from(tokens: &Tokens) -> Self {
291        let mut ranges = vec![];
292
293        for token in tokens {
294            if token.kind() == TokenKind::Comment {
295                ranges.push(token.range());
296            }
297        }
298
299        CommentRanges::new(ranges)
300    }
301}
302
303impl From<&Tokens> for TriviaRanges {
304    fn from(tokens: &Tokens) -> Self {
305        let mut comments = vec![];
306        let mut parenthesized = FxHashSet::default();
307        let mut stack = Vec::<Option<TextSize>>::new();
308        let mut previous_end = None;
309
310        for token in tokens {
311            if token.kind() == TokenKind::Comment {
312                comments.push(token.range());
313            }
314
315            if token.kind().is_trivia() {
316                continue;
317            }
318
319            match token.kind() {
320                TokenKind::Lpar => {
321                    if let Some(start) = stack.last_mut() {
322                        start.get_or_insert(token.start());
323                    }
324                    stack.push(None);
325                }
326                TokenKind::Rpar => {
327                    if let (Some(Some(start)), Some(end)) = (stack.pop(), previous_end) {
328                        parenthesized.insert(TextRange::new(start, end));
329                    }
330                }
331                _ => {
332                    if let Some(start) = stack.last_mut() {
333                        start.get_or_insert(token.start());
334                    }
335                }
336            }
337
338            previous_end = Some(token.end());
339        }
340
341        TriviaRanges::new(
342            CommentRanges::new(comments),
343            ParenthesizedExpressions::new(parenthesized),
344        )
345    }
346}
347
348/// An iterator over the [`Token`]s with context.
349///
350/// Use [`Tokens::iter_with_context`] to iterate over all tokens, or [`Self::new`] to iterate over a
351/// token slice.
352#[derive(Debug, Clone)]
353pub struct TokenIterWithContext<'a> {
354    inner: std::slice::Iter<'a, Token>,
355    nesting: u32,
356}
357
358impl<'a> TokenIterWithContext<'a> {
359    /// Creates an iterator with a nesting level of zero at the start of the token slice.
360    pub fn new(tokens: &'a [Token]) -> TokenIterWithContext<'a> {
361        TokenIterWithContext {
362            inner: tokens.iter(),
363            nesting: 0,
364        }
365    }
366
367    /// Return the nesting level the iterator is currently in.
368    pub const fn nesting(&self) -> u32 {
369        self.nesting
370    }
371
372    /// Returns `true` if the iterator is within a parenthesized context.
373    pub const fn in_parenthesized_context(&self) -> bool {
374        self.nesting > 0
375    }
376
377    /// Returns the next [`Token`] in the iterator without consuming it.
378    pub fn peek(&self) -> Option<&'a Token> {
379        self.clone().next()
380    }
381}
382
383impl<'a> Iterator for TokenIterWithContext<'a> {
384    type Item = &'a Token;
385
386    fn next(&mut self) -> Option<Self::Item> {
387        let token = self.inner.next()?;
388
389        match token.kind() {
390            TokenKind::Lpar | TokenKind::Lbrace | TokenKind::Lsqb => self.nesting += 1,
391            TokenKind::Rpar | TokenKind::Rbrace | TokenKind::Rsqb => {
392                self.nesting = self.nesting.saturating_sub(1);
393            }
394            // This mimics the behavior of re-lexing which reduces the nesting level on the lexer.
395            // We don't need to reduce it by 1 because unlike the lexer we see the final token
396            // after recovering from every unclosed parenthesis.
397            TokenKind::Newline if self.nesting > 0 => {
398                self.nesting = 0;
399            }
400            _ => {}
401        }
402
403        Some(token)
404    }
405}
406
407impl FusedIterator for TokenIterWithContext<'_> {}
408
409#[cfg(test)]
410mod tests {
411    use std::ops::Range;
412
413    use ruff_text_size::TextSize;
414
415    use crate::token::{Token, TokenFlags, TokenKind};
416
417    use super::*;
418
419    /// Test case containing a "gap" between two tokens.
420    ///
421    /// Code: <https://play.ruff.rs/a3658340-6df8-42c5-be80-178744bf1193>
422    const TEST_CASE_WITH_GAP: [(TokenKind, Range<u32>); 10] = [
423        (TokenKind::Def, 0..3),
424        (TokenKind::Identifier, 4..7),
425        (TokenKind::Lpar, 7..8),
426        (TokenKind::Rpar, 8..9),
427        (TokenKind::Colon, 9..10),
428        (TokenKind::Newline, 10..11),
429        // Gap               ||..||
430        (TokenKind::Comment, 15..24),
431        (TokenKind::NonLogicalNewline, 24..25),
432        (TokenKind::Indent, 25..29),
433        (TokenKind::Pass, 29..33),
434        // No newline at the end to keep the token set full of unique tokens
435    ];
436
437    /// Helper function to create [`Tokens`] from an iterator of (kind, range).
438    fn new_tokens(tokens: impl Iterator<Item = (TokenKind, Range<u32>)>) -> Tokens {
439        Tokens::new(
440            tokens
441                .map(|(kind, range)| {
442                    Token::new(
443                        kind,
444                        TextRange::new(TextSize::new(range.start), TextSize::new(range.end)),
445                        TokenFlags::empty(),
446                    )
447                })
448                .collect(),
449        )
450    }
451
452    #[test]
453    fn tokens_after_offset_at_token_start() {
454        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
455        let after = tokens.after(TextSize::new(8));
456        assert_eq!(after.len(), 7);
457        assert_eq!(after.first().unwrap().kind(), TokenKind::Rpar);
458    }
459
460    #[test]
461    fn tokens_after_offset_at_token_end() {
462        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
463        let after = tokens.after(TextSize::new(11));
464        assert_eq!(after.len(), 4);
465        assert_eq!(after.first().unwrap().kind(), TokenKind::Comment);
466    }
467
468    #[test]
469    fn tokens_after_offset_between_tokens() {
470        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
471        let after = tokens.after(TextSize::new(13));
472        assert_eq!(after.len(), 4);
473        assert_eq!(after.first().unwrap().kind(), TokenKind::Comment);
474    }
475
476    #[test]
477    fn tokens_after_offset_at_last_token_end() {
478        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
479        let after = tokens.after(TextSize::new(33));
480        assert_eq!(after.len(), 0);
481    }
482
483    #[test]
484    #[should_panic(expected = "Offset 5 is inside token `Identifier 4..7`")]
485    fn tokens_after_offset_inside_token() {
486        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
487        tokens.after(TextSize::new(5));
488    }
489
490    #[test]
491    fn tokens_before_offset_at_first_token_start() {
492        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
493        let before = tokens.before(TextSize::new(0));
494        assert_eq!(before.len(), 0);
495    }
496
497    #[test]
498    fn tokens_before_offset_after_first_token_gap() {
499        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
500        let before = tokens.before(TextSize::new(3));
501        assert_eq!(before.len(), 1);
502        assert_eq!(before.last().unwrap().kind(), TokenKind::Def);
503    }
504
505    #[test]
506    fn tokens_before_offset_at_second_token_start() {
507        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
508        let before = tokens.before(TextSize::new(4));
509        assert_eq!(before.len(), 1);
510        assert_eq!(before.last().unwrap().kind(), TokenKind::Def);
511    }
512
513    #[test]
514    fn tokens_before_offset_at_token_start() {
515        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
516        let before = tokens.before(TextSize::new(8));
517        assert_eq!(before.len(), 3);
518        assert_eq!(before.last().unwrap().kind(), TokenKind::Lpar);
519    }
520
521    #[test]
522    fn tokens_before_offset_at_token_end() {
523        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
524        let before = tokens.before(TextSize::new(11));
525        assert_eq!(before.len(), 6);
526        assert_eq!(before.last().unwrap().kind(), TokenKind::Newline);
527    }
528
529    #[test]
530    fn tokens_before_offset_between_tokens() {
531        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
532        let before = tokens.before(TextSize::new(13));
533        assert_eq!(before.len(), 6);
534        assert_eq!(before.last().unwrap().kind(), TokenKind::Newline);
535    }
536
537    #[test]
538    fn tokens_before_offset_at_last_token_end() {
539        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
540        let before = tokens.before(TextSize::new(33));
541        assert_eq!(before.len(), 10);
542        assert_eq!(before.last().unwrap().kind(), TokenKind::Pass);
543    }
544
545    #[test]
546    #[should_panic(expected = "Offset 5 is inside token `Identifier 4..7`")]
547    fn tokens_before_offset_inside_token() {
548        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
549        tokens.before(TextSize::new(5));
550    }
551
552    #[test]
553    fn tokens_in_range_at_token_offset() {
554        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
555        let in_range = tokens.in_range(TextRange::new(4.into(), 10.into()));
556        assert_eq!(in_range.len(), 4);
557        assert_eq!(in_range.first().unwrap().kind(), TokenKind::Identifier);
558        assert_eq!(in_range.last().unwrap().kind(), TokenKind::Colon);
559    }
560
561    #[test]
562    fn tokens_in_range_start_offset_at_token_end() {
563        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
564        let in_range = tokens.in_range(TextRange::new(11.into(), 29.into()));
565        assert_eq!(in_range.len(), 3);
566        assert_eq!(in_range.first().unwrap().kind(), TokenKind::Comment);
567        assert_eq!(in_range.last().unwrap().kind(), TokenKind::Indent);
568    }
569
570    #[test]
571    fn tokens_in_range_end_offset_at_token_start() {
572        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
573        let in_range = tokens.in_range(TextRange::new(8.into(), 15.into()));
574        assert_eq!(in_range.len(), 3);
575        assert_eq!(in_range.first().unwrap().kind(), TokenKind::Rpar);
576        assert_eq!(in_range.last().unwrap().kind(), TokenKind::Newline);
577    }
578
579    #[test]
580    fn tokens_in_range_start_offset_between_tokens() {
581        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
582        let in_range = tokens.in_range(TextRange::new(13.into(), 29.into()));
583        assert_eq!(in_range.len(), 3);
584        assert_eq!(in_range.first().unwrap().kind(), TokenKind::Comment);
585        assert_eq!(in_range.last().unwrap().kind(), TokenKind::Indent);
586    }
587
588    #[test]
589    fn tokens_in_range_end_offset_between_tokens() {
590        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
591        let in_range = tokens.in_range(TextRange::new(9.into(), 13.into()));
592        assert_eq!(in_range.len(), 2);
593        assert_eq!(in_range.first().unwrap().kind(), TokenKind::Colon);
594        assert_eq!(in_range.last().unwrap().kind(), TokenKind::Newline);
595    }
596
597    #[test]
598    #[should_panic(expected = "Offset 5 is inside token `Identifier 4..7`")]
599    fn tokens_in_range_start_offset_inside_token() {
600        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
601        tokens.in_range(TextRange::new(5.into(), 10.into()));
602    }
603
604    #[test]
605    #[should_panic(expected = "Offset 6 is inside token `Identifier 4..7`")]
606    fn tokens_in_range_end_offset_inside_token() {
607        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
608        tokens.in_range(TextRange::new(0.into(), 6.into()));
609    }
610
611    #[test]
612    fn tokens_split_at_first_token_start() {
613        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
614        let (before, after) = tokens.split_at(TextSize::new(0));
615        assert_eq!(before.len(), 0);
616        assert_eq!(after.len(), 10);
617    }
618
619    #[test]
620    fn tokens_split_at_last_token_end() {
621        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
622        let (before, after) = tokens.split_at(TextSize::new(33));
623        assert_eq!(before.len(), 10);
624        assert_eq!(after.len(), 0);
625    }
626
627    #[test]
628    fn tokens_split_at_inside_gap() {
629        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
630        let (before, after) = tokens.split_at(TextSize::new(13));
631        assert_eq!(before.len(), 6);
632        assert_eq!(after.len(), 4);
633    }
634
635    #[test]
636    #[should_panic(expected = "Offset 18 is inside token `Comment 15..24`")]
637    fn tokens_split_at_inside_token() {
638        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
639        tokens.split_at(TextSize::new(18));
640    }
641
642    #[test]
643    fn tokens_split_at_matches_before_and_after() {
644        let offset = TextSize::new(15);
645        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
646        let (before, after) = tokens.split_at(offset);
647        assert_eq!(before, tokens.before(offset));
648        assert_eq!(after, tokens.after(offset));
649    }
650
651    #[test]
652    #[should_panic(expected = "Contents of after slice different when offset at dedent")]
653    fn tokens_split_at_matches_before_and_after_zero_length() {
654        let offset = TextSize::new(13);
655        let tokens = new_tokens(
656            [
657                (TokenKind::If, 0..2),
658                (TokenKind::Identifier, 3..4),
659                (TokenKind::Colon, 4..5),
660                (TokenKind::Newline, 5..6),
661                (TokenKind::Indent, 6..7),
662                (TokenKind::Pass, 7..11),
663                (TokenKind::Newline, 11..12),
664                (TokenKind::NonLogicalNewline, 12..13),
665                (TokenKind::Dedent, 13..13),
666                (TokenKind::Identifier, 13..14),
667                (TokenKind::Newline, 14..14),
668            ]
669            .into_iter(),
670        );
671        let (before, after) = tokens.split_at(offset);
672        assert_eq!(before, tokens.before(offset));
673        assert!(
674            after == tokens.after(offset),
675            "Contents of after slice different when offset at dedent"
676        );
677    }
678}