diff --git a/crates/ruff/resources/test/fixtures/pyupgrade/UP032.py b/crates/ruff/resources/test/fixtures/pyupgrade/UP032.py index 9cac6d2dbb..173d986f05 100644 --- a/crates/ruff/resources/test/fixtures/pyupgrade/UP032.py +++ b/crates/ruff/resources/test/fixtures/pyupgrade/UP032.py @@ -86,3 +86,14 @@ async def c(): async def c(): return "{}".format(1 + await 3) + + +def d(osname, version, release): + return"{}-{}.{}".format(osname, version, release) + + +def e(): + yield"{}".format(1) + + +assert"{}".format(1) diff --git a/crates/ruff/src/rules/pyupgrade/rules/f_strings.rs b/crates/ruff/src/rules/pyupgrade/rules/f_strings.rs index cac6aaebd7..641317e159 100644 --- a/crates/ruff/src/rules/pyupgrade/rules/f_strings.rs +++ b/crates/ruff/src/rules/pyupgrade/rules/f_strings.rs @@ -2,7 +2,7 @@ use rustc_hash::FxHashMap; use rustpython_common::format::{ FieldName, FieldNamePart, FieldType, FormatPart, FormatString, FromTemplate, }; -use rustpython_parser::ast::{Constant, Expr, ExprKind, KeywordData}; +use rustpython_parser::ast::{Constant, Expr, ExprKind, KeywordData, Location}; use ruff_diagnostics::{AlwaysAutofixableViolation, Diagnostic, Fix}; use ruff_macros::{derive_message_formats, violation}; @@ -247,7 +247,7 @@ pub(crate) fn f_strings(checker: &mut Checker, summary: &FormatSummary, expr: &E // Currently, the only issue we know of is in LibCST: // https://github.com/Instagram/LibCST/issues/846 - let Some(contents) = try_convert_to_f_string(checker, expr) else { + let Some(mut contents) = try_convert_to_f_string(checker, expr) else { return; }; @@ -257,6 +257,18 @@ pub(crate) fn f_strings(checker: &mut Checker, summary: &FormatSummary, expr: &E return; } + // If necessary, add a space between any leading keyword (`return`, `yield`, `assert`, etc.) + // and the string. For example, `return"foo"` is valid, but `returnf"foo"` is not. + if expr.location.column() > 0 { + let existing = checker.locator.slice(Range::new( + Location::new(expr.location.row(), expr.location.column() - 1), + expr.end_location.unwrap(), + )); + if existing.chars().next().unwrap().is_ascii_alphabetic() { + contents.insert(0, ' '); + } + } + let mut diagnostic = Diagnostic::new(FString, Range::from(expr)); if checker.patch(diagnostic.kind.rule()) { diagnostic.amend(Fix::replacement( diff --git a/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP032.py.snap b/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP032.py.snap index 42ac7606a1..9be0af3e0c 100644 --- a/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP032.py.snap +++ b/crates/ruff/src/rules/pyupgrade/snapshots/ruff__rules__pyupgrade__tests__UP032.py.snap @@ -442,4 +442,64 @@ expression: diagnostics row: 47 column: 24 parent: ~ +- kind: + name: FString + body: "Use f-string instead of `format` call" + suggestion: Convert to f-string + fixable: true + location: + row: 92 + column: 10 + end_location: + row: 92 + column: 53 + fix: + content: " f\"{osname}-{version}.{release}\"" + location: + row: 92 + column: 10 + end_location: + row: 92 + column: 53 + parent: ~ +- kind: + name: FString + body: "Use f-string instead of `format` call" + suggestion: Convert to f-string + fixable: true + location: + row: 96 + column: 9 + end_location: + row: 96 + column: 23 + fix: + content: " f\"{1}\"" + location: + row: 96 + column: 9 + end_location: + row: 96 + column: 23 + parent: ~ +- kind: + name: FString + body: "Use f-string instead of `format` call" + suggestion: Convert to f-string + fixable: true + location: + row: 99 + column: 6 + end_location: + row: 99 + column: 20 + fix: + content: " f\"{1}\"" + location: + row: 99 + column: 6 + end_location: + row: 99 + column: 20 + parent: ~