mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-26 11:59:35 +00:00
Avoid syntax errors when removing f-string prefixes (#5319)
Closes https://github.com/astral-sh/ruff/issues/5281. Closes https://github.com/astral-sh/ruff/issues/4827.
This commit is contained in:
parent
4a81cfc51a
commit
7819b95d7f
4 changed files with 118 additions and 31 deletions
|
@ -37,7 +37,10 @@ f"{{test}}"
|
|||
f'{{ 40 }}'
|
||||
f"{{a {{x}}"
|
||||
f"{{{{x}}}}"
|
||||
""f""
|
||||
''f""
|
||||
(""f""r"")
|
||||
|
||||
# To be fixed
|
||||
# Error: f-string: single '}' is not allowed at line 41 column 8
|
||||
# f"\{{x}}"
|
||||
# f"\{{x}}"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use rustpython_parser::ast::{self, Expr, Stmt};
|
||||
use rustpython_parser::ast::{self, Stmt};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
|
@ -50,9 +50,9 @@ pub(crate) fn f_string_docstring(checker: &mut Checker, body: &[Stmt]) {
|
|||
let Stmt::Expr(ast::StmtExpr { value, range: _ }) = stmt else {
|
||||
return;
|
||||
};
|
||||
let Expr::JoinedStr ( _) = value.as_ref() else {
|
||||
if !value.is_joined_str_expr() {
|
||||
return;
|
||||
};
|
||||
}
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(FStringDocstring, stmt.identifier()));
|
||||
|
|
|
@ -79,23 +79,6 @@ fn find_useless_f_strings<'a>(
|
|||
})
|
||||
}
|
||||
|
||||
fn unescape_f_string(content: &str) -> String {
|
||||
content.replace("{{", "{").replace("}}", "}")
|
||||
}
|
||||
|
||||
fn fix_f_string_missing_placeholders(
|
||||
prefix_range: TextRange,
|
||||
tok_range: TextRange,
|
||||
checker: &mut Checker,
|
||||
) -> Fix {
|
||||
let content = &checker.locator.contents()[TextRange::new(prefix_range.end(), tok_range.end())];
|
||||
Fix::automatic(Edit::replacement(
|
||||
unescape_f_string(content),
|
||||
prefix_range.start(),
|
||||
tok_range.end(),
|
||||
))
|
||||
}
|
||||
|
||||
/// F541
|
||||
pub(crate) fn f_string_missing_placeholders(expr: &Expr, values: &[Expr], checker: &mut Checker) {
|
||||
if !values
|
||||
|
@ -105,13 +88,51 @@ pub(crate) fn f_string_missing_placeholders(expr: &Expr, values: &[Expr], checke
|
|||
for (prefix_range, tok_range) in find_useless_f_strings(expr, checker.locator) {
|
||||
let mut diagnostic = Diagnostic::new(FStringMissingPlaceholders, tok_range);
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
diagnostic.set_fix(fix_f_string_missing_placeholders(
|
||||
diagnostic.set_fix(convert_f_string_to_regular_string(
|
||||
prefix_range,
|
||||
tok_range,
|
||||
checker,
|
||||
checker.locator,
|
||||
));
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Unescape an f-string body by replacing `{{` with `{` and `}}` with `}`.
|
||||
///
|
||||
/// In Python, curly-brace literals within f-strings must be escaped by doubling the braces.
|
||||
/// When rewriting an f-string to a regular string, we need to unescape any curly-brace literals.
|
||||
/// For example, given `{{Hello, world!}}`, return `{Hello, world!}`.
|
||||
fn unescape_f_string(content: &str) -> String {
|
||||
content.replace("{{", "{").replace("}}", "}")
|
||||
}
|
||||
|
||||
/// Generate a [`Fix`] to rewrite an f-string as a regular string.
|
||||
fn convert_f_string_to_regular_string(
|
||||
prefix_range: TextRange,
|
||||
tok_range: TextRange,
|
||||
locator: &Locator,
|
||||
) -> Fix {
|
||||
// Extract the f-string body.
|
||||
let mut content =
|
||||
unescape_f_string(locator.slice(TextRange::new(prefix_range.end(), tok_range.end())));
|
||||
|
||||
// If the preceding character is equivalent to the quote character, insert a space to avoid a
|
||||
// syntax error. For example, when removing the `f` prefix in `""f""`, rewrite to `"" ""`
|
||||
// instead of `""""`.
|
||||
if locator
|
||||
.slice(TextRange::up_to(prefix_range.start()))
|
||||
.chars()
|
||||
.last()
|
||||
.map_or(false, |char| content.starts_with(char))
|
||||
{
|
||||
content.insert(0, ' ');
|
||||
}
|
||||
|
||||
Fix::automatic(Edit::replacement(
|
||||
content,
|
||||
prefix_range.start(),
|
||||
tok_range.end(),
|
||||
))
|
||||
}
|
||||
|
|
|
@ -269,7 +269,7 @@ F541.py:37:1: F541 [*] f-string without any placeholders
|
|||
37 |+'{ 40 }'
|
||||
38 38 | f"{{a {{x}}"
|
||||
39 39 | f"{{{{x}}}}"
|
||||
40 40 |
|
||||
40 40 | ""f""
|
||||
|
||||
F541.py:38:1: F541 [*] f-string without any placeholders
|
||||
|
|
||||
|
@ -278,6 +278,7 @@ F541.py:38:1: F541 [*] f-string without any placeholders
|
|||
38 | f"{{a {{x}}"
|
||||
| ^^^^^^^^^^^^ F541
|
||||
39 | f"{{{{x}}}}"
|
||||
40 | ""f""
|
||||
|
|
||||
= help: Remove extraneous `f` prefix
|
||||
|
||||
|
@ -288,8 +289,8 @@ F541.py:38:1: F541 [*] f-string without any placeholders
|
|||
38 |-f"{{a {{x}}"
|
||||
38 |+"{a {x}"
|
||||
39 39 | f"{{{{x}}}}"
|
||||
40 40 |
|
||||
41 41 | # To be fixed
|
||||
40 40 | ""f""
|
||||
41 41 | ''f""
|
||||
|
||||
F541.py:39:1: F541 [*] f-string without any placeholders
|
||||
|
|
||||
|
@ -297,8 +298,8 @@ F541.py:39:1: F541 [*] f-string without any placeholders
|
|||
38 | f"{{a {{x}}"
|
||||
39 | f"{{{{x}}}}"
|
||||
| ^^^^^^^^^^^^ F541
|
||||
40 |
|
||||
41 | # To be fixed
|
||||
40 | ""f""
|
||||
41 | ''f""
|
||||
|
|
||||
= help: Remove extraneous `f` prefix
|
||||
|
||||
|
@ -308,8 +309,70 @@ F541.py:39:1: F541 [*] f-string without any placeholders
|
|||
38 38 | f"{{a {{x}}"
|
||||
39 |-f"{{{{x}}}}"
|
||||
39 |+"{{x}}"
|
||||
40 40 |
|
||||
41 41 | # To be fixed
|
||||
42 42 | # Error: f-string: single '}' is not allowed at line 41 column 8
|
||||
40 40 | ""f""
|
||||
41 41 | ''f""
|
||||
42 42 | (""f""r"")
|
||||
|
||||
F541.py:40:3: F541 [*] f-string without any placeholders
|
||||
|
|
||||
38 | f"{{a {{x}}"
|
||||
39 | f"{{{{x}}}}"
|
||||
40 | ""f""
|
||||
| ^^^ F541
|
||||
41 | ''f""
|
||||
42 | (""f""r"")
|
||||
|
|
||||
= help: Remove extraneous `f` prefix
|
||||
|
||||
ℹ Fix
|
||||
37 37 | f'{{ 40 }}'
|
||||
38 38 | f"{{a {{x}}"
|
||||
39 39 | f"{{{{x}}}}"
|
||||
40 |-""f""
|
||||
40 |+"" ""
|
||||
41 41 | ''f""
|
||||
42 42 | (""f""r"")
|
||||
43 43 |
|
||||
|
||||
F541.py:41:3: F541 [*] f-string without any placeholders
|
||||
|
|
||||
39 | f"{{{{x}}}}"
|
||||
40 | ""f""
|
||||
41 | ''f""
|
||||
| ^^^ F541
|
||||
42 | (""f""r"")
|
||||
|
|
||||
= help: Remove extraneous `f` prefix
|
||||
|
||||
ℹ Fix
|
||||
38 38 | f"{{a {{x}}"
|
||||
39 39 | f"{{{{x}}}}"
|
||||
40 40 | ""f""
|
||||
41 |-''f""
|
||||
41 |+''""
|
||||
42 42 | (""f""r"")
|
||||
43 43 |
|
||||
44 44 | # To be fixed
|
||||
|
||||
F541.py:42:4: F541 [*] f-string without any placeholders
|
||||
|
|
||||
40 | ""f""
|
||||
41 | ''f""
|
||||
42 | (""f""r"")
|
||||
| ^^^ F541
|
||||
43 |
|
||||
44 | # To be fixed
|
||||
|
|
||||
= help: Remove extraneous `f` prefix
|
||||
|
||||
ℹ Fix
|
||||
39 39 | f"{{{{x}}}}"
|
||||
40 40 | ""f""
|
||||
41 41 | ''f""
|
||||
42 |-(""f""r"")
|
||||
42 |+("" ""r"")
|
||||
43 43 |
|
||||
44 44 | # To be fixed
|
||||
45 45 | # Error: f-string: single '}' is not allowed at line 41 column 8
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue