Skip to content

Commit

Permalink
Treat functions/classes in blocks as if they're nested
Browse files Browse the repository at this point in the history
One curveball is that we still want two preceding newlines before blocks
that are probably logically disconnected. In other words:

    if condition:

        def foo():
            return "hi"
                             # <- aside: this is the goal of this commit
    else:

        def foo():
            return "cya"
                             # <- the two newlines spacing here should stay
                             #    since this probably isn't related
    with open("db.json", encoding="utf-8") as f:
        data = f.read()

Unfortunately that means we have to special case specific clause types
instead of just being able to just for a colon leaf. The hack used here
is to check whether we're adding preceding newlines for a standalone or
dependent clause. "Standalone" being a clause that doesn't need another
clause to be valid (eg. if) and vice versa.
  • Loading branch information
ichard26 committed Sep 2, 2021
1 parent 72de89f commit c646336
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 2 deletions.
4 changes: 4 additions & 0 deletions CHANGES.md
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### _Black_

- Functions and classes in blocks now have more consistent surrounding spacing (#2472)

### Packaging

- Fix missing modules in self-contained binaries (#2466)
Expand Down
22 changes: 20 additions & 2 deletions src/black/lines.py
Expand Up @@ -447,11 +447,29 @@ def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]:
before = 0
depth = current_line.depth
while self.previous_defs and self.previous_defs[-1] >= depth:
self.previous_defs.pop()
if self.is_pyi:
before = 0 if depth else 1
else:
before = 1 if depth else 2
if depth:
before = 1
elif (
not depth
and self.previous_defs[-1]
and current_line.leaves[-1].type == token.COLON
and (
current_line.leaves[0].value
not in ("with", "with", "try", "match", "for", "while", "if")
)
):
# We shouldn't add two newlines between an indented function and
# and a dependent non-indented clause. This is to avoid issues with
# conditional function definitions that are technically top-level
# and therefore get two trailing newlines, but look weird and
# inconsistent when they're followed by elif, else, etc.
before = 1
else:
before = 2
self.previous_defs.pop()
if current_line.is_decorator or current_line.is_def or current_line.is_class:
return self._maybe_empty_lines_for_class_or_def(current_line, before)

Expand Down
63 changes: 63 additions & 0 deletions tests/data/function2.py
Expand Up @@ -23,6 +23,35 @@ def inner():
pass
print("Inner defs should breathe a little.")


if os.name == "posix":
import termios
def i_should_be_followed_by_only_one_newline():
pass
elif os.name == "nt":
try:
import msvcrt
def i_should_be_followed_by_only_one_newline():
pass

except ImportError:

def i_should_be_followed_by_only_one_newline():
pass

elif False:

class IHopeYouAreHavingALovelyDay:
def __call__(self):
print("i_should_be_followed_by_only_one_newline")
else:

def foo():
pass

with hmm_but_this_should_get_two_preceding_newlines():
pass

# output

def f(
Expand Down Expand Up @@ -56,3 +85,37 @@ def inner():
pass

print("Inner defs should breathe a little.")


if os.name == "posix":
import termios

def i_should_be_followed_by_only_one_newline():
pass

elif os.name == "nt":
try:
import msvcrt

def i_should_be_followed_by_only_one_newline():
pass

except ImportError:

def i_should_be_followed_by_only_one_newline():
pass

elif False:

class IHopeYouAreHavingALovelyDay:
def __call__(self):
print("i_should_be_followed_by_only_one_newline")

else:

def foo():
pass


with hmm_but_this_should_get_two_preceding_newlines():
pass

0 comments on commit c646336

Please sign in to comment.