Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update UP032 to unescape curly braces in literal parts of converted strings #8697

Merged
merged 2 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions crates/ruff_linter/resources/test/fixtures/pyupgrade/UP032_0.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,20 @@ async def c():
"".format(new_dict, d)

)

# The first string will be converted to an f-string and the curly braces in the second should be converted to be unescaped
(
"{}"
"{{}}"
).format(a)

("{}" "{{}}").format(a)


# Both strings will be converted to an f-string and the curly braces in the second should left escaped
(
"{}"
"{{{}}}"
).format(a, b)

("{}" "{{{}}}").format(a, b)
13 changes: 13 additions & 0 deletions crates/ruff_linter/src/rules/pyupgrade/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,16 @@ pub(super) fn curly_escape(text: &str) -> Cow<'_, str> {
}
})
}

static DOUBLE_CURLY_BRACES: Lazy<Regex> = Lazy::new(|| Regex::new(r"((\{\{)|(\}\}))").unwrap());

pub(super) fn curly_unescape(text: &str) -> Cow<'_, str> {
// Match all double curly braces and replace with a single
DOUBLE_CURLY_BRACES.replace_all(text, |caps: &Captures| {
if &caps[1] == "{{" {
"{".to_string()
} else {
"}".to_string()
}
})
}
10 changes: 6 additions & 4 deletions crates/ruff_linter/src/rules/pyupgrade/rules/f_strings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::checkers::ast::Checker;
use crate::fix::edits::fits_or_shrinks;

use crate::rules::pyflakes::format::FormatSummary;
use crate::rules::pyupgrade::helpers::curly_escape;
use crate::rules::pyupgrade::helpers::{curly_escape, curly_unescape};

/// ## What it does
/// Checks for `str.format` calls that can be replaced with f-strings.
Expand Down Expand Up @@ -357,9 +357,11 @@ pub(crate) fn f_strings(
Some((Tok::String { .. }, range)) => {
match try_convert_to_f_string(range, &mut summary, checker.locator()) {
Ok(Some(fstring)) => patches.push((range, fstring)),
// Skip any strings that don't require conversion (e.g., literal segments of an
// implicit concatenation).
Ok(None) => continue,
// Convert escaped curly brackets e.g. `{{` to `{` in literal string parts
Ok(None) => patches.push((
range,
curly_unescape(checker.locator().slice(range)).to_string(),
)),
// If any of the segments fail to convert, then we can't convert the entire
// expression.
Err(_) => return,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -995,10 +995,11 @@ UP032_0.py:212:18: UP032 [*] Use f-string instead of `format` call
211 211 | # When fixing, trim the trailing empty string.
212 |-raise ValueError("Conflicting configuration dicts: {!r} {!r}"
213 |- "".format(new_dict, d))
212 |+raise ValueError(f"Conflicting configuration dicts: {new_dict!r} {d!r}")
214 213 |
215 214 | # When fixing, trim the trailing empty string.
216 215 | raise ValueError("Conflicting configuration dicts: {!r} {!r}"
212 |+raise ValueError(f"Conflicting configuration dicts: {new_dict!r} {d!r}"
213 |+ "")
214 214 |
215 215 | # When fixing, trim the trailing empty string.
216 216 | raise ValueError("Conflicting configuration dicts: {!r} {!r}"

UP032_0.py:216:18: UP032 [*] Use f-string instead of `format` call
|
Expand Down Expand Up @@ -1041,9 +1042,10 @@ UP032_0.py:220:5: UP032 [*] Use f-string instead of `format` call
220 |- "Conflicting configuration dicts: {!r} {!r}"
221 |- "".format(new_dict, d)
220 |+ f"Conflicting configuration dicts: {new_dict!r} {d!r}"
222 221 | )
223 222 |
224 223 | raise ValueError(
221 |+ ""
222 222 | )
223 223 |
224 224 | raise ValueError(

UP032_0.py:225:5: UP032 [*] Use f-string instead of `format` call
|
Expand All @@ -1064,7 +1066,96 @@ UP032_0.py:225:5: UP032 [*] Use f-string instead of `format` call
225 |- "Conflicting configuration dicts: {!r} {!r}"
226 |- "".format(new_dict, d)
225 |+ f"Conflicting configuration dicts: {new_dict!r} {d!r}"
227 226 |
228 227 | )
226 |+ ""
227 227 |
228 228 | )
229 229 |

UP032_0.py:231:1: UP032 [*] Use f-string instead of `format` call
|
230 | # The first string will be converted to an f-string and the curly braces in the second should be converted to be unescaped
231 | / (
232 | | "{}"
233 | | "{{}}"
234 | | ).format(a)
| |___________^ UP032
235 |
236 | ("{}" "{{}}").format(a)
|
= help: Convert to f-string

ℹ Safe fix
229 229 |
230 230 | # The first string will be converted to an f-string and the curly braces in the second should be converted to be unescaped
231 231 | (
232 |+ f"{a}"
232 233 | "{}"
233 |- "{{}}"
234 |-).format(a)
234 |+)
235 235 |
236 236 | ("{}" "{{}}").format(a)
237 237 |

UP032_0.py:236:1: UP032 [*] Use f-string instead of `format` call
|
234 | ).format(a)
235 |
236 | ("{}" "{{}}").format(a)
| ^^^^^^^^^^^^^^^^^^^^^^^ UP032
|
= help: Convert to f-string

ℹ Safe fix
233 233 | "{{}}"
234 234 | ).format(a)
235 235 |
236 |-("{}" "{{}}").format(a)
236 |+(f"{a}" "{}")
237 237 |
238 238 |
239 239 | # Both strings will be converted to an f-string and the curly braces in the second should left escaped

UP032_0.py:240:1: UP032 [*] Use f-string instead of `format` call
|
239 | # Both strings will be converted to an f-string and the curly braces in the second should left escaped
240 | / (
241 | | "{}"
242 | | "{{{}}}"
243 | | ).format(a, b)
| |______________^ UP032
244 |
245 | ("{}" "{{{}}}").format(a, b)
|
= help: Convert to f-string

ℹ Safe fix
238 238 |
239 239 | # Both strings will be converted to an f-string and the curly braces in the second should left escaped
240 240 | (
241 |- "{}"
242 |- "{{{}}}"
243 |-).format(a, b)
241 |+ f"{a}"
242 |+ f"{{{b}}}"
243 |+)
244 244 |
245 245 | ("{}" "{{{}}}").format(a, b)

UP032_0.py:245:1: UP032 [*] Use f-string instead of `format` call
|
243 | ).format(a, b)
244 |
245 | ("{}" "{{{}}}").format(a, b)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP032
|
= help: Convert to f-string

ℹ Safe fix
242 242 | "{{{}}}"
243 243 | ).format(a, b)
244 244 |
245 |-("{}" "{{{}}}").format(a, b)
245 |+(f"{a}" f"{{{b}}}")


Loading