Repository: psf/black
Pull request: Fix unstable format involving backslash + whitespace at beginning of file
This PR fixes #922 - an unstable sort involve Python line continuation with `\`.  The issue describes the problem pretty well.

To my knowledge, there should be no situation where Black outputs a file formatted with empty lines at the beginning.  By performing this check in the format function, we fix the above issue.

NOTE: Open to feedback on this solution, threw it together pretty quickly after doing some basic debugging of how Black parses lines and whatnot.  Open to feedback on it.  Also, I'm pretty sure it doesn't break any invariants of the formatter, but would like feedback on that as well. 

Problem statement:
Black produced different code on the second pass of the formatter: Explicit line join in the first line
Operating system: macOS Mojave 10.14.5 (18F132)
Python version: Python 3.7.3
*Black* version: 19.3b0 (f3bb22a828)
Does also happen on master: yes

Minified reproducer:
```python
\


pass
```

Black diff:
```diff
--- source
+++ first pass
@@ -1,5 +1,3 @@
-\
-

 pass

--- first pass
+++ second pass
@@ -1,3 +1,2 @@
-
 pass
```

Looks like some kind of boundary condition in `EmptyLineTracker`?

Hints from issue discussion:

Commit message: Add newline to end of test data file

Patch to review:
diff --git a/black.py b/black.py
index 910a0ed1e37..c3e449aa96d 100644
--- a/black.py
+++ b/black.py
@@ -736,8 +736,11 @@ def format_str(src_contents: str, *, mode: FileMode) -> FileContent:
         for _ in range(after):
             dst_contents.append(str(empty_line))
         before, after = elt.maybe_empty_lines(current_line)
-        for _ in range(before):
-            dst_contents.append(str(empty_line))
+        # Black should not insert empty lines at the beginning
+        # of the file
+        if len(dst_contents) > 0:
+            for _ in range(before):
+                dst_contents.append(str(empty_line))
         for line in split_line(
             current_line, line_length=mode.line_length, features=split_line_features
         ):
diff --git a/tests/data/beginning_backslash.py b/tests/data/beginning_backslash.py
new file mode 100644
index 00000000000..66c347d99df
--- /dev/null
+++ b/tests/data/beginning_backslash.py
@@ -0,0 +1,12 @@
+\
+
+
+
+
+
+print("hello, world")
+
+# output
+
+
+print("hello, world")
diff --git a/tests/test_black.py b/tests/test_black.py
index 7b3a8b66e2b..015243f7c13 100644
--- a/tests/test_black.py
+++ b/tests/test_black.py
@@ -639,6 +639,14 @@ def test_tuple_assign(self) -> None:
         black.assert_equivalent(source, actual)
         black.assert_stable(source, actual, black.FileMode())
 
+    @patch("black.dump_to_file", dump_to_stderr)
+    def test_beginning_backslash(self) -> None:
+        source, expected = read_data("beginning_backslash")
+        actual = fs(source)
+        self.assertFormatEqual(expected, actual)
+        black.assert_equivalent(source, actual)
+        black.assert_stable(source, actual, black.FileMode())
+
     def test_tab_comment_indentation(self) -> None:
         contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
         contents_spc = "if 1:\n    if 2:\n        pass\n    # comment\n    pass\n"


Code review comments:
diff_hunk: @@ -736,8 +736,11 @@ def format_str(src_contents: str, *, mode: FileMode) -> FileContent:
         for _ in range(after):
             dst_contents.append(str(empty_line))
         before, after = elt.maybe_empty_lines(current_line)
-        for _ in range(before):
-            dst_contents.append(str(empty_line))
+        # Black should not insert empty lines at the beginning
+        # of the file
+        if len(dst_contents) > 0:
path: black.py
text: @user1:
I think this logic would be better encapsulated inside `EmptyLineTracker`. 

@author:
Will refactor so I can move it!