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
|
# Don't trigger for t-strings
|
||||||
info(t"{name}")
|
info(t"{name}")
|
||||||
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()
|
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
|
// https://github.com/astral-sh/ruff/pull/16719
|
||||||
pub(crate) const fn is_fix_manual_dict_comprehension_enabled(settings: &LinterSettings) -> bool {
|
pub(crate) const fn is_fix_manual_dict_comprehension_enabled(settings: &LinterSettings) -> bool {
|
||||||
settings.preview.is_enabled()
|
settings.preview.is_enabled()
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ mod tests {
|
||||||
#[test_case(Path::new("G002.py"))]
|
#[test_case(Path::new("G002.py"))]
|
||||||
#[test_case(Path::new("G003.py"))]
|
#[test_case(Path::new("G003.py"))]
|
||||||
#[test_case(Path::new("G004.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("G010.py"))]
|
||||||
#[test_case(Path::new("G101_1.py"))]
|
#[test_case(Path::new("G101_1.py"))]
|
||||||
#[test_case(Path::new("G101_2.py"))]
|
#[test_case(Path::new("G101_2.py"))]
|
||||||
|
|
@ -48,4 +49,24 @@ mod tests {
|
||||||
assert_diagnostics!(snapshot, diagnostics);
|
assert_diagnostics!(snapshot, diagnostics);
|
||||||
Ok(())
|
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_semantic::analyze::logging;
|
||||||
use ruff_python_stdlib::logging::LoggingLevel;
|
use ruff_python_stdlib::logging::LoggingLevel;
|
||||||
use ruff_text_size::Ranged;
|
use ruff_text_size::Ranged;
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
|
use crate::preview::is_fix_f_string_logging_enabled;
|
||||||
use crate::registry::Rule;
|
use crate::registry::Rule;
|
||||||
use crate::rules::flake8_logging_format::violations::{
|
use crate::rules::flake8_logging_format::violations::{
|
||||||
LoggingExcInfo, LoggingExtraAttrClash, LoggingFString, LoggingPercentFormat,
|
LoggingExcInfo, LoggingExtraAttrClash, LoggingFString, LoggingPercentFormat,
|
||||||
|
|
@ -11,6 +13,87 @@ use crate::rules::flake8_logging_format::violations::{
|
||||||
};
|
};
|
||||||
use crate::{Edit, Fix};
|
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`
|
/// Returns `true` if the attribute is a reserved attribute on the `logging` module's `LogRecord`
|
||||||
/// class.
|
/// class.
|
||||||
fn is_reserved_attr(attr: &str) -> bool {
|
fn is_reserved_attr(attr: &str) -> bool {
|
||||||
|
|
@ -42,7 +125,7 @@ fn is_reserved_attr(attr: &str) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check logging messages for violations.
|
/// 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 {
|
match msg {
|
||||||
// Check for string concatenation and percent format.
|
// Check for string concatenation and percent format.
|
||||||
Expr::BinOp(ast::ExprBinOp { op, .. }) => match op {
|
Expr::BinOp(ast::ExprBinOp { op, .. }) => match op {
|
||||||
|
|
@ -55,8 +138,10 @@ fn check_msg(checker: &Checker, msg: &Expr) {
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
// Check for f-strings.
|
// Check for f-strings.
|
||||||
Expr::FString(_) => {
|
Expr::FString(f_string) => {
|
||||||
checker.report_diagnostic_if_enabled(LoggingFString, msg.range());
|
if checker.is_rule_enabled(Rule::LoggingFString) {
|
||||||
|
logging_f_string(checker, msg, f_string, arguments, msg_pos);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Check for .format() calls.
|
// Check for .format() calls.
|
||||||
Expr::Call(ast::ExprCall { func, .. }) => {
|
Expr::Call(ast::ExprCall { func, .. }) => {
|
||||||
|
|
@ -168,7 +253,7 @@ pub(crate) fn logging_call(checker: &Checker, call: &ast::ExprCall) {
|
||||||
// G001, G002, G003, G004
|
// G001, G002, G003, G004
|
||||||
let msg_pos = usize::from(matches!(logging_call_type, LoggingCallType::LogCall));
|
let msg_pos = usize::from(matches!(logging_call_type, LoggingCallType::LogCall));
|
||||||
if let Some(format_arg) = call.arguments.find_argument_value("msg", msg_pos) {
|
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
|
// G010
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ G004 Logging statement uses f-string
|
||||||
| ^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^
|
||||||
5 | logging.log(logging.INFO, f"Hello {name}")
|
5 | logging.log(logging.INFO, f"Hello {name}")
|
||||||
|
|
|
|
||||||
|
help: Convert to lazy `%` formatting
|
||||||
|
|
||||||
G004 Logging statement uses f-string
|
G004 Logging statement uses f-string
|
||||||
--> G004.py:5:27
|
--> G004.py:5:27
|
||||||
|
|
@ -20,6 +21,7 @@ G004 Logging statement uses f-string
|
||||||
6 |
|
6 |
|
||||||
7 | _LOGGER = logging.getLogger()
|
7 | _LOGGER = logging.getLogger()
|
||||||
|
|
|
|
||||||
|
help: Convert to lazy `%` formatting
|
||||||
|
|
||||||
G004 Logging statement uses f-string
|
G004 Logging statement uses f-string
|
||||||
--> G004.py:8:14
|
--> G004.py:8:14
|
||||||
|
|
@ -30,6 +32,7 @@ G004 Logging statement uses f-string
|
||||||
9 |
|
9 |
|
||||||
10 | logging.getLogger().info(f"{name}")
|
10 | logging.getLogger().info(f"{name}")
|
||||||
|
|
|
|
||||||
|
help: Convert to lazy `%` formatting
|
||||||
|
|
||||||
G004 Logging statement uses f-string
|
G004 Logging statement uses f-string
|
||||||
--> G004.py:10:26
|
--> G004.py:10:26
|
||||||
|
|
@ -41,6 +44,7 @@ G004 Logging statement uses f-string
|
||||||
11 |
|
11 |
|
||||||
12 | from logging import info
|
12 | from logging import info
|
||||||
|
|
|
|
||||||
|
help: Convert to lazy `%` formatting
|
||||||
|
|
||||||
G004 Logging statement uses f-string
|
G004 Logging statement uses f-string
|
||||||
--> G004.py:14:6
|
--> G004.py:14:6
|
||||||
|
|
@ -51,6 +55,7 @@ G004 Logging statement uses f-string
|
||||||
| ^^^^^^^^^
|
| ^^^^^^^^^
|
||||||
15 | info(f"{__name__}")
|
15 | info(f"{__name__}")
|
||||||
|
|
|
|
||||||
|
help: Convert to lazy `%` formatting
|
||||||
|
|
||||||
G004 Logging statement uses f-string
|
G004 Logging statement uses f-string
|
||||||
--> G004.py:15:6
|
--> G004.py:15:6
|
||||||
|
|
@ -61,3 +66,156 @@ G004 Logging statement uses f-string
|
||||||
16 |
|
16 |
|
||||||
17 | # Don't trigger for t-strings
|
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;
|
pub(crate) struct LoggingFString;
|
||||||
|
|
||||||
impl Violation for LoggingFString {
|
impl Violation for LoggingFString {
|
||||||
|
const FIX_AVAILABILITY: crate::FixAvailability = crate::FixAvailability::Sometimes;
|
||||||
|
|
||||||
#[derive_message_formats]
|
#[derive_message_formats]
|
||||||
fn message(&self) -> String {
|
fn message(&self) -> String {
|
||||||
"Logging statement uses f-string".to_string()
|
"Logging statement uses f-string".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fix_title(&self) -> Option<String> {
|
||||||
|
Some("Convert to lazy `%` formatting".to_string())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ## What it does
|
/// ## What it does
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue