Skip to content

Commit

Permalink
properly run ourselves twice (#2807)
Browse files Browse the repository at this point in the history
The previous run-twice logic only affected the stability checks but not the output. Now, we actually output the twice-formatted code.
  • Loading branch information
JelleZijlstra committed Jan 25, 2022
1 parent 6417c99 commit 32dd9ec
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 54 deletions.
2 changes: 2 additions & 0 deletions CHANGES.md
Expand Up @@ -40,6 +40,8 @@
- Enable Python 3.10+ by default, without any extra need to specify
`--target-version=py310`. (#2758)
- Make passing `SRC` or `--code` mandatory and mutually exclusive (#2804)
- Work around bug that causes unstable formatting in some cases in the presence of the
magic trailing comma (#2807)

### Packaging

Expand Down
29 changes: 16 additions & 13 deletions src/black/__init__.py
Expand Up @@ -968,17 +968,7 @@ def check_stability_and_equivalence(
content differently.
"""
assert_equivalent(src_contents, dst_contents)

# Forced second pass to work around optional trailing commas (becoming
# forced trailing commas on pass 2) interacting differently with optional
# parentheses. Admittedly ugly.
dst_contents_pass2 = format_str(dst_contents, mode=mode)
if dst_contents != dst_contents_pass2:
dst_contents = dst_contents_pass2
assert_equivalent(src_contents, dst_contents, pass_num=2)
assert_stable(src_contents, dst_contents, mode=mode)
# Note: no need to explicitly call `assert_stable` if `dst_contents` was
# the same as `dst_contents_pass2`.
assert_stable(src_contents, dst_contents, mode=mode)


def format_file_contents(src_contents: str, *, fast: bool, mode: Mode) -> FileContent:
Expand Down Expand Up @@ -1108,7 +1098,7 @@ def format_ipynb_string(src_contents: str, *, fast: bool, mode: Mode) -> FileCon
raise NothingChanged


def format_str(src_contents: str, *, mode: Mode) -> FileContent:
def format_str(src_contents: str, *, mode: Mode) -> str:
"""Reformat a string and return new contents.
`mode` determines formatting options, such as how many characters per line are
Expand Down Expand Up @@ -1138,6 +1128,16 @@ def f(
hey
"""
dst_contents = _format_str_once(src_contents, mode=mode)
# Forced second pass to work around optional trailing commas (becoming
# forced trailing commas on pass 2) interacting differently with optional
# parentheses. Admittedly ugly.
if src_contents != dst_contents:
return _format_str_once(dst_contents, mode=mode)
return dst_contents


def _format_str_once(src_contents: str, *, mode: Mode) -> str:
src_node = lib2to3_parse(src_contents.lstrip(), mode.target_versions)
dst_contents = []
future_imports = get_future_imports(src_node)
Expand Down Expand Up @@ -1367,7 +1367,10 @@ def assert_equivalent(src: str, dst: str, *, pass_num: int = 1) -> None:

def assert_stable(src: str, dst: str, mode: Mode) -> None:
"""Raise AssertionError if `dst` reformats differently the second time."""
newdst = format_str(dst, mode=mode)
# We shouldn't call format_str() here, because that formats the string
# twice and may hide a bug where we bounce back and forth between two
# versions.
newdst = _format_str_once(dst, mode=mode)
if dst != newdst:
log = dump_to_file(
str(mode),
Expand Down
12 changes: 12 additions & 0 deletions tests/data/trailing_comma_optional_parens1.py
@@ -1,3 +1,15 @@
if e1234123412341234.winerror not in (_winapi.ERROR_SEM_TIMEOUT,
_winapi.ERROR_PIPE_BUSY) or _check_timeout(t):
pass

# output

if (
e1234123412341234.winerror
not in (
_winapi.ERROR_SEM_TIMEOUT,
_winapi.ERROR_PIPE_BUSY,
)
or _check_timeout(t)
):
pass
16 changes: 15 additions & 1 deletion tests/data/trailing_comma_optional_parens2.py
@@ -1,3 +1,17 @@
if (e123456.get_tk_patchlevel() >= (8, 6, 0, 'final') or
(8, 5, 8) <= get_tk_patchlevel() < (8, 6)):
pass
pass

# output

if (
e123456.get_tk_patchlevel() >= (8, 6, 0, "final")
or (
8,
5,
8,
)
<= get_tk_patchlevel()
< (8, 6)
):
pass
18 changes: 17 additions & 1 deletion tests/data/trailing_comma_optional_parens3.py
Expand Up @@ -5,4 +5,20 @@
"qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweas "
+ "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwegqweasdzxcqweasdzxc.",
"qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwe",
) % {"reported_username": reported_username, "report_reason": report_reason}
) % {"reported_username": reported_username, "report_reason": report_reason}


# output


if True:
if True:
if True:
return _(
"qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweas "
+ "qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwegqweasdzxcqweasdzxc.",
"qweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqweasdzxcqwe",
) % {
"reported_username": reported_username,
"report_reason": report_reason,
}
39 changes: 0 additions & 39 deletions tests/test_black.py
Expand Up @@ -228,45 +228,6 @@ def _test_wip(self) -> None:
black.assert_equivalent(source, actual)
black.assert_stable(source, actual, black.FileMode())

@unittest.expectedFailure
@patch("black.dump_to_file", dump_to_stderr)
def test_trailing_comma_optional_parens_stability1(self) -> None:
source, _expected = read_data("trailing_comma_optional_parens1")
actual = fs(source)
black.assert_stable(source, actual, DEFAULT_MODE)

@unittest.expectedFailure
@patch("black.dump_to_file", dump_to_stderr)
def test_trailing_comma_optional_parens_stability2(self) -> None:
source, _expected = read_data("trailing_comma_optional_parens2")
actual = fs(source)
black.assert_stable(source, actual, DEFAULT_MODE)

@unittest.expectedFailure
@patch("black.dump_to_file", dump_to_stderr)
def test_trailing_comma_optional_parens_stability3(self) -> None:
source, _expected = read_data("trailing_comma_optional_parens3")
actual = fs(source)
black.assert_stable(source, actual, DEFAULT_MODE)

@patch("black.dump_to_file", dump_to_stderr)
def test_trailing_comma_optional_parens_stability1_pass2(self) -> None:
source, _expected = read_data("trailing_comma_optional_parens1")
actual = fs(fs(source)) # this is what `format_file_contents` does with --safe
black.assert_stable(source, actual, DEFAULT_MODE)

@patch("black.dump_to_file", dump_to_stderr)
def test_trailing_comma_optional_parens_stability2_pass2(self) -> None:
source, _expected = read_data("trailing_comma_optional_parens2")
actual = fs(fs(source)) # this is what `format_file_contents` does with --safe
black.assert_stable(source, actual, DEFAULT_MODE)

@patch("black.dump_to_file", dump_to_stderr)
def test_trailing_comma_optional_parens_stability3_pass2(self) -> None:
source, _expected = read_data("trailing_comma_optional_parens3")
actual = fs(fs(source)) # this is what `format_file_contents` does with --safe
black.assert_stable(source, actual, DEFAULT_MODE)

def test_pep_572_version_detection(self) -> None:
source, _ = read_data("pep_572")
root = black.lib2to3_parse(source)
Expand Down
3 changes: 3 additions & 0 deletions tests/test_format.py
Expand Up @@ -52,6 +52,9 @@
"remove_parens",
"slices",
"string_prefixes",
"trailing_comma_optional_parens1",
"trailing_comma_optional_parens2",
"trailing_comma_optional_parens3",
"tricky_unicode_symbols",
"tupleassign",
]
Expand Down

0 comments on commit 32dd9ec

Please sign in to comment.