From 679c5c712767563c2107787cc9eb3ad924cbb358 Mon Sep 17 00:00:00 2001 From: "Paul \"TBBle\" Hampson" Date: Sat, 2 Jan 2021 04:19:06 +1100 Subject: [PATCH] Indicate that a final newline was added in --diff (#1897) Fixes: #1662 Work-around for https://bugs.python.org/issue2142 The test has to slightly mess with its input data, because the utility functions default to ensuring the test data has a final newline, which defeats the point of the test. Signed-off-by: Paul "TBBle" Hampson --- CHANGES.md | 2 ++ docs/authors.md | 1 + src/black/__init__.py | 23 ++++++++++++++++------- tests/data/missing_final_newline.diff | 8 ++++++++ tests/data/missing_final_newline.py | 3 +++ tests/test_black.py | 23 ++++++++++++++++++++++- 6 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 tests/data/missing_final_newline.diff create mode 100644 tests/data/missing_final_newline.py diff --git a/CHANGES.md b/CHANGES.md index 5f7ca4f4da7..3fd7ad40496 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -28,6 +28,8 @@ - speed up caching by avoiding pathlib (#1950) +- `--diff` correctly indicates when a file doesn't end in a newline (#1662) + #### _Packaging_ - Self-contained native _Black_ binaries are now provided for releases via GitHub diff --git a/docs/authors.md b/docs/authors.md index cdf5046c446..9f2ea0571b9 100644 --- a/docs/authors.md +++ b/docs/authors.md @@ -132,6 +132,7 @@ Multiple contributions by: - [Pablo Galindo](mailto:Pablogsal@gmail.com) - [Paul Ganssle](mailto:p.ganssle@gmail.com) - [Paul Meinhardt](mailto:mnhrdt@gmail.com) +- [Paul "TBBle" Hampson](mailto:Paul.Hampson@Pobox.com) - [Peter Bengtsson](mailto:mail@peterbe.com) - [Peter Grayson](mailto:pete@jpgrayson.net) - [Peter Stensmyr](mailto:peter.stensmyr@gmail.com) diff --git a/src/black/__init__.py b/src/black/__init__.py index 08930d11cea..7440c59e580 100644 --- a/src/black/__init__.py +++ b/src/black/__init__.py @@ -6423,14 +6423,14 @@ def assert_stable(src: str, dst: str, mode: Mode) -> None: @mypyc_attr(patchable=True) -def dump_to_file(*output: str) -> str: +def dump_to_file(*output: str, ensure_final_newline: bool = True) -> str: """Dump `output` to a temporary file. Return path to the file.""" with tempfile.NamedTemporaryFile( mode="w", prefix="blk_", suffix=".log", delete=False, encoding="utf8" ) as f: for lines in output: f.write(lines) - if lines and lines[-1] != "\n": + if ensure_final_newline and lines and lines[-1] != "\n": f.write("\n") return f.name @@ -6448,11 +6448,20 @@ def diff(a: str, b: str, a_name: str, b_name: str) -> str: """Return a unified diff string between strings `a` and `b`.""" import difflib - a_lines = [line + "\n" for line in a.splitlines()] - b_lines = [line + "\n" for line in b.splitlines()] - return "".join( - difflib.unified_diff(a_lines, b_lines, fromfile=a_name, tofile=b_name, n=5) - ) + a_lines = [line for line in a.splitlines(keepends=True)] + b_lines = [line for line in b.splitlines(keepends=True)] + diff_lines = [] + for line in difflib.unified_diff( + a_lines, b_lines, fromfile=a_name, tofile=b_name, n=5 + ): + # Work around https://bugs.python.org/issue2142 + # See https://www.gnu.org/software/diffutils/manual/html_node/Incomplete-Lines.html + if line[-1] == "\n": + diff_lines.append(line) + else: + diff_lines.append(line + "\n") + diff_lines.append("\\ No newline at end of file\n") + return "".join(diff_lines) def cancel(tasks: Iterable["asyncio.Task[Any]"]) -> None: diff --git a/tests/data/missing_final_newline.diff b/tests/data/missing_final_newline.diff new file mode 100644 index 00000000000..6d991c74f8f --- /dev/null +++ b/tests/data/missing_final_newline.diff @@ -0,0 +1,8 @@ +--- [Deterministic header] ++++ [Deterministic header] +@@ -1,3 +1,3 @@ + # A comment-only file, with no final EOL character + # This triggers https://bugs.python.org/issue2142 +-# This is the line without the EOL character +\ No newline at end of file ++# This is the line without the EOL character diff --git a/tests/data/missing_final_newline.py b/tests/data/missing_final_newline.py new file mode 100644 index 00000000000..687e1367552 --- /dev/null +++ b/tests/data/missing_final_newline.py @@ -0,0 +1,3 @@ +# A comment-only file, with no final EOL character +# This triggers https://bugs.python.org/issue2142 +# This is the line without the EOL character \ No newline at end of file diff --git a/tests/test_black.py b/tests/test_black.py index a2efef82422..5d14ceda8f4 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -300,7 +300,6 @@ def test_expression_diff(self) -> None: os.unlink(tmp_file) actual = result.output actual = diff_header.sub(DETERMINISTIC_HEADER, actual) - actual = actual.rstrip() + "\n" # the diff output has a trailing space if expected != actual: dump = black.dump_to_file(actual) msg = ( @@ -1798,6 +1797,28 @@ def test_newline_comment_interaction(self) -> None: output = black.format_str(source, mode=DEFAULT_MODE) black.assert_stable(source, output, mode=DEFAULT_MODE) + def test_bpo_2142_workaround(self) -> None: + + # https://bugs.python.org/issue2142 + + source, _ = read_data("missing_final_newline.py") + # read_data adds a trailing newline + source = source.rstrip() + expected, _ = read_data("missing_final_newline.diff") + tmp_file = Path(black.dump_to_file(source, ensure_final_newline=False)) + diff_header = re.compile( + rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d " + r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d" + ) + try: + result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)]) + self.assertEqual(result.exit_code, 0) + finally: + os.unlink(tmp_file) + actual = result.output + actual = diff_header.sub(DETERMINISTIC_HEADER, actual) + self.assertEqual(actual, expected) + with open(black.__file__, "r", encoding="utf-8") as _bf: black_source_lines = _bf.readlines()