From 32a6bff3756fd104a36c16bb365eb6baa49850ae Mon Sep 17 00:00:00 2001 From: Antti Kaihola Date: Fri, 10 Sep 2021 13:53:50 +0300 Subject: [PATCH] Empty and all-whitespace files become 1 empty line Fixes #2382 --- src/black/__init__.py | 10 +++++---- tests/data/whitespace.py | 6 ++++++ tests/test_black.py | 45 ++++++++++++++++++++++++++++++++-------- tests/test_format.py | 1 + 4 files changed, 49 insertions(+), 13 deletions(-) create mode 100644 tests/data/whitespace.py diff --git a/src/black/__init__.py b/src/black/__init__.py index d033e01141a..766d230fd94 100644 --- a/src/black/__init__.py +++ b/src/black/__init__.py @@ -844,7 +844,7 @@ def format_stdin_to_stdout( ) if write_back == WriteBack.YES: # Make sure there's a newline after the content - if dst and dst[-1] != "\n": + if not dst or dst[-1] != "\n": dst += "\n" f.write(dst) elif write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF): @@ -889,9 +889,6 @@ def format_file_contents(src_contents: str, *, fast: bool, mode: Mode) -> FileCo valid by calling :func:`assert_equivalent` and :func:`assert_stable` on it. `mode` is passed to :func:`format_str`. """ - if not src_contents.strip(): - raise NothingChanged - if mode.is_ipynb: dst_contents = format_ipynb_string(src_contents, fast=fast, mode=mode) else: @@ -979,6 +976,8 @@ def format_ipynb_string(src_contents: str, *, fast: bool, mode: Mode) -> FileCon Operate cell-by-cell, only on code cells, only for Python notebooks. If the ``.ipynb`` originally had a trailing newline, it'll be preserved. """ + if not src_contents: + return "" trailing_newline = src_contents[-1] == "\n" modified = False nb = json.loads(src_contents) @@ -1061,6 +1060,9 @@ def f( current_line, mode=mode, features=split_line_features ): dst_contents.append(str(line)) + if not dst_contents: + _, _, newline = decode_bytes(src_contents.encode("utf-8")) + return newline return "".join(dst_contents) diff --git a/tests/data/whitespace.py b/tests/data/whitespace.py new file mode 100644 index 00000000000..a319c0117b1 --- /dev/null +++ b/tests/data/whitespace.py @@ -0,0 +1,6 @@ + + + + + +# output diff --git a/tests/test_black.py b/tests/test_black.py index 398a528bee9..727dfd1d7b8 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -137,19 +137,39 @@ def invokeBlack( @patch("black.dump_to_file", dump_to_stderr) def test_empty(self) -> None: - source = expected = "" + source = "" + expected = "\n" actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) black.assert_stable(source, actual, DEFAULT_MODE) def test_empty_ff(self) -> None: - expected = "" + expected = "\n" tmp_file = Path(black.dump_to_file()) + try: + self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES)) + with open(tmp_file, "rb") as f: + actual = f.read().decode("utf8") + finally: + os.unlink(tmp_file) + self.assertFormatEqual(expected, actual) + + @patch("black.dump_to_file", dump_to_stderr) + def test_one_empty_line(self) -> None: + source = expected = os.linesep + actual = fs(source) + self.assertFormatEqual(expected, actual) + black.assert_equivalent(source, actual) + black.assert_stable(source, actual, DEFAULT_MODE) + + def test_one_empty_line_ff(self) -> None: + expected = os.linesep + tmp_file = Path(black.dump_to_file("\n")) try: self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES)) - with open(tmp_file, encoding="utf8") as f: - actual = f.read() + with open(tmp_file, "rb") as f: + actual = f.read().decode("utf8") finally: os.unlink(tmp_file) self.assertFormatEqual(expected, actual) @@ -990,11 +1010,17 @@ def err(msg: str, **kwargs: Any) -> None: def test_format_file_contents(self) -> None: empty = "" mode = DEFAULT_MODE - with self.assertRaises(black.NothingChanged): - black.format_file_contents(empty, mode=mode, fast=False) + actual = black.format_file_contents(empty, mode=mode, fast=False) + self.assertEqual("\n", actual) just_nl = "\n" with self.assertRaises(black.NothingChanged): black.format_file_contents(just_nl, mode=mode, fast=False) + just_whitespace_unix = "\n\t\n \n\t \n \t\n\n" + actual = black.format_file_contents(just_whitespace_unix, mode=mode, fast=False) + self.assertEqual("\n", actual) + just_whitespace_win = "\r\n\t\r\n \r\n\t \r\n \t\r\n\r\n" + actual = black.format_file_contents(just_whitespace_win, mode=mode, fast=False) + self.assertEqual("\r\n", actual) same = "j = [1, 2, 3]\n" with self.assertRaises(black.NothingChanged): black.format_file_contents(same, mode=mode, fast=False) @@ -1687,19 +1713,20 @@ def test_reformat_one_with_stdin_and_existing_path(self) -> None: # __BLACK_STDIN_FILENAME__ should have been stripped report.done.assert_called_with(expected, black.Changed.YES) - def test_reformat_one_with_stdin_empty(self) -> None: + @parameterized.expand([("", "\n"), (os.linesep, os.linesep)]) + def test_reformat_one_with_stdin_empty(self, content, expected) -> None: output = io.StringIO() with patch("io.TextIOWrapper", lambda *args, **kwargs: output): try: black.format_stdin_to_stdout( fast=True, - content="", + content=content, write_back=black.WriteBack.YES, mode=DEFAULT_MODE, ) except io.UnsupportedOperation: pass # StringIO does not support detach - assert output.getvalue() == "" + assert output.getvalue() == expected def test_gitignore_exclude(self) -> None: path = THIS_DIR / "data" / "include_exclude_tests" diff --git a/tests/test_format.py b/tests/test_format.py index fc9678ad27c..27e137624a1 100644 --- a/tests/test_format.py +++ b/tests/test_format.py @@ -52,6 +52,7 @@ "string_prefixes", "tricky_unicode_symbols", "tupleassign", + "whitespace", ] SIMPLE_CASES_PY2 = [