From c9031ce59f8c41334b90e8b35836722fba7b0ada Mon Sep 17 00:00:00 2001 From: Victor Hugo Gomes Date: Mon, 12 May 2025 17:08:12 -0300 Subject: [PATCH] [`refurb`] Mark autofix as safe only for number literals in `FURB116` (#17692) ## Summary We can only guarantee the safety of the autofix for number literals, all other cases may change the runtime behaviour of the program or introduce a syntax error. For the cases reported in the issue that would result in a syntax error, I disabled the autofix. Follow-up of #17661. Fixes #16472. ## Test Plan Snapshot tests. --- .../resources/test/fixtures/refurb/FURB116.py | 20 + crates/ruff_linter/src/rules/refurb/mod.rs | 11 + .../refurb/rules/fstring_number_format.rs | 84 ++++- ...es__refurb__tests__FURB116_FURB116.py.snap | 354 ++++++++++++------ ...sts__fstring_number_format_python_311.snap | 256 +++++++++++++ 5 files changed, 602 insertions(+), 123 deletions(-) create mode 100644 crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__fstring_number_format_python_311.snap diff --git a/crates/ruff_linter/resources/test/fixtures/refurb/FURB116.py b/crates/ruff_linter/resources/test/fixtures/refurb/FURB116.py index b95f9147e7..6ec754dadc 100644 --- a/crates/ruff_linter/resources/test/fixtures/refurb/FURB116.py +++ b/crates/ruff_linter/resources/test/fixtures/refurb/FURB116.py @@ -1,3 +1,6 @@ +import datetime +import sys + num = 1337 def return_num() -> int: @@ -10,6 +13,7 @@ print(bin(num)[2:]) # FURB116 print(oct(1337)[2:]) # FURB116 print(hex(1337)[2:]) # FURB116 print(bin(1337)[2:]) # FURB116 +print(bin(+1337)[2:]) # FURB116 print(bin(return_num())[2:]) # FURB116 (no autofix) print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix) @@ -22,3 +26,19 @@ print(hex(0x1337)[3:]) # float and complex numbers should be ignored print(bin(1.0)[2:]) print(bin(3.14j)[2:]) + +d = datetime.datetime.now(tz=datetime.UTC) +# autofix is display-only +print(bin(d)[2:]) +# no autofix for Python 3.11 and earlier, as it introduces a syntax error +print(bin(len("xyz").numerator)[2:]) + +# autofix is display-only +print(bin({0: 1}[0].numerator)[2:]) +# no autofix for Python 3.11 and earlier, as it introduces a syntax error +print(bin(ord("\\").numerator)[2:]) +print(hex(sys +.maxunicode)[2:]) + +# for negatives numbers autofix is display-only +print(bin(-1)[2:]) diff --git a/crates/ruff_linter/src/rules/refurb/mod.rs b/crates/ruff_linter/src/rules/refurb/mod.rs index 1793e0e964..d841bcafe1 100644 --- a/crates/ruff_linter/src/rules/refurb/mod.rs +++ b/crates/ruff_linter/src/rules/refurb/mod.rs @@ -89,4 +89,15 @@ mod tests { assert_messages!(diagnostics); Ok(()) } + + #[test] + fn fstring_number_format_python_311() -> Result<()> { + let diagnostics = test_path( + Path::new("refurb/FURB116.py"), + &settings::LinterSettings::for_rule(Rule::FStringNumberFormat) + .with_target_version(PythonVersion::PY311), + )?; + assert_messages!(diagnostics); + Ok(()) + } } diff --git a/crates/ruff_linter/src/rules/refurb/rules/fstring_number_format.rs b/crates/ruff_linter/src/rules/refurb/rules/fstring_number_format.rs index 0d18e8d06f..5bd2523599 100644 --- a/crates/ruff_linter/src/rules/refurb/rules/fstring_number_format.rs +++ b/crates/ruff_linter/src/rules/refurb/rules/fstring_number_format.rs @@ -1,6 +1,7 @@ -use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; use ruff_macros::{derive_message_formats, ViolationMetadata}; -use ruff_python_ast::{self as ast, Expr, ExprCall, Number}; +use ruff_python_ast::{self as ast, Expr, ExprCall, Number, PythonVersion, UnaryOp}; +use ruff_source_file::find_newline; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; @@ -24,6 +25,11 @@ use crate::fix::snippet::SourceCodeSnippet; /// ```python /// print(f"{1337:b}") /// ``` +/// +/// ## Fix safety +/// The fix is only marked as safe for integer literals, all other cases +/// are display-only, as they may change the runtime behaviour of the program +/// or introduce syntax errors. #[derive(ViolationMetadata)] pub(crate) struct FStringNumberFormat { replacement: Option, @@ -121,21 +127,24 @@ pub(crate) fn fstring_number_format(checker: &Checker, subscript: &ast::ExprSubs return; } - // Generate a replacement, if possible. - let replacement = if matches!( - arg, - Expr::NumberLiteral(_) | Expr::Name(_) | Expr::Attribute(_) - ) { - let inner_source = checker.locator().slice(arg); - - let quote = checker.stylist().quote(); - let shorthand = base.shorthand(); - - Some(format!("f{quote}{{{inner_source}:{shorthand}}}{quote}")) + let maybe_number = if let Some(maybe_number) = arg + .as_unary_op_expr() + .filter(|unary_expr| unary_expr.op == UnaryOp::UAdd) + .map(|unary_expr| &unary_expr.operand) + { + maybe_number } else { - None + arg }; + let applicability = if matches!(maybe_number, Expr::NumberLiteral(_)) { + Applicability::Safe + } else { + Applicability::DisplayOnly + }; + + let replacement = try_create_replacement(checker, arg, base); + let mut diagnostic = Diagnostic::new( FStringNumberFormat { replacement: replacement.as_deref().map(SourceCodeSnippet::from_str), @@ -145,15 +154,54 @@ pub(crate) fn fstring_number_format(checker: &Checker, subscript: &ast::ExprSubs ); if let Some(replacement) = replacement { - diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( - replacement, - subscript.range(), - ))); + let edit = Edit::range_replacement(replacement, subscript.range()); + diagnostic.set_fix(Fix::applicable_edit(edit, applicability)); } checker.report_diagnostic(diagnostic); } +/// Generate a replacement, if possible. +fn try_create_replacement(checker: &Checker, arg: &Expr, base: Base) -> Option { + if !matches!( + arg, + Expr::NumberLiteral(_) | Expr::Name(_) | Expr::Attribute(_) | Expr::UnaryOp(_) + ) { + return None; + } + + let inner_source = checker.locator().slice(arg); + + // On Python 3.11 and earlier, trying to replace an `arg` that contains a backslash + // would create a `SyntaxError` in the f-string. + if checker.target_version() <= PythonVersion::PY311 && inner_source.contains('\\') { + return None; + } + + // On Python 3.11 and earlier, trying to replace an `arg` that spans multiple lines + // would create a `SyntaxError` in the f-string. + if checker.target_version() <= PythonVersion::PY311 && find_newline(inner_source).is_some() { + return None; + } + + let quote = checker.stylist().quote(); + let shorthand = base.shorthand(); + + // If the `arg` contains double quotes we need to create the f-string with single quotes + // to avoid a `SyntaxError` in Python 3.11 and earlier. + if checker.target_version() <= PythonVersion::PY311 && inner_source.contains(quote.as_str()) { + return None; + } + + // If the `arg` contains a brace add an space before it to avoid a `SyntaxError` + // in the f-string. + if inner_source.starts_with('{') { + Some(format!("f{quote}{{ {inner_source}:{shorthand}}}{quote}")) + } else { + Some(format!("f{quote}{{{inner_source}:{shorthand}}}{quote}")) + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq)] enum Base { Hex, diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB116_FURB116.py.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB116_FURB116.py.snap index a19b474227..c39547d551 100644 --- a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB116_FURB116.py.snap +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__FURB116_FURB116.py.snap @@ -1,144 +1,288 @@ --- source: crates/ruff_linter/src/rules/refurb/mod.rs --- -FURB116.py:6:7: FURB116 [*] Replace `oct` call with `f"{num:o}"` - | -4 | return num -5 | -6 | print(oct(num)[2:]) # FURB116 - | ^^^^^^^^^^^^ FURB116 -7 | print(hex(num)[2:]) # FURB116 -8 | print(bin(num)[2:]) # FURB116 - | - = help: Replace with `f"{num:o}"` - -ℹ Safe fix -3 3 | def return_num() -> int: -4 4 | return num -5 5 | -6 |-print(oct(num)[2:]) # FURB116 - 6 |+print(f"{num:o}") # FURB116 -7 7 | print(hex(num)[2:]) # FURB116 -8 8 | print(bin(num)[2:]) # FURB116 -9 9 | - -FURB116.py:7:7: FURB116 [*] Replace `hex` call with `f"{num:x}"` - | -6 | print(oct(num)[2:]) # FURB116 -7 | print(hex(num)[2:]) # FURB116 - | ^^^^^^^^^^^^ FURB116 -8 | print(bin(num)[2:]) # FURB116 - | - = help: Replace with `f"{num:x}"` - -ℹ Safe fix -4 4 | return num -5 5 | -6 6 | print(oct(num)[2:]) # FURB116 -7 |-print(hex(num)[2:]) # FURB116 - 7 |+print(f"{num:x}") # FURB116 -8 8 | print(bin(num)[2:]) # FURB116 -9 9 | -10 10 | print(oct(1337)[2:]) # FURB116 - -FURB116.py:8:7: FURB116 [*] Replace `bin` call with `f"{num:b}"` +FURB116.py:9:7: FURB116 Replace `oct` call with `f"{num:o}"` | - 6 | print(oct(num)[2:]) # FURB116 - 7 | print(hex(num)[2:]) # FURB116 - 8 | print(bin(num)[2:]) # FURB116 + 7 | return num + 8 | + 9 | print(oct(num)[2:]) # FURB116 | ^^^^^^^^^^^^ FURB116 - 9 | -10 | print(oct(1337)[2:]) # FURB116 +10 | print(hex(num)[2:]) # FURB116 +11 | print(bin(num)[2:]) # FURB116 + | + = help: Replace with `f"{num:o}"` + +ℹ Display-only fix +6 6 | def return_num() -> int: +7 7 | return num +8 8 | +9 |-print(oct(num)[2:]) # FURB116 + 9 |+print(f"{num:o}") # FURB116 +10 10 | print(hex(num)[2:]) # FURB116 +11 11 | print(bin(num)[2:]) # FURB116 +12 12 | + +FURB116.py:10:7: FURB116 Replace `hex` call with `f"{num:x}"` + | + 9 | print(oct(num)[2:]) # FURB116 +10 | print(hex(num)[2:]) # FURB116 + | ^^^^^^^^^^^^ FURB116 +11 | print(bin(num)[2:]) # FURB116 + | + = help: Replace with `f"{num:x}"` + +ℹ Display-only fix +7 7 | return num +8 8 | +9 9 | print(oct(num)[2:]) # FURB116 +10 |-print(hex(num)[2:]) # FURB116 + 10 |+print(f"{num:x}") # FURB116 +11 11 | print(bin(num)[2:]) # FURB116 +12 12 | +13 13 | print(oct(1337)[2:]) # FURB116 + +FURB116.py:11:7: FURB116 Replace `bin` call with `f"{num:b}"` + | + 9 | print(oct(num)[2:]) # FURB116 +10 | print(hex(num)[2:]) # FURB116 +11 | print(bin(num)[2:]) # FURB116 + | ^^^^^^^^^^^^ FURB116 +12 | +13 | print(oct(1337)[2:]) # FURB116 | = help: Replace with `f"{num:b}"` -ℹ Safe fix -5 5 | -6 6 | print(oct(num)[2:]) # FURB116 -7 7 | print(hex(num)[2:]) # FURB116 -8 |-print(bin(num)[2:]) # FURB116 - 8 |+print(f"{num:b}") # FURB116 -9 9 | -10 10 | print(oct(1337)[2:]) # FURB116 -11 11 | print(hex(1337)[2:]) # FURB116 +ℹ Display-only fix +8 8 | +9 9 | print(oct(num)[2:]) # FURB116 +10 10 | print(hex(num)[2:]) # FURB116 +11 |-print(bin(num)[2:]) # FURB116 + 11 |+print(f"{num:b}") # FURB116 +12 12 | +13 13 | print(oct(1337)[2:]) # FURB116 +14 14 | print(hex(1337)[2:]) # FURB116 -FURB116.py:10:7: FURB116 [*] Replace `oct` call with `f"{1337:o}"` +FURB116.py:13:7: FURB116 [*] Replace `oct` call with `f"{1337:o}"` | - 8 | print(bin(num)[2:]) # FURB116 - 9 | -10 | print(oct(1337)[2:]) # FURB116 +11 | print(bin(num)[2:]) # FURB116 +12 | +13 | print(oct(1337)[2:]) # FURB116 | ^^^^^^^^^^^^^ FURB116 -11 | print(hex(1337)[2:]) # FURB116 -12 | print(bin(1337)[2:]) # FURB116 +14 | print(hex(1337)[2:]) # FURB116 +15 | print(bin(1337)[2:]) # FURB116 | = help: Replace with `f"{1337:o}"` ℹ Safe fix -7 7 | print(hex(num)[2:]) # FURB116 -8 8 | print(bin(num)[2:]) # FURB116 -9 9 | -10 |-print(oct(1337)[2:]) # FURB116 - 10 |+print(f"{1337:o}") # FURB116 -11 11 | print(hex(1337)[2:]) # FURB116 -12 12 | print(bin(1337)[2:]) # FURB116 -13 13 | +10 10 | print(hex(num)[2:]) # FURB116 +11 11 | print(bin(num)[2:]) # FURB116 +12 12 | +13 |-print(oct(1337)[2:]) # FURB116 + 13 |+print(f"{1337:o}") # FURB116 +14 14 | print(hex(1337)[2:]) # FURB116 +15 15 | print(bin(1337)[2:]) # FURB116 +16 16 | print(bin(+1337)[2:]) # FURB116 -FURB116.py:11:7: FURB116 [*] Replace `hex` call with `f"{1337:x}"` +FURB116.py:14:7: FURB116 [*] Replace `hex` call with `f"{1337:x}"` | -10 | print(oct(1337)[2:]) # FURB116 -11 | print(hex(1337)[2:]) # FURB116 +13 | print(oct(1337)[2:]) # FURB116 +14 | print(hex(1337)[2:]) # FURB116 | ^^^^^^^^^^^^^ FURB116 -12 | print(bin(1337)[2:]) # FURB116 +15 | print(bin(1337)[2:]) # FURB116 +16 | print(bin(+1337)[2:]) # FURB116 | = help: Replace with `f"{1337:x}"` ℹ Safe fix -8 8 | print(bin(num)[2:]) # FURB116 -9 9 | -10 10 | print(oct(1337)[2:]) # FURB116 -11 |-print(hex(1337)[2:]) # FURB116 - 11 |+print(f"{1337:x}") # FURB116 -12 12 | print(bin(1337)[2:]) # FURB116 -13 13 | -14 14 | print(bin(return_num())[2:]) # FURB116 (no autofix) +11 11 | print(bin(num)[2:]) # FURB116 +12 12 | +13 13 | print(oct(1337)[2:]) # FURB116 +14 |-print(hex(1337)[2:]) # FURB116 + 14 |+print(f"{1337:x}") # FURB116 +15 15 | print(bin(1337)[2:]) # FURB116 +16 16 | print(bin(+1337)[2:]) # FURB116 +17 17 | -FURB116.py:12:7: FURB116 [*] Replace `bin` call with `f"{1337:b}"` +FURB116.py:15:7: FURB116 [*] Replace `bin` call with `f"{1337:b}"` | -10 | print(oct(1337)[2:]) # FURB116 -11 | print(hex(1337)[2:]) # FURB116 -12 | print(bin(1337)[2:]) # FURB116 +13 | print(oct(1337)[2:]) # FURB116 +14 | print(hex(1337)[2:]) # FURB116 +15 | print(bin(1337)[2:]) # FURB116 | ^^^^^^^^^^^^^ FURB116 -13 | -14 | print(bin(return_num())[2:]) # FURB116 (no autofix) +16 | print(bin(+1337)[2:]) # FURB116 | = help: Replace with `f"{1337:b}"` ℹ Safe fix -9 9 | -10 10 | print(oct(1337)[2:]) # FURB116 -11 11 | print(hex(1337)[2:]) # FURB116 -12 |-print(bin(1337)[2:]) # FURB116 - 12 |+print(f"{1337:b}") # FURB116 -13 13 | -14 14 | print(bin(return_num())[2:]) # FURB116 (no autofix) -15 15 | print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix) +12 12 | +13 13 | print(oct(1337)[2:]) # FURB116 +14 14 | print(hex(1337)[2:]) # FURB116 +15 |-print(bin(1337)[2:]) # FURB116 + 15 |+print(f"{1337:b}") # FURB116 +16 16 | print(bin(+1337)[2:]) # FURB116 +17 17 | +18 18 | print(bin(return_num())[2:]) # FURB116 (no autofix) -FURB116.py:14:7: FURB116 Replace `bin` call with f-string +FURB116.py:16:7: FURB116 [*] Replace `bin` call with `f"{+1337:b}"` | -12 | print(bin(1337)[2:]) # FURB116 -13 | -14 | print(bin(return_num())[2:]) # FURB116 (no autofix) +14 | print(hex(1337)[2:]) # FURB116 +15 | print(bin(1337)[2:]) # FURB116 +16 | print(bin(+1337)[2:]) # FURB116 + | ^^^^^^^^^^^^^^ FURB116 +17 | +18 | print(bin(return_num())[2:]) # FURB116 (no autofix) + | + = help: Replace with `f"{+1337:b}"` + +ℹ Safe fix +13 13 | print(oct(1337)[2:]) # FURB116 +14 14 | print(hex(1337)[2:]) # FURB116 +15 15 | print(bin(1337)[2:]) # FURB116 +16 |-print(bin(+1337)[2:]) # FURB116 + 16 |+print(f"{+1337:b}") # FURB116 +17 17 | +18 18 | print(bin(return_num())[2:]) # FURB116 (no autofix) +19 19 | print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix) + +FURB116.py:18:7: FURB116 Replace `bin` call with f-string + | +16 | print(bin(+1337)[2:]) # FURB116 +17 | +18 | print(bin(return_num())[2:]) # FURB116 (no autofix) | ^^^^^^^^^^^^^^^^^^^^^ FURB116 -15 | print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix) +19 | print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix) | = help: Replace with f-string -FURB116.py:15:7: FURB116 Replace `bin` call with f-string +FURB116.py:19:7: FURB116 Replace `bin` call with f-string | -14 | print(bin(return_num())[2:]) # FURB116 (no autofix) -15 | print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix) +18 | print(bin(return_num())[2:]) # FURB116 (no autofix) +19 | print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix) | ^^^^^^^^^^^^^^^^^^^^^^ FURB116 -16 | -17 | ## invalid +20 | +21 | ## invalid | = help: Replace with f-string + +FURB116.py:32:7: FURB116 Replace `bin` call with `f"{d:b}"` + | +30 | d = datetime.datetime.now(tz=datetime.UTC) +31 | # autofix is display-only +32 | print(bin(d)[2:]) + | ^^^^^^^^^^ FURB116 +33 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +34 | print(bin(len("xyz").numerator)[2:]) + | + = help: Replace with `f"{d:b}"` + +ℹ Display-only fix +29 29 | +30 30 | d = datetime.datetime.now(tz=datetime.UTC) +31 31 | # autofix is display-only +32 |-print(bin(d)[2:]) + 32 |+print(f"{d:b}") +33 33 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +34 34 | print(bin(len("xyz").numerator)[2:]) +35 35 | + +FURB116.py:34:7: FURB116 Replace `bin` call with `f"{len("xyz").numerator:b}"` + | +32 | print(bin(d)[2:]) +33 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +34 | print(bin(len("xyz").numerator)[2:]) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB116 +35 | +36 | # autofix is display-only + | + = help: Replace with `f"{len("xyz").numerator:b}"` + +ℹ Display-only fix +31 31 | # autofix is display-only +32 32 | print(bin(d)[2:]) +33 33 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +34 |-print(bin(len("xyz").numerator)[2:]) + 34 |+print(f"{len("xyz").numerator:b}") +35 35 | +36 36 | # autofix is display-only +37 37 | print(bin({0: 1}[0].numerator)[2:]) + +FURB116.py:37:7: FURB116 Replace `bin` call with `f"{ {0: 1}[0].numerator:b}"` + | +36 | # autofix is display-only +37 | print(bin({0: 1}[0].numerator)[2:]) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB116 +38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +39 | print(bin(ord("\\").numerator)[2:]) + | + = help: Replace with `f"{ {0: 1}[0].numerator:b}"` + +ℹ Display-only fix +34 34 | print(bin(len("xyz").numerator)[2:]) +35 35 | +36 36 | # autofix is display-only +37 |-print(bin({0: 1}[0].numerator)[2:]) + 37 |+print(f"{ {0: 1}[0].numerator:b}") +38 38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +39 39 | print(bin(ord("\\").numerator)[2:]) +40 40 | print(hex(sys + +FURB116.py:39:7: FURB116 Replace `bin` call with `f"{ord("\\").numerator:b}"` + | +37 | print(bin({0: 1}[0].numerator)[2:]) +38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +39 | print(bin(ord("\\").numerator)[2:]) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB116 +40 | print(hex(sys +41 | .maxunicode)[2:]) + | + = help: Replace with `f"{ord("\\").numerator:b}"` + +ℹ Display-only fix +36 36 | # autofix is display-only +37 37 | print(bin({0: 1}[0].numerator)[2:]) +38 38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +39 |-print(bin(ord("\\").numerator)[2:]) + 39 |+print(f"{ord("\\").numerator:b}") +40 40 | print(hex(sys +41 41 | .maxunicode)[2:]) +42 42 | + +FURB116.py:40:7: FURB116 Replace `hex` call with f-string + | +38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +39 | print(bin(ord("\\").numerator)[2:]) +40 | print(hex(sys + | _______^ +41 | | .maxunicode)[2:]) + | |________________^ FURB116 +42 | +43 | # for negatives numbers autofix is display-only + | + = help: Replace with f-string + +ℹ Display-only fix +37 37 | print(bin({0: 1}[0].numerator)[2:]) +38 38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +39 39 | print(bin(ord("\\").numerator)[2:]) +40 |-print(hex(sys +41 |-.maxunicode)[2:]) + 40 |+print(f"{sys + 41 |+.maxunicode:x}") +42 42 | +43 43 | # for negatives numbers autofix is display-only +44 44 | print(bin(-1)[2:]) + +FURB116.py:44:7: FURB116 Replace `bin` call with `f"{-1:b}"` + | +43 | # for negatives numbers autofix is display-only +44 | print(bin(-1)[2:]) + | ^^^^^^^^^^^ FURB116 + | + = help: Replace with `f"{-1:b}"` + +ℹ Display-only fix +41 41 | .maxunicode)[2:]) +42 42 | +43 43 | # for negatives numbers autofix is display-only +44 |-print(bin(-1)[2:]) + 44 |+print(f"{-1:b}") diff --git a/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__fstring_number_format_python_311.snap b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__fstring_number_format_python_311.snap new file mode 100644 index 0000000000..37b1e3aea6 --- /dev/null +++ b/crates/ruff_linter/src/rules/refurb/snapshots/ruff_linter__rules__refurb__tests__fstring_number_format_python_311.snap @@ -0,0 +1,256 @@ +--- +source: crates/ruff_linter/src/rules/refurb/mod.rs +--- +FURB116.py:9:7: FURB116 Replace `oct` call with `f"{num:o}"` + | + 7 | return num + 8 | + 9 | print(oct(num)[2:]) # FURB116 + | ^^^^^^^^^^^^ FURB116 +10 | print(hex(num)[2:]) # FURB116 +11 | print(bin(num)[2:]) # FURB116 + | + = help: Replace with `f"{num:o}"` + +ℹ Display-only fix +6 6 | def return_num() -> int: +7 7 | return num +8 8 | +9 |-print(oct(num)[2:]) # FURB116 + 9 |+print(f"{num:o}") # FURB116 +10 10 | print(hex(num)[2:]) # FURB116 +11 11 | print(bin(num)[2:]) # FURB116 +12 12 | + +FURB116.py:10:7: FURB116 Replace `hex` call with `f"{num:x}"` + | + 9 | print(oct(num)[2:]) # FURB116 +10 | print(hex(num)[2:]) # FURB116 + | ^^^^^^^^^^^^ FURB116 +11 | print(bin(num)[2:]) # FURB116 + | + = help: Replace with `f"{num:x}"` + +ℹ Display-only fix +7 7 | return num +8 8 | +9 9 | print(oct(num)[2:]) # FURB116 +10 |-print(hex(num)[2:]) # FURB116 + 10 |+print(f"{num:x}") # FURB116 +11 11 | print(bin(num)[2:]) # FURB116 +12 12 | +13 13 | print(oct(1337)[2:]) # FURB116 + +FURB116.py:11:7: FURB116 Replace `bin` call with `f"{num:b}"` + | + 9 | print(oct(num)[2:]) # FURB116 +10 | print(hex(num)[2:]) # FURB116 +11 | print(bin(num)[2:]) # FURB116 + | ^^^^^^^^^^^^ FURB116 +12 | +13 | print(oct(1337)[2:]) # FURB116 + | + = help: Replace with `f"{num:b}"` + +ℹ Display-only fix +8 8 | +9 9 | print(oct(num)[2:]) # FURB116 +10 10 | print(hex(num)[2:]) # FURB116 +11 |-print(bin(num)[2:]) # FURB116 + 11 |+print(f"{num:b}") # FURB116 +12 12 | +13 13 | print(oct(1337)[2:]) # FURB116 +14 14 | print(hex(1337)[2:]) # FURB116 + +FURB116.py:13:7: FURB116 [*] Replace `oct` call with `f"{1337:o}"` + | +11 | print(bin(num)[2:]) # FURB116 +12 | +13 | print(oct(1337)[2:]) # FURB116 + | ^^^^^^^^^^^^^ FURB116 +14 | print(hex(1337)[2:]) # FURB116 +15 | print(bin(1337)[2:]) # FURB116 + | + = help: Replace with `f"{1337:o}"` + +ℹ Safe fix +10 10 | print(hex(num)[2:]) # FURB116 +11 11 | print(bin(num)[2:]) # FURB116 +12 12 | +13 |-print(oct(1337)[2:]) # FURB116 + 13 |+print(f"{1337:o}") # FURB116 +14 14 | print(hex(1337)[2:]) # FURB116 +15 15 | print(bin(1337)[2:]) # FURB116 +16 16 | print(bin(+1337)[2:]) # FURB116 + +FURB116.py:14:7: FURB116 [*] Replace `hex` call with `f"{1337:x}"` + | +13 | print(oct(1337)[2:]) # FURB116 +14 | print(hex(1337)[2:]) # FURB116 + | ^^^^^^^^^^^^^ FURB116 +15 | print(bin(1337)[2:]) # FURB116 +16 | print(bin(+1337)[2:]) # FURB116 + | + = help: Replace with `f"{1337:x}"` + +ℹ Safe fix +11 11 | print(bin(num)[2:]) # FURB116 +12 12 | +13 13 | print(oct(1337)[2:]) # FURB116 +14 |-print(hex(1337)[2:]) # FURB116 + 14 |+print(f"{1337:x}") # FURB116 +15 15 | print(bin(1337)[2:]) # FURB116 +16 16 | print(bin(+1337)[2:]) # FURB116 +17 17 | + +FURB116.py:15:7: FURB116 [*] Replace `bin` call with `f"{1337:b}"` + | +13 | print(oct(1337)[2:]) # FURB116 +14 | print(hex(1337)[2:]) # FURB116 +15 | print(bin(1337)[2:]) # FURB116 + | ^^^^^^^^^^^^^ FURB116 +16 | print(bin(+1337)[2:]) # FURB116 + | + = help: Replace with `f"{1337:b}"` + +ℹ Safe fix +12 12 | +13 13 | print(oct(1337)[2:]) # FURB116 +14 14 | print(hex(1337)[2:]) # FURB116 +15 |-print(bin(1337)[2:]) # FURB116 + 15 |+print(f"{1337:b}") # FURB116 +16 16 | print(bin(+1337)[2:]) # FURB116 +17 17 | +18 18 | print(bin(return_num())[2:]) # FURB116 (no autofix) + +FURB116.py:16:7: FURB116 [*] Replace `bin` call with `f"{+1337:b}"` + | +14 | print(hex(1337)[2:]) # FURB116 +15 | print(bin(1337)[2:]) # FURB116 +16 | print(bin(+1337)[2:]) # FURB116 + | ^^^^^^^^^^^^^^ FURB116 +17 | +18 | print(bin(return_num())[2:]) # FURB116 (no autofix) + | + = help: Replace with `f"{+1337:b}"` + +ℹ Safe fix +13 13 | print(oct(1337)[2:]) # FURB116 +14 14 | print(hex(1337)[2:]) # FURB116 +15 15 | print(bin(1337)[2:]) # FURB116 +16 |-print(bin(+1337)[2:]) # FURB116 + 16 |+print(f"{+1337:b}") # FURB116 +17 17 | +18 18 | print(bin(return_num())[2:]) # FURB116 (no autofix) +19 19 | print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix) + +FURB116.py:18:7: FURB116 Replace `bin` call with f-string + | +16 | print(bin(+1337)[2:]) # FURB116 +17 | +18 | print(bin(return_num())[2:]) # FURB116 (no autofix) + | ^^^^^^^^^^^^^^^^^^^^^ FURB116 +19 | print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix) + | + = help: Replace with f-string + +FURB116.py:19:7: FURB116 Replace `bin` call with f-string + | +18 | print(bin(return_num())[2:]) # FURB116 (no autofix) +19 | print(bin(int(f"{num}"))[2:]) # FURB116 (no autofix) + | ^^^^^^^^^^^^^^^^^^^^^^ FURB116 +20 | +21 | ## invalid + | + = help: Replace with f-string + +FURB116.py:32:7: FURB116 Replace `bin` call with `f"{d:b}"` + | +30 | d = datetime.datetime.now(tz=datetime.UTC) +31 | # autofix is display-only +32 | print(bin(d)[2:]) + | ^^^^^^^^^^ FURB116 +33 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +34 | print(bin(len("xyz").numerator)[2:]) + | + = help: Replace with `f"{d:b}"` + +ℹ Display-only fix +29 29 | +30 30 | d = datetime.datetime.now(tz=datetime.UTC) +31 31 | # autofix is display-only +32 |-print(bin(d)[2:]) + 32 |+print(f"{d:b}") +33 33 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +34 34 | print(bin(len("xyz").numerator)[2:]) +35 35 | + +FURB116.py:34:7: FURB116 Replace `bin` call with f-string + | +32 | print(bin(d)[2:]) +33 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +34 | print(bin(len("xyz").numerator)[2:]) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB116 +35 | +36 | # autofix is display-only + | + = help: Replace with f-string + +FURB116.py:37:7: FURB116 Replace `bin` call with `f"{ {0: 1}[0].numerator:b}"` + | +36 | # autofix is display-only +37 | print(bin({0: 1}[0].numerator)[2:]) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB116 +38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +39 | print(bin(ord("\\").numerator)[2:]) + | + = help: Replace with `f"{ {0: 1}[0].numerator:b}"` + +ℹ Display-only fix +34 34 | print(bin(len("xyz").numerator)[2:]) +35 35 | +36 36 | # autofix is display-only +37 |-print(bin({0: 1}[0].numerator)[2:]) + 37 |+print(f"{ {0: 1}[0].numerator:b}") +38 38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +39 39 | print(bin(ord("\\").numerator)[2:]) +40 40 | print(hex(sys + +FURB116.py:39:7: FURB116 Replace `bin` call with f-string + | +37 | print(bin({0: 1}[0].numerator)[2:]) +38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +39 | print(bin(ord("\\").numerator)[2:]) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FURB116 +40 | print(hex(sys +41 | .maxunicode)[2:]) + | + = help: Replace with f-string + +FURB116.py:40:7: FURB116 Replace `hex` call with f-string + | +38 | # no autofix for Python 3.11 and earlier, as it introduces a syntax error +39 | print(bin(ord("\\").numerator)[2:]) +40 | print(hex(sys + | _______^ +41 | | .maxunicode)[2:]) + | |________________^ FURB116 +42 | +43 | # for negatives numbers autofix is display-only + | + = help: Replace with f-string + +FURB116.py:44:7: FURB116 Replace `bin` call with `f"{-1:b}"` + | +43 | # for negatives numbers autofix is display-only +44 | print(bin(-1)[2:]) + | ^^^^^^^^^^^ FURB116 + | + = help: Replace with `f"{-1:b}"` + +ℹ Display-only fix +41 41 | .maxunicode)[2:]) +42 42 | +43 43 | # for negatives numbers autofix is display-only +44 |-print(bin(-1)[2:]) + 44 |+print(f"{-1:b}")