diff --git a/crates/ruff/resources/test/fixtures/pyupgrade/UP032_2.py b/crates/ruff/resources/test/fixtures/pyupgrade/UP032_2.py new file mode 100644 index 0000000000..e6d567c7cd --- /dev/null +++ b/crates/ruff/resources/test/fixtures/pyupgrade/UP032_2.py @@ -0,0 +1,16 @@ +# Errors +"{.real}".format(1) +"{0.real}".format(1) +"{a.real}".format(a=1) + +"{.real}".format(1.0) +"{0.real}".format(1.0) +"{a.real}".format(a=1.0) + +"{.real}".format(1j) +"{0.real}".format(1j) +"{a.real}".format(a=1j) + +"{.real}".format(0b01) +"{0.real}".format(0b01) +"{a.real}".format(a=0b01) diff --git a/crates/ruff/src/rules/pyupgrade/mod.rs b/crates/ruff/src/rules/pyupgrade/mod.rs index 22c30c7e7b..9a5c07dc59 100644 --- a/crates/ruff/src/rules/pyupgrade/mod.rs +++ b/crates/ruff/src/rules/pyupgrade/mod.rs @@ -59,6 +59,7 @@ mod tests { #[test_case(Rule::PrintfStringFormatting, Path::new("UP031_1.py"); "UP031_1")] #[test_case(Rule::FString, Path::new("UP032_0.py"); "UP032_0")] #[test_case(Rule::FString, Path::new("UP032_1.py"); "UP032_1")] + #[test_case(Rule::FString, Path::new("UP032_2.py"); "UP032_2")] #[test_case(Rule::LRUCacheWithMaxsizeNone, Path::new("UP033_0.py"); "UP033_0")] #[test_case(Rule::LRUCacheWithMaxsizeNone, Path::new("UP033_1.py"); "UP033_1")] #[test_case(Rule::ExtraneousParentheses, Path::new("UP034.py"); "UP034")] diff --git a/crates/ruff/src/rules/pyupgrade/rules/f_strings.rs b/crates/ruff/src/rules/pyupgrade/rules/f_strings.rs index e21bbf0869..3ed11820e8 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/f_strings.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/f_strings.rs @@ -171,24 +171,37 @@ fn try_convert_to_f_string(checker: &Checker, expr: &Expr) -> Option { converted.push('{'); let field = FieldName::parse(&field_name).ok()?; + let has_field_parts = !field.parts.is_empty(); match field.field_type { FieldType::Auto => { let Some(arg) = summary.consume_next() else { return None; }; - converted.push_str(&arg); + if has_field_parts && arg.chars().all(|c| c.is_ascii_digit()) { + converted.push_str(&format!("({arg})")); + } else { + converted.push_str(&arg); + } } FieldType::Index(index) => { let Some(arg) = summary.consume_arg(index) else { return None; }; - converted.push_str(&arg); + if has_field_parts && arg.chars().all(|c| c.is_ascii_digit()) { + converted.push_str(&format!("({arg})")); + } else { + converted.push_str(&arg); + } } FieldType::Keyword(name) => { let Some(arg) = summary.consume_kwarg(&name) else { return None; }; - converted.push_str(&arg); + if has_field_parts && arg.chars().all(|c| c.is_ascii_digit()) { + converted.push_str(&format!("({arg})")); + } else { + converted.push_str(&arg); + } } } diff --git a/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP032_2.py.snap b/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP032_2.py.snap new file mode 100644 index 0000000000..1905bcd70b --- /dev/null +++ b/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP032_2.py.snap @@ -0,0 +1,237 @@ +--- +source: crates/ruff/src/rules/pyupgrade/mod.rs +--- +UP032_2.py:2:1: UP032 [*] Use f-string instead of `format` call + | +2 | # Errors +3 | "{.real}".format(1) + | ^^^^^^^^^^^^^^^^^^^ UP032 +4 | "{0.real}".format(1) +5 | "{a.real}".format(a=1) + | + = help: Convert to f-string + +ℹ Suggested fix +1 1 | # Errors +2 |-"{.real}".format(1) + 2 |+f"{(1).real}" +3 3 | "{0.real}".format(1) +4 4 | "{a.real}".format(a=1) +5 5 | + +UP032_2.py:3:1: UP032 [*] Use f-string instead of `format` call + | +3 | # Errors +4 | "{.real}".format(1) +5 | "{0.real}".format(1) + | ^^^^^^^^^^^^^^^^^^^^ UP032 +6 | "{a.real}".format(a=1) + | + = help: Convert to f-string + +ℹ Suggested fix +1 1 | # Errors +2 2 | "{.real}".format(1) +3 |-"{0.real}".format(1) + 3 |+f"{(1).real}" +4 4 | "{a.real}".format(a=1) +5 5 | +6 6 | "{.real}".format(1.0) + +UP032_2.py:4:1: UP032 [*] Use f-string instead of `format` call + | +4 | "{.real}".format(1) +5 | "{0.real}".format(1) +6 | "{a.real}".format(a=1) + | ^^^^^^^^^^^^^^^^^^^^^^ UP032 +7 | +8 | "{.real}".format(1.0) + | + = help: Convert to f-string + +ℹ Suggested fix +1 1 | # Errors +2 2 | "{.real}".format(1) +3 3 | "{0.real}".format(1) +4 |-"{a.real}".format(a=1) + 4 |+f"{(1).real}" +5 5 | +6 6 | "{.real}".format(1.0) +7 7 | "{0.real}".format(1.0) + +UP032_2.py:6:1: UP032 [*] Use f-string instead of `format` call + | + 6 | "{a.real}".format(a=1) + 7 | + 8 | "{.real}".format(1.0) + | ^^^^^^^^^^^^^^^^^^^^^ UP032 + 9 | "{0.real}".format(1.0) +10 | "{a.real}".format(a=1.0) + | + = help: Convert to f-string + +ℹ Suggested fix +3 3 | "{0.real}".format(1) +4 4 | "{a.real}".format(a=1) +5 5 | +6 |-"{.real}".format(1.0) + 6 |+f"{1.0.real}" +7 7 | "{0.real}".format(1.0) +8 8 | "{a.real}".format(a=1.0) +9 9 | + +UP032_2.py:7:1: UP032 [*] Use f-string instead of `format` call + | +7 | "{.real}".format(1.0) +8 | "{0.real}".format(1.0) + | ^^^^^^^^^^^^^^^^^^^^^^ UP032 +9 | "{a.real}".format(a=1.0) + | + = help: Convert to f-string + +ℹ Suggested fix +4 4 | "{a.real}".format(a=1) +5 5 | +6 6 | "{.real}".format(1.0) +7 |-"{0.real}".format(1.0) + 7 |+f"{1.0.real}" +8 8 | "{a.real}".format(a=1.0) +9 9 | +10 10 | "{.real}".format(1j) + +UP032_2.py:8:1: UP032 [*] Use f-string instead of `format` call + | + 8 | "{.real}".format(1.0) + 9 | "{0.real}".format(1.0) +10 | "{a.real}".format(a=1.0) + | ^^^^^^^^^^^^^^^^^^^^^^^^ UP032 +11 | +12 | "{.real}".format(1j) + | + = help: Convert to f-string + +ℹ Suggested fix +5 5 | +6 6 | "{.real}".format(1.0) +7 7 | "{0.real}".format(1.0) +8 |-"{a.real}".format(a=1.0) + 8 |+f"{1.0.real}" +9 9 | +10 10 | "{.real}".format(1j) +11 11 | "{0.real}".format(1j) + +UP032_2.py:10:1: UP032 [*] Use f-string instead of `format` call + | +10 | "{a.real}".format(a=1.0) +11 | +12 | "{.real}".format(1j) + | ^^^^^^^^^^^^^^^^^^^^ UP032 +13 | "{0.real}".format(1j) +14 | "{a.real}".format(a=1j) + | + = help: Convert to f-string + +ℹ Suggested fix +7 7 | "{0.real}".format(1.0) +8 8 | "{a.real}".format(a=1.0) +9 9 | +10 |-"{.real}".format(1j) + 10 |+f"{1j.real}" +11 11 | "{0.real}".format(1j) +12 12 | "{a.real}".format(a=1j) +13 13 | + +UP032_2.py:11:1: UP032 [*] Use f-string instead of `format` call + | +11 | "{.real}".format(1j) +12 | "{0.real}".format(1j) + | ^^^^^^^^^^^^^^^^^^^^^ UP032 +13 | "{a.real}".format(a=1j) + | + = help: Convert to f-string + +ℹ Suggested fix +8 8 | "{a.real}".format(a=1.0) +9 9 | +10 10 | "{.real}".format(1j) +11 |-"{0.real}".format(1j) + 11 |+f"{1j.real}" +12 12 | "{a.real}".format(a=1j) +13 13 | +14 14 | "{.real}".format(0b01) + +UP032_2.py:12:1: UP032 [*] Use f-string instead of `format` call + | +12 | "{.real}".format(1j) +13 | "{0.real}".format(1j) +14 | "{a.real}".format(a=1j) + | ^^^^^^^^^^^^^^^^^^^^^^^ UP032 +15 | +16 | "{.real}".format(0b01) + | + = help: Convert to f-string + +ℹ Suggested fix +9 9 | +10 10 | "{.real}".format(1j) +11 11 | "{0.real}".format(1j) +12 |-"{a.real}".format(a=1j) + 12 |+f"{1j.real}" +13 13 | +14 14 | "{.real}".format(0b01) +15 15 | "{0.real}".format(0b01) + +UP032_2.py:14:1: UP032 [*] Use f-string instead of `format` call + | +14 | "{a.real}".format(a=1j) +15 | +16 | "{.real}".format(0b01) + | ^^^^^^^^^^^^^^^^^^^^^^ UP032 +17 | "{0.real}".format(0b01) +18 | "{a.real}".format(a=0b01) + | + = help: Convert to f-string + +ℹ Suggested fix +11 11 | "{0.real}".format(1j) +12 12 | "{a.real}".format(a=1j) +13 13 | +14 |-"{.real}".format(0b01) + 14 |+f"{0b01.real}" +15 15 | "{0.real}".format(0b01) +16 16 | "{a.real}".format(a=0b01) + +UP032_2.py:15:1: UP032 [*] Use f-string instead of `format` call + | +15 | "{.real}".format(0b01) +16 | "{0.real}".format(0b01) + | ^^^^^^^^^^^^^^^^^^^^^^^ UP032 +17 | "{a.real}".format(a=0b01) + | + = help: Convert to f-string + +ℹ Suggested fix +12 12 | "{a.real}".format(a=1j) +13 13 | +14 14 | "{.real}".format(0b01) +15 |-"{0.real}".format(0b01) + 15 |+f"{0b01.real}" +16 16 | "{a.real}".format(a=0b01) + +UP032_2.py:16:1: UP032 [*] Use f-string instead of `format` call + | +16 | "{.real}".format(0b01) +17 | "{0.real}".format(0b01) +18 | "{a.real}".format(a=0b01) + | ^^^^^^^^^^^^^^^^^^^^^^^^^ UP032 + | + = help: Convert to f-string + +ℹ Suggested fix +13 13 | +14 14 | "{.real}".format(0b01) +15 15 | "{0.real}".format(0b01) +16 |-"{a.real}".format(a=0b01) + 16 |+f"{0b01.real}" + +