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#[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 pub fn iter_with_context(&self) -> TokenIterWithContext<'_> {
22 TokenIterWithContext::new(&self.raw)
23 }
24
25 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 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 pub fn at_offset(&self, offset: TextSize) -> TokenAt {
93 match self.binary_search_by_start(offset) {
94 Ok(index) => {
100 let token = self[index];
101 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 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 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 assert!(
157 offset >= last.end(),
158 "Offset {offset:?} is inside token `{last:?}`",
159 );
160 }
161 before
162 }
163
164 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 assert!(
182 offset <= first.start(),
183 "Offset {offset:?} is inside token `{first:?}`",
184 );
185 }
186
187 after
188 }
189
190 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 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#[derive(Debug, Clone)]
257pub enum TokenAt {
258 None,
260
261 Single(Token),
263
264 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#[derive(Debug, Clone)]
353pub struct TokenIterWithContext<'a> {
354 inner: std::slice::Iter<'a, Token>,
355 nesting: u32,
356}
357
358impl<'a> TokenIterWithContext<'a> {
359 pub fn new(tokens: &'a [Token]) -> TokenIterWithContext<'a> {
361 TokenIterWithContext {
362 inner: tokens.iter(),
363 nesting: 0,
364 }
365 }
366
367 pub const fn nesting(&self) -> u32 {
369 self.nesting
370 }
371
372 pub const fn in_parenthesized_context(&self) -> bool {
374 self.nesting > 0
375 }
376
377 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 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 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 (TokenKind::Comment, 15..24),
431 (TokenKind::NonLogicalNewline, 24..25),
432 (TokenKind::Indent, 25..29),
433 (TokenKind::Pass, 29..33),
434 ];
436
437 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}