Skip to content

Commit 6a5ff8e

Browse files
authored
[pyupgrade] Improve diagnostic range for tuples (UP024) (#23013)
## Summary This PR refines the diagnostic highlighting for **UP024**. Previously, when multiple exceptions were in a tuple, the linter would underline the entire tuple. Now specific aliases within a tuple with be highlighted. Fixes #19696
1 parent c8703cb commit 6a5ff8e

4 files changed

Lines changed: 309 additions & 1 deletion

File tree

crates/ruff_linter/src/preview.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,3 +272,8 @@ pub(crate) const fn is_mutable_default_in_dataclass_field_enabled(
272272
) -> bool {
273273
settings.preview.is_enabled()
274274
}
275+
276+
// https://github.com/astral-sh/ruff/pull/23013
277+
pub(crate) const fn is_up024_precise_highlighting_enabled(settings: &LinterSettings) -> bool {
278+
settings.preview.is_enabled()
279+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ mod tests {
145145
}
146146

147147
#[test_case(Rule::TypingTextStrAlias, Path::new("UP019.py"))]
148+
#[test_case(Rule::OSErrorAlias, Path::new("UP024_0.py"))]
148149
fn rules_preview(rule_code: Rule, path: &Path) -> Result<()> {
149150
let snapshot = format!("{}__preview", path.to_string_lossy());
150151
let diagnostics = test_path(

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use ruff_python_semantic::SemanticModel;
77

88
use crate::checkers::ast::Checker;
99
use crate::fix::edits::pad;
10+
use crate::preview::is_up024_precise_highlighting_enabled;
1011
use crate::{AlwaysFixableViolation, Edit, Fix};
1112

1213
/// ## What it does
@@ -93,7 +94,20 @@ fn atom_diagnostic(checker: &Checker, target: &Expr) {
9394

9495
/// Create a [`Diagnostic`] for a tuple of expressions.
9596
fn tuple_diagnostic(checker: &Checker, tuple: &ast::ExprTuple, aliases: &[&Expr]) {
96-
let mut diagnostic = checker.report_diagnostic(OSErrorAlias { name: None }, tuple.range());
97+
let Some(first_alias) = aliases.first() else {
98+
return;
99+
};
100+
let diagnostic_range = if is_up024_precise_highlighting_enabled(checker.settings()) {
101+
first_alias.range()
102+
} else {
103+
tuple.range()
104+
};
105+
let mut diagnostic = checker.report_diagnostic(OSErrorAlias { name: None }, diagnostic_range);
106+
if is_up024_precise_highlighting_enabled(checker.settings()) {
107+
for alias in aliases.iter().skip(1) {
108+
diagnostic.secondary_annotation("", alias.range());
109+
}
110+
}
97111
let semantic = checker.semantic();
98112
if semantic.has_builtin_binding("OSError") {
99113
// Filter out any `OSErrors` aliases.
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
---
2+
source: crates/ruff_linter/src/rules/pyupgrade/mod.rs
3+
---
4+
UP024 [*] Replace aliased errors with `OSError`
5+
--> UP024_0.py:6:8
6+
|
7+
4 | try:
8+
5 | pass
9+
6 | except EnvironmentError:
10+
| ^^^^^^^^^^^^^^^^
11+
7 | pass
12+
|
13+
help: Replace `EnvironmentError` with builtin `OSError`
14+
3 | # These should be fixed
15+
4 | try:
16+
5 | pass
17+
- except EnvironmentError:
18+
6 + except OSError:
19+
7 | pass
20+
8 |
21+
9 | try:
22+
23+
UP024 [*] Replace aliased errors with `OSError`
24+
--> UP024_0.py:11:8
25+
|
26+
9 | try:
27+
10 | pass
28+
11 | except IOError:
29+
| ^^^^^^^
30+
12 | pass
31+
|
32+
help: Replace `IOError` with builtin `OSError`
33+
8 |
34+
9 | try:
35+
10 | pass
36+
- except IOError:
37+
11 + except OSError:
38+
12 | pass
39+
13 |
40+
14 | try:
41+
42+
UP024 [*] Replace aliased errors with `OSError`
43+
--> UP024_0.py:16:8
44+
|
45+
14 | try:
46+
15 | pass
47+
16 | except WindowsError:
48+
| ^^^^^^^^^^^^
49+
17 | pass
50+
|
51+
help: Replace `WindowsError` with builtin `OSError`
52+
13 |
53+
14 | try:
54+
15 | pass
55+
- except WindowsError:
56+
16 + except OSError:
57+
17 | pass
58+
18 |
59+
19 | try:
60+
61+
UP024 [*] Replace aliased errors with `OSError`
62+
--> UP024_0.py:21:8
63+
|
64+
19 | try:
65+
20 | pass
66+
21 | except mmap.error:
67+
| ^^^^^^^^^^
68+
22 | pass
69+
|
70+
help: Replace `mmap.error` with builtin `OSError`
71+
18 |
72+
19 | try:
73+
20 | pass
74+
- except mmap.error:
75+
21 + except OSError:
76+
22 | pass
77+
23 |
78+
24 | try:
79+
80+
UP024 [*] Replace aliased errors with `OSError`
81+
--> UP024_0.py:26:8
82+
|
83+
24 | try:
84+
25 | pass
85+
26 | except select.error:
86+
| ^^^^^^^^^^^^
87+
27 | pass
88+
|
89+
help: Replace `select.error` with builtin `OSError`
90+
23 |
91+
24 | try:
92+
25 | pass
93+
- except select.error:
94+
26 + except OSError:
95+
27 | pass
96+
28 |
97+
29 | try:
98+
99+
UP024 [*] Replace aliased errors with `OSError`
100+
--> UP024_0.py:31:8
101+
|
102+
29 | try:
103+
30 | pass
104+
31 | except socket.error:
105+
| ^^^^^^^^^^^^
106+
32 | pass
107+
|
108+
help: Replace `socket.error` with builtin `OSError`
109+
28 |
110+
29 | try:
111+
30 | pass
112+
- except socket.error:
113+
31 + except OSError:
114+
32 | pass
115+
33 |
116+
34 | try:
117+
118+
UP024 [*] Replace aliased errors with `OSError`
119+
--> UP024_0.py:36:8
120+
|
121+
34 | try:
122+
35 | pass
123+
36 | except error:
124+
| ^^^^^
125+
37 | pass
126+
|
127+
help: Replace `error` with builtin `OSError`
128+
33 |
129+
34 | try:
130+
35 | pass
131+
- except error:
132+
36 + except OSError:
133+
37 | pass
134+
38 |
135+
39 | # Should NOT be in parentheses when replaced
136+
137+
UP024 [*] Replace aliased errors with `OSError`
138+
--> UP024_0.py:43:9
139+
|
140+
41 | try:
141+
42 | pass
142+
43 | except (IOError,):
143+
| ^^^^^^^
144+
44 | pass
145+
45 | try:
146+
|
147+
help: Replace with builtin `OSError`
148+
40 |
149+
41 | try:
150+
42 | pass
151+
- except (IOError,):
152+
43 + except OSError:
153+
44 | pass
154+
45 | try:
155+
46 | pass
156+
157+
UP024 [*] Replace aliased errors with `OSError`
158+
--> UP024_0.py:47:9
159+
|
160+
45 | try:
161+
46 | pass
162+
47 | except (mmap.error,):
163+
| ^^^^^^^^^^
164+
48 | pass
165+
49 | try:
166+
|
167+
help: Replace with builtin `OSError`
168+
44 | pass
169+
45 | try:
170+
46 | pass
171+
- except (mmap.error,):
172+
47 + except OSError:
173+
48 | pass
174+
49 | try:
175+
50 | pass
176+
177+
UP024 [*] Replace aliased errors with `OSError`
178+
--> UP024_0.py:51:9
179+
|
180+
49 | try:
181+
50 | pass
182+
51 | except (EnvironmentError, IOError, OSError, select.error):
183+
| ^^^^^^^^^^^^^^^^ ------- ------------
184+
52 | pass
185+
|
186+
help: Replace with builtin `OSError`
187+
48 | pass
188+
49 | try:
189+
50 | pass
190+
- except (EnvironmentError, IOError, OSError, select.error):
191+
51 + except OSError:
192+
52 | pass
193+
53 |
194+
54 | # Should be kept in parentheses (because multiple)
195+
196+
UP024 [*] Replace aliased errors with `OSError`
197+
--> UP024_0.py:58:9
198+
|
199+
56 | try:
200+
57 | pass
201+
58 | except (IOError, KeyError, OSError):
202+
| ^^^^^^^
203+
59 | pass
204+
|
205+
help: Replace with builtin `OSError`
206+
55 |
207+
56 | try:
208+
57 | pass
209+
- except (IOError, KeyError, OSError):
210+
58 + except (KeyError, OSError):
211+
59 | pass
212+
60 |
213+
61 | # First should change, second should not
214+
215+
UP024 [*] Replace aliased errors with `OSError`
216+
--> UP024_0.py:65:9
217+
|
218+
63 | try:
219+
64 | pass
220+
65 | except (IOError, error):
221+
| ^^^^^^^
222+
66 | pass
223+
67 | # These should not change
224+
|
225+
help: Replace with builtin `OSError`
226+
62 | from .mmap import error
227+
63 | try:
228+
64 | pass
229+
- except (IOError, error):
230+
65 + except (OSError, error):
231+
66 | pass
232+
67 | # These should not change
233+
68 |
234+
235+
UP024 [*] Replace aliased errors with `OSError`
236+
--> UP024_0.py:87:8
237+
|
238+
85 | try:
239+
86 | pass
240+
87 | except (mmap).error:
241+
| ^^^^^^^^^^^^
242+
88 | pass
243+
|
244+
help: Replace `mmap.error` with builtin `OSError`
245+
84 | pass
246+
85 | try:
247+
86 | pass
248+
- except (mmap).error:
249+
87 + except OSError:
250+
88 | pass
251+
89 |
252+
90 | try:
253+
254+
UP024 [*] Replace aliased errors with `OSError`
255+
--> UP024_0.py:105:12
256+
|
257+
103 | try:
258+
104 | mac_address = get_primary_mac_address()
259+
105 | except(IOError, OSError) as ex:
260+
| ^^^^^^^
261+
106 | msg = 'Unable to query URL to get Owner ID: {u}\n{e}'.format(u=owner_id_url, e=ex)
262+
|
263+
help: Replace with builtin `OSError`
264+
102 | def get_owner_id_from_mac_address():
265+
103 | try:
266+
104 | mac_address = get_primary_mac_address()
267+
- except(IOError, OSError) as ex:
268+
105 + except OSError as ex:
269+
106 | msg = 'Unable to query URL to get Owner ID: {u}\n{e}'.format(u=owner_id_url, e=ex)
270+
107 |
271+
108 |
272+
273+
UP024 [*] Replace aliased errors with `OSError`
274+
--> UP024_0.py:114:8
275+
|
276+
112 | try:
277+
113 | pass
278+
114 | except os.error:
279+
| ^^^^^^^^
280+
115 | pass
281+
|
282+
help: Replace `os.error` with builtin `OSError`
283+
111 |
284+
112 | try:
285+
113 | pass
286+
- except os.error:
287+
114 + except OSError:
288+
115 | pass

0 commit comments

Comments
 (0)