diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF065.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF065.py index d7de9df7d2..879fb186bf 100644 --- a/crates/ruff_linter/resources/test/fixtures/ruff/RUF065.py +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF065.py @@ -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)) + diff --git a/crates/ruff_linter/src/rules/ruff/rules/logging_eager_conversion.rs b/crates/ruff_linter/src/rules/ruff/rules/logging_eager_conversion.rs index a1bfc622cc..2b09c8a1e0 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/logging_eager_conversion.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/logging_eager_conversion.rs @@ -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) - { - checker - .report_diagnostic(LoggingEagerConversion { format_conversion }, arg.range()); + // 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, + 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(), + ); + } + _ => {} } } } diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF065_RUF065.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF065_RUF065.py.snap index 9589c4555d..9f96c36307 100644 --- a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF065_RUF065.py.snap +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF065_RUF065.py.snap @@ -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)) + | ^^^^^^^^ + |