Handle Docstrings as bytes + strip all whitespace (#2037)

(fixes #1844, fixes #1923, fixes #1851, fixes #2002, fixes #2103)
This commit is contained in:
CiderMan 2021-04-22 16:40:51 +01:00 committed by GitHub
parent 1fc3215e8c
commit 5316bbff0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 35 additions and 2 deletions

View File

@ -52,6 +52,9 @@
- Exclude `venv` directory by default (#1683)
- Fixed "Black produced code that is not equivalent to the source" when formatting
Python 2 docstrings (#2037)
#### _Packaging_
- Self-contained native _Black_ binaries are now provided for releases via GitHub

View File

@ -6466,12 +6466,22 @@ def _stringify_ast(
# Constant strings may be indented across newlines, if they are
# docstrings; fold spaces after newlines when comparing. Similarly,
# trailing and leading space may be removed.
# Note that when formatting Python 2 code, at least with Windows
# line-endings, docstrings can end up here as bytes instead of
# str so make sure that we handle both cases.
if (
isinstance(node, ast.Constant)
and field == "value"
and isinstance(value, str)
and isinstance(value, (str, bytes))
):
normalized = re.sub(r" *\n[ \t]*", "\n", value).strip()
lineend = "\n" if isinstance(value, str) else b"\n"
# To normalize, we strip any leading and trailing space from
# each line...
stripped = [line.strip() for line in value.splitlines()]
normalized = lineend.join(stripped) # type: ignore[attr-defined]
# ...and remove any blank lines at the beginning and end of
# the whole string
normalized = normalized.strip()
else:
normalized = value
yield f"{' ' * (depth+2)}{normalized!r}, # {value.__class__.__name__}"

View File

@ -1918,6 +1918,26 @@ def test_bpo_2142_workaround(self) -> None:
actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
self.assertEqual(actual, expected)
def test_docstring_reformat_for_py27(self) -> None:
"""
Check that stripping trailing whitespace from Python 2 docstrings
doesn't trigger a "not equivalent to source" error
"""
source = (
b'def foo():\r\n """Testing\r\n Testing """\r\n print "Foo"\r\n'
)
expected = 'def foo():\n """Testing\n Testing"""\n print "Foo"\n'
result = CliRunner().invoke(
black.main,
["-", "-q", "--target-version=py27"],
input=BytesIO(source),
)
self.assertEqual(result.exit_code, 0)
actual = result.output
self.assertFormatEqual(actual, expected)
with open(black.__file__, "r", encoding="utf-8") as _bf:
black_source_lines = _bf.readlines()