[`ruff`] Add support for additional eager conversion patterns (`RUF065`) (#20657)

## Summary

Fixes #20583
This commit is contained in:
Dan Parizher 2025-10-29 17:45:08 -04:00 committed by GitHub
parent 980b4c55b2
commit 1ebedf6df5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 301 additions and 14 deletions

View File

@ -43,3 +43,29 @@ logging.warning("Value: %r", repr(42))
logging.error("Error: %r", repr([1, 2, 3]))
logging.info("Debug info: %s", repr("test\nstring"))
logging.warning("Value: %s", repr(42))
# %s + ascii()
logging.info("ASCII: %s", ascii("Hello\nWorld"))
logging.warning("ASCII: %s", ascii("test"))
# %s + oct()
logging.info("Octal: %s", oct(42))
logging.warning("Octal: %s", oct(255))
# %s + hex()
logging.info("Hex: %s", hex(42))
logging.warning("Hex: %s", hex(255))
# Test with imported functions
from logging import info, log
info("ASCII: %s", ascii("Hello\nWorld"))
log(logging.INFO, "ASCII: %s", ascii("test"))
info("Octal: %s", oct(42))
log(logging.INFO, "Octal: %s", oct(255))
info("Hex: %s", hex(42))
log(logging.INFO, "Hex: %s", hex(255))

View File

@ -63,19 +63,44 @@ use crate::rules::flake8_logging_format::rules::{LoggingCallType, find_logging_c
#[violation_metadata(preview_since = "0.13.2")]
pub(crate) struct LoggingEagerConversion {
pub(crate) format_conversion: FormatConversion,
pub(crate) function_name: Option<&'static str>,
}
impl Violation for LoggingEagerConversion {
#[derive_message_formats]
fn message(&self) -> String {
let LoggingEagerConversion { format_conversion } = self;
let (format_str, call_arg) = match format_conversion {
FormatConversion::Str => ("%s", "str()"),
FormatConversion::Repr => ("%r", "repr()"),
FormatConversion::Ascii => ("%a", "ascii()"),
FormatConversion::Bytes => ("%b", "bytes()"),
};
format!("Unnecessary `{call_arg}` conversion when formatting with `{format_str}`")
let LoggingEagerConversion {
format_conversion,
function_name,
} = self;
match (format_conversion, function_name.as_deref()) {
(FormatConversion::Str, Some("oct")) => {
"Unnecessary `oct()` conversion when formatting with `%s`. \
Use `%#o` instead of `%s`"
.to_string()
}
(FormatConversion::Str, Some("hex")) => {
"Unnecessary `hex()` conversion when formatting with `%s`. \
Use `%#x` instead of `%s`"
.to_string()
}
(FormatConversion::Str, _) => {
"Unnecessary `str()` conversion when formatting with `%s`".to_string()
}
(FormatConversion::Repr, _) => {
"Unnecessary `repr()` conversion when formatting with `%s`. \
Use `%r` instead of `%s`"
.to_string()
}
(FormatConversion::Ascii, _) => {
"Unnecessary `ascii()` conversion when formatting with `%s`. \
Use `%a` instead of `%s`"
.to_string()
}
(FormatConversion::Bytes, _) => {
"Unnecessary `bytes()` conversion when formatting with `%b`".to_string()
}
}
}
}
@ -118,12 +143,71 @@ pub(crate) fn logging_eager_conversion(checker: &Checker, call: &ast::ExprCall)
continue;
};
// Check for use of %s with str()
if checker.semantic().match_builtin_expr(func.as_ref(), "str")
&& matches!(format_conversion, FormatConversion::Str)
// Check for various eager conversion patterns
match format_conversion {
// %s with str() - remove str() call
FormatConversion::Str
if checker.semantic().match_builtin_expr(func.as_ref(), "str") =>
{
checker
.report_diagnostic(LoggingEagerConversion { format_conversion }, arg.range());
checker.report_diagnostic(
LoggingEagerConversion {
format_conversion,
function_name: None,
},
arg.range(),
);
}
// %s with repr() - suggest using %r instead
FormatConversion::Str
if checker.semantic().match_builtin_expr(func.as_ref(), "repr") =>
{
checker.report_diagnostic(
LoggingEagerConversion {
format_conversion: FormatConversion::Repr,
function_name: None,
},
arg.range(),
);
}
// %s with ascii() - suggest using %a instead
FormatConversion::Str
if checker
.semantic()
.match_builtin_expr(func.as_ref(), "ascii") =>
{
checker.report_diagnostic(
LoggingEagerConversion {
format_conversion: FormatConversion::Ascii,
function_name: None,
},
arg.range(),
);
}
// %s with oct() - suggest using %#o instead
FormatConversion::Str
if checker.semantic().match_builtin_expr(func.as_ref(), "oct") =>
{
checker.report_diagnostic(
LoggingEagerConversion {
format_conversion: FormatConversion::Str,
function_name: Some("oct"),
},
arg.range(),
);
}
// %s with hex() - suggest using %#x instead
FormatConversion::Str
if checker.semantic().match_builtin_expr(func.as_ref(), "hex") =>
{
checker.report_diagnostic(
LoggingEagerConversion {
format_conversion: FormatConversion::Str,
function_name: Some("hex"),
},
arg.range(),
);
}
_ => {}
}
}
}

View File

@ -21,6 +21,26 @@ RUF065 Unnecessary `str()` conversion when formatting with `%s`
7 | # %s + repr()
|
RUF065 Unnecessary `repr()` conversion when formatting with `%s`. Use `%r` instead of `%s`
--> RUF065.py:8:26
|
7 | # %s + repr()
8 | logging.info("Hello %s", repr("World!"))
| ^^^^^^^^^^^^^^
9 | logging.log(logging.INFO, "Hello %s", repr("World!"))
|
RUF065 Unnecessary `repr()` conversion when formatting with `%s`. Use `%r` instead of `%s`
--> RUF065.py:9:39
|
7 | # %s + repr()
8 | logging.info("Hello %s", repr("World!"))
9 | logging.log(logging.INFO, "Hello %s", repr("World!"))
| ^^^^^^^^^^^^^^
10 |
11 | # %r + str()
|
RUF065 Unnecessary `str()` conversion when formatting with `%s`
--> RUF065.py:22:18
|
@ -40,3 +60,160 @@ RUF065 Unnecessary `str()` conversion when formatting with `%s`
24 |
25 | # %s + repr()
|
RUF065 Unnecessary `repr()` conversion when formatting with `%s`. Use `%r` instead of `%s`
--> RUF065.py:26:18
|
25 | # %s + repr()
26 | info("Hello %s", repr("World!"))
| ^^^^^^^^^^^^^^
27 | log(logging.INFO, "Hello %s", repr("World!"))
|
RUF065 Unnecessary `repr()` conversion when formatting with `%s`. Use `%r` instead of `%s`
--> RUF065.py:27:31
|
25 | # %s + repr()
26 | info("Hello %s", repr("World!"))
27 | log(logging.INFO, "Hello %s", repr("World!"))
| ^^^^^^^^^^^^^^
28 |
29 | # %r + str()
|
RUF065 Unnecessary `repr()` conversion when formatting with `%s`. Use `%r` instead of `%s`
--> RUF065.py:44:32
|
42 | logging.warning("Value: %r", repr(42))
43 | logging.error("Error: %r", repr([1, 2, 3]))
44 | logging.info("Debug info: %s", repr("test\nstring"))
| ^^^^^^^^^^^^^^^^^^^^
45 | logging.warning("Value: %s", repr(42))
|
RUF065 Unnecessary `repr()` conversion when formatting with `%s`. Use `%r` instead of `%s`
--> RUF065.py:45:30
|
43 | logging.error("Error: %r", repr([1, 2, 3]))
44 | logging.info("Debug info: %s", repr("test\nstring"))
45 | logging.warning("Value: %s", repr(42))
| ^^^^^^^^
46 |
47 | # %s + ascii()
|
RUF065 Unnecessary `ascii()` conversion when formatting with `%s`. Use `%a` instead of `%s`
--> RUF065.py:48:27
|
47 | # %s + ascii()
48 | logging.info("ASCII: %s", ascii("Hello\nWorld"))
| ^^^^^^^^^^^^^^^^^^^^^
49 | logging.warning("ASCII: %s", ascii("test"))
|
RUF065 Unnecessary `ascii()` conversion when formatting with `%s`. Use `%a` instead of `%s`
--> RUF065.py:49:30
|
47 | # %s + ascii()
48 | logging.info("ASCII: %s", ascii("Hello\nWorld"))
49 | logging.warning("ASCII: %s", ascii("test"))
| ^^^^^^^^^^^^^
50 |
51 | # %s + oct()
|
RUF065 Unnecessary `oct()` conversion when formatting with `%s`. Use `%#o` instead of `%s`
--> RUF065.py:52:27
|
51 | # %s + oct()
52 | logging.info("Octal: %s", oct(42))
| ^^^^^^^
53 | logging.warning("Octal: %s", oct(255))
|
RUF065 Unnecessary `oct()` conversion when formatting with `%s`. Use `%#o` instead of `%s`
--> RUF065.py:53:30
|
51 | # %s + oct()
52 | logging.info("Octal: %s", oct(42))
53 | logging.warning("Octal: %s", oct(255))
| ^^^^^^^^
54 |
55 | # %s + hex()
|
RUF065 Unnecessary `hex()` conversion when formatting with `%s`. Use `%#x` instead of `%s`
--> RUF065.py:56:25
|
55 | # %s + hex()
56 | logging.info("Hex: %s", hex(42))
| ^^^^^^^
57 | logging.warning("Hex: %s", hex(255))
|
RUF065 Unnecessary `hex()` conversion when formatting with `%s`. Use `%#x` instead of `%s`
--> RUF065.py:57:28
|
55 | # %s + hex()
56 | logging.info("Hex: %s", hex(42))
57 | logging.warning("Hex: %s", hex(255))
| ^^^^^^^^
|
RUF065 Unnecessary `ascii()` conversion when formatting with `%s`. Use `%a` instead of `%s`
--> RUF065.py:63:19
|
61 | from logging import info, log
62 |
63 | info("ASCII: %s", ascii("Hello\nWorld"))
| ^^^^^^^^^^^^^^^^^^^^^
64 | log(logging.INFO, "ASCII: %s", ascii("test"))
|
RUF065 Unnecessary `ascii()` conversion when formatting with `%s`. Use `%a` instead of `%s`
--> RUF065.py:64:32
|
63 | info("ASCII: %s", ascii("Hello\nWorld"))
64 | log(logging.INFO, "ASCII: %s", ascii("test"))
| ^^^^^^^^^^^^^
65 |
66 | info("Octal: %s", oct(42))
|
RUF065 Unnecessary `oct()` conversion when formatting with `%s`. Use `%#o` instead of `%s`
--> RUF065.py:66:19
|
64 | log(logging.INFO, "ASCII: %s", ascii("test"))
65 |
66 | info("Octal: %s", oct(42))
| ^^^^^^^
67 | log(logging.INFO, "Octal: %s", oct(255))
|
RUF065 Unnecessary `oct()` conversion when formatting with `%s`. Use `%#o` instead of `%s`
--> RUF065.py:67:32
|
66 | info("Octal: %s", oct(42))
67 | log(logging.INFO, "Octal: %s", oct(255))
| ^^^^^^^^
68 |
69 | info("Hex: %s", hex(42))
|
RUF065 Unnecessary `hex()` conversion when formatting with `%s`. Use `%#x` instead of `%s`
--> RUF065.py:69:17
|
67 | log(logging.INFO, "Octal: %s", oct(255))
68 |
69 | info("Hex: %s", hex(42))
| ^^^^^^^
70 | log(logging.INFO, "Hex: %s", hex(255))
|
RUF065 Unnecessary `hex()` conversion when formatting with `%s`. Use `%#x` instead of `%s`
--> RUF065.py:70:30
|
69 | info("Hex: %s", hex(42))
70 | log(logging.INFO, "Hex: %s", hex(255))
| ^^^^^^^^
|