Handle backslashes in f-string interpolations for RUF027

Updated the RUF027 rule to only skip f-string suggestions for Python < 3.12 when backslashes appear in interpolations, not in the entire string. Adjusted test cases and snapshots to reflect this refined behavior.
This commit is contained in:
Dan 2025-10-25 17:25:09 -04:00
parent a955902525
commit 4b4447664b
3 changed files with 16 additions and 14 deletions

View File

@ -85,8 +85,8 @@ def in_type_def():
def fuzz_bug(): def fuzz_bug():
c('{\t"i}') c('{\t"i}')
# Test case for backslash handling in f-strings # Test case for backslash handling in f-string interpolations
# Should not trigger RUF027 for Python < 3.12 due to backslashes # Should not trigger RUF027 for Python < 3.12 due to backslashes in interpolations
def backslash_test(): def backslash_test():
x = "test" x = "test"
print("Hello {x}\\n") # Should not trigger RUF027 for Python < 3.12 print("Hello {'\\n'}{x}") # Should not trigger RUF027 for Python < 3.12

View File

@ -186,12 +186,6 @@ fn should_be_fstring(
return false; return false;
} }
// Check if the string contains backslashes and target Python version is < 3.12
// F-strings with backslashes are only valid in Python 3.12+
if literal.value.contains('\\') && checker.target_version() < PythonVersion::PY312 {
return false;
}
let fstring_expr = format!("f{}", locator.slice(literal)); let fstring_expr = format!("f{}", locator.slice(literal));
let Ok(parsed) = parse_expression(&fstring_expr) else { let Ok(parsed) = parse_expression(&fstring_expr) else {
return false; return false;
@ -223,6 +217,14 @@ fn should_be_fstring(
for f_string in value.f_strings() { for f_string in value.f_strings() {
let mut has_name = false; let mut has_name = false;
for element in f_string.elements.interpolations() { for element in f_string.elements.interpolations() {
// Check if the interpolation expression contains backslashes
// F-strings with backslashes in interpolations are only valid in Python 3.12+
let interpolation_text = &fstring_expr[element.range()];
if interpolation_text.contains('\\') && checker.target_version() < PythonVersion::PY312
{
return false;
}
if let ast::Expr::Name(ast::ExprName { id, .. }) = element.expression.as_ref() { if let ast::Expr::Name(ast::ExprName { id, .. }) = element.expression.as_ref() {
if arg_names.contains(id) { if arg_names.contains(id) {
return false; return false;

View File

@ -326,13 +326,13 @@ RUF027 [*] Possible f-string without an `f` prefix
| |
90 | def backslash_test(): 90 | def backslash_test():
91 | x = "test" 91 | x = "test"
92 | print("Hello {x}\\n") # Should not trigger RUF027 for Python < 3.12 92 | print("Hello {'\\n'}{x}") # Should not trigger RUF027 for Python < 3.12
| ^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^
| |
help: Add `f` prefix help: Add `f` prefix
89 | # Should not trigger RUF027 for Python < 3.12 due to backslashes 89 | # Should not trigger RUF027 for Python < 3.12 due to backslashes in interpolations
90 | def backslash_test(): 90 | def backslash_test():
91 | x = "test" 91 | x = "test"
- print("Hello {x}\\n") # Should not trigger RUF027 for Python < 3.12 - print("Hello {'\\n'}{x}") # Should not trigger RUF027 for Python < 3.12
92 + print(f"Hello {x}\\n") # Should not trigger RUF027 for Python < 3.12 92 + print(f"Hello {'\\n'}{x}") # Should not trigger RUF027 for Python < 3.12
note: This is an unsafe fix and may change runtime behavior note: This is an unsafe fix and may change runtime behavior