diff --git a/crates/ruff_python_ast/src/parenthesize.rs b/crates/ruff_python_ast/src/parenthesize.rs index a7fb1224ce..786ca0572c 100644 --- a/crates/ruff_python_ast/src/parenthesize.rs +++ b/crates/ruff_python_ast/src/parenthesize.rs @@ -11,6 +11,8 @@ use crate::ExprRef; /// Note that without a parent the range can be inaccurate, e.g. `f(a)` we falsely return a set of /// parentheses around `a` even if the parentheses actually belong to `f`. That is why you should /// generally prefer [`parenthesized_range`]. +/// +/// Prefer [`crate::token::parentheses_iterator`] if you have access to [`crate::token::Tokens`]. pub fn parentheses_iterator<'a>( expr: ExprRef<'a>, parent: Option, @@ -57,6 +59,8 @@ pub fn parentheses_iterator<'a>( /// Returns the [`TextRange`] of a given expression including parentheses, if the expression is /// parenthesized; or `None`, if the expression is not parenthesized. +/// +/// Prefer [`crate::token::parenthesized_range`] if you have access to [`crate::token::Tokens`]. pub fn parenthesized_range( expr: ExprRef, parent: AnyNodeRef, diff --git a/crates/ruff_python_ast/src/token.rs b/crates/ruff_python_ast/src/token.rs index fc1b62a366..4b9d98ec5c 100644 --- a/crates/ruff_python_ast/src/token.rs +++ b/crates/ruff_python_ast/src/token.rs @@ -16,8 +16,10 @@ use crate::str_prefix::{ use crate::{AnyStringFlags, BoolOp, Operator, StringFlags, UnaryOp}; use ruff_text_size::{Ranged, TextRange}; +mod parentheses; mod tokens; +pub use parentheses::{parentheses_iterator, parenthesized_range}; pub use tokens::{TokenAt, TokenIterWithContext, Tokens}; #[derive(Clone, Copy, PartialEq, Eq)] diff --git a/crates/ruff_python_ast/src/token/parentheses.rs b/crates/ruff_python_ast/src/token/parentheses.rs new file mode 100644 index 0000000000..c1d6f40650 --- /dev/null +++ b/crates/ruff_python_ast/src/token/parentheses.rs @@ -0,0 +1,58 @@ +use ruff_text_size::{Ranged, TextLen, TextRange}; + +use super::{TokenKind, Tokens}; +use crate::{AnyNodeRef, ExprRef}; + +/// Returns an iterator over the ranges of the optional parentheses surrounding an expression. +/// +/// E.g. for `((f()))` with `f()` as expression, the iterator returns the ranges (1, 6) and (0, 7). +/// +/// Note that without a parent the range can be inaccurate, e.g. `f(a)` we falsely return a set of +/// parentheses around `a` even if the parentheses actually belong to `f`. That is why you should +/// generally prefer [`parenthesized_range`]. +pub fn parentheses_iterator<'a>( + expr: ExprRef<'a>, + parent: Option, + tokens: &'a Tokens, +) -> impl Iterator + 'a { + let after_tokens = if let Some(parent) = parent { + // If the parent is a node that brings its own parentheses, exclude the closing parenthesis + // from our search range. Otherwise, we risk matching on calls, like `func(x)`, for which + // the open and close parentheses are part of the `Arguments` node. + let exclusive_parent_end = if parent.is_arguments() { + parent.end() - ")".text_len() + } else { + parent.end() + }; + + tokens.in_range(TextRange::new(expr.end(), exclusive_parent_end)) + } else { + tokens.after(expr.end()) + }; + + let right_parens = after_tokens + .iter() + .filter(|token| !token.kind().is_trivia()) + .take_while(move |token| token.kind() == TokenKind::Rpar); + + let left_parens = tokens + .before(expr.start()) + .iter() + .rev() + .filter(|token| !token.kind().is_trivia()) + .take_while(|token| token.kind() == TokenKind::Lpar); + + right_parens + .zip(left_parens) + .map(|(right, left)| TextRange::new(left.start(), right.end())) +} + +/// Returns the [`TextRange`] of a given expression including parentheses, if the expression is +/// parenthesized; or `None`, if the expression is not parenthesized. +pub fn parenthesized_range( + expr: ExprRef, + parent: AnyNodeRef, + tokens: &Tokens, +) -> Option { + parentheses_iterator(expr, Some(parent), tokens).last() +} diff --git a/crates/ruff_python_ast_integration_tests/tests/parentheses.rs b/crates/ruff_python_ast_integration_tests/tests/parentheses.rs new file mode 100644 index 0000000000..479c456c39 --- /dev/null +++ b/crates/ruff_python_ast_integration_tests/tests/parentheses.rs @@ -0,0 +1,199 @@ +//! Tests for [`ruff_python_ast::tokens::parentheses_iterator`] and +//! [`ruff_python_ast::tokens::parenthesized_range`]. + +use ruff_python_ast::{ + self as ast, Expr, + token::{parentheses_iterator, parenthesized_range}, +}; +use ruff_python_parser::parse_module; + +#[test] +fn test_no_parentheses() { + let source = "x = 2 + 2"; + let parsed = parse_module(source).expect("should parse valid python"); + let tokens = parsed.tokens(); + let module = parsed.syntax(); + + let stmt = module.body.first().expect("module should have a statement"); + let ast::Stmt::Assign(assign) = stmt else { + panic!("expected `Assign` statement, got {stmt:?}"); + }; + + let result = parenthesized_range(assign.value.as_ref().into(), stmt.into(), tokens); + assert_eq!(result, None); +} + +#[test] +fn test_single_parentheses() { + let source = "x = (2 + 2)"; + let parsed = parse_module(source).expect("should parse valid python"); + let tokens = parsed.tokens(); + let module = parsed.syntax(); + + let stmt = module.body.first().expect("module should have a statement"); + let ast::Stmt::Assign(assign) = stmt else { + panic!("expected `Assign` statement, got {stmt:?}"); + }; + + let result = parenthesized_range(assign.value.as_ref().into(), stmt.into(), tokens); + let range = result.expect("should find parentheses"); + assert_eq!(&source[range], "(2 + 2)"); +} + +#[test] +fn test_double_parentheses() { + let source = "x = ((2 + 2))"; + let parsed = parse_module(source).expect("should parse valid python"); + let tokens = parsed.tokens(); + let module = parsed.syntax(); + + let stmt = module.body.first().expect("module should have a statement"); + let ast::Stmt::Assign(assign) = stmt else { + panic!("expected `Assign` statement, got {stmt:?}"); + }; + + let result = parenthesized_range(assign.value.as_ref().into(), stmt.into(), tokens); + let range = result.expect("should find parentheses"); + assert_eq!(&source[range], "((2 + 2))"); +} + +#[test] +fn test_parentheses_with_whitespace() { + let source = "x = ( 2 + 2 )"; + let parsed = parse_module(source).expect("should parse valid python"); + let tokens = parsed.tokens(); + let module = parsed.syntax(); + + let stmt = module.body.first().expect("module should have a statement"); + let ast::Stmt::Assign(assign) = stmt else { + panic!("expected `Assign` statement, got {stmt:?}"); + }; + + let result = parenthesized_range(assign.value.as_ref().into(), stmt.into(), tokens); + let range = result.expect("should find parentheses"); + assert_eq!(&source[range], "( 2 + 2 )"); +} + +#[test] +fn test_parentheses_with_comments() { + let source = "x = ( # comment\n 2 + 2\n)"; + let parsed = parse_module(source).expect("should parse valid python"); + let tokens = parsed.tokens(); + let module = parsed.syntax(); + + let stmt = module.body.first().expect("module should have a statement"); + let ast::Stmt::Assign(assign) = stmt else { + panic!("expected `Assign` statement, got {stmt:?}"); + }; + + let result = parenthesized_range(assign.value.as_ref().into(), stmt.into(), tokens); + let range = result.expect("should find parentheses"); + assert_eq!(&source[range], "( # comment\n 2 + 2\n)"); +} + +#[test] +fn test_parenthesized_range_multiple() { + let source = "x = (((2 + 2)))"; + let parsed = parse_module(source).expect("should parse valid python"); + let tokens = parsed.tokens(); + let module = parsed.syntax(); + + let stmt = module.body.first().expect("module should have a statement"); + let ast::Stmt::Assign(assign) = stmt else { + panic!("expected `Assign` statement, got {stmt:?}"); + }; + + let result = parenthesized_range(assign.value.as_ref().into(), stmt.into(), tokens); + let range = result.expect("should find parentheses"); + assert_eq!(&source[range], "(((2 + 2)))"); +} + +#[test] +fn test_parentheses_iterator_multiple() { + let source = "x = (((2 + 2)))"; + let parsed = parse_module(source).expect("should parse valid python"); + let tokens = parsed.tokens(); + let module = parsed.syntax(); + + let stmt = module.body.first().expect("module should have a statement"); + let ast::Stmt::Assign(assign) = stmt else { + panic!("expected `Assign` statement, got {stmt:?}"); + }; + + let ranges: Vec<_> = + parentheses_iterator(assign.value.as_ref().into(), Some(stmt.into()), tokens).collect(); + assert_eq!(ranges.len(), 3); + assert_eq!(&source[ranges[0]], "(2 + 2)"); + assert_eq!(&source[ranges[1]], "((2 + 2))"); + assert_eq!(&source[ranges[2]], "(((2 + 2)))"); +} + +#[test] +fn test_call_arguments_not_counted() { + let source = "f(x)"; + let parsed = parse_module(source).expect("should parse valid python"); + let tokens = parsed.tokens(); + let module = parsed.syntax(); + + let stmt = module.body.first().expect("module should have a statement"); + let ast::Stmt::Expr(expr_stmt) = stmt else { + panic!("expected `Expr` statement, got {stmt:?}"); + }; + + let Expr::Call(call) = expr_stmt.value.as_ref() else { + panic!("expected Call expression, got {:?}", expr_stmt.value); + }; + + let arg = call + .arguments + .args + .first() + .expect("call should have an argument"); + let result = parenthesized_range(arg.into(), (&call.arguments).into(), tokens); + // The parentheses belong to the call, not the argument + assert_eq!(result, None); +} + +#[test] +fn test_call_with_parenthesized_argument() { + let source = "f((x))"; + let parsed = parse_module(source).expect("should parse valid python"); + let tokens = parsed.tokens(); + let module = parsed.syntax(); + + let stmt = module.body.first().expect("module should have a statement"); + let ast::Stmt::Expr(expr_stmt) = stmt else { + panic!("expected Expr statement, got {stmt:?}"); + }; + + let Expr::Call(call) = expr_stmt.value.as_ref() else { + panic!("expected `Call` expression, got {:?}", expr_stmt.value); + }; + + let arg = call + .arguments + .args + .first() + .expect("call should have an argument"); + let result = parenthesized_range(arg.into(), (&call.arguments).into(), tokens); + + let range = result.expect("should find parentheses around argument"); + assert_eq!(&source[range], "(x)"); +} + +#[test] +fn test_multiline_with_parentheses() { + let source = "x = (\n 2 + 2 + 2\n)"; + let parsed = parse_module(source).expect("should parse valid python"); + let tokens = parsed.tokens(); + let module = parsed.syntax(); + + let stmt = module.body.first().expect("module should have a statement"); + let ast::Stmt::Assign(assign) = stmt else { + panic!("expected `Assign` statement, got {stmt:?}"); + }; + + let result = parenthesized_range(assign.value.as_ref().into(), stmt.into(), tokens); + let range = result.expect("should find parentheses"); + assert_eq!(&source[range], "(\n 2 + 2 + 2\n)"); +} diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md index 12eb74d15a..5ac36c4fb9 100644 --- a/crates/ty/docs/rules.md +++ b/crates/ty/docs/rules.md @@ -39,7 +39,7 @@ def test(): -> "int": Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -63,7 +63,7 @@ Calling a non-callable object will raise a `TypeError` at runtime. Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -95,7 +95,7 @@ f(int) # error Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -126,7 +126,7 @@ a = 1 Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -158,7 +158,7 @@ class C(A, B): ... Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -190,7 +190,7 @@ class B(A): ... Default level: error · Preview (since 1.0.0) · Related issues · -View source +View source @@ -218,7 +218,7 @@ type B = A Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -245,7 +245,7 @@ class B(A, A): ... Default level: error · Added in 0.0.1-alpha.12 · Related issues · -View source +View source @@ -357,7 +357,7 @@ def test(): -> "Literal[5]": Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -387,7 +387,7 @@ class C(A, B): ... Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -413,7 +413,7 @@ t[3] # IndexError: tuple index out of range Default level: error · Added in 0.0.1-alpha.12 · Related issues · -View source +View source @@ -502,7 +502,7 @@ an atypical memory layout. Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -529,7 +529,7 @@ func("foo") # error: [invalid-argument-type] Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -557,7 +557,7 @@ a: int = '' Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -591,7 +591,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable Default level: error · Added in 0.0.1-alpha.19 · Related issues · -View source +View source @@ -627,7 +627,7 @@ asyncio.run(main()) Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -651,7 +651,7 @@ class A(42): ... # error: [invalid-base] Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -678,7 +678,7 @@ with 1: Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -707,7 +707,7 @@ a: str Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -751,7 +751,7 @@ except ZeroDivisionError: Default level: error · Added in 0.0.1-alpha.28 · Related issues · -View source +View source @@ -793,7 +793,7 @@ class D(A): Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -826,7 +826,7 @@ class C[U](Generic[T]): ... Default level: error · Added in 0.0.1-alpha.17 · Related issues · -View source +View source @@ -865,7 +865,7 @@ carol = Person(name="Carol", age=25) # typo! Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -900,7 +900,7 @@ def f(t: TypeVar("U")): ... Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -934,7 +934,7 @@ class B(metaclass=f): ... Default level: error · Added in 0.0.1-alpha.20 · Related issues · -View source +View source @@ -1041,7 +1041,7 @@ Correct use of `@override` is enforced by ty's `invalid-explicit-override` rule. Default level: error · Added in 0.0.1-alpha.19 · Related issues · -View source +View source @@ -1095,7 +1095,7 @@ AttributeError: Cannot overwrite NamedTuple attribute _asdict Default level: error · Preview (since 1.0.0) · Related issues · -View source +View source @@ -1125,7 +1125,7 @@ Baz = NewType("Baz", int | str) # error: invalid base for `typing.NewType` Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1175,7 +1175,7 @@ def foo(x: int) -> int: ... Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1201,7 +1201,7 @@ def f(a: int = ''): ... Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1232,7 +1232,7 @@ P2 = ParamSpec("S2") # error: ParamSpec name must match the variable it's assig Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1266,7 +1266,7 @@ TypeError: Protocols can only inherit from other protocols, got Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1315,7 +1315,7 @@ def g(): Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1340,7 +1340,7 @@ def func() -> int: Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1398,7 +1398,7 @@ TODO #14889 Default level: error · Added in 0.0.1-alpha.6 · Related issues · -View source +View source @@ -1425,7 +1425,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus Default level: error · Added in 0.0.1-alpha.29 · Related issues · -View source +View source @@ -1472,7 +1472,7 @@ Bar[int] # error: too few arguments Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1502,7 +1502,7 @@ TYPE_CHECKING = '' Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1532,7 +1532,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments Default level: error · Added in 0.0.1-alpha.11 · Related issues · -View source +View source @@ -1566,7 +1566,7 @@ f(10) # Error Default level: error · Added in 0.0.1-alpha.11 · Related issues · -View source +View source @@ -1600,7 +1600,7 @@ class C: Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1635,7 +1635,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1660,7 +1660,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x' Default level: error · Added in 0.0.1-alpha.20 · Related issues · -View source +View source @@ -1693,7 +1693,7 @@ alice["age"] # KeyError Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1722,7 +1722,7 @@ func("string") # error: [no-matching-overload] Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1746,7 +1746,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1772,7 +1772,7 @@ for i in 34: # TypeError: 'int' object is not iterable Default level: error · Added in 0.0.1-alpha.29 · Related issues · -View source +View source @@ -1805,7 +1805,7 @@ class B(A): Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1832,7 +1832,7 @@ f(1, x=2) # Error raised here Default level: error · Added in 0.0.1-alpha.22 · Related issues · -View source +View source @@ -1890,7 +1890,7 @@ def test(): -> "int": Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1920,7 +1920,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1949,7 +1949,7 @@ class B(A): ... # Error raised here Default level: error · Preview (since 0.0.1-alpha.30) · Related issues · -View source +View source @@ -1983,7 +1983,7 @@ class F(NamedTuple): Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2010,7 +2010,7 @@ f("foo") # Error raised here Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2038,7 +2038,7 @@ def _(x: int): Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2084,7 +2084,7 @@ class A: Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2111,7 +2111,7 @@ f(x=1, y=2) # Error raised here Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2139,7 +2139,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo' Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2164,7 +2164,7 @@ import foo # ModuleNotFoundError: No module named 'foo' Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2189,7 +2189,7 @@ print(x) # NameError: name 'x' is not defined Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2226,7 +2226,7 @@ b1 < b2 < b1 # exception raised here Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2254,7 +2254,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A' Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2279,7 +2279,7 @@ l[1:10:0] # ValueError: slice step cannot be zero Default level: warn · Added in 0.0.1-alpha.20 · Related issues · -View source +View source @@ -2320,7 +2320,7 @@ class SubProto(BaseProto, Protocol): Default level: warn · Added in 0.0.1-alpha.16 · Related issues · -View source +View source @@ -2408,7 +2408,7 @@ a = 20 / 0 # type: ignore Default level: warn · Added in 0.0.1-alpha.22 · Related issues · -View source +View source @@ -2436,7 +2436,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c' Default level: warn · Added in 0.0.1-alpha.22 · Related issues · -View source +View source @@ -2468,7 +2468,7 @@ A()[0] # TypeError: 'A' object is not subscriptable Default level: warn · Added in 0.0.1-alpha.22 · Related issues · -View source +View source @@ -2500,7 +2500,7 @@ from module import a # ImportError: cannot import name 'a' from 'module' Default level: warn · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2527,7 +2527,7 @@ cast(int, f()) # Redundant Default level: warn · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2551,7 +2551,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined Default level: warn · Added in 0.0.1-alpha.15 · Related issues · -View source +View source @@ -2609,7 +2609,7 @@ def g(): Default level: warn · Added in 0.0.1-alpha.7 · Related issues · -View source +View source @@ -2648,7 +2648,7 @@ class D(C): ... # error: [unsupported-base] Default level: warn · Added in 0.0.1-alpha.22 · Related issues · -View source +View source @@ -2711,7 +2711,7 @@ def foo(x: int | str) -> int | str: Default level: ignore · Preview (since 0.0.1-alpha.1) · Related issues · -View source +View source @@ -2735,7 +2735,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime. Default level: ignore · Added in 0.0.1-alpha.1 · Related issues · -View source +View source diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index 8b20466b51..d72cbf8dbc 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -37,13 +37,11 @@ use itertools::Itertools; use ruff_db::{ diagnostic::{Annotation, Diagnostic, Span, SubDiagnostic, SubDiagnosticSeverity}, parsed::parsed_module, - source::source_text, }; use ruff_diagnostics::{Edit, Fix}; use ruff_python_ast::name::Name; -use ruff_python_ast::parenthesize::parentheses_iterator; +use ruff_python_ast::token::parentheses_iterator; use ruff_python_ast::{self as ast, AnyNodeRef, StringFlags}; -use ruff_python_trivia::CommentRanges; use ruff_text_size::{Ranged, TextRange}; use rustc_hash::FxHashSet; use std::fmt::{self, Formatter}; @@ -2423,9 +2421,7 @@ pub(super) fn report_invalid_assignment<'db>( // ) # ty: ignore <- or here // ``` - let comment_ranges = CommentRanges::from(context.module().tokens()); - let source = source_text(context.db(), context.file()); - parentheses_iterator(value_node.into(), None, &comment_ranges, &source) + parentheses_iterator(value_node.into(), None, context.module().tokens()) .last() .unwrap_or(value_node.range()) } else {