Skip to content

Commit 1ebedf6

Browse files
authored
[ruff] Add support for additional eager conversion patterns (RUF065) (#20657)
## Summary Fixes #20583
1 parent 980b4c5 commit 1ebedf6

File tree

3 files changed

+301
-14
lines changed

3 files changed

+301
-14
lines changed

crates/ruff_linter/resources/test/fixtures/ruff/RUF065.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,29 @@ def str(s): return f"str = {s}"
4343
logging.error("Error: %r", repr([1, 2, 3]))
4444
logging.info("Debug info: %s", repr("test\nstring"))
4545
logging.warning("Value: %s", repr(42))
46+
47+
# %s + ascii()
48+
logging.info("ASCII: %s", ascii("Hello\nWorld"))
49+
logging.warning("ASCII: %s", ascii("test"))
50+
51+
# %s + oct()
52+
logging.info("Octal: %s", oct(42))
53+
logging.warning("Octal: %s", oct(255))
54+
55+
# %s + hex()
56+
logging.info("Hex: %s", hex(42))
57+
logging.warning("Hex: %s", hex(255))
58+
59+
60+
# Test with imported functions
61+
from logging import info, log
62+
63+
info("ASCII: %s", ascii("Hello\nWorld"))
64+
log(logging.INFO, "ASCII: %s", ascii("test"))
65+
66+
info("Octal: %s", oct(42))
67+
log(logging.INFO, "Octal: %s", oct(255))
68+
69+
info("Hex: %s", hex(42))
70+
log(logging.INFO, "Hex: %s", hex(255))
71+

crates/ruff_linter/src/rules/ruff/rules/logging_eager_conversion.rs

Lines changed: 98 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -63,19 +63,44 @@ use crate::rules::flake8_logging_format::rules::{LoggingCallType, find_logging_c
6363
#[violation_metadata(preview_since = "0.13.2")]
6464
pub(crate) struct LoggingEagerConversion {
6565
pub(crate) format_conversion: FormatConversion,
66+
pub(crate) function_name: Option<&'static str>,
6667
}
6768

6869
impl Violation for LoggingEagerConversion {
6970
#[derive_message_formats]
7071
fn message(&self) -> String {
71-
let LoggingEagerConversion { format_conversion } = self;
72-
let (format_str, call_arg) = match format_conversion {
73-
FormatConversion::Str => ("%s", "str()"),
74-
FormatConversion::Repr => ("%r", "repr()"),
75-
FormatConversion::Ascii => ("%a", "ascii()"),
76-
FormatConversion::Bytes => ("%b", "bytes()"),
77-
};
78-
format!("Unnecessary `{call_arg}` conversion when formatting with `{format_str}`")
72+
let LoggingEagerConversion {
73+
format_conversion,
74+
function_name,
75+
} = self;
76+
match (format_conversion, function_name.as_deref()) {
77+
(FormatConversion::Str, Some("oct")) => {
78+
"Unnecessary `oct()` conversion when formatting with `%s`. \
79+
Use `%#o` instead of `%s`"
80+
.to_string()
81+
}
82+
(FormatConversion::Str, Some("hex")) => {
83+
"Unnecessary `hex()` conversion when formatting with `%s`. \
84+
Use `%#x` instead of `%s`"
85+
.to_string()
86+
}
87+
(FormatConversion::Str, _) => {
88+
"Unnecessary `str()` conversion when formatting with `%s`".to_string()
89+
}
90+
(FormatConversion::Repr, _) => {
91+
"Unnecessary `repr()` conversion when formatting with `%s`. \
92+
Use `%r` instead of `%s`"
93+
.to_string()
94+
}
95+
(FormatConversion::Ascii, _) => {
96+
"Unnecessary `ascii()` conversion when formatting with `%s`. \
97+
Use `%a` instead of `%s`"
98+
.to_string()
99+
}
100+
(FormatConversion::Bytes, _) => {
101+
"Unnecessary `bytes()` conversion when formatting with `%b`".to_string()
102+
}
103+
}
79104
}
80105
}
81106

@@ -118,12 +143,71 @@ pub(crate) fn logging_eager_conversion(checker: &Checker, call: &ast::ExprCall)
118143
continue;
119144
};
120145

121-
// Check for use of %s with str()
122-
if checker.semantic().match_builtin_expr(func.as_ref(), "str")
123-
&& matches!(format_conversion, FormatConversion::Str)
124-
{
125-
checker
126-
.report_diagnostic(LoggingEagerConversion { format_conversion }, arg.range());
146+
// Check for various eager conversion patterns
147+
match format_conversion {
148+
// %s with str() - remove str() call
149+
FormatConversion::Str
150+
if checker.semantic().match_builtin_expr(func.as_ref(), "str") =>
151+
{
152+
checker.report_diagnostic(
153+
LoggingEagerConversion {
154+
format_conversion,
155+
function_name: None,
156+
},
157+
arg.range(),
158+
);
159+
}
160+
// %s with repr() - suggest using %r instead
161+
FormatConversion::Str
162+
if checker.semantic().match_builtin_expr(func.as_ref(), "repr") =>
163+
{
164+
checker.report_diagnostic(
165+
LoggingEagerConversion {
166+
format_conversion: FormatConversion::Repr,
167+
function_name: None,
168+
},
169+
arg.range(),
170+
);
171+
}
172+
// %s with ascii() - suggest using %a instead
173+
FormatConversion::Str
174+
if checker
175+
.semantic()
176+
.match_builtin_expr(func.as_ref(), "ascii") =>
177+
{
178+
checker.report_diagnostic(
179+
LoggingEagerConversion {
180+
format_conversion: FormatConversion::Ascii,
181+
function_name: None,
182+
},
183+
arg.range(),
184+
);
185+
}
186+
// %s with oct() - suggest using %#o instead
187+
FormatConversion::Str
188+
if checker.semantic().match_builtin_expr(func.as_ref(), "oct") =>
189+
{
190+
checker.report_diagnostic(
191+
LoggingEagerConversion {
192+
format_conversion: FormatConversion::Str,
193+
function_name: Some("oct"),
194+
},
195+
arg.range(),
196+
);
197+
}
198+
// %s with hex() - suggest using %#x instead
199+
FormatConversion::Str
200+
if checker.semantic().match_builtin_expr(func.as_ref(), "hex") =>
201+
{
202+
checker.report_diagnostic(
203+
LoggingEagerConversion {
204+
format_conversion: FormatConversion::Str,
205+
function_name: Some("hex"),
206+
},
207+
arg.range(),
208+
);
209+
}
210+
_ => {}
127211
}
128212
}
129213
}

crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF065_RUF065.py.snap

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,26 @@ RUF065 Unnecessary `str()` conversion when formatting with `%s`
2121
7 | # %s + repr()
2222
|
2323

24+
RUF065 Unnecessary `repr()` conversion when formatting with `%s`. Use `%r` instead of `%s`
25+
--> RUF065.py:8:26
26+
|
27+
7 | # %s + repr()
28+
8 | logging.info("Hello %s", repr("World!"))
29+
| ^^^^^^^^^^^^^^
30+
9 | logging.log(logging.INFO, "Hello %s", repr("World!"))
31+
|
32+
33+
RUF065 Unnecessary `repr()` conversion when formatting with `%s`. Use `%r` instead of `%s`
34+
--> RUF065.py:9:39
35+
|
36+
7 | # %s + repr()
37+
8 | logging.info("Hello %s", repr("World!"))
38+
9 | logging.log(logging.INFO, "Hello %s", repr("World!"))
39+
| ^^^^^^^^^^^^^^
40+
10 |
41+
11 | # %r + str()
42+
|
43+
2444
RUF065 Unnecessary `str()` conversion when formatting with `%s`
2545
--> RUF065.py:22:18
2646
|
@@ -40,3 +60,160 @@ RUF065 Unnecessary `str()` conversion when formatting with `%s`
4060
24 |
4161
25 | # %s + repr()
4262
|
63+
64+
RUF065 Unnecessary `repr()` conversion when formatting with `%s`. Use `%r` instead of `%s`
65+
--> RUF065.py:26:18
66+
|
67+
25 | # %s + repr()
68+
26 | info("Hello %s", repr("World!"))
69+
| ^^^^^^^^^^^^^^
70+
27 | log(logging.INFO, "Hello %s", repr("World!"))
71+
|
72+
73+
RUF065 Unnecessary `repr()` conversion when formatting with `%s`. Use `%r` instead of `%s`
74+
--> RUF065.py:27:31
75+
|
76+
25 | # %s + repr()
77+
26 | info("Hello %s", repr("World!"))
78+
27 | log(logging.INFO, "Hello %s", repr("World!"))
79+
| ^^^^^^^^^^^^^^
80+
28 |
81+
29 | # %r + str()
82+
|
83+
84+
RUF065 Unnecessary `repr()` conversion when formatting with `%s`. Use `%r` instead of `%s`
85+
--> RUF065.py:44:32
86+
|
87+
42 | logging.warning("Value: %r", repr(42))
88+
43 | logging.error("Error: %r", repr([1, 2, 3]))
89+
44 | logging.info("Debug info: %s", repr("test\nstring"))
90+
| ^^^^^^^^^^^^^^^^^^^^
91+
45 | logging.warning("Value: %s", repr(42))
92+
|
93+
94+
RUF065 Unnecessary `repr()` conversion when formatting with `%s`. Use `%r` instead of `%s`
95+
--> RUF065.py:45:30
96+
|
97+
43 | logging.error("Error: %r", repr([1, 2, 3]))
98+
44 | logging.info("Debug info: %s", repr("test\nstring"))
99+
45 | logging.warning("Value: %s", repr(42))
100+
| ^^^^^^^^
101+
46 |
102+
47 | # %s + ascii()
103+
|
104+
105+
RUF065 Unnecessary `ascii()` conversion when formatting with `%s`. Use `%a` instead of `%s`
106+
--> RUF065.py:48:27
107+
|
108+
47 | # %s + ascii()
109+
48 | logging.info("ASCII: %s", ascii("Hello\nWorld"))
110+
| ^^^^^^^^^^^^^^^^^^^^^
111+
49 | logging.warning("ASCII: %s", ascii("test"))
112+
|
113+
114+
RUF065 Unnecessary `ascii()` conversion when formatting with `%s`. Use `%a` instead of `%s`
115+
--> RUF065.py:49:30
116+
|
117+
47 | # %s + ascii()
118+
48 | logging.info("ASCII: %s", ascii("Hello\nWorld"))
119+
49 | logging.warning("ASCII: %s", ascii("test"))
120+
| ^^^^^^^^^^^^^
121+
50 |
122+
51 | # %s + oct()
123+
|
124+
125+
RUF065 Unnecessary `oct()` conversion when formatting with `%s`. Use `%#o` instead of `%s`
126+
--> RUF065.py:52:27
127+
|
128+
51 | # %s + oct()
129+
52 | logging.info("Octal: %s", oct(42))
130+
| ^^^^^^^
131+
53 | logging.warning("Octal: %s", oct(255))
132+
|
133+
134+
RUF065 Unnecessary `oct()` conversion when formatting with `%s`. Use `%#o` instead of `%s`
135+
--> RUF065.py:53:30
136+
|
137+
51 | # %s + oct()
138+
52 | logging.info("Octal: %s", oct(42))
139+
53 | logging.warning("Octal: %s", oct(255))
140+
| ^^^^^^^^
141+
54 |
142+
55 | # %s + hex()
143+
|
144+
145+
RUF065 Unnecessary `hex()` conversion when formatting with `%s`. Use `%#x` instead of `%s`
146+
--> RUF065.py:56:25
147+
|
148+
55 | # %s + hex()
149+
56 | logging.info("Hex: %s", hex(42))
150+
| ^^^^^^^
151+
57 | logging.warning("Hex: %s", hex(255))
152+
|
153+
154+
RUF065 Unnecessary `hex()` conversion when formatting with `%s`. Use `%#x` instead of `%s`
155+
--> RUF065.py:57:28
156+
|
157+
55 | # %s + hex()
158+
56 | logging.info("Hex: %s", hex(42))
159+
57 | logging.warning("Hex: %s", hex(255))
160+
| ^^^^^^^^
161+
|
162+
163+
RUF065 Unnecessary `ascii()` conversion when formatting with `%s`. Use `%a` instead of `%s`
164+
--> RUF065.py:63:19
165+
|
166+
61 | from logging import info, log
167+
62 |
168+
63 | info("ASCII: %s", ascii("Hello\nWorld"))
169+
| ^^^^^^^^^^^^^^^^^^^^^
170+
64 | log(logging.INFO, "ASCII: %s", ascii("test"))
171+
|
172+
173+
RUF065 Unnecessary `ascii()` conversion when formatting with `%s`. Use `%a` instead of `%s`
174+
--> RUF065.py:64:32
175+
|
176+
63 | info("ASCII: %s", ascii("Hello\nWorld"))
177+
64 | log(logging.INFO, "ASCII: %s", ascii("test"))
178+
| ^^^^^^^^^^^^^
179+
65 |
180+
66 | info("Octal: %s", oct(42))
181+
|
182+
183+
RUF065 Unnecessary `oct()` conversion when formatting with `%s`. Use `%#o` instead of `%s`
184+
--> RUF065.py:66:19
185+
|
186+
64 | log(logging.INFO, "ASCII: %s", ascii("test"))
187+
65 |
188+
66 | info("Octal: %s", oct(42))
189+
| ^^^^^^^
190+
67 | log(logging.INFO, "Octal: %s", oct(255))
191+
|
192+
193+
RUF065 Unnecessary `oct()` conversion when formatting with `%s`. Use `%#o` instead of `%s`
194+
--> RUF065.py:67:32
195+
|
196+
66 | info("Octal: %s", oct(42))
197+
67 | log(logging.INFO, "Octal: %s", oct(255))
198+
| ^^^^^^^^
199+
68 |
200+
69 | info("Hex: %s", hex(42))
201+
|
202+
203+
RUF065 Unnecessary `hex()` conversion when formatting with `%s`. Use `%#x` instead of `%s`
204+
--> RUF065.py:69:17
205+
|
206+
67 | log(logging.INFO, "Octal: %s", oct(255))
207+
68 |
208+
69 | info("Hex: %s", hex(42))
209+
| ^^^^^^^
210+
70 | log(logging.INFO, "Hex: %s", hex(255))
211+
|
212+
213+
RUF065 Unnecessary `hex()` conversion when formatting with `%s`. Use `%#x` instead of `%s`
214+
--> RUF065.py:70:30
215+
|
216+
69 | info("Hex: %s", hex(42))
217+
70 | log(logging.INFO, "Hex: %s", hex(255))
218+
| ^^^^^^^^
219+
|

0 commit comments

Comments
 (0)