Skip to content

Commit 56f956a

Browse files
authored
[pyupgrade] Handle end-of-line comments for quoted-annotation (UP037) (#15824)
This PR uses the tokens of the parsed annotation available in the `Checker`, instead of re-lexing (using `SimpleTokenizer`) the annotation. This avoids some limitations of the `SimpleTokenizer`, such as not being able to handle number and string literals. Closes #15816 .
1 parent 7a10a40 commit 56f956a

File tree

3 files changed

+90
-10
lines changed

3 files changed

+90
-10
lines changed

crates/ruff_linter/resources/test/fixtures/pyupgrade/UP037_0.py

+14
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,17 @@ def foo(*, inplace):
106106
x = cast("str", x)
107107

108108
X = List["MyClass"]
109+
110+
# Handle end of line comment in string annotation
111+
# See https://github.com/astral-sh/ruff/issues/15816
112+
def f() -> "Literal[0]#":
113+
return 0
114+
115+
def g(x: "Literal['abc']#") -> None:
116+
return
117+
118+
def f() -> """Literal[0]
119+
#
120+
121+
""":
122+
return 0

crates/ruff_linter/src/rules/pyupgrade/rules/quoted_annotation.rs

+7-9
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1+
use ruff_python_parser::TokenKind;
12
use ruff_text_size::{TextLen, TextRange, TextSize};
23

34
use crate::checkers::ast::Checker;
45
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
56
use ruff_macros::{derive_message_formats, ViolationMetadata};
67
use ruff_python_ast::Stmt;
78
use ruff_python_semantic::SemanticModel;
8-
use ruff_python_trivia::{SimpleToken, SimpleTokenKind, SimpleTokenizer};
99
use ruff_source_file::LineRanges;
1010

1111
/// ## What it does
@@ -87,14 +87,12 @@ pub(crate) fn quoted_annotation(checker: &mut Checker, annotation: &str, range:
8787
let placeholder_range = TextRange::up_to(annotation.text_len());
8888
let spans_multiple_lines = annotation.contains_line_break(placeholder_range);
8989

90-
let tokenizer = SimpleTokenizer::new(annotation, placeholder_range);
91-
let last_token_is_comment = matches!(
92-
tokenizer.last(),
93-
Some(SimpleToken {
94-
kind: SimpleTokenKind::Comment,
95-
..
96-
})
97-
);
90+
let last_token_is_comment = checker
91+
.tokens()
92+
// The actual last token will always be a logical newline,
93+
// so we check the second to last
94+
.get(checker.tokens().len().saturating_sub(2))
95+
.is_some_and(|tok| tok.kind() == TokenKind::Comment);
9896

9997
let new_content = match (spans_multiple_lines, last_token_is_comment) {
10098
(_, false) if in_parameter_annotation(range.start(), checker.semantic()) => {

crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP037_0.py.snap

+69-1
Original file line numberDiff line numberDiff line change
@@ -554,4 +554,72 @@ UP037_0.py:67:45: UP037 [*] Remove quotes from type annotation
554554
67 |+x: NamedTuple(typename="X", fields=[("foo", int)])
555555
68 68 |
556556
69 69 | X: MyCallable("X")
557-
70 70 |
557+
70 70 |
558+
559+
UP037_0.py:112:12: UP037 [*] Remove quotes from type annotation
560+
|
561+
110 | # Handle end of line comment in string annotation
562+
111 | # See https://github.com/astral-sh/ruff/issues/15816
563+
112 | def f() -> "Literal[0]#":
564+
| ^^^^^^^^^^^^^ UP037
565+
113 | return 0
566+
|
567+
= help: Remove quotes
568+
569+
Safe fix
570+
109 109 |
571+
110 110 | # Handle end of line comment in string annotation
572+
111 111 | # See https://github.com/astral-sh/ruff/issues/15816
573+
112 |-def f() -> "Literal[0]#":
574+
112 |+def f() -> (Literal[0]#
575+
113 |+):
576+
113 114 | return 0
577+
114 115 |
578+
115 116 | def g(x: "Literal['abc']#") -> None:
579+
580+
UP037_0.py:115:10: UP037 [*] Remove quotes from type annotation
581+
|
582+
113 | return 0
583+
114 |
584+
115 | def g(x: "Literal['abc']#") -> None:
585+
| ^^^^^^^^^^^^^^^^^ UP037
586+
116 | return
587+
|
588+
= help: Remove quotes
589+
590+
Safe fix
591+
112 112 | def f() -> "Literal[0]#":
592+
113 113 | return 0
593+
114 114 |
594+
115 |-def g(x: "Literal['abc']#") -> None:
595+
115 |+def g(x: (Literal['abc']#
596+
116 |+)) -> None:
597+
116 117 | return
598+
117 118 |
599+
118 119 | def f() -> """Literal[0]
600+
601+
UP037_0.py:118:12: UP037 [*] Remove quotes from type annotation
602+
|
603+
116 | return
604+
117 |
605+
118 | def f() -> """Literal[0]
606+
| ____________^
607+
119 | | #
608+
120 | |
609+
121 | | """:
610+
| |_______^ UP037
611+
122 | return 0
612+
|
613+
= help: Remove quotes
614+
615+
Safe fix
616+
115 115 | def g(x: "Literal['abc']#") -> None:
617+
116 116 | return
618+
117 117 |
619+
118 |-def f() -> """Literal[0]
620+
118 |+def f() -> (Literal[0]
621+
119 119 | #
622+
120 120 |
623+
121 |- """:
624+
121 |+ ):
625+
122 122 | return 0

0 commit comments

Comments
 (0)