Repository: psf/black

Issue:
Zero length stdin input causes black to fail (regression)
**Describe the bug**

Using latest version 21.6b0 piping an empty file into black as below causes "string index out of range".  This is a regression from 21.5b2.  

NOTE: referencing an empty file works as expected this is only when piping into - as example below.
i.e. this works:
```
touch __init__.py
python -m black __init__.py
All done! ✨ 🍰 ✨
1 file left unchanged.
```

**To Reproduce**

```
touch __init__.py
cat __init__.py | python -m black -
error: cannot format -: string index out of range
Oh no! 💥 💔 💥
1 file failed to reformat.
```

**Expected behavior**

Using stdin with empty file works as previous version without error.

<!-- A clear and concise description of what you expected to happen. -->

**Environment (please complete the following information):**

- Version: <!-- e.g. [main] -->
- OS and Python version: <!-- e.g. [Linux/Python 3.7.4rc1] -->

**Does this bug also happen on main?**

Yes
<!-- To answer this, you have two options:

1. Use the online formatter at <https://black.vercel.app/?version=main>, which will use
   the latest main branch.
1. Or run _Black_ on your machine:
   - create a new virtualenv (make sure it's the same Python version);
   - clone this repository;
   - run `pip install -e .[d,python2]`;
   - run `pip install -r test_requirements.txt`
   - make sure it's sane by running `python -m pytest`; and
   - run `black` like you did last time. -->

**Additional context**

A bit of a tails case but we're using this approach within a CI script.

<!-- Add any other context about the problem here. -->

Zero length stdin input causes black to fail (regression)
**Describe the bug**

Using latest version 21.6b0 piping an empty file into black as below causes "string index out of range".  This is a regression from 21.5b2.  

NOTE: referencing an empty file works as expected this is only when piping into - as example below.
i.e. this works:
```
touch __init__.py
python -m black __init__.py
All done! ✨ 🍰 ✨
1 file left unchanged.
```

**To Reproduce**

```
touch __init__.py
cat __init__.py | python -m black -
error: cannot format -: string index out of range
Oh no! 💥 💔 💥
1 file failed to reformat.
```

**Expected behavior**

Using stdin with empty file works as previous version without error.

<!-- A clear and concise description of what you expected to happen. -->

**Environment (please complete the following information):**

- Version: <!-- e.g. [main] -->
- OS and Python version: <!-- e.g. [Linux/Python 3.7.4rc1] -->

**Does this bug also happen on main?**

Yes
<!-- To answer this, you have two options:

1. Use the online formatter at <https://black.vercel.app/?version=main>, which will use
   the latest main branch.
1. Or run _Black_ on your machine:
   - create a new virtualenv (make sure it's the same Python version);
   - clone this repository;
   - run `pip install -e .[d,python2]`;
   - run `pip install -r test_requirements.txt`
   - make sure it's sane by running `python -m pytest`; and
   - run `black` like you did last time. -->

**Additional context**

A bit of a tails case but we're using this approach within a CI script.

<!-- Add any other context about the problem here. -->



Discussion:
I feel the opposite here and that we were broken before and this is now fixed. We should error when nothing is piped in. It will allow people to optimize their calls sending empty strings and save those resources from starting black for no reason.

I feel your CI script should check if the file is empty before trying to pipe it into black.

Edit: That said, I can see expectations here due to other Unix CLI tool behavior tho. So we should do what majority thinks is right here.
- maybe we only care with —check
That's a fair point, but not immediately obvious at least to me. The error happens when manually inputting an empty stream as well.

At the very least, I think "string index out of range" is not the error we want to be throwing. If we want to restrict to nonempty inputs, maybe it should read "cannot format empty stream" or something like that. Thoughts?
I think we should allow this, since Unix tools generally accept empty input (e.g. cat, grep, awk).
Bug here - https://github.com/psf/black/blob/main/src/black/__init__.py#L806
May I work on this?
Please do!
I feel the opposite here and that we were broken before and this is now fixed. We should error when nothing is piped in. It will allow people to optimize their calls sending empty strings and save those resources from starting black for no reason.

I feel your CI script should check if the file is empty before trying to pipe it into black.

Edit: That said, I can see expectations here due to other Unix CLI tool behavior tho. So we should do what majority thinks is right here.
- maybe we only care with —check
That's a fair point, but not immediately obvious at least to me. The error happens when manually inputting an empty stream as well.

At the very least, I think "string index out of range" is not the error we want to be throwing. If we want to restrict to nonempty inputs, maybe it should read "cannot format empty stream" or something like that. Thoughts?
I think we should allow this, since Unix tools generally accept empty input (e.g. cat, grep, awk).
Bug here - https://github.com/psf/black/blob/main/src/black/__init__.py#L806
May I work on this?
Please do!

Resolving patch:
diff --git a/src/black/__init__.py b/src/black/__init__.py
--- a/src/black/__init__.py
+++ b/src/black/__init__.py
@@ -803,7 +803,8 @@ def format_stdin_to_stdout(
         )
         if write_back == WriteBack.YES:
             # Make sure there's a newline after the content
-            dst += "" if dst[-1] == "\n" else "\n"
+            if dst and dst[-1] != "\n":
+                dst += "\n"
             f.write(dst)
         elif write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
             now = datetime.utcnow()


Test patch:
diff --git a/tests/test_black.py b/tests/test_black.py
--- a/tests/test_black.py
+++ b/tests/test_black.py
@@ -6,6 +6,7 @@
 from contextlib import contextmanager
 from dataclasses import replace
 import inspect
+import io
 from io import BytesIO
 import os
 from pathlib import Path
@@ -1679,6 +1680,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:
+        output = io.StringIO()
+        with patch("io.TextIOWrapper", lambda *args, **kwargs: output):
+            try:
+                black.format_stdin_to_stdout(
+                    fast=True,
+                    content="",
+                    write_back=black.WriteBack.YES,
+                    mode=DEFAULT_MODE,
+                )
+            except io.UnsupportedOperation:
+                pass  # StringIO does not support detach
+            assert output.getvalue() == ""
+
     def test_gitignore_exclude(self) -> None:
         path = THIS_DIR / "data" / "include_exclude_tests"
         include = re.compile(r"\.pyi?$")