Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove whitespaces of whitespace-only files #3348

Merged
merged 7 commits into from Nov 12, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.md
Expand Up @@ -29,6 +29,8 @@
- Parsing support has been added for walruses inside generator expression that are
passed as function args (for example,
`any(match := my_re.match(text) for text in texts)`) (#3327).
- Reformat empty and whitespace-only files as either an empty file (if no newline is
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to go into the preview style (not just in the changelog, but also in the code).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be added in the future style page too?

present) or as a single newline character (if a newline is present) (#3348)

### Performance

Expand Down
11 changes: 8 additions & 3 deletions src/black/__init__.py
Expand Up @@ -914,9 +914,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:
Expand Down Expand Up @@ -1011,6 +1008,9 @@ 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:
raise NothingChanged

trailing_newline = src_contents[-1] == "\n"
modified = False
nb = json.loads(src_contents)
Expand Down Expand Up @@ -1100,6 +1100,11 @@ def _format_str_once(src_contents: str, *, mode: Mode) -> str:
current_line, mode=mode, features=split_line_features
):
dst_contents.append(str(line))
if not dst_contents:
normalized_content, _, newline = decode_bytes(src_contents.encode("utf-8"))
aaossa marked this conversation as resolved.
Show resolved Hide resolved
if "\n" in normalized_content:
return newline
return ""
return "".join(dst_contents)


Expand Down
6 changes: 6 additions & 0 deletions tests/data/simple_cases/whitespace.py
@@ -0,0 +1,6 @@





# output
88 changes: 76 additions & 12 deletions tests/test_black.py
Expand Up @@ -25,6 +25,7 @@
List,
Optional,
Sequence,
Type,
TypeVar,
Union,
)
Expand Down Expand Up @@ -153,6 +154,33 @@ def test_empty_ff(self) -> None:
os.unlink(tmp_file)
self.assertFormatEqual(expected, actual)

@patch("black.dump_to_file", dump_to_stderr)
def test_one_empty_line(self) -> None:
for nl in ["\n", "\r\n"]:
source = expected = nl
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:
for nl in ["\n", "\r\n"]:
expected = nl
tmp_file = Path(black.dump_to_file(nl))
if system() == "Windows":
# Writing files in text mode automatically uses the system newline,
# but in this case we don't want this for testing reasons. See:
# https://github.com/psf/black/pull/3348
with open(tmp_file, "wb") as f:
f.write(nl.encode("utf-8"))
try:
self.assertFalse(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)

def test_experimental_string_processing_warns(self) -> None:
self.assertWarns(
black.mode.Deprecated, black.Mode, experimental_string_processing=True
Expand Down Expand Up @@ -936,6 +964,15 @@ def test_format_file_contents(self) -> None:
just_nl = "\n"
with self.assertRaises(black.NothingChanged):
black.format_file_contents(just_nl, mode=mode, fast=False)
just_crlf = "\r\n"
with self.assertRaises(black.NothingChanged):
black.format_file_contents(just_crlf, mode=mode, fast=False)
just_whitespace_nl = "\n\t\n \n\t \n \t\n\n"
actual = black.format_file_contents(just_whitespace_nl, mode=mode, fast=False)
self.assertEqual("\n", actual)
just_whitespace_crlf = "\r\n\t\r\n \r\n\t \r\n \t\r\n\r\n"
actual = black.format_file_contents(just_whitespace_crlf, 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)
Expand Down Expand Up @@ -1239,18 +1276,45 @@ def test_reformat_one_with_stdin_and_existing_path(self) -> None:
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() == ""
cases = [
("", ""),
("\n", "\n"),
("\r\n", "\r\n"),
(" \t", ""),
(" \t\n\t ", "\n"),
(" \t\r\n\t ", "\r\n"),
]

def _new_wrapper(
output: io.StringIO, io_TextIOWrapper: Type[io.TextIOWrapper]
) -> Callable[[Any, Any], io.TextIOWrapper]:
def get_output(*args: Any, **kwargs: Any) -> io.TextIOWrapper:
if args == (sys.stdout.buffer,):
# It's `format_stdin_to_stdout()` calling `io.TextIOWrapper()`,
# return our mock object.
return output
# It's something else (i.e. `decode_bytes()`) calling
# `io.TextIOWrapper()`, pass through to the original implementation.
# See discussion in https://github.com/psf/black/pull/2489
return io_TextIOWrapper(*args, **kwargs)

return get_output

for content, expected in cases:
output = io.StringIO()
io_TextIOWrapper = io.TextIOWrapper

with patch("io.TextIOWrapper", _new_wrapper(output, io_TextIOWrapper)):
try:
black.format_stdin_to_stdout(
fast=True,
content=content,
write_back=black.WriteBack.YES,
mode=DEFAULT_MODE,
)
except io.UnsupportedOperation:
pass # StringIO does not support detach
assert output.getvalue() == expected

def test_invalid_cli_regex(self) -> None:
for option in ["--include", "--exclude", "--extend-exclude", "--force-exclude"]:
Expand Down