Skip to content

Commit 9e1aafd

Browse files
[pyupgrade] Extend UP019 to detect typing_extensions.Text (UP019) (#20825)
Co-authored-by: Micha Reiser <micha@reiser.io>
1 parent abf685b commit 9e1aafd

File tree

6 files changed

+183
-9
lines changed

6 files changed

+183
-9
lines changed

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,20 @@ def print_third_word(word: Hello.Text) -> None:
1818

1919
def print_fourth_word(word: Goodbye) -> None:
2020
print(word)
21+
22+
23+
import typing_extensions
24+
import typing_extensions as TypingExt
25+
from typing_extensions import Text as TextAlias
26+
27+
28+
def print_fifth_word(word: typing_extensions.Text) -> None:
29+
print(word)
30+
31+
32+
def print_sixth_word(word: TypingExt.Text) -> None:
33+
print(word)
34+
35+
36+
def print_seventh_word(word: TextAlias) -> None:
37+
print(word)

crates/ruff_linter/src/preview.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,3 +265,7 @@ pub(crate) const fn is_fix_read_whole_file_enabled(settings: &LinterSettings) ->
265265
pub(crate) const fn is_fix_write_whole_file_enabled(settings: &LinterSettings) -> bool {
266266
settings.preview.is_enabled()
267267
}
268+
269+
pub(crate) const fn is_typing_extensions_str_alias_enabled(settings: &LinterSettings) -> bool {
270+
settings.preview.is_enabled()
271+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ mod tests {
126126
}
127127

128128
#[test_case(Rule::SuperCallWithParameters, Path::new("UP008.py"))]
129+
#[test_case(Rule::TypingTextStrAlias, Path::new("UP019.py"))]
129130
fn rules_preview(rule_code: Rule, path: &Path) -> Result<()> {
130131
let snapshot = format!("{}__preview", path.to_string_lossy());
131132
let diagnostics = test_path(

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

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
use ruff_python_ast::Expr;
2+
use std::fmt::{Display, Formatter};
23

34
use ruff_macros::{ViolationMetadata, derive_message_formats};
45
use ruff_python_semantic::Modules;
56
use ruff_text_size::Ranged;
67

78
use crate::checkers::ast::Checker;
9+
use crate::preview::is_typing_extensions_str_alias_enabled;
810
use crate::{Edit, Fix, FixAvailability, Violation};
911

1012
/// ## What it does
1113
/// Checks for uses of `typing.Text`.
1214
///
15+
/// In preview mode, also checks for `typing_extensions.Text`.
16+
///
1317
/// ## Why is this bad?
1418
/// `typing.Text` is an alias for `str`, and only exists for Python 2
1519
/// compatibility. As of Python 3.11, `typing.Text` is deprecated. Use `str`
@@ -30,14 +34,16 @@ use crate::{Edit, Fix, FixAvailability, Violation};
3034
/// ## References
3135
/// - [Python documentation: `typing.Text`](https://docs.python.org/3/library/typing.html#typing.Text)
3236
#[derive(ViolationMetadata)]
33-
pub(crate) struct TypingTextStrAlias;
37+
pub(crate) struct TypingTextStrAlias {
38+
module: TypingModule,
39+
}
3440

3541
impl Violation for TypingTextStrAlias {
3642
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
3743

3844
#[derive_message_formats]
3945
fn message(&self) -> String {
40-
"`typing.Text` is deprecated, use `str`".to_string()
46+
format!("`{}.Text` is deprecated, use `str`", self.module)
4147
}
4248

4349
fn fix_title(&self) -> Option<String> {
@@ -47,16 +53,26 @@ impl Violation for TypingTextStrAlias {
4753

4854
/// UP019
4955
pub(crate) fn typing_text_str_alias(checker: &Checker, expr: &Expr) {
50-
if !checker.semantic().seen_module(Modules::TYPING) {
56+
if !checker
57+
.semantic()
58+
.seen_module(Modules::TYPING | Modules::TYPING_EXTENSIONS)
59+
{
5160
return;
5261
}
5362

54-
if checker
55-
.semantic()
56-
.resolve_qualified_name(expr)
57-
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["typing", "Text"]))
58-
{
59-
let mut diagnostic = checker.report_diagnostic(TypingTextStrAlias, expr.range());
63+
if let Some(qualified_name) = checker.semantic().resolve_qualified_name(expr) {
64+
let segments = qualified_name.segments();
65+
let module = match segments {
66+
["typing", "Text"] => TypingModule::Typing,
67+
["typing_extensions", "Text"]
68+
if is_typing_extensions_str_alias_enabled(checker.settings()) =>
69+
{
70+
TypingModule::TypingExtensions
71+
}
72+
_ => return,
73+
};
74+
75+
let mut diagnostic = checker.report_diagnostic(TypingTextStrAlias { module }, expr.range());
6076
diagnostic.add_primary_tag(ruff_db::diagnostic::DiagnosticTag::Deprecated);
6177
diagnostic.try_set_fix(|| {
6278
let (import_edit, binding) = checker.importer().get_or_import_builtin_symbol(
@@ -71,3 +87,18 @@ pub(crate) fn typing_text_str_alias(checker: &Checker, expr: &Expr) {
7187
});
7288
}
7389
}
90+
91+
#[derive(Copy, Clone, Debug)]
92+
enum TypingModule {
93+
Typing,
94+
TypingExtensions,
95+
}
96+
97+
impl Display for TypingModule {
98+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
99+
match self {
100+
TypingModule::Typing => f.write_str("typing"),
101+
TypingModule::TypingExtensions => f.write_str("typing_extensions"),
102+
}
103+
}
104+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,5 @@ help: Replace with `str`
6666
- def print_fourth_word(word: Goodbye) -> None:
6767
19 + def print_fourth_word(word: str) -> None:
6868
20 | print(word)
69+
21 |
70+
22 |
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
---
2+
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
3+
---
4+
UP019 [*] `typing.Text` is deprecated, use `str`
5+
--> UP019.py:7:22
6+
|
7+
7 | def print_word(word: Text) -> None:
8+
| ^^^^
9+
8 | print(word)
10+
|
11+
help: Replace with `str`
12+
4 | from typing import Text as Goodbye
13+
5 |
14+
6 |
15+
- def print_word(word: Text) -> None:
16+
7 + def print_word(word: str) -> None:
17+
8 | print(word)
18+
9 |
19+
10 |
20+
21+
UP019 [*] `typing.Text` is deprecated, use `str`
22+
--> UP019.py:11:29
23+
|
24+
11 | def print_second_word(word: typing.Text) -> None:
25+
| ^^^^^^^^^^^
26+
12 | print(word)
27+
|
28+
help: Replace with `str`
29+
8 | print(word)
30+
9 |
31+
10 |
32+
- def print_second_word(word: typing.Text) -> None:
33+
11 + def print_second_word(word: str) -> None:
34+
12 | print(word)
35+
13 |
36+
14 |
37+
38+
UP019 [*] `typing.Text` is deprecated, use `str`
39+
--> UP019.py:15:28
40+
|
41+
15 | def print_third_word(word: Hello.Text) -> None:
42+
| ^^^^^^^^^^
43+
16 | print(word)
44+
|
45+
help: Replace with `str`
46+
12 | print(word)
47+
13 |
48+
14 |
49+
- def print_third_word(word: Hello.Text) -> None:
50+
15 + def print_third_word(word: str) -> None:
51+
16 | print(word)
52+
17 |
53+
18 |
54+
55+
UP019 [*] `typing.Text` is deprecated, use `str`
56+
--> UP019.py:19:29
57+
|
58+
19 | def print_fourth_word(word: Goodbye) -> None:
59+
| ^^^^^^^
60+
20 | print(word)
61+
|
62+
help: Replace with `str`
63+
16 | print(word)
64+
17 |
65+
18 |
66+
- def print_fourth_word(word: Goodbye) -> None:
67+
19 + def print_fourth_word(word: str) -> None:
68+
20 | print(word)
69+
21 |
70+
22 |
71+
72+
UP019 [*] `typing_extensions.Text` is deprecated, use `str`
73+
--> UP019.py:28:28
74+
|
75+
28 | def print_fifth_word(word: typing_extensions.Text) -> None:
76+
| ^^^^^^^^^^^^^^^^^^^^^^
77+
29 | print(word)
78+
|
79+
help: Replace with `str`
80+
25 | from typing_extensions import Text as TextAlias
81+
26 |
82+
27 |
83+
- def print_fifth_word(word: typing_extensions.Text) -> None:
84+
28 + def print_fifth_word(word: str) -> None:
85+
29 | print(word)
86+
30 |
87+
31 |
88+
89+
UP019 [*] `typing_extensions.Text` is deprecated, use `str`
90+
--> UP019.py:32:28
91+
|
92+
32 | def print_sixth_word(word: TypingExt.Text) -> None:
93+
| ^^^^^^^^^^^^^^
94+
33 | print(word)
95+
|
96+
help: Replace with `str`
97+
29 | print(word)
98+
30 |
99+
31 |
100+
- def print_sixth_word(word: TypingExt.Text) -> None:
101+
32 + def print_sixth_word(word: str) -> None:
102+
33 | print(word)
103+
34 |
104+
35 |
105+
106+
UP019 [*] `typing_extensions.Text` is deprecated, use `str`
107+
--> UP019.py:36:30
108+
|
109+
36 | def print_seventh_word(word: TextAlias) -> None:
110+
| ^^^^^^^^^
111+
37 | print(word)
112+
|
113+
help: Replace with `str`
114+
33 | print(word)
115+
34 |
116+
35 |
117+
- def print_seventh_word(word: TextAlias) -> None:
118+
36 + def print_seventh_word(word: str) -> None:
119+
37 | print(word)

0 commit comments

Comments
 (0)