diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP031_0.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP031_0.py index 8ce722a1f7..5b58658bcf 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP031_0.py +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP031_0.py @@ -139,3 +139,33 @@ print("%.20X" % 1) print("%2X" % 1) print("%02X" % 1) + +# UP031 (no longer false negatives, but offer no fix because of more complex syntax) + +"%d.%d" % (a, b) + +"%*s" % (5, "hi") + +"%d" % (flt,) + +"%c" % (some_string,) + +"%.2r" % (1.25) + +"%.*s" % (5, "hi") + +"%i" % (flt,) + +"%()s" % {"": "empty"} + +"%s" % {"k": "v"} + +"%()s" % {"": "bar"} + +"%(1)s" % {"1": "bar"} + +"%(a)s" % {"a": 1, "a": 2} + +"%(1)s" % {1: 2, "1": 2} + +"%(and)s" % {"and": 2} diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP031_1.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP031_1.py index a03cbd1d1a..0a62345f90 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP031_1.py +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP031_1.py @@ -1,34 +1,8 @@ # OK b"%s" % (b"bytestring",) -"%*s" % (5, "hi") - -"%d" % (flt,) - -"%c" % (some_string,) - "%4%" % () -"%.2r" % (1.25) - i % 3 -"%.*s" % (5, "hi") - -"%i" % (flt,) - -"%()s" % {"": "empty"} - -"%s" % {"k": "v"} - -"%(1)s" % {"1": "bar"} - -"%(a)s" % {"a": 1, "a": 2} - pytest.param('"%8s" % (None,)', id="unsafe width-string conversion"), - -"%()s" % {"": "bar"} - -"%(1)s" % {1: 2, "1": 2} - -"%(and)s" % {"and": 2} diff --git a/crates/ruff_linter/src/rules/pyupgrade/mod.rs b/crates/ruff_linter/src/rules/pyupgrade/mod.rs index 12577c87de..bb13c8ecbe 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/mod.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/mod.rs @@ -14,7 +14,7 @@ mod tests { use crate::registry::Rule; use crate::rules::pyupgrade; - use crate::settings::types::PythonVersion; + use crate::settings::types::{PreviewMode, PythonVersion}; use crate::test::test_path; use crate::{assert_messages, settings}; @@ -100,6 +100,19 @@ mod tests { Ok(()) } + #[test_case(Rule::PrintfStringFormatting, Path::new("UP031_0.py"))] + fn preview(rule_code: Rule, path: &Path) -> Result<()> { + let diagnostics = test_path( + Path::new("pyupgrade").join(path), + &settings::LinterSettings { + preview: PreviewMode::Enabled, + ..settings::LinterSettings::for_rule(rule_code) + }, + )?; + assert_messages!(diagnostics); + Ok(()) + } + #[test] fn async_timeout_error_alias_not_applied_py310() -> Result<()> { let diagnostics = test_path( diff --git a/crates/ruff_linter/src/rules/pyupgrade/rules/printf_string_formatting.rs b/crates/ruff_linter/src/rules/pyupgrade/rules/printf_string_formatting.rs index aa5700617d..48e6a85aae 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/rules/printf_string_formatting.rs +++ b/crates/ruff_linter/src/rules/pyupgrade/rules/printf_string_formatting.rs @@ -28,21 +28,29 @@ use crate::rules::pyupgrade::helpers::curly_escape; /// formatting. /// /// ## Example +/// /// ```python /// "%s, %s" % ("Hello", "World") # "Hello, World" /// ``` /// /// Use instead: +/// /// ```python /// "{}, {}".format("Hello", "World") # "Hello, World" /// ``` /// +/// ```python +/// f"{'Hello'}, {'World'}" # "Hello, World" +/// ``` +/// /// ## Fix safety +/// /// In cases where the format string contains a single generic format specifier /// (e.g. `%s`), and the right-hand side is an ambiguous expression, /// we cannot offer a safe fix. /// /// For example, given: +/// /// ```python /// "%s" % val /// ``` @@ -379,6 +387,11 @@ pub(crate) fn printf_string_formatting( return; }; if !convertible(&format_string, right) { + if checker.settings.preview.is_enabled() { + checker + .diagnostics + .push(Diagnostic::new(PrintfStringFormatting, string_expr.range())); + } return; } @@ -437,6 +450,11 @@ pub(crate) fn printf_string_formatting( let Some(params_string) = clean_params_dictionary(right, checker.locator(), checker.stylist()) else { + if checker.settings.preview.is_enabled() { + checker + .diagnostics + .push(Diagnostic::new(PrintfStringFormatting, string_expr.range())); + } return; }; Cow::Owned(params_string) diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP031_0.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP031_0.py.snap index 01a7e21e92..2b72d13959 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP031_0.py.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP031_0.py.snap @@ -1142,12 +1142,16 @@ UP031_0.py:140:7: UP031 [*] Use format specifiers instead of percent format 140 |-print("%2X" % 1) 140 |+print("{:2X}".format(1)) 141 141 | print("%02X" % 1) +142 142 | +143 143 | # UP031 (no longer false negatives, but offer no fix because of more complex syntax) UP031_0.py:141:7: UP031 [*] Use format specifiers instead of percent format | 140 | print("%2X" % 1) 141 | print("%02X" % 1) | ^^^^^^^^^^ UP031 +142 | +143 | # UP031 (no longer false negatives, but offer no fix because of more complex syntax) | = help: Replace with format specifiers @@ -1157,3 +1161,6 @@ UP031_0.py:141:7: UP031 [*] Use format specifiers instead of percent format 140 140 | print("%2X" % 1) 141 |-print("%02X" % 1) 141 |+print("{:02X}".format(1)) +142 142 | +143 143 | # UP031 (no longer false negatives, but offer no fix because of more complex syntax) +144 144 | diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__preview.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__preview.snap new file mode 100644 index 0000000000..79c32ccea2 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__preview.snap @@ -0,0 +1,1318 @@ +--- +source: crates/ruff_linter/src/rules/pyupgrade/mod.rs +--- +UP031_0.py:4:7: UP031 [*] Use format specifiers instead of percent format + | +3 | # UP031 +4 | print('%s %s' % (a, b)) + | ^^^^^^^^^^^^^^^^ UP031 +5 | +6 | print('%s%s' % (a, b)) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +1 1 | a, b, x, y = 1, 2, 3, 4 +2 2 | +3 3 | # UP031 +4 |-print('%s %s' % (a, b)) + 4 |+print('{} {}'.format(a, b)) +5 5 | +6 6 | print('%s%s' % (a, b)) +7 7 | + +UP031_0.py:6:7: UP031 [*] Use format specifiers instead of percent format + | +4 | print('%s %s' % (a, b)) +5 | +6 | print('%s%s' % (a, b)) + | ^^^^^^^^^^^^^^^ UP031 +7 | +8 | print("trivial" % ()) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +3 3 | # UP031 +4 4 | print('%s %s' % (a, b)) +5 5 | +6 |-print('%s%s' % (a, b)) + 6 |+print('{}{}'.format(a, b)) +7 7 | +8 8 | print("trivial" % ()) +9 9 | + +UP031_0.py:8:7: UP031 [*] Use format specifiers instead of percent format + | + 6 | print('%s%s' % (a, b)) + 7 | + 8 | print("trivial" % ()) + | ^^^^^^^^^^^^^^ UP031 + 9 | +10 | print("%s" % ("simple",)) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +5 5 | +6 6 | print('%s%s' % (a, b)) +7 7 | +8 |-print("trivial" % ()) + 8 |+print("trivial".format()) +9 9 | +10 10 | print("%s" % ("simple",)) +11 11 | + +UP031_0.py:10:7: UP031 [*] Use format specifiers instead of percent format + | + 8 | print("trivial" % ()) + 9 | +10 | print("%s" % ("simple",)) + | ^^^^^^^^^^^^^^^^^^ UP031 +11 | +12 | print("%s" % ("%s" % ("nested",),)) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +7 7 | +8 8 | print("trivial" % ()) +9 9 | +10 |-print("%s" % ("simple",)) + 10 |+print("{}".format("simple")) +11 11 | +12 12 | print("%s" % ("%s" % ("nested",),)) +13 13 | + +UP031_0.py:12:7: UP031 [*] Use format specifiers instead of percent format + | +10 | print("%s" % ("simple",)) +11 | +12 | print("%s" % ("%s" % ("nested",),)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP031 +13 | +14 | print("%s%% percent" % (15,)) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +9 9 | +10 10 | print("%s" % ("simple",)) +11 11 | +12 |-print("%s" % ("%s" % ("nested",),)) + 12 |+print("{}".format("%s" % ("nested",))) +13 13 | +14 14 | print("%s%% percent" % (15,)) +15 15 | + +UP031_0.py:12:15: UP031 [*] Use format specifiers instead of percent format + | +10 | print("%s" % ("simple",)) +11 | +12 | print("%s" % ("%s" % ("nested",),)) + | ^^^^^^^^^^^^^^^^^^ UP031 +13 | +14 | print("%s%% percent" % (15,)) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +9 9 | +10 10 | print("%s" % ("simple",)) +11 11 | +12 |-print("%s" % ("%s" % ("nested",),)) + 12 |+print("%s" % ("{}".format("nested"),)) +13 13 | +14 14 | print("%s%% percent" % (15,)) +15 15 | + +UP031_0.py:14:7: UP031 [*] Use format specifiers instead of percent format + | +12 | print("%s" % ("%s" % ("nested",),)) +13 | +14 | print("%s%% percent" % (15,)) + | ^^^^^^^^^^^^^^^^^^^^^^ UP031 +15 | +16 | print("%f" % (15,)) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +11 11 | +12 12 | print("%s" % ("%s" % ("nested",),)) +13 13 | +14 |-print("%s%% percent" % (15,)) + 14 |+print("{}% percent".format(15)) +15 15 | +16 16 | print("%f" % (15,)) +17 17 | + +UP031_0.py:16:7: UP031 [*] Use format specifiers instead of percent format + | +14 | print("%s%% percent" % (15,)) +15 | +16 | print("%f" % (15,)) + | ^^^^^^^^^^^^ UP031 +17 | +18 | print("%.f" % (15,)) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +13 13 | +14 14 | print("%s%% percent" % (15,)) +15 15 | +16 |-print("%f" % (15,)) + 16 |+print("{:f}".format(15)) +17 17 | +18 18 | print("%.f" % (15,)) +19 19 | + +UP031_0.py:18:7: UP031 [*] Use format specifiers instead of percent format + | +16 | print("%f" % (15,)) +17 | +18 | print("%.f" % (15,)) + | ^^^^^^^^^^^^^ UP031 +19 | +20 | print("%.3f" % (15,)) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +15 15 | +16 16 | print("%f" % (15,)) +17 17 | +18 |-print("%.f" % (15,)) + 18 |+print("{:.0f}".format(15)) +19 19 | +20 20 | print("%.3f" % (15,)) +21 21 | + +UP031_0.py:20:7: UP031 [*] Use format specifiers instead of percent format + | +18 | print("%.f" % (15,)) +19 | +20 | print("%.3f" % (15,)) + | ^^^^^^^^^^^^^^ UP031 +21 | +22 | print("%3f" % (15,)) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +17 17 | +18 18 | print("%.f" % (15,)) +19 19 | +20 |-print("%.3f" % (15,)) + 20 |+print("{:.3f}".format(15)) +21 21 | +22 22 | print("%3f" % (15,)) +23 23 | + +UP031_0.py:22:7: UP031 [*] Use format specifiers instead of percent format + | +20 | print("%.3f" % (15,)) +21 | +22 | print("%3f" % (15,)) + | ^^^^^^^^^^^^^ UP031 +23 | +24 | print("%-5f" % (5,)) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +19 19 | +20 20 | print("%.3f" % (15,)) +21 21 | +22 |-print("%3f" % (15,)) + 22 |+print("{:3f}".format(15)) +23 23 | +24 24 | print("%-5f" % (5,)) +25 25 | + +UP031_0.py:24:7: UP031 [*] Use format specifiers instead of percent format + | +22 | print("%3f" % (15,)) +23 | +24 | print("%-5f" % (5,)) + | ^^^^^^^^^^^^^ UP031 +25 | +26 | print("%9f" % (5,)) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +21 21 | +22 22 | print("%3f" % (15,)) +23 23 | +24 |-print("%-5f" % (5,)) + 24 |+print("{:<5f}".format(5)) +25 25 | +26 26 | print("%9f" % (5,)) +27 27 | + +UP031_0.py:26:7: UP031 [*] Use format specifiers instead of percent format + | +24 | print("%-5f" % (5,)) +25 | +26 | print("%9f" % (5,)) + | ^^^^^^^^^^^^ UP031 +27 | +28 | print("%#o" % (123,)) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +23 23 | +24 24 | print("%-5f" % (5,)) +25 25 | +26 |-print("%9f" % (5,)) + 26 |+print("{:9f}".format(5)) +27 27 | +28 28 | print("%#o" % (123,)) +29 29 | + +UP031_0.py:28:7: UP031 [*] Use format specifiers instead of percent format + | +26 | print("%9f" % (5,)) +27 | +28 | print("%#o" % (123,)) + | ^^^^^^^^^^^^^^ UP031 +29 | +30 | print("brace {} %s" % (1,)) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +25 25 | +26 26 | print("%9f" % (5,)) +27 27 | +28 |-print("%#o" % (123,)) + 28 |+print("{:#o}".format(123)) +29 29 | +30 30 | print("brace {} %s" % (1,)) +31 31 | + +UP031_0.py:30:7: UP031 [*] Use format specifiers instead of percent format + | +28 | print("%#o" % (123,)) +29 | +30 | print("brace {} %s" % (1,)) + | ^^^^^^^^^^^^^^^^^^^^ UP031 +31 | +32 | print(( + | + = help: Replace with format specifiers + +ℹ Unsafe fix +27 27 | +28 28 | print("%#o" % (123,)) +29 29 | +30 |-print("brace {} %s" % (1,)) + 30 |+print("brace {{}} {}".format(1)) +31 31 | +32 32 | print(( +33 33 | "foo %s " + +UP031_0.py:33:5: UP031 [*] Use format specifiers instead of percent format + | +32 | print(( +33 | "foo %s " + | _____^ +34 | | "bar %s" % (x, y) + | |_____________________^ UP031 +35 | )) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +30 30 | print("brace {} %s" % (1,)) +31 31 | +32 32 | print(( +33 |- "foo %s " +34 |- "bar %s" % (x, y) + 33 |+ "foo {} " + 34 |+ "bar {}".format(x, y) +35 35 | )) +36 36 | +37 37 | print( + +UP031_0.py:38:3: UP031 [*] Use format specifiers instead of percent format + | +37 | print( +38 | "%s" % ( + | ___^ +39 | | "trailing comma", +40 | | ) + | |_________^ UP031 +41 | ) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +35 35 | )) +36 36 | +37 37 | print( +38 |- "%s" % ( + 38 |+ "{}".format( +39 39 | "trailing comma", +40 40 | ) +41 41 | ) + +UP031_0.py:43:7: UP031 [*] Use format specifiers instead of percent format + | +41 | ) +42 | +43 | print("foo %s " % (x,)) + | ^^^^^^^^^^^^^^^^ UP031 +44 | +45 | print("%(k)s" % {"k": "v"}) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +40 40 | ) +41 41 | ) +42 42 | +43 |-print("foo %s " % (x,)) + 43 |+print("foo {} ".format(x)) +44 44 | +45 45 | print("%(k)s" % {"k": "v"}) +46 46 | + +UP031_0.py:45:7: UP031 [*] Use format specifiers instead of percent format + | +43 | print("foo %s " % (x,)) +44 | +45 | print("%(k)s" % {"k": "v"}) + | ^^^^^^^^^^^^^^^^^^^^ UP031 +46 | +47 | print("%(k)s" % { + | + = help: Replace with format specifiers + +ℹ Unsafe fix +42 42 | +43 43 | print("foo %s " % (x,)) +44 44 | +45 |-print("%(k)s" % {"k": "v"}) + 45 |+print("{k}".format(k="v")) +46 46 | +47 47 | print("%(k)s" % { +48 48 | "k": "v", + +UP031_0.py:47:7: UP031 [*] Use format specifiers instead of percent format + | +45 | print("%(k)s" % {"k": "v"}) +46 | +47 | print("%(k)s" % { + | _______^ +48 | | "k": "v", +49 | | "i": "j" +50 | | }) + | |_^ UP031 +51 | +52 | print("%(to_list)s" % {"to_list": []}) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +44 44 | +45 45 | print("%(k)s" % {"k": "v"}) +46 46 | +47 |-print("%(k)s" % { +48 |- "k": "v", +49 |- "i": "j" +50 |-}) + 47 |+print("{k}".format( + 48 |+ k="v", + 49 |+ i="j", + 50 |+)) +51 51 | +52 52 | print("%(to_list)s" % {"to_list": []}) +53 53 | + +UP031_0.py:52:7: UP031 [*] Use format specifiers instead of percent format + | +50 | }) +51 | +52 | print("%(to_list)s" % {"to_list": []}) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP031 +53 | +54 | print("%(k)s" % {"k": "v", "i": 1, "j": []}) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +49 49 | "i": "j" +50 50 | }) +51 51 | +52 |-print("%(to_list)s" % {"to_list": []}) + 52 |+print("{to_list}".format(to_list=[])) +53 53 | +54 54 | print("%(k)s" % {"k": "v", "i": 1, "j": []}) +55 55 | + +UP031_0.py:54:7: UP031 [*] Use format specifiers instead of percent format + | +52 | print("%(to_list)s" % {"to_list": []}) +53 | +54 | print("%(k)s" % {"k": "v", "i": 1, "j": []}) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP031 +55 | +56 | print("%(ab)s" % {"a" "b": 1}) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +51 51 | +52 52 | print("%(to_list)s" % {"to_list": []}) +53 53 | +54 |-print("%(k)s" % {"k": "v", "i": 1, "j": []}) + 54 |+print("{k}".format(k="v", i=1, j=[])) +55 55 | +56 56 | print("%(ab)s" % {"a" "b": 1}) +57 57 | + +UP031_0.py:56:7: UP031 [*] Use format specifiers instead of percent format + | +54 | print("%(k)s" % {"k": "v", "i": 1, "j": []}) +55 | +56 | print("%(ab)s" % {"a" "b": 1}) + | ^^^^^^^^^^^^^^^^^^^^^^^ UP031 +57 | +58 | print("%(a)s" % {"a" : 1}) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +53 53 | +54 54 | print("%(k)s" % {"k": "v", "i": 1, "j": []}) +55 55 | +56 |-print("%(ab)s" % {"a" "b": 1}) + 56 |+print("{ab}".format(ab=1)) +57 57 | +58 58 | print("%(a)s" % {"a" : 1}) +59 59 | + +UP031_0.py:58:7: UP031 [*] Use format specifiers instead of percent format + | +56 | print("%(ab)s" % {"a" "b": 1}) +57 | +58 | print("%(a)s" % {"a" : 1}) + | ^^^^^^^^^^^^^^^^^^^^^ UP031 + | + = help: Replace with format specifiers + +ℹ Unsafe fix +55 55 | +56 56 | print("%(ab)s" % {"a" "b": 1}) +57 57 | +58 |-print("%(a)s" % {"a" : 1}) + 58 |+print("{a}".format(a=1)) +59 59 | +60 60 | +61 61 | print( + +UP031_0.py:62:5: UP031 [*] Use format specifiers instead of percent format + | +61 | print( +62 | "foo %(foo)s " + | _____^ +63 | | "bar %(bar)s" % {"foo": x, "bar": y} + | |________________________________________^ UP031 +64 | ) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +59 59 | +60 60 | +61 61 | print( +62 |- "foo %(foo)s " +63 |- "bar %(bar)s" % {"foo": x, "bar": y} + 62 |+ "foo {foo} " + 63 |+ "bar {bar}".format(foo=x, bar=y) +64 64 | ) +65 65 | +66 66 | bar = {"bar": y} + +UP031_0.py:68:5: UP031 [*] Use format specifiers instead of percent format + | +66 | bar = {"bar": y} +67 | print( +68 | "foo %(foo)s " + | _____^ +69 | | "bar %(bar)s" % {"foo": x, **bar} + | |_____________________________________^ UP031 +70 | ) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +65 65 | +66 66 | bar = {"bar": y} +67 67 | print( +68 |- "foo %(foo)s " +69 |- "bar %(bar)s" % {"foo": x, **bar} + 68 |+ "foo {foo} " + 69 |+ "bar {bar}".format(foo=x, **bar) +70 70 | ) +71 71 | +72 72 | print("%s \N{snowman}" % (a,)) + +UP031_0.py:72:7: UP031 [*] Use format specifiers instead of percent format + | +70 | ) +71 | +72 | print("%s \N{snowman}" % (a,)) + | ^^^^^^^^^^^^^^^^^^^^^^^ UP031 +73 | +74 | print("%(foo)s \N{snowman}" % {"foo": 1}) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +69 69 | "bar %(bar)s" % {"foo": x, **bar} +70 70 | ) +71 71 | +72 |-print("%s \N{snowman}" % (a,)) + 72 |+print("{} \N{snowman}".format(a)) +73 73 | +74 74 | print("%(foo)s \N{snowman}" % {"foo": 1}) +75 75 | + +UP031_0.py:74:7: UP031 [*] Use format specifiers instead of percent format + | +72 | print("%s \N{snowman}" % (a,)) +73 | +74 | print("%(foo)s \N{snowman}" % {"foo": 1}) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP031 +75 | +76 | print(("foo %s " "bar %s") % (x, y)) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +71 71 | +72 72 | print("%s \N{snowman}" % (a,)) +73 73 | +74 |-print("%(foo)s \N{snowman}" % {"foo": 1}) + 74 |+print("{foo} \N{snowman}".format(foo=1)) +75 75 | +76 76 | print(("foo %s " "bar %s") % (x, y)) +77 77 | + +UP031_0.py:76:7: UP031 [*] Use format specifiers instead of percent format + | +74 | print("%(foo)s \N{snowman}" % {"foo": 1}) +75 | +76 | print(("foo %s " "bar %s") % (x, y)) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP031 +77 | +78 | # Single-value expressions + | + = help: Replace with format specifiers + +ℹ Unsafe fix +73 73 | +74 74 | print("%(foo)s \N{snowman}" % {"foo": 1}) +75 75 | +76 |-print(("foo %s " "bar %s") % (x, y)) + 76 |+print(("foo {} " "bar {}").format(x, y)) +77 77 | +78 78 | # Single-value expressions +79 79 | print('Hello %s' % "World") + +UP031_0.py:79:7: UP031 [*] Use format specifiers instead of percent format + | +78 | # Single-value expressions +79 | print('Hello %s' % "World") + | ^^^^^^^^^^^^^^^^^^^^ UP031 +80 | print('Hello %s' % f"World") +81 | print('Hello %s (%s)' % bar) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +76 76 | print(("foo %s " "bar %s") % (x, y)) +77 77 | +78 78 | # Single-value expressions +79 |-print('Hello %s' % "World") + 79 |+print('Hello {}'.format("World")) +80 80 | print('Hello %s' % f"World") +81 81 | print('Hello %s (%s)' % bar) +82 82 | print('Hello %s (%s)' % bar.baz) + +UP031_0.py:80:7: UP031 [*] Use format specifiers instead of percent format + | +78 | # Single-value expressions +79 | print('Hello %s' % "World") +80 | print('Hello %s' % f"World") + | ^^^^^^^^^^^^^^^^^^^^^ UP031 +81 | print('Hello %s (%s)' % bar) +82 | print('Hello %s (%s)' % bar.baz) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +77 77 | +78 78 | # Single-value expressions +79 79 | print('Hello %s' % "World") +80 |-print('Hello %s' % f"World") + 80 |+print('Hello {}'.format(f"World")) +81 81 | print('Hello %s (%s)' % bar) +82 82 | print('Hello %s (%s)' % bar.baz) +83 83 | print('Hello %s (%s)' % bar['bop']) + +UP031_0.py:81:7: UP031 [*] Use format specifiers instead of percent format + | +79 | print('Hello %s' % "World") +80 | print('Hello %s' % f"World") +81 | print('Hello %s (%s)' % bar) + | ^^^^^^^^^^^^^^^^^^^^^ UP031 +82 | print('Hello %s (%s)' % bar.baz) +83 | print('Hello %s (%s)' % bar['bop']) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +78 78 | # Single-value expressions +79 79 | print('Hello %s' % "World") +80 80 | print('Hello %s' % f"World") +81 |-print('Hello %s (%s)' % bar) + 81 |+print('Hello {} ({})'.format(*bar)) +82 82 | print('Hello %s (%s)' % bar.baz) +83 83 | print('Hello %s (%s)' % bar['bop']) +84 84 | print('Hello %(arg)s' % bar) + +UP031_0.py:82:7: UP031 [*] Use format specifiers instead of percent format + | +80 | print('Hello %s' % f"World") +81 | print('Hello %s (%s)' % bar) +82 | print('Hello %s (%s)' % bar.baz) + | ^^^^^^^^^^^^^^^^^^^^^^^^^ UP031 +83 | print('Hello %s (%s)' % bar['bop']) +84 | print('Hello %(arg)s' % bar) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +79 79 | print('Hello %s' % "World") +80 80 | print('Hello %s' % f"World") +81 81 | print('Hello %s (%s)' % bar) +82 |-print('Hello %s (%s)' % bar.baz) + 82 |+print('Hello {} ({})'.format(*bar.baz)) +83 83 | print('Hello %s (%s)' % bar['bop']) +84 84 | print('Hello %(arg)s' % bar) +85 85 | print('Hello %(arg)s' % bar.baz) + +UP031_0.py:83:7: UP031 [*] Use format specifiers instead of percent format + | +81 | print('Hello %s (%s)' % bar) +82 | print('Hello %s (%s)' % bar.baz) +83 | print('Hello %s (%s)' % bar['bop']) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP031 +84 | print('Hello %(arg)s' % bar) +85 | print('Hello %(arg)s' % bar.baz) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +80 80 | print('Hello %s' % f"World") +81 81 | print('Hello %s (%s)' % bar) +82 82 | print('Hello %s (%s)' % bar.baz) +83 |-print('Hello %s (%s)' % bar['bop']) + 83 |+print('Hello {} ({})'.format(*bar['bop'])) +84 84 | print('Hello %(arg)s' % bar) +85 85 | print('Hello %(arg)s' % bar.baz) +86 86 | print('Hello %(arg)s' % bar['bop']) + +UP031_0.py:84:7: UP031 [*] Use format specifiers instead of percent format + | +82 | print('Hello %s (%s)' % bar.baz) +83 | print('Hello %s (%s)' % bar['bop']) +84 | print('Hello %(arg)s' % bar) + | ^^^^^^^^^^^^^^^^^^^^^ UP031 +85 | print('Hello %(arg)s' % bar.baz) +86 | print('Hello %(arg)s' % bar['bop']) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +81 81 | print('Hello %s (%s)' % bar) +82 82 | print('Hello %s (%s)' % bar.baz) +83 83 | print('Hello %s (%s)' % bar['bop']) +84 |-print('Hello %(arg)s' % bar) + 84 |+print('Hello {arg}'.format(**bar)) +85 85 | print('Hello %(arg)s' % bar.baz) +86 86 | print('Hello %(arg)s' % bar['bop']) +87 87 | + +UP031_0.py:85:7: UP031 [*] Use format specifiers instead of percent format + | +83 | print('Hello %s (%s)' % bar['bop']) +84 | print('Hello %(arg)s' % bar) +85 | print('Hello %(arg)s' % bar.baz) + | ^^^^^^^^^^^^^^^^^^^^^^^^^ UP031 +86 | print('Hello %(arg)s' % bar['bop']) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +82 82 | print('Hello %s (%s)' % bar.baz) +83 83 | print('Hello %s (%s)' % bar['bop']) +84 84 | print('Hello %(arg)s' % bar) +85 |-print('Hello %(arg)s' % bar.baz) + 85 |+print('Hello {arg}'.format(**bar.baz)) +86 86 | print('Hello %(arg)s' % bar['bop']) +87 87 | +88 88 | # Hanging modulos + +UP031_0.py:86:7: UP031 [*] Use format specifiers instead of percent format + | +84 | print('Hello %(arg)s' % bar) +85 | print('Hello %(arg)s' % bar.baz) +86 | print('Hello %(arg)s' % bar['bop']) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP031 +87 | +88 | # Hanging modulos + | + = help: Replace with format specifiers + +ℹ Unsafe fix +83 83 | print('Hello %s (%s)' % bar['bop']) +84 84 | print('Hello %(arg)s' % bar) +85 85 | print('Hello %(arg)s' % bar.baz) +86 |-print('Hello %(arg)s' % bar['bop']) + 86 |+print('Hello {arg}'.format(**bar['bop'])) +87 87 | +88 88 | # Hanging modulos +89 89 | ( + +UP031_0.py:89:1: UP031 [*] Use format specifiers instead of percent format + | +88 | # Hanging modulos +89 | / ( +90 | | "foo %s " +91 | | "bar %s" +92 | | ) % (x, y) + | |__________^ UP031 +93 | +94 | ( + | + = help: Replace with format specifiers + +ℹ Unsafe fix +87 87 | +88 88 | # Hanging modulos +89 89 | ( +90 |- "foo %s " +91 |- "bar %s" +92 |-) % (x, y) + 90 |+ "foo {} " + 91 |+ "bar {}" + 92 |+).format(x, y) +93 93 | +94 94 | ( +95 95 | "foo %(foo)s " + +UP031_0.py:94:1: UP031 [*] Use format specifiers instead of percent format + | +92 | ) % (x, y) +93 | +94 | / ( +95 | | "foo %(foo)s " +96 | | "bar %(bar)s" +97 | | ) % {"foo": x, "bar": y} + | |________________________^ UP031 +98 | +99 | ( + | + = help: Replace with format specifiers + +ℹ Unsafe fix +92 92 | ) % (x, y) +93 93 | +94 94 | ( +95 |- "foo %(foo)s " +96 |- "bar %(bar)s" +97 |-) % {"foo": x, "bar": y} + 95 |+ "foo {foo} " + 96 |+ "bar {bar}" + 97 |+).format(foo=x, bar=y) +98 98 | +99 99 | ( +100 100 | """foo %s""" + +UP031_0.py:100:5: UP031 [*] Use format specifiers instead of percent format + | + 99 | ( +100 | """foo %s""" + | _____^ +101 | | % (x,) + | |__________^ UP031 +102 | ) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +97 97 | ) % {"foo": x, "bar": y} +98 98 | +99 99 | ( +100 |- """foo %s""" +101 |- % (x,) + 100 |+ """foo {}""".format(x) +102 101 | ) +103 102 | +104 103 | ( + +UP031_0.py:105:5: UP031 [*] Use format specifiers instead of percent format + | +104 | ( +105 | """ + | _____^ +106 | | foo %s +107 | | """ +108 | | % (x,) + | |__________^ UP031 +109 | ) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +103 103 | +104 104 | ( +105 105 | """ +106 |- foo %s +107 |- """ +108 |- % (x,) + 106 |+ foo {} + 107 |+ """.format(x) +109 108 | ) +110 109 | +111 110 | "%s" % ( + +UP031_0.py:111:1: UP031 [*] Use format specifiers instead of percent format + | +109 | ) +110 | +111 | / "%s" % ( +112 | | x, # comment +113 | | ) + | |_^ UP031 + | + = help: Replace with format specifiers + +ℹ Unsafe fix +108 108 | % (x,) +109 109 | ) +110 110 | +111 |-"%s" % ( + 111 |+"{}".format( +112 112 | x, # comment +113 113 | ) +114 114 | + +UP031_0.py:116:8: UP031 [*] Use format specifiers instead of percent format + | +116 | path = "%s-%s-%s.pem" % ( + | ________^ +117 | | safe_domain_name(cn), # common name, which should be filename safe because it is IDNA-encoded, but in case of a malformed cert make sure it's ok to use as a filename +118 | | cert.not_valid_after.date().isoformat().replace("-", ""), # expiration date +119 | | hexlify(cert.fingerprint(hashes.SHA256())).decode("ascii")[0:8], # fingerprint prefix +120 | | ) + | |_^ UP031 +121 | +122 | # UP031 (no longer false negatives; now offer potentially unsafe fixes) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +113 113 | ) +114 114 | +115 115 | +116 |-path = "%s-%s-%s.pem" % ( + 116 |+path = "{}-{}-{}.pem".format( +117 117 | safe_domain_name(cn), # common name, which should be filename safe because it is IDNA-encoded, but in case of a malformed cert make sure it's ok to use as a filename +118 118 | cert.not_valid_after.date().isoformat().replace("-", ""), # expiration date +119 119 | hexlify(cert.fingerprint(hashes.SHA256())).decode("ascii")[0:8], # fingerprint prefix + +UP031_0.py:123:1: UP031 [*] Use format specifiers instead of percent format + | +122 | # UP031 (no longer false negatives; now offer potentially unsafe fixes) +123 | 'Hello %s' % bar + | ^^^^^^^^^^^^^^^^ UP031 +124 | +125 | 'Hello %s' % bar.baz + | + = help: Replace with format specifiers + +ℹ Unsafe fix +120 120 | ) +121 121 | +122 122 | # UP031 (no longer false negatives; now offer potentially unsafe fixes) +123 |-'Hello %s' % bar + 123 |+'Hello {}'.format(bar) +124 124 | +125 125 | 'Hello %s' % bar.baz +126 126 | + +UP031_0.py:125:1: UP031 [*] Use format specifiers instead of percent format + | +123 | 'Hello %s' % bar +124 | +125 | 'Hello %s' % bar.baz + | ^^^^^^^^^^^^^^^^^^^^ UP031 +126 | +127 | 'Hello %s' % bar['bop'] + | + = help: Replace with format specifiers + +ℹ Unsafe fix +122 122 | # UP031 (no longer false negatives; now offer potentially unsafe fixes) +123 123 | 'Hello %s' % bar +124 124 | +125 |-'Hello %s' % bar.baz + 125 |+'Hello {}'.format(bar.baz) +126 126 | +127 127 | 'Hello %s' % bar['bop'] +128 128 | + +UP031_0.py:127:1: UP031 [*] Use format specifiers instead of percent format + | +125 | 'Hello %s' % bar.baz +126 | +127 | 'Hello %s' % bar['bop'] + | ^^^^^^^^^^^^^^^^^^^^^^^ UP031 +128 | +129 | # Not a valid type annotation but this test shouldn't result in a panic. + | + = help: Replace with format specifiers + +ℹ Unsafe fix +124 124 | +125 125 | 'Hello %s' % bar.baz +126 126 | +127 |-'Hello %s' % bar['bop'] + 127 |+'Hello {}'.format(bar['bop']) +128 128 | +129 129 | # Not a valid type annotation but this test shouldn't result in a panic. +130 130 | # Refer: https://github.com/astral-sh/ruff/issues/11736 + +UP031_0.py:131:5: UP031 [*] Use format specifiers instead of percent format + | +129 | # Not a valid type annotation but this test shouldn't result in a panic. +130 | # Refer: https://github.com/astral-sh/ruff/issues/11736 +131 | x: "'%s + %s' % (1, 2)" + | ^^^^^^^^^^^^^^^^^^ UP031 +132 | +133 | # See: https://github.com/astral-sh/ruff/issues/12421 + | + = help: Replace with format specifiers + +ℹ Unsafe fix +128 128 | +129 129 | # Not a valid type annotation but this test shouldn't result in a panic. +130 130 | # Refer: https://github.com/astral-sh/ruff/issues/11736 +131 |-x: "'%s + %s' % (1, 2)" + 131 |+x: "'{} + {}'.format(1, 2)" +132 132 | +133 133 | # See: https://github.com/astral-sh/ruff/issues/12421 +134 134 | print("%.2X" % 1) + +UP031_0.py:134:7: UP031 [*] Use format specifiers instead of percent format + | +133 | # See: https://github.com/astral-sh/ruff/issues/12421 +134 | print("%.2X" % 1) + | ^^^^^^^^^^ UP031 +135 | print("%.02X" % 1) +136 | print("%02X" % 1) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +131 131 | x: "'%s + %s' % (1, 2)" +132 132 | +133 133 | # See: https://github.com/astral-sh/ruff/issues/12421 +134 |-print("%.2X" % 1) + 134 |+print("{:02X}".format(1)) +135 135 | print("%.02X" % 1) +136 136 | print("%02X" % 1) +137 137 | print("%.00002X" % 1) + +UP031_0.py:135:7: UP031 [*] Use format specifiers instead of percent format + | +133 | # See: https://github.com/astral-sh/ruff/issues/12421 +134 | print("%.2X" % 1) +135 | print("%.02X" % 1) + | ^^^^^^^^^^^ UP031 +136 | print("%02X" % 1) +137 | print("%.00002X" % 1) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +132 132 | +133 133 | # See: https://github.com/astral-sh/ruff/issues/12421 +134 134 | print("%.2X" % 1) +135 |-print("%.02X" % 1) + 135 |+print("{:02X}".format(1)) +136 136 | print("%02X" % 1) +137 137 | print("%.00002X" % 1) +138 138 | print("%.20X" % 1) + +UP031_0.py:136:7: UP031 [*] Use format specifiers instead of percent format + | +134 | print("%.2X" % 1) +135 | print("%.02X" % 1) +136 | print("%02X" % 1) + | ^^^^^^^^^^ UP031 +137 | print("%.00002X" % 1) +138 | print("%.20X" % 1) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +133 133 | # See: https://github.com/astral-sh/ruff/issues/12421 +134 134 | print("%.2X" % 1) +135 135 | print("%.02X" % 1) +136 |-print("%02X" % 1) + 136 |+print("{:02X}".format(1)) +137 137 | print("%.00002X" % 1) +138 138 | print("%.20X" % 1) +139 139 | + +UP031_0.py:137:7: UP031 [*] Use format specifiers instead of percent format + | +135 | print("%.02X" % 1) +136 | print("%02X" % 1) +137 | print("%.00002X" % 1) + | ^^^^^^^^^^^^^^ UP031 +138 | print("%.20X" % 1) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +134 134 | print("%.2X" % 1) +135 135 | print("%.02X" % 1) +136 136 | print("%02X" % 1) +137 |-print("%.00002X" % 1) + 137 |+print("{:02X}".format(1)) +138 138 | print("%.20X" % 1) +139 139 | +140 140 | print("%2X" % 1) + +UP031_0.py:138:7: UP031 [*] Use format specifiers instead of percent format + | +136 | print("%02X" % 1) +137 | print("%.00002X" % 1) +138 | print("%.20X" % 1) + | ^^^^^^^^^^^ UP031 +139 | +140 | print("%2X" % 1) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +135 135 | print("%.02X" % 1) +136 136 | print("%02X" % 1) +137 137 | print("%.00002X" % 1) +138 |-print("%.20X" % 1) + 138 |+print("{:020X}".format(1)) +139 139 | +140 140 | print("%2X" % 1) +141 141 | print("%02X" % 1) + +UP031_0.py:140:7: UP031 [*] Use format specifiers instead of percent format + | +138 | print("%.20X" % 1) +139 | +140 | print("%2X" % 1) + | ^^^^^^^^^ UP031 +141 | print("%02X" % 1) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +137 137 | print("%.00002X" % 1) +138 138 | print("%.20X" % 1) +139 139 | +140 |-print("%2X" % 1) + 140 |+print("{:2X}".format(1)) +141 141 | print("%02X" % 1) +142 142 | +143 143 | # UP031 (no longer false negatives, but offer no fix because of more complex syntax) + +UP031_0.py:141:7: UP031 [*] Use format specifiers instead of percent format + | +140 | print("%2X" % 1) +141 | print("%02X" % 1) + | ^^^^^^^^^^ UP031 +142 | +143 | # UP031 (no longer false negatives, but offer no fix because of more complex syntax) + | + = help: Replace with format specifiers + +ℹ Unsafe fix +138 138 | print("%.20X" % 1) +139 139 | +140 140 | print("%2X" % 1) +141 |-print("%02X" % 1) + 141 |+print("{:02X}".format(1)) +142 142 | +143 143 | # UP031 (no longer false negatives, but offer no fix because of more complex syntax) +144 144 | + +UP031_0.py:145:1: UP031 Use format specifiers instead of percent format + | +143 | # UP031 (no longer false negatives, but offer no fix because of more complex syntax) +144 | +145 | "%d.%d" % (a, b) + | ^^^^^^^ UP031 +146 | +147 | "%*s" % (5, "hi") + | + = help: Replace with format specifiers + +UP031_0.py:147:1: UP031 Use format specifiers instead of percent format + | +145 | "%d.%d" % (a, b) +146 | +147 | "%*s" % (5, "hi") + | ^^^^^ UP031 +148 | +149 | "%d" % (flt,) + | + = help: Replace with format specifiers + +UP031_0.py:149:1: UP031 Use format specifiers instead of percent format + | +147 | "%*s" % (5, "hi") +148 | +149 | "%d" % (flt,) + | ^^^^ UP031 +150 | +151 | "%c" % (some_string,) + | + = help: Replace with format specifiers + +UP031_0.py:151:1: UP031 Use format specifiers instead of percent format + | +149 | "%d" % (flt,) +150 | +151 | "%c" % (some_string,) + | ^^^^ UP031 +152 | +153 | "%.2r" % (1.25) + | + = help: Replace with format specifiers + +UP031_0.py:153:1: UP031 Use format specifiers instead of percent format + | +151 | "%c" % (some_string,) +152 | +153 | "%.2r" % (1.25) + | ^^^^^^ UP031 +154 | +155 | "%.*s" % (5, "hi") + | + = help: Replace with format specifiers + +UP031_0.py:155:1: UP031 Use format specifiers instead of percent format + | +153 | "%.2r" % (1.25) +154 | +155 | "%.*s" % (5, "hi") + | ^^^^^^ UP031 +156 | +157 | "%i" % (flt,) + | + = help: Replace with format specifiers + +UP031_0.py:157:1: UP031 Use format specifiers instead of percent format + | +155 | "%.*s" % (5, "hi") +156 | +157 | "%i" % (flt,) + | ^^^^ UP031 +158 | +159 | "%()s" % {"": "empty"} + | + = help: Replace with format specifiers + +UP031_0.py:159:1: UP031 Use format specifiers instead of percent format + | +157 | "%i" % (flt,) +158 | +159 | "%()s" % {"": "empty"} + | ^^^^^^ UP031 +160 | +161 | "%s" % {"k": "v"} + | + = help: Replace with format specifiers + +UP031_0.py:161:1: UP031 Use format specifiers instead of percent format + | +159 | "%()s" % {"": "empty"} +160 | +161 | "%s" % {"k": "v"} + | ^^^^ UP031 +162 | +163 | "%()s" % {"": "bar"} + | + = help: Replace with format specifiers + +UP031_0.py:163:1: UP031 Use format specifiers instead of percent format + | +161 | "%s" % {"k": "v"} +162 | +163 | "%()s" % {"": "bar"} + | ^^^^^^ UP031 +164 | +165 | "%(1)s" % {"1": "bar"} + | + = help: Replace with format specifiers + +UP031_0.py:165:1: UP031 Use format specifiers instead of percent format + | +163 | "%()s" % {"": "bar"} +164 | +165 | "%(1)s" % {"1": "bar"} + | ^^^^^^^ UP031 +166 | +167 | "%(a)s" % {"a": 1, "a": 2} + | + = help: Replace with format specifiers + +UP031_0.py:167:1: UP031 Use format specifiers instead of percent format + | +165 | "%(1)s" % {"1": "bar"} +166 | +167 | "%(a)s" % {"a": 1, "a": 2} + | ^^^^^^^ UP031 +168 | +169 | "%(1)s" % {1: 2, "1": 2} + | + = help: Replace with format specifiers + +UP031_0.py:169:1: UP031 Use format specifiers instead of percent format + | +167 | "%(a)s" % {"a": 1, "a": 2} +168 | +169 | "%(1)s" % {1: 2, "1": 2} + | ^^^^^^^ UP031 +170 | +171 | "%(and)s" % {"and": 2} + | + = help: Replace with format specifiers + +UP031_0.py:171:1: UP031 Use format specifiers instead of percent format + | +169 | "%(1)s" % {1: 2, "1": 2} +170 | +171 | "%(and)s" % {"and": 2} + | ^^^^^^^^^ UP031 + | + = help: Replace with format specifiers