mirror of https://github.com/astral-sh/ruff
[`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:
parent
bc7274d148
commit
136abace92
|
|
@ -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)
|
||||
|
|
|
|||
10
crates/ruff_linter/resources/test/fixtures/flake8_logging_format/G004_arg_order.py
vendored
Normal file
10
crates/ruff_linter/resources/test/fixtures/flake8_logging_format/G004_arg_order.py
vendored
Normal 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)
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue