Repository: psf/black

Issue:
black removes empty line between imports and overloads in stubs
Please consider the follow **stub** file (e.g. `foo.pyi`):

```python
import y
  
@overload
def foo(): ...
```

Running `black foo.pyi` (no configuration file) removes the empty line 2.

- Version: 19.10b0
- OS and Python version: Linux/Python 3.8.3

**Does this bug also happen on master?**

Using the online formatter: yes.



Discussion:

Resolving patch:
diff --git a/src/black/__init__.py b/src/black/__init__.py
--- a/src/black/__init__.py
+++ b/src/black/__init__.py
@@ -1824,6 +1824,10 @@ def _maybe_empty_lines_for_class_or_def(
             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 (
@@ -1847,8 +1851,11 @@ def _maybe_empty_lines_for_class_or_def(
                     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


Test patch:
diff --git a/tests/data/force_pyi.py b/tests/data/force_pyi.py
--- a/tests/data/force_pyi.py
+++ b/tests/data/force_pyi.py
@@ -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]: ...
diff --git a/tests/test_black.py b/tests/test_black.py
--- a/tests/test_black.py
+++ b/tests/test_black.py
@@ -1527,7 +1527,6 @@ def test_tricky_unicode_symbols(self) -> None:
         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:
@@ -1540,9 +1539,11 @@ def test_single_file_force_pyi(self) -> None:
             # 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: