Skip to content

Commit 6159a8e

Browse files
authored
[pyupgrade] Generate diagnostic for all valid f-string conversions regardless of line-length (UP032) (#10238)
## Summary Fixes #10235 This PR changes `UP032` to flag all `"".format` calls that can technically be rewritten to an f-string, even if rewritting it to an fstring, at least automatically, exceeds the line length (or increases the amount by which it goes over the line length). I looked at the Git history to understand whether the check prevents some false positives (reported by an issue), but i haven't found a compelling reason to limit the rule to only flag format calls that stay in the line length limit: * #7818 Changed the heuristic to determine if the fix fits to address #7810 * #1905 first version of the rule I did take a look at pyupgrade and couldn't find a similar check, at least not in the rule code (maybe it's checked somewhere else?) https://github.com/asottile/pyupgrade/blob/main/pyupgrade/_plugins/fstrings.py ## Breaking Change? This could be seen as a breaking change according to ruff's [versioning policy](https://docs.astral.sh/ruff/versioning/): > The behavior of a stable rule is changed * The scope of a stable rule is significantly increased * The intent of the rule changes * Does not include bug fixes that follow the original intent of the rule It does increase the scope of the rule, but it is in the original intent of the rule (so it's not). ## Test Plan See changed test output
1 parent 8ea5b08 commit 6159a8e

4 files changed

+60
-20
lines changed

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

+13-14
Original file line numberDiff line numberDiff line change
@@ -447,17 +447,6 @@ pub(crate) fn f_strings(
447447
contents.insert(0, ' ');
448448
}
449449

450-
// Avoid refactors that exceed the line length limit.
451-
if !fits_or_shrinks(
452-
&contents,
453-
template.into(),
454-
checker.locator(),
455-
checker.settings.pycodestyle.max_line_length,
456-
checker.settings.tab_size,
457-
) {
458-
return;
459-
}
460-
461450
// Finally, avoid refactors that would introduce a runtime error.
462451
// For example, Django's `gettext` supports `format`-style arguments, but not f-strings.
463452
// See: https://docs.djangoproject.com/en/4.2/topics/i18n/translation
@@ -479,17 +468,27 @@ pub(crate) fn f_strings(
479468

480469
let mut diagnostic = Diagnostic::new(FString, call.range());
481470

471+
// Avoid refactors that exceed the line length limit or make it exceed by more.
472+
let f_string_fits_or_shrinks = fits_or_shrinks(
473+
&contents,
474+
template.into(),
475+
checker.locator(),
476+
checker.settings.pycodestyle.max_line_length,
477+
checker.settings.tab_size,
478+
);
479+
482480
// Avoid fix if there are comments within the call:
483481
// ```
484482
// "{}".format(
485483
// 0, # 0
486484
// )
487485
// ```
488-
if !checker
486+
let has_comments = checker
489487
.indexer()
490488
.comment_ranges()
491-
.intersects(call.arguments.range())
492-
{
489+
.intersects(call.arguments.range());
490+
491+
if f_string_fits_or_shrinks && !has_comments {
493492
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
494493
contents,
495494
call.range(),

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

+47-2
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,53 @@ UP032_0.py:129:1: UP032 [*] Use f-string instead of `format` call
945945
131 131 | ###
946946
132 132 | # Non-errors
947947

948+
UP032_0.py:160:1: UP032 Use f-string instead of `format` call
949+
|
950+
158 | r'"\N{snowman} {}".format(a)'
951+
159 |
952+
160 | / "123456789 {}".format(
953+
161 | | 11111111111111111111111111111111111111111111111111111111111111111111111111,
954+
162 | | )
955+
| |_^ UP032
956+
163 |
957+
164 | """
958+
|
959+
= help: Convert to f-string
960+
961+
UP032_0.py:164:1: UP032 Use f-string instead of `format` call
962+
|
963+
162 | )
964+
163 |
965+
164 | / """
966+
165 | | {}
967+
166 | | {}
968+
167 | | {}
969+
168 | | """.format(
970+
169 | | 1,
971+
170 | | 2,
972+
171 | | 111111111111111111111111111111111111111111111111111111111111111111111111111111111111111,
973+
172 | | )
974+
| |_^ UP032
975+
173 |
976+
174 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = """{}
977+
|
978+
= help: Convert to f-string
979+
980+
UP032_0.py:174:84: UP032 Use f-string instead of `format` call
981+
|
982+
172 | )
983+
173 |
984+
174 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa = """{}
985+
| ____________________________________________________________________________________^
986+
175 | | """.format(
987+
176 | | 111111
988+
177 | | )
989+
| |_^ UP032
990+
178 |
991+
179 | "{}".format(
992+
|
993+
= help: Convert to f-string
994+
948995
UP032_0.py:202:1: UP032 Use f-string instead of `format` call
949996
|
950997
200 | "{}".format(**c)
@@ -1218,5 +1265,3 @@ UP032_0.py:254:1: UP032 [*] Use f-string instead of `format` call
12181265
253 253 | # The dictionary should be parenthesized.
12191266
254 |-"{}".format({0: 1}())
12201267
254 |+f"{({0: 1}())}"
1221-
1222-

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

-2
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,3 @@ UP032_1.py:1:1: UP032 [*] Use f-string instead of `format` call
1111
Safe fix
1212
1 |-"{} {}".format(a, b) # Intentionally at start-of-file, to ensure graceful handling.
1313
1 |+f"{a} {b}" # Intentionally at start-of-file, to ensure graceful handling.
14-
15-

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

-2
Original file line numberDiff line numberDiff line change
@@ -439,5 +439,3 @@ UP032_2.py:28:1: UP032 [*] Use f-string instead of `format` call
439439
27 27 | "{.real}".format({1: 2, 3: 4})
440440
28 |-"{}".format((i for i in range(2)))
441441
28 |+f"{(i for i in range(2))}"
442-
443-

0 commit comments

Comments
 (0)