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    /// | `Name`              | `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 `Name`, `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/// This struct is created by the [`iter_with_context`] method on [`Tokens`]. Refer to its
351/// documentation for more details.
352///
353/// [`iter_with_context`]: Tokens::iter_with_context
354#[derive(Debug, Clone)]
355pub struct TokenIterWithContext<'a> {
356    inner: std::slice::Iter<'a, Token>,
357    nesting: u32,
358}
359
360impl<'a> TokenIterWithContext<'a> {
361    fn new(tokens: &'a [Token]) -> TokenIterWithContext<'a> {
362        TokenIterWithContext {
363            inner: tokens.iter(),
364            nesting: 0,
365        }
366    }
367
368    /// Return the nesting level the iterator is currently in.
369    pub const fn nesting(&self) -> u32 {
370        self.nesting
371    }
372
373    /// Returns `true` if the iterator is within a parenthesized context.
374    pub const fn in_parenthesized_context(&self) -> bool {
375        self.nesting > 0
376    }
377
378    /// Returns the next [`Token`] in the iterator without consuming it.
379    pub fn peek(&self) -> Option<&'a Token> {
380        self.clone().next()
381    }
382}
383
384impl<'a> Iterator for TokenIterWithContext<'a> {
385    type Item = &'a Token;
386
387    fn next(&mut self) -> Option<Self::Item> {
388        let token = self.inner.next()?;
389
390        match token.kind() {
391            TokenKind::Lpar | TokenKind::Lbrace | TokenKind::Lsqb => self.nesting += 1,
392            TokenKind::Rpar | TokenKind::Rbrace | TokenKind::Rsqb => {
393                self.nesting = self.nesting.saturating_sub(1);
394            }
395            // This mimics the behavior of re-lexing which reduces the nesting level on the lexer.
396            // We don't need to reduce it by 1 because unlike the lexer we see the final token
397            // after recovering from every unclosed parenthesis.
398            TokenKind::Newline if self.nesting > 0 => {
399                self.nesting = 0;
400            }
401            _ => {}
402        }
403
404        Some(token)
405    }
406}
407
408impl FusedIterator for TokenIterWithContext<'_> {}
409
410#[cfg(test)]
411mod tests {
412    use std::ops::Range;
413
414    use ruff_text_size::TextSize;
415
416    use crate::token::{Token, TokenFlags, TokenKind};
417
418    use super::*;
419
420    /// Test case containing a "gap" between two tokens.
421    ///
422    /// Code: <https://play.ruff.rs/a3658340-6df8-42c5-be80-178744bf1193>
423    const TEST_CASE_WITH_GAP: [(TokenKind, Range<u32>); 10] = [
424        (TokenKind::Def, 0..3),
425        (TokenKind::Name, 4..7),
426        (TokenKind::Lpar, 7..8),
427        (TokenKind::Rpar, 8..9),
428        (TokenKind::Colon, 9..10),
429        (TokenKind::Newline, 10..11),
430        // Gap               ||..||
431        (TokenKind::Comment, 15..24),
432        (TokenKind::NonLogicalNewline, 24..25),
433        (TokenKind::Indent, 25..29),
434        (TokenKind::Pass, 29..33),
435        // No newline at the end to keep the token set full of unique tokens
436    ];
437
438    /// Helper function to create [`Tokens`] from an iterator of (kind, range).
439    fn new_tokens(tokens: impl Iterator<Item = (TokenKind, Range<u32>)>) -> Tokens {
440        Tokens::new(
441            tokens
442                .map(|(kind, range)| {
443                    Token::new(
444                        kind,
445                        TextRange::new(TextSize::new(range.start), TextSize::new(range.end)),
446                        TokenFlags::empty(),
447                    )
448                })
449                .collect(),
450        )
451    }
452
453    #[test]
454    fn tokens_after_offset_at_token_start() {
455        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
456        let after = tokens.after(TextSize::new(8));
457        assert_eq!(after.len(), 7);
458        assert_eq!(after.first().unwrap().kind(), TokenKind::Rpar);
459    }
460
461    #[test]
462    fn tokens_after_offset_at_token_end() {
463        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
464        let after = tokens.after(TextSize::new(11));
465        assert_eq!(after.len(), 4);
466        assert_eq!(after.first().unwrap().kind(), TokenKind::Comment);
467    }
468
469    #[test]
470    fn tokens_after_offset_between_tokens() {
471        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
472        let after = tokens.after(TextSize::new(13));
473        assert_eq!(after.len(), 4);
474        assert_eq!(after.first().unwrap().kind(), TokenKind::Comment);
475    }
476
477    #[test]
478    fn tokens_after_offset_at_last_token_end() {
479        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
480        let after = tokens.after(TextSize::new(33));
481        assert_eq!(after.len(), 0);
482    }
483
484    #[test]
485    #[should_panic(expected = "Offset 5 is inside token `Name 4..7`")]
486    fn tokens_after_offset_inside_token() {
487        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
488        tokens.after(TextSize::new(5));
489    }
490
491    #[test]
492    fn tokens_before_offset_at_first_token_start() {
493        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
494        let before = tokens.before(TextSize::new(0));
495        assert_eq!(before.len(), 0);
496    }
497
498    #[test]
499    fn tokens_before_offset_after_first_token_gap() {
500        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
501        let before = tokens.before(TextSize::new(3));
502        assert_eq!(before.len(), 1);
503        assert_eq!(before.last().unwrap().kind(), TokenKind::Def);
504    }
505
506    #[test]
507    fn tokens_before_offset_at_second_token_start() {
508        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
509        let before = tokens.before(TextSize::new(4));
510        assert_eq!(before.len(), 1);
511        assert_eq!(before.last().unwrap().kind(), TokenKind::Def);
512    }
513
514    #[test]
515    fn tokens_before_offset_at_token_start() {
516        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
517        let before = tokens.before(TextSize::new(8));
518        assert_eq!(before.len(), 3);
519        assert_eq!(before.last().unwrap().kind(), TokenKind::Lpar);
520    }
521
522    #[test]
523    fn tokens_before_offset_at_token_end() {
524        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
525        let before = tokens.before(TextSize::new(11));
526        assert_eq!(before.len(), 6);
527        assert_eq!(before.last().unwrap().kind(), TokenKind::Newline);
528    }
529
530    #[test]
531    fn tokens_before_offset_between_tokens() {
532        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
533        let before = tokens.before(TextSize::new(13));
534        assert_eq!(before.len(), 6);
535        assert_eq!(before.last().unwrap().kind(), TokenKind::Newline);
536    }
537
538    #[test]
539    fn tokens_before_offset_at_last_token_end() {
540        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
541        let before = tokens.before(TextSize::new(33));
542        assert_eq!(before.len(), 10);
543        assert_eq!(before.last().unwrap().kind(), TokenKind::Pass);
544    }
545
546    #[test]
547    #[should_panic(expected = "Offset 5 is inside token `Name 4..7`")]
548    fn tokens_before_offset_inside_token() {
549        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
550        tokens.before(TextSize::new(5));
551    }
552
553    #[test]
554    fn tokens_in_range_at_token_offset() {
555        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
556        let in_range = tokens.in_range(TextRange::new(4.into(), 10.into()));
557        assert_eq!(in_range.len(), 4);
558        assert_eq!(in_range.first().unwrap().kind(), TokenKind::Name);
559        assert_eq!(in_range.last().unwrap().kind(), TokenKind::Colon);
560    }
561
562    #[test]
563    fn tokens_in_range_start_offset_at_token_end() {
564        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
565        let in_range = tokens.in_range(TextRange::new(11.into(), 29.into()));
566        assert_eq!(in_range.len(), 3);
567        assert_eq!(in_range.first().unwrap().kind(), TokenKind::Comment);
568        assert_eq!(in_range.last().unwrap().kind(), TokenKind::Indent);
569    }
570
571    #[test]
572    fn tokens_in_range_end_offset_at_token_start() {
573        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
574        let in_range = tokens.in_range(TextRange::new(8.into(), 15.into()));
575        assert_eq!(in_range.len(), 3);
576        assert_eq!(in_range.first().unwrap().kind(), TokenKind::Rpar);
577        assert_eq!(in_range.last().unwrap().kind(), TokenKind::Newline);
578    }
579
580    #[test]
581    fn tokens_in_range_start_offset_between_tokens() {
582        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
583        let in_range = tokens.in_range(TextRange::new(13.into(), 29.into()));
584        assert_eq!(in_range.len(), 3);
585        assert_eq!(in_range.first().unwrap().kind(), TokenKind::Comment);
586        assert_eq!(in_range.last().unwrap().kind(), TokenKind::Indent);
587    }
588
589    #[test]
590    fn tokens_in_range_end_offset_between_tokens() {
591        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
592        let in_range = tokens.in_range(TextRange::new(9.into(), 13.into()));
593        assert_eq!(in_range.len(), 2);
594        assert_eq!(in_range.first().unwrap().kind(), TokenKind::Colon);
595        assert_eq!(in_range.last().unwrap().kind(), TokenKind::Newline);
596    }
597
598    #[test]
599    #[should_panic(expected = "Offset 5 is inside token `Name 4..7`")]
600    fn tokens_in_range_start_offset_inside_token() {
601        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
602        tokens.in_range(TextRange::new(5.into(), 10.into()));
603    }
604
605    #[test]
606    #[should_panic(expected = "Offset 6 is inside token `Name 4..7`")]
607    fn tokens_in_range_end_offset_inside_token() {
608        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
609        tokens.in_range(TextRange::new(0.into(), 6.into()));
610    }
611
612    #[test]
613    fn tokens_split_at_first_token_start() {
614        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
615        let (before, after) = tokens.split_at(TextSize::new(0));
616        assert_eq!(before.len(), 0);
617        assert_eq!(after.len(), 10);
618    }
619
620    #[test]
621    fn tokens_split_at_last_token_end() {
622        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
623        let (before, after) = tokens.split_at(TextSize::new(33));
624        assert_eq!(before.len(), 10);
625        assert_eq!(after.len(), 0);
626    }
627
628    #[test]
629    fn tokens_split_at_inside_gap() {
630        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
631        let (before, after) = tokens.split_at(TextSize::new(13));
632        assert_eq!(before.len(), 6);
633        assert_eq!(after.len(), 4);
634    }
635
636    #[test]
637    #[should_panic(expected = "Offset 18 is inside token `Comment 15..24`")]
638    fn tokens_split_at_inside_token() {
639        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
640        tokens.split_at(TextSize::new(18));
641    }
642
643    #[test]
644    fn tokens_split_at_matches_before_and_after() {
645        let offset = TextSize::new(15);
646        let tokens = new_tokens(TEST_CASE_WITH_GAP.into_iter());
647        let (before, after) = tokens.split_at(offset);
648        assert_eq!(before, tokens.before(offset));
649        assert_eq!(after, tokens.after(offset));
650    }
651
652    #[test]
653    #[should_panic(expected = "Contents of after slice different when offset at dedent")]
654    fn tokens_split_at_matches_before_and_after_zero_length() {
655        let offset = TextSize::new(13);
656        let tokens = new_tokens(
657            [
658                (TokenKind::If, 0..2),
659                (TokenKind::Name, 3..4),
660                (TokenKind::Colon, 4..5),
661                (TokenKind::Newline, 5..6),
662                (TokenKind::Indent, 6..7),
663                (TokenKind::Pass, 7..11),
664                (TokenKind::Newline, 11..12),
665                (TokenKind::NonLogicalNewline, 12..13),
666                (TokenKind::Dedent, 13..13),
667                (TokenKind::Name, 13..14),
668                (TokenKind::Newline, 14..14),
669            ]
670            .into_iter(),
671        );
672        let (before, after) = tokens.split_at(offset);
673        assert_eq!(before, tokens.before(offset));
674        assert!(
675            after == tokens.after(offset),
676            "Contents of after slice different when offset at dedent"
677        );
678    }
679}