Skip to content

Commit d449c54

Browse files
danparizherntBre
andauthored
[pyupgrade] Fix UP030 to avoid modifying double curly braces in format strings (#19378)
## Summary Fixes #19348 --------- Co-authored-by: Brent Westbrook <[email protected]>
1 parent f7c6a6b commit d449c54

File tree

3 files changed

+62
-6
lines changed

3 files changed

+62
-6
lines changed

crates/ruff_linter/resources/test/fixtures/pyupgrade/UP030_0.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,7 @@
5959
"{1}_{0}".format(1, 2, *args)
6060

6161
"{1}_{0}".format(1, 2)
62+
63+
r"\d{{1,2}} {0}".format(42)
64+
65+
"{{{0}}}".format(123)

crates/ruff_linter/src/rules/pyupgrade/rules/format_literals.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,18 +124,28 @@ fn is_sequential(indices: &[usize]) -> bool {
124124
indices.iter().enumerate().all(|(idx, value)| idx == *value)
125125
}
126126

127-
// An opening curly brace, followed by any integer, followed by any text,
128-
// followed by a closing brace.
129-
static FORMAT_SPECIFIER: LazyLock<Regex> =
130-
LazyLock::new(|| Regex::new(r"\{(?P<int>\d+)(?P<fmt>.*?)}").unwrap());
127+
static FORMAT_SPECIFIER: LazyLock<Regex> = LazyLock::new(|| {
128+
Regex::new(
129+
r"(?x)
130+
(?P<prefix>
131+
^|[^{]|(?:\{{2})+ # preceded by nothing, a non-brace, or an even number of braces
132+
)
133+
\{ # opening curly brace
134+
(?P<int>\d+) # followed by any integer
135+
(?P<fmt>.*?) # followed by any text
136+
} # followed by a closing brace
137+
",
138+
)
139+
.unwrap()
140+
});
131141

132142
/// Remove the explicit positional indices from a format string.
133143
fn remove_specifiers<'a>(value: &mut Expression<'a>, arena: &'a typed_arena::Arena<String>) {
134144
match value {
135145
Expression::SimpleString(expr) => {
136146
expr.value = arena.alloc(
137147
FORMAT_SPECIFIER
138-
.replace_all(expr.value, "{$fmt}")
148+
.replace_all(expr.value, "$prefix{$fmt}")
139149
.to_string(),
140150
);
141151
}
@@ -146,7 +156,7 @@ fn remove_specifiers<'a>(value: &mut Expression<'a>, arena: &'a typed_arena::Are
146156
libcst_native::String::Simple(string) => {
147157
string.value = arena.alloc(
148158
FORMAT_SPECIFIER
149-
.replace_all(string.value, "{$fmt}")
159+
.replace_all(string.value, "$prefix{$fmt}")
150160
.to_string(),
151161
);
152162
}

crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP030_0.py.snap

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,13 +481,16 @@ UP030_0.py:59:1: UP030 [*] Use implicit references for positional format fields
481481
59 |+"{}_{}".format(2, 1, )
482482
60 60 |
483483
61 61 | "{1}_{0}".format(1, 2)
484+
62 62 |
484485

485486
UP030_0.py:61:1: UP030 [*] Use implicit references for positional format fields
486487
|
487488
59 | "{1}_{0}".format(1, 2, *args)
488489
60 |
489490
61 | "{1}_{0}".format(1, 2)
490491
| ^^^^^^^^^^^^^^^^^^^^^^ UP030
492+
62 |
493+
63 | r"\d{{1,2}} {0}".format(42)
491494
|
492495
= help: Remove explicit positional indices
493496

@@ -497,3 +500,42 @@ UP030_0.py:61:1: UP030 [*] Use implicit references for positional format fields
497500
60 60 |
498501
61 |-"{1}_{0}".format(1, 2)
499502
61 |+"{}_{}".format(2, 1)
503+
62 62 |
504+
63 63 | r"\d{{1,2}} {0}".format(42)
505+
64 64 |
506+
507+
UP030_0.py:63:1: UP030 [*] Use implicit references for positional format fields
508+
|
509+
61 | "{1}_{0}".format(1, 2)
510+
62 |
511+
63 | r"\d{{1,2}} {0}".format(42)
512+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP030
513+
64 |
514+
65 | "{{{0}}}".format(123)
515+
|
516+
= help: Remove explicit positional indices
517+
518+
Unsafe fix
519+
60 60 |
520+
61 61 | "{1}_{0}".format(1, 2)
521+
62 62 |
522+
63 |-r"\d{{1,2}} {0}".format(42)
523+
63 |+r"\d{{1,2}} {}".format(42)
524+
64 64 |
525+
65 65 | "{{{0}}}".format(123)
526+
527+
UP030_0.py:65:1: UP030 [*] Use implicit references for positional format fields
528+
|
529+
63 | r"\d{{1,2}} {0}".format(42)
530+
64 |
531+
65 | "{{{0}}}".format(123)
532+
| ^^^^^^^^^^^^^^^^^^^^^ UP030
533+
|
534+
= help: Remove explicit positional indices
535+
536+
Unsafe fix
537+
62 62 |
538+
63 63 | r"\d{{1,2}} {0}".format(42)
539+
64 64 |
540+
65 |-"{{{0}}}".format(123)
541+
65 |+"{{{}}}".format(123)

0 commit comments

Comments
 (0)