mirror of
https://github.com/astral-sh/ruff.git
synced 2025-12-23 09:19:58 +00:00
fix: Avoid autofix for raw strings with \N escapes
This commit is contained in:
parent
4a937543b9
commit
f8a26700f0
3 changed files with 42 additions and 7 deletions
|
|
@ -276,5 +276,8 @@ if __name__ == "__main__":
|
|||
string = "{}".format(number := number + 1)
|
||||
print(string)
|
||||
|
||||
# Unicode escape
|
||||
# Unicode escape in regular string, should convert.
|
||||
"\N{angle}AOB = {angle}°".format(angle=180)
|
||||
|
||||
# Unicode escape in raw string, should not convert - would change semantics.
|
||||
r"\N{angle}AOB = {angle}°".format(angle=180)
|
||||
|
|
|
|||
|
|
@ -229,6 +229,9 @@ enum FStringConversion {
|
|||
/// The format call uses arguments with side effects which are repeated within the
|
||||
/// format string. For example: `"{x} {x}".format(x=foo())`.
|
||||
SideEffects,
|
||||
/// The format string is a raw string containing `\N{...}` which would be
|
||||
/// misinterpreted in an f-string.
|
||||
UnsafeRawString,
|
||||
/// The format string should be converted to an f-string.
|
||||
Convert(String),
|
||||
}
|
||||
|
|
@ -286,6 +289,15 @@ impl FStringConversion {
|
|||
return Ok(Self::NonEmptyLiteral);
|
||||
}
|
||||
|
||||
// `\N{...}` is literal in raw strings but becomes a Unicode escape in f-strings.
|
||||
if raw
|
||||
&& format_string.format_parts.iter().any(
|
||||
|part| matches!(part, FormatPart::Literal(literal) if literal.contains("\\N{")),
|
||||
)
|
||||
{
|
||||
return Ok(Self::UnsafeRawString);
|
||||
}
|
||||
|
||||
let mut converted = String::with_capacity(contents.len());
|
||||
let mut seen = FxHashSet::default();
|
||||
for part in format_string.format_parts {
|
||||
|
|
@ -416,6 +428,7 @@ pub(crate) fn f_strings(checker: &Checker, call: &ast::ExprCall, summary: &Forma
|
|||
|
||||
let mut patches: Vec<(TextRange, FStringConversion)> = vec![];
|
||||
let mut tokens = checker.tokens().in_range(call.func.range()).iter();
|
||||
let mut unsafe_conversion = false;
|
||||
let end = loop {
|
||||
let Some(token) = tokens.next() else {
|
||||
unreachable!("Should break from the `Tok::Dot` arm");
|
||||
|
|
@ -441,6 +454,11 @@ pub(crate) fn f_strings(checker: &Checker, call: &ast::ExprCall, summary: &Forma
|
|||
// If the format string contains side effects that would need to be repeated,
|
||||
// we can't convert it to an f-string.
|
||||
Ok(FStringConversion::SideEffects) => return,
|
||||
// If the format string is a raw string with `\N{...}`, conversion would be unsafe.
|
||||
// We still want to emit the diagnostic, but without offering a fix.
|
||||
Ok(FStringConversion::UnsafeRawString) => {
|
||||
unsafe_conversion = true;
|
||||
}
|
||||
// If any of the segments fail to convert, then we can't convert the entire
|
||||
// expression.
|
||||
Err(_) => return,
|
||||
|
|
@ -451,7 +469,7 @@ pub(crate) fn f_strings(checker: &Checker, call: &ast::ExprCall, summary: &Forma
|
|||
_ => {}
|
||||
}
|
||||
};
|
||||
if patches.is_empty() {
|
||||
if patches.is_empty() && !unsafe_conversion {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -466,7 +484,7 @@ pub(crate) fn f_strings(checker: &Checker, call: &ast::ExprCall, summary: &Forma
|
|||
Some(curly_unescape(checker.locator().slice(range)).to_string())
|
||||
}
|
||||
// We handled this in the previous loop.
|
||||
FStringConversion::SideEffects => unreachable!(),
|
||||
FStringConversion::SideEffects | FStringConversion::UnsafeRawString => unreachable!(),
|
||||
};
|
||||
if let Some(fstring) = fstring {
|
||||
contents.push_str(
|
||||
|
|
@ -520,7 +538,7 @@ pub(crate) fn f_strings(checker: &Checker, call: &ast::ExprCall, summary: &Forma
|
|||
// ```
|
||||
let has_comments = checker.comment_ranges().intersects(call.arguments.range());
|
||||
|
||||
if !has_comments {
|
||||
if !has_comments && !unsafe_conversion {
|
||||
if contents.is_empty() {
|
||||
// Ex) `''.format(self.project)`
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
|
|
|
|||
|
|
@ -1370,18 +1370,32 @@ help: Convert to f-string
|
|||
276 + string = f"{(number := number + 1)}"
|
||||
277 | print(string)
|
||||
278 |
|
||||
279 | # Unicode escape
|
||||
279 | # Unicode escape in regular string, should convert.
|
||||
|
||||
UP032 [*] Use f-string instead of `format` call
|
||||
--> UP032_0.py:280:1
|
||||
|
|
||||
279 | # Unicode escape
|
||||
279 | # Unicode escape in regular string, should convert.
|
||||
280 | "\N{angle}AOB = {angle}°".format(angle=180)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
281 |
|
||||
282 | # Unicode escape in raw string, should not convert - would change semantics.
|
||||
|
|
||||
help: Convert to f-string
|
||||
277 | print(string)
|
||||
278 |
|
||||
279 | # Unicode escape
|
||||
279 | # Unicode escape in regular string, should convert.
|
||||
- "\N{angle}AOB = {angle}°".format(angle=180)
|
||||
280 + f"\N{angle}AOB = {180}°"
|
||||
281 |
|
||||
282 | # Unicode escape in raw string, should not convert - would change semantics.
|
||||
283 | r"\N{angle}AOB = {angle}°".format(angle=180)
|
||||
|
||||
UP032 Use f-string instead of `format` call
|
||||
--> UP032_0.py:283:1
|
||||
|
|
||||
282 | # Unicode escape in raw string, should not convert - would change semantics.
|
||||
283 | r"\N{angle}AOB = {angle}°".format(angle=180)
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
help: Convert to f-string
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue