]> git.madduck.net Git - etc/vim.git/commitdiff

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Fix empty line handling when formatting typing stubs (#1646)
authorRichard Si <63936253+ichard26@users.noreply.github.com>
Thu, 10 Sep 2020 20:21:37 +0000 (16:21 -0400)
committerGitHub <noreply@github.com>
Thu, 10 Sep 2020 20:21:37 +0000 (13:21 -0700)
Black used to erroneously remove all empty lines between non-function
code and decorators when formatting typing stubs. Now a single empty
line is enforced.

I chose for putting empty lines around decorated classes that have empty
bodies since removing empty lines around such classes would cause a
formatting issue that seems to be impossible to fix.

For example:

```
class A: ...
@some_decorator
class B: ...
class C: ...
class D: ...

@some_other_decorator
def foo(): -> None: ...
```

It is easy to enforce no empty lines between class A, B, and C.
Just return 0, 0 for a line that is a decorator and precedes an stub
class. Fortunately before this commit, empty lines after that class
would be removed already.

Now let's look at the empty line between class D and function foo. In
this case, there should be an empty line there since it's class code next
to function code. The problem is that when deciding to add X empty lines
before a decorator, you can't tell whether it's before a class or a
function. If the decorator is before a function, then an empty line
is needed, while no empty lines are needed when the decorator is
before a class.

So even though I personally prefer no empty lines around decorated
classes, I had to go the other way surrounding decorated classes with
empty lines.

Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
CHANGES.md
docs/change_log.md
src/black/__init__.py
tests/data/force_pyi.py
tests/test_black.py

index 52c8016a257a3341e4014db194fa5c83edb722e2..59d9320639be70d3eca9b7062d82aacd260809e5 100644 (file)
@@ -7,6 +7,9 @@
 - `Black` now respects `--skip-string-normalization` when normalizing multiline
   docstring quotes (#1637)
 
+- `Black` no longer removes all empty lines between non-function code and decorators
+  when formatting typing stubs. Now `Black` enforces a single empty line. (#1646)
+
 - `Black` no longer adds an incorrect space after a parenthesized assignment expression
   in if/while statements (#1655)
 
index cc5015f873c26d3417b4c99afc0909808caf0969..e183ca545b6e60dc1d474a9bec34c1a38b8ef863 100644 (file)
@@ -9,6 +9,9 @@
 - `Black` now respects `--skip-string-normalization` when normalizing multiline
   docstring quotes (#1637)
 
+- `Black` no longer removes all empty lines between non-function code and decorators
+  when formatting typing stubs. Now `Black` enforces a single empty line. (#1646)
+
 - `Black` no longer adds an incorrect space after a parenthesized assignment expression
   in if/while statements (#1655)
 
index bfb77126218c303426dccf1bf8c2200cb97bbd3b..9e18a7d904b3c3f11a7c15eef7f3e160029e3cb0 100644 (file)
@@ -1834,6 +1834,10 @@ class EmptyLineTracker:
             return 0, 0
 
         if self.previous_line.is_decorator:
+            if self.is_pyi and current_line.is_stub_class:
+                # Insert an empty line after a decorated stub class
+                return 0, 1
+
             return 0, 0
 
         if self.previous_line.depth < current_line.depth and (
@@ -1857,8 +1861,11 @@ class EmptyLineTracker:
                     newlines = 0
                 else:
                     newlines = 1
-            elif current_line.is_def and not self.previous_line.is_def:
-                # Blank line between a block of functions and a block of non-functions
+            elif (
+                current_line.is_def or current_line.is_decorator
+            ) and not self.previous_line.is_def:
+                # Blank line between a block of functions (maybe with preceding
+                # decorators) and a block of non-functions
                 newlines = 1
             else:
                 newlines = 0
index 25246c22ca78eeb1dc86e074daac07f793eb128d..07ed93c6879363f22cde9de9e3c3c393aa2d41a1 100644 (file)
@@ -1,6 +1,65 @@
-def f(): ...
+from typing import Union
+
+@bird
+def zoo(): ...
+
+class A: ...
+@bar
+class B:
+    def BMethod(self) -> None: ...
+    @overload
+    def BMethod(self, arg : List[str]) -> None: ...
+
+class C: ...
+@hmm
+class D: ...
+class E: ...
+
+@baz
+def foo() -> None:
+    ...
+
+class F (A , C): ...
+def spam() -> None: ...
+
+@overload
+def spam(arg: str) -> str: ...
+
+var  : int = 1
+
+def eggs() -> Union[str, int]: ...
 
-def g(): ...
 # output
-def f(): ...
-def g(): ...
+
+from typing import Union
+
+@bird
+def zoo(): ...
+
+class A: ...
+
+@bar
+class B:
+    def BMethod(self) -> None: ...
+    @overload
+    def BMethod(self, arg: List[str]) -> None: ...
+
+class C: ...
+
+@hmm
+class D: ...
+
+class E: ...
+
+@baz
+def foo() -> None: ...
+
+class F(A, C): ...
+
+def spam() -> None: ...
+@overload
+def spam(arg: str) -> str: ...
+
+var: int = 1
+
+def eggs() -> Union[str, int]: ...
index edcf7208b46aa685166dad061640cb8531c5717e..e928dc0498457259d88c00429b5b085cb1a718fc 100644 (file)
@@ -1574,7 +1574,6 @@ class BlackTestCase(unittest.TestCase):
         black.assert_stable(source, actual, DEFAULT_MODE)
 
     def test_single_file_force_pyi(self) -> None:
-        reg_mode = DEFAULT_MODE
         pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
         contents, expected = read_data("force_pyi")
         with cache_dir() as workspace:
@@ -1587,9 +1586,11 @@ class BlackTestCase(unittest.TestCase):
             # verify cache with --pyi is separate
             pyi_cache = black.read_cache(pyi_mode)
             self.assertIn(path, pyi_cache)
-            normal_cache = black.read_cache(reg_mode)
+            normal_cache = black.read_cache(DEFAULT_MODE)
             self.assertNotIn(path, normal_cache)
-        self.assertEqual(actual, expected)
+        self.assertFormatEqual(expected, actual)
+        black.assert_equivalent(contents, actual)
+        black.assert_stable(contents, actual, pyi_mode)
 
     @event_loop()
     def test_multi_file_force_pyi(self) -> None: