Implement flynt's "join and concat to f-string" transforms
akx opened this issue · comments
flynt
is a specialized linter for f-string usage.
UP031 and UP032 implement flynt
's core features, but the two extra transforms
-tc, --transform-concats
Replace string concatenations (defined as +
operations involving string literals) with
f-strings. Available only if flynt is
installed with 3.8+ interpreter.
-tj, --transform-joins
Replace static joins (where the joiner is a
string literal and the joinee is a static-
length list) with f-strings. Available only
if flynt is installed with 3.8+ interpreter.
i.e.
a = "Hello"
-msg = a + " World"
+msg = f"{a} World"
-msg2 = "Finally, " + a + " World"
+msg2 = "Finally, {a} World"
and
a = "Hello"
-msg1 = " ".join([a, " World"])
-msg2 = "".join(["Finally, ", a, " World"])
-msg3 = "x".join(("1", "2", "3"))
-msg4 = "x".join({"4", "5", "yee"})
-msg5 = "y".join([1, 2, 3]) # Should be transformed
+msg1 = f"{a} World"
+msg2 = f"Finally, {a} World"
+msg3 = "1x2x3"
+msg4 = "4x5xyee"
+msg5 = f"{1}y{2}y{3}" # Should be transformed
msg6 = a.join(["1", "2", "3"]) # Should not be transformed (not a static joiner)
msg7 = "a".join(a) # Should not be transformed (not a static joinee)
msg8 = "a".join([a, a, *a]) # Should not be transformed (not a static length)
respectively could be implemented in Ruff too. (I'd like to work on them! 😄) Should these be FLY
series, or should they be RUF
?
-
FLY001
: consider replacing string concatenation with f-string -
FLY002
: consider replacing static string joining with f-string (#4196)
Refs #2097 (relating to f-strings)
Let's put these in FLY
(but we should do a quick check to verify that FLY
isn't used by any other popular plugins). Then, we can mark UP031
and UP032
as aliases of FLY
rules once we support rule aliasing, so all the string upgrade stuff is in one place (or vice versa -- add FLY
rules that alias the UP
rules).
Separately, have you been using UP031
and UP032
much? Any issues in practice? Any notable false negatives? There are a few cases we don't yet fix (e.g., if you put the %
in a percent-formatted string on its own line, we abort to avoid wonky formatting).
Separately, do you have an opinion on whether UP031
and UP032
should always flag %
and .format
calls? Or only flag the ones it can fix? Right now, it does the latter. But doing the former would effectively implement #2097.
I have UP031 and UP032 enabled by way of select = "UP"
and haven't seen any issues yet! 👍
Maybe (and this is tangential to the #2142 RUF005 discussion) UP03[12] should have a "suggestion" flag for %
and .format
they can't auto-fix, like "Consider using an f-string".
That said, I could see it being a nuisance if we had to noqa places like "foo {bar} {baz}".format(**something_dynamic)
(though that's better served by format_map
anyway), or "foo %(bar)s %(baz)s" % something_dynamic
)...
Idk if transform-concats
/FLY001
is always desirable on only two items. Especially given possible overrides of __add__
and __radd__
. And it's not even more concise.
But for 3 items, especially when a variable is inserted between two strings*, is definitely something I've been looking for.
* I think it's safe to expect people shouldn't be writing code where "a" + b + "c"
does not return a string using b's string representation. Or just not use FLY001
at that point.
I'm also for keeping these different rules. Makes adoption easier, especially when gradually linting or if a rule's style change is undesired.