Skip to content

Commit

Permalink
Remove newline after code block open (#3035)
Browse files Browse the repository at this point in the history
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
  • Loading branch information
saroad2 and JelleZijlstra committed Jun 11, 2022
1 parent 6d32ab0 commit 4bb7bf2
Show file tree
Hide file tree
Showing 9 changed files with 271 additions and 1 deletion.
1 change: 1 addition & 0 deletions AUTHORS.md
Expand Up @@ -148,6 +148,7 @@ Multiple contributions by:
- [Rishikesh Jha](mailto:rishijha424@gmail.com)
- [Rupert Bedford](mailto:rupert@rupertb.com)
- Russell Davis
- [Sagi Shadur](mailto:saroad2@gmail.com)
- [Rémi Verschelde](mailto:rverschelde@gmail.com)
- [Sami Salonen](mailto:sakki@iki.fi)
- [Samuel Cormier-Iijima](mailto:samuel@cormier-iijima.com)
Expand Down
1 change: 1 addition & 0 deletions CHANGES.md
Expand Up @@ -21,6 +21,7 @@
- Remove redundant parentheses around awaited objects (#2991)
- Parentheses around return annotations are now managed (#2990)
- Remove unnecessary parentheses from `with` statements (#2926)
- Remove trailing newlines after code block open (#3035)

### _Blackd_

Expand Down
25 changes: 25 additions & 0 deletions docs/the_black_code_style/future_style.md
Expand Up @@ -49,3 +49,28 @@ plain strings. User-made splits are respected when they do not exceed the line l
limit. Line continuation backslashes are converted into parenthesized strings.
Unnecessary parentheses are stripped. The stability and status of this feature is
tracked in [this issue](https://github.com/psf/black/issues/2188).

### Removing trailing newlines after code block open

_Black_ will remove trailing newlines after code block openings. That means that the
following code:

```python
def my_func():

print("The line above me will be deleted!")

print("But the line above me won't!")
```

Will be changed to:

```python
def my_func():
print("The line above me will be deleted!")

print("But the line above me won't!")
```

This new feature will be applied to **all code blocks**: `def`, `class`, `if`, `for`,
`while`, `with`, `case` and `match`.
13 changes: 13 additions & 0 deletions src/black/lines.py
Expand Up @@ -168,6 +168,13 @@ def is_triple_quoted_string(self) -> bool:
and self.leaves[0].value.startswith(('"""', "'''"))
)

@property
def opens_block(self) -> bool:
"""Does this line open a new level of indentation."""
if len(self.leaves) == 0:
return False
return self.leaves[-1].type == token.COLON

def contains_standalone_comments(self, depth_limit: int = sys.maxsize) -> bool:
"""If so, needs to be split before emitting."""
for leaf in self.leaves:
Expand Down Expand Up @@ -513,6 +520,12 @@ def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]:
):
return before, 1

if (
Preview.remove_block_trailing_newline in current_line.mode
and self.previous_line
and self.previous_line.opens_block
):
return 0, 0
return before, 0

def _maybe_empty_lines_for_class_or_def(
Expand Down
1 change: 1 addition & 0 deletions src/black/mode.py
Expand Up @@ -150,6 +150,7 @@ class Preview(Enum):
one_element_subscript = auto()
annotation_parens = auto()
long_docstring_quotes_on_newline = auto()
remove_block_trailing_newline = auto()


class Deprecated(UserWarning):
Expand Down
189 changes: 189 additions & 0 deletions tests/data/preview/remove_newline_after_code_block_open.py
@@ -0,0 +1,189 @@
import random


def foo1():

print("The newline above me should be deleted!")


def foo2():



print("All the newlines above me should be deleted!")


def foo3():

print("No newline above me!")

print("There is a newline above me, and that's OK!")


def foo4():

# There is a comment here

print("The newline above me should not be deleted!")


class Foo:
def bar(self):

print("The newline above me should be deleted!")


for i in range(5):

print(f"{i}) The line above me should be removed!")


for i in range(5):



print(f"{i}) The lines above me should be removed!")


for i in range(5):

for j in range(7):

print(f"{i}) The lines above me should be removed!")


if random.randint(0, 3) == 0:

print("The new line above me is about to be removed!")


if random.randint(0, 3) == 0:




print("The new lines above me is about to be removed!")


if random.randint(0, 3) == 0:
if random.uniform(0, 1) > 0.5:
print("Two lines above me are about to be removed!")


while True:

print("The newline above me should be deleted!")


while True:



print("The newlines above me should be deleted!")


while True:

while False:

print("The newlines above me should be deleted!")


with open("/path/to/file.txt", mode="w") as file:

file.write("The new line above me is about to be removed!")


with open("/path/to/file.txt", mode="w") as file:



file.write("The new lines above me is about to be removed!")


with open("/path/to/file.txt", mode="r") as read_file:

with open("/path/to/output_file.txt", mode="w") as write_file:

write_file.writelines(read_file.readlines())

# output

import random


def foo1():
print("The newline above me should be deleted!")


def foo2():
print("All the newlines above me should be deleted!")


def foo3():
print("No newline above me!")

print("There is a newline above me, and that's OK!")


def foo4():
# There is a comment here

print("The newline above me should not be deleted!")


class Foo:
def bar(self):
print("The newline above me should be deleted!")


for i in range(5):
print(f"{i}) The line above me should be removed!")


for i in range(5):
print(f"{i}) The lines above me should be removed!")


for i in range(5):
for j in range(7):
print(f"{i}) The lines above me should be removed!")


if random.randint(0, 3) == 0:
print("The new line above me is about to be removed!")


if random.randint(0, 3) == 0:
print("The new lines above me is about to be removed!")


if random.randint(0, 3) == 0:
if random.uniform(0, 1) > 0.5:
print("Two lines above me are about to be removed!")


while True:
print("The newline above me should be deleted!")


while True:
print("The newlines above me should be deleted!")


while True:
while False:
print("The newlines above me should be deleted!")


with open("/path/to/file.txt", mode="w") as file:
file.write("The new line above me is about to be removed!")


with open("/path/to/file.txt", mode="w") as file:
file.write("The new lines above me is about to be removed!")


with open("/path/to/file.txt", mode="r") as read_file:
with open("/path/to/output_file.txt", mode="w") as write_file:
write_file.writelines(read_file.readlines())
34 changes: 34 additions & 0 deletions tests/data/preview_310/remove_newline_after match.py
@@ -0,0 +1,34 @@
def http_status(status):

match status:

case 400:

return "Bad request"

case 401:

return "Unauthorized"

case 403:

return "Forbidden"

case 404:

return "Not found"

# output
def http_status(status):
match status:
case 400:
return "Bad request"

case 401:
return "Unauthorized"

case 403:
return "Forbidden"

case 404:
return "Not found"
1 change: 0 additions & 1 deletion tests/test_black.py
Expand Up @@ -1461,7 +1461,6 @@ def test_newline_comment_interaction(self) -> None:
black.assert_stable(source, output, mode=DEFAULT_MODE)

def test_bpo_2142_workaround(self) -> None:

# https://bugs.python.org/issue2142

source, _ = read_data("miscellaneous", "missing_final_newline")
Expand Down
7 changes: 7 additions & 0 deletions tests/test_format.py
Expand Up @@ -86,6 +86,13 @@ def test_preview_minimum_python_39_format(filename: str) -> None:
assert_format(source, expected, mode, minimum_version=(3, 9))


@pytest.mark.parametrize("filename", all_data_cases("preview_310"))
def test_preview_minimum_python_310_format(filename: str) -> None:
source, expected = read_data("preview_310", filename)
mode = black.Mode(preview=True)
assert_format(source, expected, mode, minimum_version=(3, 10))


@pytest.mark.parametrize("filename", SOURCES)
def test_source_is_formatted(filename: str) -> None:
check_file("", filename, DEFAULT_MODE, data=False)
Expand Down

0 comments on commit 4bb7bf2

Please sign in to comment.