Skip to content

Commit 4bb7bf2

Browse files
Remove newline after code block open (#3035)
Co-authored-by: Jelle Zijlstra <[email protected]>
1 parent 6d32ab0 commit 4bb7bf2

File tree

9 files changed

+271
-1
lines changed

9 files changed

+271
-1
lines changed

AUTHORS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ Multiple contributions by:
148148
- [Rishikesh Jha](mailto:[email protected])
149149
- [Rupert Bedford](mailto:[email protected])
150150
- Russell Davis
151+
- [Sagi Shadur](mailto:[email protected])
151152
- [Rémi Verschelde](mailto:[email protected])
152153
- [Sami Salonen](mailto:[email protected])
153154
- [Samuel Cormier-Iijima](mailto:[email protected])

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
- Remove redundant parentheses around awaited objects (#2991)
2222
- Parentheses around return annotations are now managed (#2990)
2323
- Remove unnecessary parentheses from `with` statements (#2926)
24+
- Remove trailing newlines after code block open (#3035)
2425

2526
### _Blackd_
2627

docs/the_black_code_style/future_style.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,28 @@ plain strings. User-made splits are respected when they do not exceed the line l
4949
limit. Line continuation backslashes are converted into parenthesized strings.
5050
Unnecessary parentheses are stripped. The stability and status of this feature is
5151
tracked in [this issue](https://github.com/psf/black/issues/2188).
52+
53+
### Removing trailing newlines after code block open
54+
55+
_Black_ will remove trailing newlines after code block openings. That means that the
56+
following code:
57+
58+
```python
59+
def my_func():
60+
61+
print("The line above me will be deleted!")
62+
63+
print("But the line above me won't!")
64+
```
65+
66+
Will be changed to:
67+
68+
```python
69+
def my_func():
70+
print("The line above me will be deleted!")
71+
72+
print("But the line above me won't!")
73+
```
74+
75+
This new feature will be applied to **all code blocks**: `def`, `class`, `if`, `for`,
76+
`while`, `with`, `case` and `match`.

src/black/lines.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,13 @@ def is_triple_quoted_string(self) -> bool:
168168
and self.leaves[0].value.startswith(('"""', "'''"))
169169
)
170170

171+
@property
172+
def opens_block(self) -> bool:
173+
"""Does this line open a new level of indentation."""
174+
if len(self.leaves) == 0:
175+
return False
176+
return self.leaves[-1].type == token.COLON
177+
171178
def contains_standalone_comments(self, depth_limit: int = sys.maxsize) -> bool:
172179
"""If so, needs to be split before emitting."""
173180
for leaf in self.leaves:
@@ -513,6 +520,12 @@ def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]:
513520
):
514521
return before, 1
515522

523+
if (
524+
Preview.remove_block_trailing_newline in current_line.mode
525+
and self.previous_line
526+
and self.previous_line.opens_block
527+
):
528+
return 0, 0
516529
return before, 0
517530

518531
def _maybe_empty_lines_for_class_or_def(

src/black/mode.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ class Preview(Enum):
150150
one_element_subscript = auto()
151151
annotation_parens = auto()
152152
long_docstring_quotes_on_newline = auto()
153+
remove_block_trailing_newline = auto()
153154

154155

155156
class Deprecated(UserWarning):
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import random
2+
3+
4+
def foo1():
5+
6+
print("The newline above me should be deleted!")
7+
8+
9+
def foo2():
10+
11+
12+
13+
print("All the newlines above me should be deleted!")
14+
15+
16+
def foo3():
17+
18+
print("No newline above me!")
19+
20+
print("There is a newline above me, and that's OK!")
21+
22+
23+
def foo4():
24+
25+
# There is a comment here
26+
27+
print("The newline above me should not be deleted!")
28+
29+
30+
class Foo:
31+
def bar(self):
32+
33+
print("The newline above me should be deleted!")
34+
35+
36+
for i in range(5):
37+
38+
print(f"{i}) The line above me should be removed!")
39+
40+
41+
for i in range(5):
42+
43+
44+
45+
print(f"{i}) The lines above me should be removed!")
46+
47+
48+
for i in range(5):
49+
50+
for j in range(7):
51+
52+
print(f"{i}) The lines above me should be removed!")
53+
54+
55+
if random.randint(0, 3) == 0:
56+
57+
print("The new line above me is about to be removed!")
58+
59+
60+
if random.randint(0, 3) == 0:
61+
62+
63+
64+
65+
print("The new lines above me is about to be removed!")
66+
67+
68+
if random.randint(0, 3) == 0:
69+
if random.uniform(0, 1) > 0.5:
70+
print("Two lines above me are about to be removed!")
71+
72+
73+
while True:
74+
75+
print("The newline above me should be deleted!")
76+
77+
78+
while True:
79+
80+
81+
82+
print("The newlines above me should be deleted!")
83+
84+
85+
while True:
86+
87+
while False:
88+
89+
print("The newlines above me should be deleted!")
90+
91+
92+
with open("/path/to/file.txt", mode="w") as file:
93+
94+
file.write("The new line above me is about to be removed!")
95+
96+
97+
with open("/path/to/file.txt", mode="w") as file:
98+
99+
100+
101+
file.write("The new lines above me is about to be removed!")
102+
103+
104+
with open("/path/to/file.txt", mode="r") as read_file:
105+
106+
with open("/path/to/output_file.txt", mode="w") as write_file:
107+
108+
write_file.writelines(read_file.readlines())
109+
110+
# output
111+
112+
import random
113+
114+
115+
def foo1():
116+
print("The newline above me should be deleted!")
117+
118+
119+
def foo2():
120+
print("All the newlines above me should be deleted!")
121+
122+
123+
def foo3():
124+
print("No newline above me!")
125+
126+
print("There is a newline above me, and that's OK!")
127+
128+
129+
def foo4():
130+
# There is a comment here
131+
132+
print("The newline above me should not be deleted!")
133+
134+
135+
class Foo:
136+
def bar(self):
137+
print("The newline above me should be deleted!")
138+
139+
140+
for i in range(5):
141+
print(f"{i}) The line above me should be removed!")
142+
143+
144+
for i in range(5):
145+
print(f"{i}) The lines above me should be removed!")
146+
147+
148+
for i in range(5):
149+
for j in range(7):
150+
print(f"{i}) The lines above me should be removed!")
151+
152+
153+
if random.randint(0, 3) == 0:
154+
print("The new line above me is about to be removed!")
155+
156+
157+
if random.randint(0, 3) == 0:
158+
print("The new lines above me is about to be removed!")
159+
160+
161+
if random.randint(0, 3) == 0:
162+
if random.uniform(0, 1) > 0.5:
163+
print("Two lines above me are about to be removed!")
164+
165+
166+
while True:
167+
print("The newline above me should be deleted!")
168+
169+
170+
while True:
171+
print("The newlines above me should be deleted!")
172+
173+
174+
while True:
175+
while False:
176+
print("The newlines above me should be deleted!")
177+
178+
179+
with open("/path/to/file.txt", mode="w") as file:
180+
file.write("The new line above me is about to be removed!")
181+
182+
183+
with open("/path/to/file.txt", mode="w") as file:
184+
file.write("The new lines above me is about to be removed!")
185+
186+
187+
with open("/path/to/file.txt", mode="r") as read_file:
188+
with open("/path/to/output_file.txt", mode="w") as write_file:
189+
write_file.writelines(read_file.readlines())
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
def http_status(status):
2+
3+
match status:
4+
5+
case 400:
6+
7+
return "Bad request"
8+
9+
case 401:
10+
11+
return "Unauthorized"
12+
13+
case 403:
14+
15+
return "Forbidden"
16+
17+
case 404:
18+
19+
return "Not found"
20+
21+
# output
22+
def http_status(status):
23+
match status:
24+
case 400:
25+
return "Bad request"
26+
27+
case 401:
28+
return "Unauthorized"
29+
30+
case 403:
31+
return "Forbidden"
32+
33+
case 404:
34+
return "Not found"

tests/test_black.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1461,7 +1461,6 @@ def test_newline_comment_interaction(self) -> None:
14611461
black.assert_stable(source, output, mode=DEFAULT_MODE)
14621462

14631463
def test_bpo_2142_workaround(self) -> None:
1464-
14651464
# https://bugs.python.org/issue2142
14661465

14671466
source, _ = read_data("miscellaneous", "missing_final_newline")

tests/test_format.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,13 @@ def test_preview_minimum_python_39_format(filename: str) -> None:
8686
assert_format(source, expected, mode, minimum_version=(3, 9))
8787

8888

89+
@pytest.mark.parametrize("filename", all_data_cases("preview_310"))
90+
def test_preview_minimum_python_310_format(filename: str) -> None:
91+
source, expected = read_data("preview_310", filename)
92+
mode = black.Mode(preview=True)
93+
assert_format(source, expected, mode, minimum_version=(3, 10))
94+
95+
8996
@pytest.mark.parametrize("filename", SOURCES)
9097
def test_source_is_formatted(filename: str) -> None:
9198
check_file("", filename, DEFAULT_MODE, data=False)

0 commit comments

Comments
 (0)