From af2a95e220a0d9f21e708889dde8e2abf168892f Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Wed, 29 May 2024 23:44:49 -0400 Subject: [PATCH] Infer indentation with imports when logical indent is absent --- .../test/fixtures/isort/two_space.py | 10 + .../test/fixtures/pyupgrade/UP031_0.py | 9 +- crates/ruff_linter/src/rules/isort/mod.rs | 1 + .../src/rules/isort/rules/organize_imports.rs | 2 + ...er__rules__isort__tests__two_space.py.snap | 32 + ...__rules__pyupgrade__tests__UP031_0.py.snap | 944 +++++++++--------- crates/ruff_python_codegen/src/stylist.rs | 34 +- 7 files changed, 554 insertions(+), 478 deletions(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/isort/two_space.py create mode 100644 crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__two_space.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/isort/two_space.py b/crates/ruff_linter/resources/test/fixtures/isort/two_space.py new file mode 100644 index 00000000000000..f8a36d54b0f0f5 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/isort/two_space.py @@ -0,0 +1,10 @@ +# If the file doesn't contain a logical indent token, we should still detect two-space indentation on imports. +from math import ( + sin, + tan, + cos, + nan, + pi, +) + +del sin, cos, tan, pi, nan diff --git a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP031_0.py b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP031_0.py index 1bdcbc88c18c1b..5f47e2213fcd78 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP031_0.py +++ b/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP031_0.py @@ -29,6 +29,11 @@ print("brace {} %s" % (1,)) +print(( + "foo %s " + "bar %s" % (x, y) +)) + print( "%s" % ( "trailing comma", @@ -52,10 +57,6 @@ print("%(a)s" % {"a" : 1}) -print(( - "foo %s " - "bar %s" % (x, y) -)) print( "foo %(foo)s " diff --git a/crates/ruff_linter/src/rules/isort/mod.rs b/crates/ruff_linter/src/rules/isort/mod.rs index b7ad9e1ff4f5ed..71af19faf1f54e 100644 --- a/crates/ruff_linter/src/rules/isort/mod.rs +++ b/crates/ruff_linter/src/rules/isort/mod.rs @@ -341,6 +341,7 @@ mod tests { #[test_case(Path::new("split.py"))] #[test_case(Path::new("star_before_others.py"))] #[test_case(Path::new("trailing_suffix.py"))] + #[test_case(Path::new("two_space.py"))] #[test_case(Path::new("type_comments.py"))] #[test_case(Path::new("unicode.py"))] fn default(path: &Path) -> Result<()> { diff --git a/crates/ruff_linter/src/rules/isort/rules/organize_imports.rs b/crates/ruff_linter/src/rules/isort/rules/organize_imports.rs index e571271d08c003..b9f9e1448c9d2c 100644 --- a/crates/ruff_linter/src/rules/isort/rules/organize_imports.rs +++ b/crates/ruff_linter/src/rules/isort/rules/organize_imports.rs @@ -137,6 +137,8 @@ pub(crate) fn organize_imports( return None; } + println!("Expected: {expected}"); + let mut diagnostic = Diagnostic::new(UnsortedImports, range); diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement( indent(&expected, indentation).to_string(), diff --git a/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__two_space.py.snap b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__two_space.py.snap new file mode 100644 index 00000000000000..3d7aa735039ba1 --- /dev/null +++ b/crates/ruff_linter/src/rules/isort/snapshots/ruff_linter__rules__isort__tests__two_space.py.snap @@ -0,0 +1,32 @@ +--- +source: crates/ruff_linter/src/rules/isort/mod.rs +--- +two_space.py:2:1: I001 [*] Import block is un-sorted or un-formatted + | + 1 | # If the file doesn't contain a logical indent token, we should still detect two-space indentation on imports. + 2 | / from math import ( + 3 | | sin, + 4 | | tan, + 5 | | cos, + 6 | | nan, + 7 | | pi, + 8 | | ) + 9 | | +10 | | del sin, cos, tan, pi, nan + | |_^ I001 + | + = help: Organize imports + +ℹ Safe fix +1 1 | # If the file doesn't contain a logical indent token, we should still detect two-space indentation on imports. +2 2 | from math import ( +3 |- sin, +4 |- tan, +5 3 | cos, +6 4 | nan, +7 5 | pi, + 6 |+ sin, + 7 |+ tan, +8 8 | ) +9 9 | +10 10 | del sin, cos, tan, pi, nan diff --git a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP031_0.py.snap b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP031_0.py.snap index 1a200861b65f8a..cccf672ad5c9fd 100644 --- a/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP031_0.py.snap +++ b/crates/ruff_linter/src/rules/pyupgrade/snapshots/ruff_linter__rules__pyupgrade__tests__UP031_0.py.snap @@ -301,7 +301,7 @@ UP031_0.py:30:7: UP031 [*] Use format specifiers instead of percent format 30 | print("brace {} %s" % (1,)) | ^^^^^^^^^^^^^^^^^^^^ UP031 31 | -32 | print( +32 | print(( | = help: Replace with format specifiers @@ -312,687 +312,685 @@ UP031_0.py:30:7: UP031 [*] Use format specifiers instead of percent format 30 |-print("brace {} %s" % (1,)) 30 |+print("brace {{}} {}".format(1)) 31 31 | -32 32 | print( -33 33 | "%s" % ( +32 32 | print(( +33 33 | "foo %s " -UP031_0.py:33:3: UP031 [*] Use format specifiers instead of percent format +UP031_0.py:33:5: UP031 [*] Use format specifiers instead of percent format | -32 | print( -33 | "%s" % ( - | ___^ -34 | | "trailing comma", -35 | | ) - | |_________^ UP031 -36 | ) +32 | print(( +33 | "foo %s " + | _____^ +34 | | "bar %s" % (x, y) + | |_____________________^ UP031 +35 | )) | = help: Replace with format specifiers ℹ Unsafe fix 30 30 | print("brace {} %s" % (1,)) 31 31 | -32 32 | print( -33 |- "%s" % ( - 33 |+ "{}".format( -34 34 | "trailing comma", -35 35 | ) -36 36 | ) - -UP031_0.py:38:7: UP031 [*] Use format specifiers instead of percent format - | -36 | ) -37 | -38 | print("foo %s " % (x,)) - | ^^^^^^^^^^^^^^^^ UP031 -39 | -40 | print("%(k)s" % {"k": "v"}) +32 32 | print(( +33 |- "foo %s " +34 |- "bar %s" % (x, y) + 33 |+ "foo {} " + 34 |+ "bar {}".format(x, y) +35 35 | )) +36 36 | +37 37 | print( + +UP031_0.py:38:3: UP031 [*] Use format specifiers instead of percent format + | +37 | print( +38 | "%s" % ( + | ___^ +39 | | "trailing comma", +40 | | ) + | |_________^ UP031 +41 | ) | = help: Replace with format specifiers ℹ Unsafe fix -35 35 | ) -36 36 | ) -37 37 | -38 |-print("foo %s " % (x,)) - 38 |+print("foo {} ".format(x)) -39 39 | -40 40 | print("%(k)s" % {"k": "v"}) -41 41 | +35 35 | )) +36 36 | +37 37 | print( +38 |- "%s" % ( + 38 |+ "{}".format( +39 39 | "trailing comma", +40 40 | ) +41 41 | ) -UP031_0.py:40:7: UP031 [*] Use format specifiers instead of percent format +UP031_0.py:43:7: UP031 [*] Use format specifiers instead of percent format | -38 | print("foo %s " % (x,)) -39 | -40 | print("%(k)s" % {"k": "v"}) - | ^^^^^^^^^^^^^^^^^^^^ UP031 -41 | -42 | print("%(k)s" % { +41 | ) +42 | +43 | print("foo %s " % (x,)) + | ^^^^^^^^^^^^^^^^ UP031 +44 | +45 | print("%(k)s" % {"k": "v"}) | = help: Replace with format specifiers ℹ Unsafe fix -37 37 | -38 38 | print("foo %s " % (x,)) -39 39 | -40 |-print("%(k)s" % {"k": "v"}) - 40 |+print("{k}".format(k="v")) -41 41 | -42 42 | print("%(k)s" % { -43 43 | "k": "v", +40 40 | ) +41 41 | ) +42 42 | +43 |-print("foo %s " % (x,)) + 43 |+print("foo {} ".format(x)) +44 44 | +45 45 | print("%(k)s" % {"k": "v"}) +46 46 | -UP031_0.py:42:7: UP031 [*] Use format specifiers instead of percent format +UP031_0.py:45:7: UP031 [*] Use format specifiers instead of percent format | -40 | print("%(k)s" % {"k": "v"}) -41 | -42 | print("%(k)s" % { - | _______^ -43 | | "k": "v", -44 | | "i": "j" -45 | | }) - | |_^ UP031 -46 | -47 | print("%(to_list)s" % {"to_list": []}) +43 | print("foo %s " % (x,)) +44 | +45 | print("%(k)s" % {"k": "v"}) + | ^^^^^^^^^^^^^^^^^^^^ UP031 +46 | +47 | print("%(k)s" % { | = help: Replace with format specifiers ℹ Unsafe fix -39 39 | -40 40 | print("%(k)s" % {"k": "v"}) -41 41 | -42 |-print("%(k)s" % { -43 |- "k": "v", -44 |- "i": "j" -45 |-}) - 42 |+print("{k}".format( - 43 |+ k="v", - 44 |+ i="j", - 45 |+)) +42 42 | +43 43 | print("foo %s " % (x,)) +44 44 | +45 |-print("%(k)s" % {"k": "v"}) + 45 |+print("{k}".format(k="v")) 46 46 | -47 47 | print("%(to_list)s" % {"to_list": []}) -48 48 | +47 47 | print("%(k)s" % { +48 48 | "k": "v", UP031_0.py:47:7: UP031 [*] Use format specifiers instead of percent format | -45 | }) -46 | -47 | print("%(to_list)s" % {"to_list": []}) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP031 -48 | -49 | print("%(k)s" % {"k": "v", "i": 1, "j": []}) +45 | print("%(k)s" % {"k": "v"}) +46 | +47 | print("%(k)s" % { + | _______^ +48 | | "k": "v", +49 | | "i": "j" +50 | | }) + | |_^ UP031 +51 | +52 | print("%(to_list)s" % {"to_list": []}) | = help: Replace with format specifiers ℹ Unsafe fix -44 44 | "i": "j" -45 45 | }) +44 44 | +45 45 | print("%(k)s" % {"k": "v"}) 46 46 | -47 |-print("%(to_list)s" % {"to_list": []}) - 47 |+print("{to_list}".format(to_list=[])) -48 48 | -49 49 | print("%(k)s" % {"k": "v", "i": 1, "j": []}) -50 50 | - -UP031_0.py:49:7: UP031 [*] Use format specifiers instead of percent format - | -47 | print("%(to_list)s" % {"to_list": []}) -48 | -49 | print("%(k)s" % {"k": "v", "i": 1, "j": []}) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP031 -50 | -51 | print("%(ab)s" % {"a" "b": 1}) +47 |-print("%(k)s" % { +48 |- "k": "v", +49 |- "i": "j" +50 |-}) + 47 |+print("{k}".format( + 48 |+ k="v", + 49 |+ i="j", + 50 |+)) +51 51 | +52 52 | print("%(to_list)s" % {"to_list": []}) +53 53 | + +UP031_0.py:52:7: UP031 [*] Use format specifiers instead of percent format + | +50 | }) +51 | +52 | print("%(to_list)s" % {"to_list": []}) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP031 +53 | +54 | print("%(k)s" % {"k": "v", "i": 1, "j": []}) | = help: Replace with format specifiers ℹ Unsafe fix -46 46 | -47 47 | print("%(to_list)s" % {"to_list": []}) -48 48 | -49 |-print("%(k)s" % {"k": "v", "i": 1, "j": []}) - 49 |+print("{k}".format(k="v", i=1, j=[])) -50 50 | -51 51 | print("%(ab)s" % {"a" "b": 1}) -52 52 | - -UP031_0.py:51:7: UP031 [*] Use format specifiers instead of percent format - | -49 | print("%(k)s" % {"k": "v", "i": 1, "j": []}) -50 | -51 | print("%(ab)s" % {"a" "b": 1}) - | ^^^^^^^^^^^^^^^^^^^^^^^ UP031 -52 | -53 | print("%(a)s" % {"a" : 1}) +49 49 | "i": "j" +50 50 | }) +51 51 | +52 |-print("%(to_list)s" % {"to_list": []}) + 52 |+print("{to_list}".format(to_list=[])) +53 53 | +54 54 | print("%(k)s" % {"k": "v", "i": 1, "j": []}) +55 55 | + +UP031_0.py:54:7: UP031 [*] Use format specifiers instead of percent format + | +52 | print("%(to_list)s" % {"to_list": []}) +53 | +54 | print("%(k)s" % {"k": "v", "i": 1, "j": []}) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP031 +55 | +56 | print("%(ab)s" % {"a" "b": 1}) | = help: Replace with format specifiers ℹ Unsafe fix -48 48 | -49 49 | print("%(k)s" % {"k": "v", "i": 1, "j": []}) -50 50 | -51 |-print("%(ab)s" % {"a" "b": 1}) - 51 |+print("{ab}".format(ab=1)) -52 52 | -53 53 | print("%(a)s" % {"a" : 1}) -54 54 | +51 51 | +52 52 | print("%(to_list)s" % {"to_list": []}) +53 53 | +54 |-print("%(k)s" % {"k": "v", "i": 1, "j": []}) + 54 |+print("{k}".format(k="v", i=1, j=[])) +55 55 | +56 56 | print("%(ab)s" % {"a" "b": 1}) +57 57 | -UP031_0.py:53:7: UP031 [*] Use format specifiers instead of percent format +UP031_0.py:56:7: UP031 [*] Use format specifiers instead of percent format | -51 | print("%(ab)s" % {"a" "b": 1}) -52 | -53 | print("%(a)s" % {"a" : 1}) - | ^^^^^^^^^^^^^^^^^^^^^ UP031 -54 | -55 | print(( +54 | print("%(k)s" % {"k": "v", "i": 1, "j": []}) +55 | +56 | print("%(ab)s" % {"a" "b": 1}) + | ^^^^^^^^^^^^^^^^^^^^^^^ UP031 +57 | +58 | print("%(a)s" % {"a" : 1}) | = help: Replace with format specifiers ℹ Unsafe fix -50 50 | -51 51 | print("%(ab)s" % {"a" "b": 1}) -52 52 | -53 |-print("%(a)s" % {"a" : 1}) - 53 |+print("{a}".format(a=1)) -54 54 | -55 55 | print(( -56 56 | "foo %s " +53 53 | +54 54 | print("%(k)s" % {"k": "v", "i": 1, "j": []}) +55 55 | +56 |-print("%(ab)s" % {"a" "b": 1}) + 56 |+print("{ab}".format(ab=1)) +57 57 | +58 58 | print("%(a)s" % {"a" : 1}) +59 59 | -UP031_0.py:56:5: UP031 [*] Use format specifiers instead of percent format +UP031_0.py:58:7: UP031 [*] Use format specifiers instead of percent format | -55 | print(( -56 | "foo %s " - | _____^ -57 | | "bar %s" % (x, y) - | |_____________________^ UP031 -58 | )) +56 | print("%(ab)s" % {"a" "b": 1}) +57 | +58 | print("%(a)s" % {"a" : 1}) + | ^^^^^^^^^^^^^^^^^^^^^ UP031 | = help: Replace with format specifiers ℹ Unsafe fix -53 53 | print("%(a)s" % {"a" : 1}) -54 54 | -55 55 | print(( -56 |- "foo %s " -57 |- "bar %s" % (x, y) - 56 |+ "foo {} " - 57 |+ "bar {}".format(x, y) -58 58 | )) +55 55 | +56 56 | print("%(ab)s" % {"a" "b": 1}) +57 57 | +58 |-print("%(a)s" % {"a" : 1}) + 58 |+print("{a}".format(a=1)) 59 59 | -60 60 | print( +60 60 | +61 61 | print( -UP031_0.py:61:5: UP031 [*] Use format specifiers instead of percent format +UP031_0.py:62:5: UP031 [*] Use format specifiers instead of percent format | -60 | print( -61 | "foo %(foo)s " +61 | print( +62 | "foo %(foo)s " | _____^ -62 | | "bar %(bar)s" % {"foo": x, "bar": y} +63 | | "bar %(bar)s" % {"foo": x, "bar": y} | |________________________________________^ UP031 -63 | ) +64 | ) | = help: Replace with format specifiers ℹ Unsafe fix -58 58 | )) 59 59 | -60 60 | print( -61 |- "foo %(foo)s " -62 |- "bar %(bar)s" % {"foo": x, "bar": y} - 61 |+ "foo {foo} " - 62 |+ "bar {bar}".format(foo=x, bar=y) -63 63 | ) -64 64 | -65 65 | bar = {"bar": y} - -UP031_0.py:67:5: UP031 [*] Use format specifiers instead of percent format - | -65 | bar = {"bar": y} -66 | print( -67 | "foo %(foo)s " +60 60 | +61 61 | print( +62 |- "foo %(foo)s " +63 |- "bar %(bar)s" % {"foo": x, "bar": y} + 62 |+ "foo {foo} " + 63 |+ "bar {bar}".format(foo=x, bar=y) +64 64 | ) +65 65 | +66 66 | bar = {"bar": y} + +UP031_0.py:68:5: UP031 [*] Use format specifiers instead of percent format + | +66 | bar = {"bar": y} +67 | print( +68 | "foo %(foo)s " | _____^ -68 | | "bar %(bar)s" % {"foo": x, **bar} +69 | | "bar %(bar)s" % {"foo": x, **bar} | |_____________________________________^ UP031 -69 | ) +70 | ) | = help: Replace with format specifiers ℹ Unsafe fix -64 64 | -65 65 | bar = {"bar": y} -66 66 | print( -67 |- "foo %(foo)s " -68 |- "bar %(bar)s" % {"foo": x, **bar} - 67 |+ "foo {foo} " - 68 |+ "bar {bar}".format(foo=x, **bar) -69 69 | ) -70 70 | -71 71 | print("%s \N{snowman}" % (a,)) +65 65 | +66 66 | bar = {"bar": y} +67 67 | print( +68 |- "foo %(foo)s " +69 |- "bar %(bar)s" % {"foo": x, **bar} + 68 |+ "foo {foo} " + 69 |+ "bar {bar}".format(foo=x, **bar) +70 70 | ) +71 71 | +72 72 | print("%s \N{snowman}" % (a,)) -UP031_0.py:71:7: UP031 [*] Use format specifiers instead of percent format +UP031_0.py:72:7: UP031 [*] Use format specifiers instead of percent format | -69 | ) -70 | -71 | print("%s \N{snowman}" % (a,)) +70 | ) +71 | +72 | print("%s \N{snowman}" % (a,)) | ^^^^^^^^^^^^^^^^^^^^^^^ UP031 -72 | -73 | print("%(foo)s \N{snowman}" % {"foo": 1}) +73 | +74 | print("%(foo)s \N{snowman}" % {"foo": 1}) | = help: Replace with format specifiers ℹ Unsafe fix -68 68 | "bar %(bar)s" % {"foo": x, **bar} -69 69 | ) -70 70 | -71 |-print("%s \N{snowman}" % (a,)) - 71 |+print("{} \N{snowman}".format(a)) -72 72 | -73 73 | print("%(foo)s \N{snowman}" % {"foo": 1}) -74 74 | +69 69 | "bar %(bar)s" % {"foo": x, **bar} +70 70 | ) +71 71 | +72 |-print("%s \N{snowman}" % (a,)) + 72 |+print("{} \N{snowman}".format(a)) +73 73 | +74 74 | print("%(foo)s \N{snowman}" % {"foo": 1}) +75 75 | -UP031_0.py:73:7: UP031 [*] Use format specifiers instead of percent format +UP031_0.py:74:7: UP031 [*] Use format specifiers instead of percent format | -71 | print("%s \N{snowman}" % (a,)) -72 | -73 | print("%(foo)s \N{snowman}" % {"foo": 1}) +72 | print("%s \N{snowman}" % (a,)) +73 | +74 | print("%(foo)s \N{snowman}" % {"foo": 1}) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP031 -74 | -75 | print(("foo %s " "bar %s") % (x, y)) +75 | +76 | print(("foo %s " "bar %s") % (x, y)) | = help: Replace with format specifiers ℹ Unsafe fix -70 70 | -71 71 | print("%s \N{snowman}" % (a,)) -72 72 | -73 |-print("%(foo)s \N{snowman}" % {"foo": 1}) - 73 |+print("{foo} \N{snowman}".format(foo=1)) -74 74 | -75 75 | print(("foo %s " "bar %s") % (x, y)) -76 76 | +71 71 | +72 72 | print("%s \N{snowman}" % (a,)) +73 73 | +74 |-print("%(foo)s \N{snowman}" % {"foo": 1}) + 74 |+print("{foo} \N{snowman}".format(foo=1)) +75 75 | +76 76 | print(("foo %s " "bar %s") % (x, y)) +77 77 | -UP031_0.py:75:7: UP031 [*] Use format specifiers instead of percent format +UP031_0.py:76:7: UP031 [*] Use format specifiers instead of percent format | -73 | print("%(foo)s \N{snowman}" % {"foo": 1}) -74 | -75 | print(("foo %s " "bar %s") % (x, y)) +74 | print("%(foo)s \N{snowman}" % {"foo": 1}) +75 | +76 | print(("foo %s " "bar %s") % (x, y)) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP031 -76 | -77 | # Single-value expressions +77 | +78 | # Single-value expressions | = help: Replace with format specifiers ℹ Unsafe fix -72 72 | -73 73 | print("%(foo)s \N{snowman}" % {"foo": 1}) -74 74 | -75 |-print(("foo %s " "bar %s") % (x, y)) - 75 |+print(("foo {} " "bar {}").format(x, y)) -76 76 | -77 77 | # Single-value expressions -78 78 | print('Hello %s' % "World") +73 73 | +74 74 | print("%(foo)s \N{snowman}" % {"foo": 1}) +75 75 | +76 |-print(("foo %s " "bar %s") % (x, y)) + 76 |+print(("foo {} " "bar {}").format(x, y)) +77 77 | +78 78 | # Single-value expressions +79 79 | print('Hello %s' % "World") -UP031_0.py:78:7: UP031 [*] Use format specifiers instead of percent format +UP031_0.py:79:7: UP031 [*] Use format specifiers instead of percent format | -77 | # Single-value expressions -78 | print('Hello %s' % "World") +78 | # Single-value expressions +79 | print('Hello %s' % "World") | ^^^^^^^^^^^^^^^^^^^^ UP031 -79 | print('Hello %s' % f"World") -80 | print('Hello %s (%s)' % bar) +80 | print('Hello %s' % f"World") +81 | print('Hello %s (%s)' % bar) | = help: Replace with format specifiers ℹ Unsafe fix -75 75 | print(("foo %s " "bar %s") % (x, y)) -76 76 | -77 77 | # Single-value expressions -78 |-print('Hello %s' % "World") - 78 |+print('Hello {}'.format("World")) -79 79 | print('Hello %s' % f"World") -80 80 | print('Hello %s (%s)' % bar) -81 81 | print('Hello %s (%s)' % bar.baz) +76 76 | print(("foo %s " "bar %s") % (x, y)) +77 77 | +78 78 | # Single-value expressions +79 |-print('Hello %s' % "World") + 79 |+print('Hello {}'.format("World")) +80 80 | print('Hello %s' % f"World") +81 81 | print('Hello %s (%s)' % bar) +82 82 | print('Hello %s (%s)' % bar.baz) -UP031_0.py:79:7: UP031 [*] Use format specifiers instead of percent format +UP031_0.py:80:7: UP031 [*] Use format specifiers instead of percent format | -77 | # Single-value expressions -78 | print('Hello %s' % "World") -79 | print('Hello %s' % f"World") +78 | # Single-value expressions +79 | print('Hello %s' % "World") +80 | print('Hello %s' % f"World") | ^^^^^^^^^^^^^^^^^^^^^ UP031 -80 | print('Hello %s (%s)' % bar) -81 | print('Hello %s (%s)' % bar.baz) +81 | print('Hello %s (%s)' % bar) +82 | print('Hello %s (%s)' % bar.baz) | = help: Replace with format specifiers ℹ Unsafe fix -76 76 | -77 77 | # Single-value expressions -78 78 | print('Hello %s' % "World") -79 |-print('Hello %s' % f"World") - 79 |+print('Hello {}'.format(f"World")) -80 80 | print('Hello %s (%s)' % bar) -81 81 | print('Hello %s (%s)' % bar.baz) -82 82 | print('Hello %s (%s)' % bar['bop']) +77 77 | +78 78 | # Single-value expressions +79 79 | print('Hello %s' % "World") +80 |-print('Hello %s' % f"World") + 80 |+print('Hello {}'.format(f"World")) +81 81 | print('Hello %s (%s)' % bar) +82 82 | print('Hello %s (%s)' % bar.baz) +83 83 | print('Hello %s (%s)' % bar['bop']) -UP031_0.py:80:7: UP031 [*] Use format specifiers instead of percent format +UP031_0.py:81:7: UP031 [*] Use format specifiers instead of percent format | -78 | print('Hello %s' % "World") -79 | print('Hello %s' % f"World") -80 | print('Hello %s (%s)' % bar) +79 | print('Hello %s' % "World") +80 | print('Hello %s' % f"World") +81 | print('Hello %s (%s)' % bar) | ^^^^^^^^^^^^^^^^^^^^^ UP031 -81 | print('Hello %s (%s)' % bar.baz) -82 | print('Hello %s (%s)' % bar['bop']) +82 | print('Hello %s (%s)' % bar.baz) +83 | print('Hello %s (%s)' % bar['bop']) | = help: Replace with format specifiers ℹ Unsafe fix -77 77 | # Single-value expressions -78 78 | print('Hello %s' % "World") -79 79 | print('Hello %s' % f"World") -80 |-print('Hello %s (%s)' % bar) - 80 |+print('Hello {} ({})'.format(*bar)) -81 81 | print('Hello %s (%s)' % bar.baz) -82 82 | print('Hello %s (%s)' % bar['bop']) -83 83 | print('Hello %(arg)s' % bar) +78 78 | # Single-value expressions +79 79 | print('Hello %s' % "World") +80 80 | print('Hello %s' % f"World") +81 |-print('Hello %s (%s)' % bar) + 81 |+print('Hello {} ({})'.format(*bar)) +82 82 | print('Hello %s (%s)' % bar.baz) +83 83 | print('Hello %s (%s)' % bar['bop']) +84 84 | print('Hello %(arg)s' % bar) -UP031_0.py:81:7: UP031 [*] Use format specifiers instead of percent format +UP031_0.py:82:7: UP031 [*] Use format specifiers instead of percent format | -79 | print('Hello %s' % f"World") -80 | print('Hello %s (%s)' % bar) -81 | print('Hello %s (%s)' % bar.baz) +80 | print('Hello %s' % f"World") +81 | print('Hello %s (%s)' % bar) +82 | print('Hello %s (%s)' % bar.baz) | ^^^^^^^^^^^^^^^^^^^^^^^^^ UP031 -82 | print('Hello %s (%s)' % bar['bop']) -83 | print('Hello %(arg)s' % bar) +83 | print('Hello %s (%s)' % bar['bop']) +84 | print('Hello %(arg)s' % bar) | = help: Replace with format specifiers ℹ Unsafe fix -78 78 | print('Hello %s' % "World") -79 79 | print('Hello %s' % f"World") -80 80 | print('Hello %s (%s)' % bar) -81 |-print('Hello %s (%s)' % bar.baz) - 81 |+print('Hello {} ({})'.format(*bar.baz)) -82 82 | print('Hello %s (%s)' % bar['bop']) -83 83 | print('Hello %(arg)s' % bar) -84 84 | print('Hello %(arg)s' % bar.baz) +79 79 | print('Hello %s' % "World") +80 80 | print('Hello %s' % f"World") +81 81 | print('Hello %s (%s)' % bar) +82 |-print('Hello %s (%s)' % bar.baz) + 82 |+print('Hello {} ({})'.format(*bar.baz)) +83 83 | print('Hello %s (%s)' % bar['bop']) +84 84 | print('Hello %(arg)s' % bar) +85 85 | print('Hello %(arg)s' % bar.baz) -UP031_0.py:82:7: UP031 [*] Use format specifiers instead of percent format +UP031_0.py:83:7: UP031 [*] Use format specifiers instead of percent format | -80 | print('Hello %s (%s)' % bar) -81 | print('Hello %s (%s)' % bar.baz) -82 | print('Hello %s (%s)' % bar['bop']) +81 | print('Hello %s (%s)' % bar) +82 | print('Hello %s (%s)' % bar.baz) +83 | print('Hello %s (%s)' % bar['bop']) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP031 -83 | print('Hello %(arg)s' % bar) -84 | print('Hello %(arg)s' % bar.baz) +84 | print('Hello %(arg)s' % bar) +85 | print('Hello %(arg)s' % bar.baz) | = help: Replace with format specifiers ℹ Unsafe fix -79 79 | print('Hello %s' % f"World") -80 80 | print('Hello %s (%s)' % bar) -81 81 | print('Hello %s (%s)' % bar.baz) -82 |-print('Hello %s (%s)' % bar['bop']) - 82 |+print('Hello {} ({})'.format(*bar['bop'])) -83 83 | print('Hello %(arg)s' % bar) -84 84 | print('Hello %(arg)s' % bar.baz) -85 85 | print('Hello %(arg)s' % bar['bop']) +80 80 | print('Hello %s' % f"World") +81 81 | print('Hello %s (%s)' % bar) +82 82 | print('Hello %s (%s)' % bar.baz) +83 |-print('Hello %s (%s)' % bar['bop']) + 83 |+print('Hello {} ({})'.format(*bar['bop'])) +84 84 | print('Hello %(arg)s' % bar) +85 85 | print('Hello %(arg)s' % bar.baz) +86 86 | print('Hello %(arg)s' % bar['bop']) -UP031_0.py:83:7: UP031 [*] Use format specifiers instead of percent format +UP031_0.py:84:7: UP031 [*] Use format specifiers instead of percent format | -81 | print('Hello %s (%s)' % bar.baz) -82 | print('Hello %s (%s)' % bar['bop']) -83 | print('Hello %(arg)s' % bar) +82 | print('Hello %s (%s)' % bar.baz) +83 | print('Hello %s (%s)' % bar['bop']) +84 | print('Hello %(arg)s' % bar) | ^^^^^^^^^^^^^^^^^^^^^ UP031 -84 | print('Hello %(arg)s' % bar.baz) -85 | print('Hello %(arg)s' % bar['bop']) +85 | print('Hello %(arg)s' % bar.baz) +86 | print('Hello %(arg)s' % bar['bop']) | = help: Replace with format specifiers ℹ Unsafe fix -80 80 | print('Hello %s (%s)' % bar) -81 81 | print('Hello %s (%s)' % bar.baz) -82 82 | print('Hello %s (%s)' % bar['bop']) -83 |-print('Hello %(arg)s' % bar) - 83 |+print('Hello {arg}'.format(**bar)) -84 84 | print('Hello %(arg)s' % bar.baz) -85 85 | print('Hello %(arg)s' % bar['bop']) -86 86 | +81 81 | print('Hello %s (%s)' % bar) +82 82 | print('Hello %s (%s)' % bar.baz) +83 83 | print('Hello %s (%s)' % bar['bop']) +84 |-print('Hello %(arg)s' % bar) + 84 |+print('Hello {arg}'.format(**bar)) +85 85 | print('Hello %(arg)s' % bar.baz) +86 86 | print('Hello %(arg)s' % bar['bop']) +87 87 | -UP031_0.py:84:7: UP031 [*] Use format specifiers instead of percent format +UP031_0.py:85:7: UP031 [*] Use format specifiers instead of percent format | -82 | print('Hello %s (%s)' % bar['bop']) -83 | print('Hello %(arg)s' % bar) -84 | print('Hello %(arg)s' % bar.baz) +83 | print('Hello %s (%s)' % bar['bop']) +84 | print('Hello %(arg)s' % bar) +85 | print('Hello %(arg)s' % bar.baz) | ^^^^^^^^^^^^^^^^^^^^^^^^^ UP031 -85 | print('Hello %(arg)s' % bar['bop']) +86 | print('Hello %(arg)s' % bar['bop']) | = help: Replace with format specifiers ℹ Unsafe fix -81 81 | print('Hello %s (%s)' % bar.baz) -82 82 | print('Hello %s (%s)' % bar['bop']) -83 83 | print('Hello %(arg)s' % bar) -84 |-print('Hello %(arg)s' % bar.baz) - 84 |+print('Hello {arg}'.format(**bar.baz)) -85 85 | print('Hello %(arg)s' % bar['bop']) -86 86 | -87 87 | # Hanging modulos +82 82 | print('Hello %s (%s)' % bar.baz) +83 83 | print('Hello %s (%s)' % bar['bop']) +84 84 | print('Hello %(arg)s' % bar) +85 |-print('Hello %(arg)s' % bar.baz) + 85 |+print('Hello {arg}'.format(**bar.baz)) +86 86 | print('Hello %(arg)s' % bar['bop']) +87 87 | +88 88 | # Hanging modulos -UP031_0.py:85:7: UP031 [*] Use format specifiers instead of percent format +UP031_0.py:86:7: UP031 [*] Use format specifiers instead of percent format | -83 | print('Hello %(arg)s' % bar) -84 | print('Hello %(arg)s' % bar.baz) -85 | print('Hello %(arg)s' % bar['bop']) +84 | print('Hello %(arg)s' % bar) +85 | print('Hello %(arg)s' % bar.baz) +86 | print('Hello %(arg)s' % bar['bop']) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP031 -86 | -87 | # Hanging modulos +87 | +88 | # Hanging modulos | = help: Replace with format specifiers ℹ Unsafe fix -82 82 | print('Hello %s (%s)' % bar['bop']) -83 83 | print('Hello %(arg)s' % bar) -84 84 | print('Hello %(arg)s' % bar.baz) -85 |-print('Hello %(arg)s' % bar['bop']) - 85 |+print('Hello {arg}'.format(**bar['bop'])) -86 86 | -87 87 | # Hanging modulos -88 88 | ( +83 83 | print('Hello %s (%s)' % bar['bop']) +84 84 | print('Hello %(arg)s' % bar) +85 85 | print('Hello %(arg)s' % bar.baz) +86 |-print('Hello %(arg)s' % bar['bop']) + 86 |+print('Hello {arg}'.format(**bar['bop'])) +87 87 | +88 88 | # Hanging modulos +89 89 | ( -UP031_0.py:88:1: UP031 [*] Use format specifiers instead of percent format +UP031_0.py:89:1: UP031 [*] Use format specifiers instead of percent format | -87 | # Hanging modulos -88 | / ( -89 | | "foo %s " -90 | | "bar %s" -91 | | ) % (x, y) +88 | # Hanging modulos +89 | / ( +90 | | "foo %s " +91 | | "bar %s" +92 | | ) % (x, y) | |__________^ UP031 -92 | -93 | ( +93 | +94 | ( | = help: Replace with format specifiers ℹ Unsafe fix -86 86 | -87 87 | # Hanging modulos -88 88 | ( -89 |- "foo %s " -90 |- "bar %s" -91 |-) % (x, y) - 89 |+ "foo {} " - 90 |+ "bar {}" - 91 |+).format(x, y) -92 92 | -93 93 | ( -94 94 | "foo %(foo)s " +87 87 | +88 88 | # Hanging modulos +89 89 | ( +90 |- "foo %s " +91 |- "bar %s" +92 |-) % (x, y) + 90 |+ "foo {} " + 91 |+ "bar {}" + 92 |+).format(x, y) +93 93 | +94 94 | ( +95 95 | "foo %(foo)s " -UP031_0.py:93:1: UP031 [*] Use format specifiers instead of percent format +UP031_0.py:94:1: UP031 [*] Use format specifiers instead of percent format | -91 | ) % (x, y) -92 | -93 | / ( -94 | | "foo %(foo)s " -95 | | "bar %(bar)s" -96 | | ) % {"foo": x, "bar": y} +92 | ) % (x, y) +93 | +94 | / ( +95 | | "foo %(foo)s " +96 | | "bar %(bar)s" +97 | | ) % {"foo": x, "bar": y} | |________________________^ UP031 -97 | -98 | ( +98 | +99 | ( | = help: Replace with format specifiers ℹ Unsafe fix -91 91 | ) % (x, y) -92 92 | -93 93 | ( -94 |- "foo %(foo)s " -95 |- "bar %(bar)s" -96 |-) % {"foo": x, "bar": y} - 94 |+ "foo {foo} " - 95 |+ "bar {bar}" - 96 |+).format(foo=x, bar=y) -97 97 | -98 98 | ( -99 99 | """foo %s""" +92 92 | ) % (x, y) +93 93 | +94 94 | ( +95 |- "foo %(foo)s " +96 |- "bar %(bar)s" +97 |-) % {"foo": x, "bar": y} + 95 |+ "foo {foo} " + 96 |+ "bar {bar}" + 97 |+).format(foo=x, bar=y) +98 98 | +99 99 | ( +100 100 | """foo %s""" -UP031_0.py:99:5: UP031 [*] Use format specifiers instead of percent format +UP031_0.py:100:5: UP031 [*] Use format specifiers instead of percent format | - 98 | ( - 99 | """foo %s""" + 99 | ( +100 | """foo %s""" | _____^ -100 | | % (x,) +101 | | % (x,) | |__________^ UP031 -101 | ) +102 | ) | = help: Replace with format specifiers ℹ Unsafe fix -96 96 | ) % {"foo": x, "bar": y} -97 97 | -98 98 | ( -99 |- """foo %s""" -100 |- % (x,) - 99 |+ """foo {}""".format(x) -101 100 | ) -102 101 | -103 102 | ( +97 97 | ) % {"foo": x, "bar": y} +98 98 | +99 99 | ( +100 |- """foo %s""" +101 |- % (x,) + 100 |+ """foo {}""".format(x) +102 101 | ) +103 102 | +104 103 | ( -UP031_0.py:104:5: UP031 [*] Use format specifiers instead of percent format +UP031_0.py:105:5: UP031 [*] Use format specifiers instead of percent format | -103 | ( -104 | """ +104 | ( +105 | """ | _____^ -105 | | foo %s -106 | | """ -107 | | % (x,) +106 | | foo %s +107 | | """ +108 | | % (x,) | |__________^ UP031 -108 | ) +109 | ) | = help: Replace with format specifiers ℹ Unsafe fix -102 102 | -103 103 | ( -104 104 | """ -105 |- foo %s -106 |- """ -107 |- % (x,) - 105 |+ foo {} - 106 |+ """.format(x) -108 107 | ) -109 108 | -110 109 | "%s" % ( +103 103 | +104 104 | ( +105 105 | """ +106 |- foo %s +107 |- """ +108 |- % (x,) + 106 |+ foo {} + 107 |+ """.format(x) +109 108 | ) +110 109 | +111 110 | "%s" % ( -UP031_0.py:110:1: UP031 [*] Use format specifiers instead of percent format +UP031_0.py:111:1: UP031 [*] Use format specifiers instead of percent format | -108 | ) -109 | -110 | / "%s" % ( -111 | | x, # comment -112 | | ) +109 | ) +110 | +111 | / "%s" % ( +112 | | x, # comment +113 | | ) | |_^ UP031 | = help: Replace with format specifiers ℹ Unsafe fix -107 107 | % (x,) -108 108 | ) -109 109 | -110 |-"%s" % ( - 110 |+"{}".format( -111 111 | x, # comment -112 112 | ) -113 113 | +108 108 | % (x,) +109 109 | ) +110 110 | +111 |-"%s" % ( + 111 |+"{}".format( +112 112 | x, # comment +113 113 | ) +114 114 | -UP031_0.py:115:8: UP031 [*] Use format specifiers instead of percent format +UP031_0.py:116:8: UP031 [*] Use format specifiers instead of percent format | -115 | path = "%s-%s-%s.pem" % ( +116 | path = "%s-%s-%s.pem" % ( | ________^ -116 | | safe_domain_name(cn), # common name, which should be filename safe because it is IDNA-encoded, but in case of a malformed cert make sure it's ok to use as a filename -117 | | cert.not_valid_after.date().isoformat().replace("-", ""), # expiration date -118 | | hexlify(cert.fingerprint(hashes.SHA256())).decode("ascii")[0:8], # fingerprint prefix -119 | | ) +117 | | safe_domain_name(cn), # common name, which should be filename safe because it is IDNA-encoded, but in case of a malformed cert make sure it's ok to use as a filename +118 | | cert.not_valid_after.date().isoformat().replace("-", ""), # expiration date +119 | | hexlify(cert.fingerprint(hashes.SHA256())).decode("ascii")[0:8], # fingerprint prefix +120 | | ) | |_^ UP031 -120 | -121 | # UP031 (no longer false negatives; now offer potentially unsafe fixes) +121 | +122 | # UP031 (no longer false negatives; now offer potentially unsafe fixes) | = help: Replace with format specifiers ℹ Unsafe fix -112 112 | ) -113 113 | +113 113 | ) 114 114 | -115 |-path = "%s-%s-%s.pem" % ( - 115 |+path = "{}-{}-{}.pem".format( -116 116 | safe_domain_name(cn), # common name, which should be filename safe because it is IDNA-encoded, but in case of a malformed cert make sure it's ok to use as a filename -117 117 | cert.not_valid_after.date().isoformat().replace("-", ""), # expiration date -118 118 | hexlify(cert.fingerprint(hashes.SHA256())).decode("ascii")[0:8], # fingerprint prefix - -UP031_0.py:122:1: UP031 [*] Use format specifiers instead of percent format +115 115 | +116 |-path = "%s-%s-%s.pem" % ( + 116 |+path = "{}-{}-{}.pem".format( +117 117 | safe_domain_name(cn), # common name, which should be filename safe because it is IDNA-encoded, but in case of a malformed cert make sure it's ok to use as a filename +118 118 | cert.not_valid_after.date().isoformat().replace("-", ""), # expiration date +119 119 | hexlify(cert.fingerprint(hashes.SHA256())).decode("ascii")[0:8], # fingerprint prefix + +UP031_0.py:123:1: UP031 [*] Use format specifiers instead of percent format | -121 | # UP031 (no longer false negatives; now offer potentially unsafe fixes) -122 | 'Hello %s' % bar +122 | # UP031 (no longer false negatives; now offer potentially unsafe fixes) +123 | 'Hello %s' % bar | ^^^^^^^^^^^^^^^^ UP031 -123 | -124 | 'Hello %s' % bar.baz +124 | +125 | 'Hello %s' % bar.baz | = help: Replace with format specifiers ℹ Unsafe fix -119 119 | ) -120 120 | -121 121 | # UP031 (no longer false negatives; now offer potentially unsafe fixes) -122 |-'Hello %s' % bar - 122 |+'Hello {}'.format(bar) -123 123 | -124 124 | 'Hello %s' % bar.baz -125 125 | +120 120 | ) +121 121 | +122 122 | # UP031 (no longer false negatives; now offer potentially unsafe fixes) +123 |-'Hello %s' % bar + 123 |+'Hello {}'.format(bar) +124 124 | +125 125 | 'Hello %s' % bar.baz +126 126 | -UP031_0.py:124:1: UP031 [*] Use format specifiers instead of percent format +UP031_0.py:125:1: UP031 [*] Use format specifiers instead of percent format | -122 | 'Hello %s' % bar -123 | -124 | 'Hello %s' % bar.baz +123 | 'Hello %s' % bar +124 | +125 | 'Hello %s' % bar.baz | ^^^^^^^^^^^^^^^^^^^^ UP031 -125 | -126 | 'Hello %s' % bar['bop'] +126 | +127 | 'Hello %s' % bar['bop'] | = help: Replace with format specifiers ℹ Unsafe fix -121 121 | # UP031 (no longer false negatives; now offer potentially unsafe fixes) -122 122 | 'Hello %s' % bar -123 123 | -124 |-'Hello %s' % bar.baz - 124 |+'Hello {}'.format(bar.baz) -125 125 | -126 126 | 'Hello %s' % bar['bop'] +122 122 | # UP031 (no longer false negatives; now offer potentially unsafe fixes) +123 123 | 'Hello %s' % bar +124 124 | +125 |-'Hello %s' % bar.baz + 125 |+'Hello {}'.format(bar.baz) +126 126 | +127 127 | 'Hello %s' % bar['bop'] -UP031_0.py:126:1: UP031 [*] Use format specifiers instead of percent format +UP031_0.py:127:1: UP031 [*] Use format specifiers instead of percent format | -124 | 'Hello %s' % bar.baz -125 | -126 | 'Hello %s' % bar['bop'] +125 | 'Hello %s' % bar.baz +126 | +127 | 'Hello %s' % bar['bop'] | ^^^^^^^^^^^^^^^^^^^^^^^ UP031 | = help: Replace with format specifiers ℹ Unsafe fix -123 123 | -124 124 | 'Hello %s' % bar.baz -125 125 | -126 |-'Hello %s' % bar['bop'] - 126 |+'Hello {}'.format(bar['bop']) +124 124 | +125 125 | 'Hello %s' % bar.baz +126 126 | +127 |-'Hello %s' % bar['bop'] + 127 |+'Hello {}'.format(bar['bop']) diff --git a/crates/ruff_python_codegen/src/stylist.rs b/crates/ruff_python_codegen/src/stylist.rs index 27516dcd510eb4..6e3740e7dba08f 100644 --- a/crates/ruff_python_codegen/src/stylist.rs +++ b/crates/ruff_python_codegen/src/stylist.rs @@ -6,7 +6,7 @@ use once_cell::unsync::OnceCell; use ruff_python_ast::{str::Quote, StringFlags}; use ruff_python_parser::lexer::LexResult; -use ruff_python_parser::Tok; +use ruff_python_parser::{Tok, TokenKind}; use ruff_source_file::{find_newline, LineEnding, Locator}; #[derive(Debug, Clone)] @@ -86,6 +86,38 @@ fn detect_indention(tokens: &[LexResult], locator: &Locator) -> Indentation { Indentation(whitespace.to_string()) } else { + // If we can't find a logical indent token, search for a non-logical indent, to cover cases + // like: + //```python + // from math import ( + // sin, + // tan, + // cos, + // ) + // ``` + let mut depth = 0usize; + for (token, range) in tokens.iter().flatten() { + match token.kind() { + TokenKind::Lpar | TokenKind::Lbrace | TokenKind::Lsqb => { + depth = depth.saturating_add(1); + } + TokenKind::Rpar | TokenKind::Rbrace | TokenKind::Rsqb => { + depth = depth.saturating_sub(1); + } + TokenKind::NonLogicalNewline => { + let line = locator.line(range.end()); + let indent_index = line.chars().position(|c| !c.is_whitespace()); + if let Some(indent_index) = indent_index { + if indent_index > 0 { + let whitespace = &line[..indent_index]; + return Indentation(whitespace.to_string()); + } + } + } + _ => {} + } + } + Indentation::default() } }