From 0359b85b5800dd77f8f1cfaa88ca8ab8215df685 Mon Sep 17 00:00:00 2001 From: KotlinIsland <65446343+KotlinIsland@users.noreply.github.com> Date: Wed, 5 Oct 2022 06:10:11 +1000 Subject: [PATCH] Preserve crlf line endings in blackd (#3257) Co-authored-by: KotlinIsland --- CHANGES.md | 2 +- docs/the_black_code_style/current_style.md | 5 +++++ src/blackd/__init__.py | 7 +++++++ tests/test_black.py | 11 +++++++++++ tests/test_blackd.py | 17 +++++++++++++++++ 5 files changed, 41 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index d3ba53f..4ff1816 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -54,7 +54,7 @@ ### _Blackd_ - +- Windows style (CRLF) newlines will be preserved (#3257). ### Integrations diff --git a/docs/the_black_code_style/current_style.md b/docs/the_black_code_style/current_style.md index 3db49e2..59d79c4 100644 --- a/docs/the_black_code_style/current_style.md +++ b/docs/the_black_code_style/current_style.md @@ -406,6 +406,11 @@ file that are not enforced yet but might be in a future version of the formatter - use variable annotations instead of type comments, even for stubs that target older versions of Python. +### Line endings + +_Black_ will normalize line endings (`\n` or `\r\n`) based on the first line ending of +the file. + ## Pragmatism Early versions of _Black_ used to be absolutist in some respects. They took after its diff --git a/src/blackd/__init__.py b/src/blackd/__init__.py index e52a991..6bbc7c5 100644 --- a/src/blackd/__init__.py +++ b/src/blackd/__init__.py @@ -133,6 +133,13 @@ async def handle(request: web.Request, executor: Executor) -> web.Response: executor, partial(black.format_file_contents, req_str, fast=fast, mode=mode) ) + # Preserve CRLF line endings + if req_str[req_str.find("\n") - 1] == "\r": + formatted_str = formatted_str.replace("\n", "\r\n") + # If, after swapping line endings, nothing changed, then say so + if formatted_str == req_str: + raise black.NothingChanged + # Only output the diff in the HTTP response only_diff = bool(request.headers.get(DIFF_HEADER, False)) if only_diff: diff --git a/tests/test_black.py b/tests/test_black.py index abd4d00..96e6f1e 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -1286,6 +1286,17 @@ class BlackTestCase(BlackBaseTestCase): if nl == "\n": self.assertNotIn(b"\r\n", output) + def test_normalize_line_endings(self) -> None: + with TemporaryDirectory() as workspace: + test_file = Path(workspace) / "test.py" + for data, expected in ( + (b"c\r\nc\n ", b"c\r\nc\r\n"), + (b"l\nl\r\n ", b"l\nl\n"), + ): + test_file.write_bytes(data) + ff(test_file, write_back=black.WriteBack.YES) + self.assertEqual(test_file.read_bytes(), expected) + def test_assert_equivalent_different_asts(self) -> None: with self.assertRaises(AssertionError): black.assert_equivalent("{}", "None") diff --git a/tests/test_blackd.py b/tests/test_blackd.py index 511bd86..db9a165 100644 --- a/tests/test_blackd.py +++ b/tests/test_blackd.py @@ -209,3 +209,20 @@ class BlackDTestCase(AioHTTPTestCase): # type: ignore[misc] response = await self.client.post("/", headers={"Origin": "*"}) self.assertIsNotNone(response.headers.get("Access-Control-Allow-Origin")) self.assertIsNotNone(response.headers.get("Access-Control-Expose-Headers")) + + @unittest_run_loop + async def test_preserves_line_endings(self) -> None: + for data in (b"c\r\nc\r\n", b"l\nl\n"): + # test preserved newlines when reformatted + response = await self.client.post("/", data=data + b" ") + self.assertEqual(await response.text(), data.decode()) + # test 204 when no change + response = await self.client.post("/", data=data) + self.assertEqual(response.status, 204) + + @unittest_run_loop + async def test_normalizes_line_endings(self) -> None: + for data, expected in ((b"c\r\nc\n", "c\r\nc\r\n"), (b"l\nl\r\n", "l\nl\n")): + response = await self.client.post("/", data=data) + self.assertEqual(await response.text(), expected) + self.assertEqual(response.status, 200) -- 2.39.2