asottile / pyupgrade

A tool (and pre-commit hook) to automatically upgrade syntax for newer versions of the language.
MIT License
3.58k stars 182 forks source link

Execution should be idempotent #907

Closed jaraco closed 1 year ago

jaraco commented 1 year ago

I recently discovered that although pyupgrade --py36-plus will upgrade perent-formatted strings to .format strings and it will upgrade .format strings to "f-strings", it won't do both in one run, meaning one gets inconsistent formatting and needs to run the command multiple times to achieve a consistent result:

 draft @ cat old.py
var = 42
a = "a %s" % (var,)
b = "b {0}".format(var)
c = f"c {var}"
 draft @ pip-run pyupgrade -- -m pyupgrade --py36-plus old.py
Rewriting old.py
 draft @ cat old.py
var = 42
a = "a {}".format(var)
b = f"b {var}"
c = f"c {var}"
 draft @ pip-run pyupgrade -- -m pyupgrade --py36-plus old.py
Rewriting old.py
 draft @ cat old.py
var = 42
a = f"a {var}"
b = f"b {var}"
c = f"c {var}"

This limitation led to me sending a PR to a colleague with only the first transformation enacted and wondering about the inconsistency.

I'd expect pyupgrade to perform all of the transformations until none have any effect, such that the operation is idempotent.

asottile commented 1 year ago

some things require multiple passes to stabilize

jaraco commented 1 year ago

Would you consider a pull request to document this expectation?