[`flake8-logging-format`] Add auto-fix for f-string logging calls (`G004`) (#19303)

Closes #19302

<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title? (Please prefix
with `[ty]` for ty pull
  requests.)
- Does this pull request include references to any relevant issues?
-->

## Summary
This adds an auto-fix for `Logging statement uses f-string` Ruff G004,
so users don't have to resolve it manually.
<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan
I ran the auto-fixes on a Python file locally and and it worked as
expected.
<!-- How was it tested? -->

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
This commit is contained in:
Hamir Mahal 2025-08-26 07:51:24 -07:00 committed by GitHub
parent bc7274d148
commit 136abace92
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 674 additions and 5 deletions

View File

@ -17,3 +17,50 @@ info(f"{__name__}")
# Don't trigger for t-strings
info(t"{name}")
info(t"{__name__}")
count = 5
total = 9
directory_path = "/home/hamir/ruff/crates/ruff_linter/resources/test/"
logging.info(f"{count} out of {total} files in {directory_path} checked")
x = 99
fmt = "08d"
logger.info(f"{x:{'08d'}}")
logger.info(f"{x:>10} {x:{fmt}}")
logging.info(f"")
logging.info(f"This message doesn't have any variables.")
obj = {"key": "value"}
logging.info(f"Object: {obj!r}")
items_count = 3
logging.warning(f"Items: {items_count:d}")
data = {"status": "active"}
logging.info(f"Processing {len(data)} items")
logging.info(f"Status: {data.get('status', 'unknown').upper()}")
result = 123
logging.info(f"Calculated result: {result + 100}")
temperature = 123
logging.info(f"Temperature: {temperature:.1f}°C")
class FilePath:
def __init__(self, name: str):
self.name = name
logging.info(f"No changes made to {file_path.name}.")
user = "tron"
balance = 123.45
logging.error(f"Error {404}: User {user} has insufficient balance ${balance:.2f}")
import logging
x = 1
logging.error(f"{x} -> %s", x)

View File

@ -0,0 +1,10 @@
"""Test f-string argument order."""
import logging
logger = logging.getLogger(__name__)
X = 1
Y = 2
logger.error(f"{X} -> %s", Y)
logger.error(f"{Y} -> %s", X)

View File

@ -40,6 +40,11 @@ pub(crate) const fn is_bad_version_info_in_non_stub_enabled(settings: &LinterSet
settings.preview.is_enabled()
}
/// <https://github.com/astral-sh/ruff/pull/19303>
pub(crate) const fn is_fix_f_string_logging_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()
}
// https://github.com/astral-sh/ruff/pull/16719
pub(crate) const fn is_fix_manual_dict_comprehension_enabled(settings: &LinterSettings) -> bool {
settings.preview.is_enabled()

View File

@ -22,6 +22,7 @@ mod tests {
#[test_case(Path::new("G002.py"))]
#[test_case(Path::new("G003.py"))]
#[test_case(Path::new("G004.py"))]
#[test_case(Path::new("G004_arg_order.py"))]
#[test_case(Path::new("G010.py"))]
#[test_case(Path::new("G101_1.py"))]
#[test_case(Path::new("G101_2.py"))]
@ -48,4 +49,24 @@ mod tests {
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
#[test_case(Rule::LoggingFString, Path::new("G004.py"))]
#[test_case(Rule::LoggingFString, Path::new("G004_arg_order.py"))]
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"preview__{}_{}",
rule_code.noqa_code(),
path.to_string_lossy()
);
let diagnostics = test_path(
Path::new("flake8_logging_format").join(path).as_path(),
&settings::LinterSettings {
logger_objects: vec!["logging_setup.logger".to_string()],
preview: settings::types::PreviewMode::Enabled,
..settings::LinterSettings::for_rule(rule_code)
},
)?;
assert_diagnostics!(snapshot, diagnostics);
Ok(())
}
}

View File

@ -1,9 +1,11 @@
use ruff_python_ast::{self as ast, Arguments, Expr, Keyword, Operator};
use ruff_python_ast::InterpolatedStringElement;
use ruff_python_ast::{self as ast, Arguments, Expr, Keyword, Operator, StringFlags};
use ruff_python_semantic::analyze::logging;
use ruff_python_stdlib::logging::LoggingLevel;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::preview::is_fix_f_string_logging_enabled;
use crate::registry::Rule;
use crate::rules::flake8_logging_format::violations::{
LoggingExcInfo, LoggingExtraAttrClash, LoggingFString, LoggingPercentFormat,
@ -11,6 +13,87 @@ use crate::rules::flake8_logging_format::violations::{
};
use crate::{Edit, Fix};
fn logging_f_string(
checker: &Checker,
msg: &Expr,
f_string: &ast::ExprFString,
arguments: &Arguments,
msg_pos: usize,
) {
// Report the diagnostic up-front so we can attach a fix later only when preview is enabled.
let mut diagnostic = checker.report_diagnostic(LoggingFString, msg.range());
// Preview gate for the automatic fix.
if !is_fix_f_string_logging_enabled(checker.settings()) {
return;
}
// If there are existing positional arguments after the message, bail out.
// This could indicate a mistake or complex usage we shouldn't try to fix.
if arguments.args.len() > msg_pos + 1 {
return;
}
let mut format_string = String::new();
let mut args: Vec<&str> = Vec::new();
// Try to reuse the first part's quote style when building the replacement.
// Default to double quotes if we can't determine it.
let quote_str = f_string
.value
.f_strings()
.next()
.map(|f| f.flags.quote_str())
.unwrap_or("\"");
for f in f_string.value.f_strings() {
for element in &f.elements {
match element {
InterpolatedStringElement::Literal(lit) => {
// If the literal text contains a '%' placeholder, bail out: mixing
// f-string interpolation with '%' placeholders is ambiguous for our
// automatic conversion, so don't offer a fix for this case.
if lit.value.as_ref().contains('%') {
return;
}
format_string.push_str(lit.value.as_ref());
}
InterpolatedStringElement::Interpolation(interpolated) => {
if interpolated.format_spec.is_some()
|| !matches!(
interpolated.conversion,
ruff_python_ast::ConversionFlag::None
)
{
return;
}
match interpolated.expression.as_ref() {
Expr::Name(name) => {
format_string.push_str("%s");
args.push(name.id.as_str());
}
_ => return,
}
}
}
}
}
if args.is_empty() {
return;
}
let replacement = format!(
"{q}{format_string}{q}, {args}",
q = quote_str,
format_string = format_string,
args = args.join(", ")
);
let fix = Fix::safe_edit(Edit::range_replacement(replacement, msg.range()));
diagnostic.set_fix(fix);
}
/// Returns `true` if the attribute is a reserved attribute on the `logging` module's `LogRecord`
/// class.
fn is_reserved_attr(attr: &str) -> bool {
@ -42,7 +125,7 @@ fn is_reserved_attr(attr: &str) -> bool {
}
/// Check logging messages for violations.
fn check_msg(checker: &Checker, msg: &Expr) {
fn check_msg(checker: &Checker, msg: &Expr, arguments: &Arguments, msg_pos: usize) {
match msg {
// Check for string concatenation and percent format.
Expr::BinOp(ast::ExprBinOp { op, .. }) => match op {
@ -55,8 +138,10 @@ fn check_msg(checker: &Checker, msg: &Expr) {
_ => {}
},
// Check for f-strings.
Expr::FString(_) => {
checker.report_diagnostic_if_enabled(LoggingFString, msg.range());
Expr::FString(f_string) => {
if checker.is_rule_enabled(Rule::LoggingFString) {
logging_f_string(checker, msg, f_string, arguments, msg_pos);
}
}
// Check for .format() calls.
Expr::Call(ast::ExprCall { func, .. }) => {
@ -168,7 +253,7 @@ pub(crate) fn logging_call(checker: &Checker, call: &ast::ExprCall) {
// G001, G002, G003, G004
let msg_pos = usize::from(matches!(logging_call_type, LoggingCallType::LogCall));
if let Some(format_arg) = call.arguments.find_argument_value("msg", msg_pos) {
check_msg(checker, format_arg);
check_msg(checker, format_arg, &call.arguments, msg_pos);
}
// G010

View File

@ -9,6 +9,7 @@ G004 Logging statement uses f-string
| ^^^^^^^^^^^^^^^
5 | logging.log(logging.INFO, f"Hello {name}")
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:5:27
@ -20,6 +21,7 @@ G004 Logging statement uses f-string
6 |
7 | _LOGGER = logging.getLogger()
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:8:14
@ -30,6 +32,7 @@ G004 Logging statement uses f-string
9 |
10 | logging.getLogger().info(f"{name}")
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:10:26
@ -41,6 +44,7 @@ G004 Logging statement uses f-string
11 |
12 | from logging import info
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:14:6
@ -51,6 +55,7 @@ G004 Logging statement uses f-string
| ^^^^^^^^^
15 | info(f"{__name__}")
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:15:6
@ -61,3 +66,156 @@ G004 Logging statement uses f-string
16 |
17 | # Don't trigger for t-strings
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:24:14
|
22 | total = 9
23 | directory_path = "/home/hamir/ruff/crates/ruff_linter/resources/test/"
24 | logging.info(f"{count} out of {total} files in {directory_path} checked")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:30:13
|
28 | x = 99
29 | fmt = "08d"
30 | logger.info(f"{x:{'08d'}}")
| ^^^^^^^^^^^^^^
31 | logger.info(f"{x:>10} {x:{fmt}}")
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:31:13
|
29 | fmt = "08d"
30 | logger.info(f"{x:{'08d'}}")
31 | logger.info(f"{x:>10} {x:{fmt}}")
| ^^^^^^^^^^^^^^^^^^^^
32 |
33 | logging.info(f"")
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:33:14
|
31 | logger.info(f"{x:>10} {x:{fmt}}")
32 |
33 | logging.info(f"")
| ^^^
34 | logging.info(f"This message doesn't have any variables.")
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:34:14
|
33 | logging.info(f"")
34 | logging.info(f"This message doesn't have any variables.")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
35 |
36 | obj = {"key": "value"}
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:37:14
|
36 | obj = {"key": "value"}
37 | logging.info(f"Object: {obj!r}")
| ^^^^^^^^^^^^^^^^^^
38 |
39 | items_count = 3
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:40:17
|
39 | items_count = 3
40 | logging.warning(f"Items: {items_count:d}")
| ^^^^^^^^^^^^^^^^^^^^^^^^^
41 |
42 | data = {"status": "active"}
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:43:14
|
42 | data = {"status": "active"}
43 | logging.info(f"Processing {len(data)} items")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
44 | logging.info(f"Status: {data.get('status', 'unknown').upper()}")
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:44:14
|
42 | data = {"status": "active"}
43 | logging.info(f"Processing {len(data)} items")
44 | logging.info(f"Status: {data.get('status', 'unknown').upper()}")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:48:14
|
47 | result = 123
48 | logging.info(f"Calculated result: {result + 100}")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
49 |
50 | temperature = 123
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:51:14
|
50 | temperature = 123
51 | logging.info(f"Temperature: {temperature:.1f}°C")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
52 |
53 | class FilePath:
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:57:14
|
55 | self.name = name
56 |
57 | logging.info(f"No changes made to {file_path.name}.")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
58 |
59 | user = "tron"
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:61:15
|
59 | user = "tron"
60 | balance = 123.45
61 | logging.error(f"Error {404}: User {user} has insufficient balance ${balance:.2f}")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
62 |
63 | import logging
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:66:15
|
65 | x = 1
66 | logging.error(f"{x} -> %s", x)
| ^^^^^^^^^^^^
|
help: Convert to lazy `%` formatting

View File

@ -0,0 +1,23 @@
---
source: crates/ruff_linter/src/rules/flake8_logging_format/mod.rs
---
G004 Logging statement uses f-string
--> G004_arg_order.py:9:14
|
7 | X = 1
8 | Y = 2
9 | logger.error(f"{X} -> %s", Y)
| ^^^^^^^^^^^^
10 | logger.error(f"{Y} -> %s", X)
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004_arg_order.py:10:14
|
8 | Y = 2
9 | logger.error(f"{X} -> %s", Y)
10 | logger.error(f"{Y} -> %s", X)
| ^^^^^^^^^^^^
|
help: Convert to lazy `%` formatting

View File

@ -0,0 +1,291 @@
---
source: crates/ruff_linter/src/rules/flake8_logging_format/mod.rs
---
G004 [*] Logging statement uses f-string
--> G004.py:4:14
|
3 | name = "world"
4 | logging.info(f"Hello {name}")
| ^^^^^^^^^^^^^^^
5 | logging.log(logging.INFO, f"Hello {name}")
|
help: Convert to lazy `%` formatting
Safe fix
1 1 | import logging
2 2 |
3 3 | name = "world"
4 |-logging.info(f"Hello {name}")
4 |+logging.info("Hello %s", name)
5 5 | logging.log(logging.INFO, f"Hello {name}")
6 6 |
7 7 | _LOGGER = logging.getLogger()
G004 [*] Logging statement uses f-string
--> G004.py:5:27
|
3 | name = "world"
4 | logging.info(f"Hello {name}")
5 | logging.log(logging.INFO, f"Hello {name}")
| ^^^^^^^^^^^^^^^
6 |
7 | _LOGGER = logging.getLogger()
|
help: Convert to lazy `%` formatting
Safe fix
2 2 |
3 3 | name = "world"
4 4 | logging.info(f"Hello {name}")
5 |-logging.log(logging.INFO, f"Hello {name}")
5 |+logging.log(logging.INFO, "Hello %s", name)
6 6 |
7 7 | _LOGGER = logging.getLogger()
8 8 | _LOGGER.info(f"{__name__}")
G004 [*] Logging statement uses f-string
--> G004.py:8:14
|
7 | _LOGGER = logging.getLogger()
8 | _LOGGER.info(f"{__name__}")
| ^^^^^^^^^^^^^
9 |
10 | logging.getLogger().info(f"{name}")
|
help: Convert to lazy `%` formatting
Safe fix
5 5 | logging.log(logging.INFO, f"Hello {name}")
6 6 |
7 7 | _LOGGER = logging.getLogger()
8 |-_LOGGER.info(f"{__name__}")
8 |+_LOGGER.info("%s", __name__)
9 9 |
10 10 | logging.getLogger().info(f"{name}")
11 11 |
G004 [*] Logging statement uses f-string
--> G004.py:10:26
|
8 | _LOGGER.info(f"{__name__}")
9 |
10 | logging.getLogger().info(f"{name}")
| ^^^^^^^^^
11 |
12 | from logging import info
|
help: Convert to lazy `%` formatting
Safe fix
7 7 | _LOGGER = logging.getLogger()
8 8 | _LOGGER.info(f"{__name__}")
9 9 |
10 |-logging.getLogger().info(f"{name}")
10 |+logging.getLogger().info("%s", name)
11 11 |
12 12 | from logging import info
13 13 |
G004 [*] Logging statement uses f-string
--> G004.py:14:6
|
12 | from logging import info
13 |
14 | info(f"{name}")
| ^^^^^^^^^
15 | info(f"{__name__}")
|
help: Convert to lazy `%` formatting
Safe fix
11 11 |
12 12 | from logging import info
13 13 |
14 |-info(f"{name}")
14 |+info("%s", name)
15 15 | info(f"{__name__}")
16 16 |
17 17 | # Don't trigger for t-strings
G004 [*] Logging statement uses f-string
--> G004.py:15:6
|
14 | info(f"{name}")
15 | info(f"{__name__}")
| ^^^^^^^^^^^^^
16 |
17 | # Don't trigger for t-strings
|
help: Convert to lazy `%` formatting
Safe fix
12 12 | from logging import info
13 13 |
14 14 | info(f"{name}")
15 |-info(f"{__name__}")
15 |+info("%s", __name__)
16 16 |
17 17 | # Don't trigger for t-strings
18 18 | info(t"{name}")
G004 [*] Logging statement uses f-string
--> G004.py:24:14
|
22 | total = 9
23 | directory_path = "/home/hamir/ruff/crates/ruff_linter/resources/test/"
24 | logging.info(f"{count} out of {total} files in {directory_path} checked")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: Convert to lazy `%` formatting
Safe fix
21 21 | count = 5
22 22 | total = 9
23 23 | directory_path = "/home/hamir/ruff/crates/ruff_linter/resources/test/"
24 |-logging.info(f"{count} out of {total} files in {directory_path} checked")
24 |+logging.info("%s out of %s files in %s checked", count, total, directory_path)
25 25 |
26 26 |
27 27 |
G004 Logging statement uses f-string
--> G004.py:30:13
|
28 | x = 99
29 | fmt = "08d"
30 | logger.info(f"{x:{'08d'}}")
| ^^^^^^^^^^^^^^
31 | logger.info(f"{x:>10} {x:{fmt}}")
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:31:13
|
29 | fmt = "08d"
30 | logger.info(f"{x:{'08d'}}")
31 | logger.info(f"{x:>10} {x:{fmt}}")
| ^^^^^^^^^^^^^^^^^^^^
32 |
33 | logging.info(f"")
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:33:14
|
31 | logger.info(f"{x:>10} {x:{fmt}}")
32 |
33 | logging.info(f"")
| ^^^
34 | logging.info(f"This message doesn't have any variables.")
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:34:14
|
33 | logging.info(f"")
34 | logging.info(f"This message doesn't have any variables.")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
35 |
36 | obj = {"key": "value"}
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:37:14
|
36 | obj = {"key": "value"}
37 | logging.info(f"Object: {obj!r}")
| ^^^^^^^^^^^^^^^^^^
38 |
39 | items_count = 3
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:40:17
|
39 | items_count = 3
40 | logging.warning(f"Items: {items_count:d}")
| ^^^^^^^^^^^^^^^^^^^^^^^^^
41 |
42 | data = {"status": "active"}
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:43:14
|
42 | data = {"status": "active"}
43 | logging.info(f"Processing {len(data)} items")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
44 | logging.info(f"Status: {data.get('status', 'unknown').upper()}")
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:44:14
|
42 | data = {"status": "active"}
43 | logging.info(f"Processing {len(data)} items")
44 | logging.info(f"Status: {data.get('status', 'unknown').upper()}")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:48:14
|
47 | result = 123
48 | logging.info(f"Calculated result: {result + 100}")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
49 |
50 | temperature = 123
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:51:14
|
50 | temperature = 123
51 | logging.info(f"Temperature: {temperature:.1f}°C")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
52 |
53 | class FilePath:
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:57:14
|
55 | self.name = name
56 |
57 | logging.info(f"No changes made to {file_path.name}.")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
58 |
59 | user = "tron"
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:61:15
|
59 | user = "tron"
60 | balance = 123.45
61 | logging.error(f"Error {404}: User {user} has insufficient balance ${balance:.2f}")
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
62 |
63 | import logging
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004.py:66:15
|
65 | x = 1
66 | logging.error(f"{x} -> %s", x)
| ^^^^^^^^^^^^
|
help: Convert to lazy `%` formatting

View File

@ -0,0 +1,23 @@
---
source: crates/ruff_linter/src/rules/flake8_logging_format/mod.rs
---
G004 Logging statement uses f-string
--> G004_arg_order.py:9:14
|
7 | X = 1
8 | Y = 2
9 | logger.error(f"{X} -> %s", Y)
| ^^^^^^^^^^^^
10 | logger.error(f"{Y} -> %s", X)
|
help: Convert to lazy `%` formatting
G004 Logging statement uses f-string
--> G004_arg_order.py:10:14
|
8 | Y = 2
9 | logger.error(f"{X} -> %s", Y)
10 | logger.error(f"{Y} -> %s", X)
| ^^^^^^^^^^^^
|
help: Convert to lazy `%` formatting

View File

@ -327,10 +327,16 @@ impl Violation for LoggingStringConcat {
pub(crate) struct LoggingFString;
impl Violation for LoggingFString {
const FIX_AVAILABILITY: crate::FixAvailability = crate::FixAvailability::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
"Logging statement uses f-string".to_string()
}
fn fix_title(&self) -> Option<String> {
Some("Convert to lazy `%` formatting".to_string())
}
}
/// ## What it does