Skip to main content

rustpython_ruff_python_ast/token/
parentheses.rs

1use ruff_text_size::{Ranged, TextLen, TextRange};
2
3use super::{TokenKind, Tokens};
4use crate::{AnyNodeRef, ExprRef};
5
6/// Returns an iterator over the ranges of the optional parentheses surrounding an expression.
7///
8/// E.g. for `((f()))` with `f()` as expression, the iterator returns the ranges (1, 6) and (0, 7).
9///
10/// Note that without a parent the range can be inaccurate, e.g. `f(a)` we falsely return a set of
11/// parentheses around `a` even if the parentheses actually belong to `f`. That is why you should
12/// generally prefer [`parenthesized_range`].
13pub fn parentheses_iterator<'a>(
14    expr: ExprRef<'a>,
15    parent: Option<AnyNodeRef>,
16    tokens: &'a Tokens,
17) -> impl Iterator<Item = TextRange> + 'a {
18    let after_tokens = if let Some(parent) = parent {
19        // If the parent is a node that brings its own parentheses, exclude the closing parenthesis
20        // from our search range. Otherwise, we risk matching on calls, like `func(x)`, for which
21        // the open and close parentheses are part of the `Arguments` node.
22        let exclusive_parent_end = if parent.is_arguments() {
23            parent.end() - ")".text_len()
24        } else {
25            parent.end()
26        };
27
28        tokens.in_range(TextRange::new(expr.end(), exclusive_parent_end))
29    } else {
30        tokens.after(expr.end())
31    };
32
33    let right_parens = after_tokens
34        .iter()
35        .filter(|token| !token.kind().is_trivia())
36        .take_while(move |token| token.kind() == TokenKind::Rpar);
37
38    let left_parens = tokens
39        .before(expr.start())
40        .iter()
41        .rev()
42        .filter(|token| !token.kind().is_trivia())
43        .take_while(|token| token.kind() == TokenKind::Lpar);
44
45    right_parens
46        .zip(left_parens)
47        .map(|(right, left)| TextRange::new(left.start(), right.end()))
48}
49
50/// Returns the [`TextRange`] of a given expression including parentheses, if the expression is
51/// parenthesized; or `None`, if the expression is not parenthesized.
52pub fn parenthesized_range(
53    expr: ExprRef,
54    parent: AnyNodeRef,
55    tokens: &Tokens,
56) -> Option<TextRange> {
57    parentheses_iterator(expr, Some(parent), tokens).last()
58}