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

Run second pass of formatter #2549

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions CHANGES.md
Expand Up @@ -7,6 +7,7 @@
- Add new `--workers` parameter (#2514)
- Fixed feature detection for positional-only arguments in lambdas (#2532)
- Bumped typed-ast version minimum to 1.4.3 for 3.10 compatiblity (#2519)
- Run formatter twice internally to ensure stability (#2549)

### _Blackd_

Expand Down
30 changes: 18 additions & 12 deletions src/black/__init__.py
Expand Up @@ -879,24 +879,22 @@ def format_stdin_to_stdout(


def check_stability_and_equivalence(
src_contents: str, dst_contents: str, *, mode: Mode
src_contents: str, dst_contents_pass1: str, dst_contents_pass2: str, *, mode: Mode
) -> None:
"""Perform stability and equivalence checks.

Raise AssertionError if source and destination contents are not
equivalent, or if a second pass of the formatter would format the
content differently.
"""
assert_equivalent(src_contents, dst_contents)
assert_equivalent(src_contents, dst_contents_pass1)

# Forced second pass to work around optional trailing commas (becoming
# Require 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)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Previously, the second pass only happened here in the check_stability_and_equivalence method, and thus wasn't actually being written out / returned from format_file_contents.

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)
if dst_contents_pass1 != dst_contents_pass2:
assert_equivalent(src_contents, dst_contents_pass2, pass_num=2)
assert_stable(src_contents, dst_contents_pass2, mode=mode)
# Note: no need to explicitly call `assert_stable` if `dst_contents` was
# the same as `dst_contents_pass2`.

Expand All @@ -914,13 +912,18 @@ def format_file_contents(src_contents: str, *, fast: bool, mode: Mode) -> FileCo
if mode.is_ipynb:
dst_contents = format_ipynb_string(src_contents, fast=fast, mode=mode)
else:
dst_contents = format_str(src_contents, mode=mode)
dst_contents_pass1 = format_str(src_contents, mode=mode)
dst_contents = format_str(dst_contents_pass1, mode=mode)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This actually runs the second pass of the formatter and returns the results of the second pass out


if src_contents == dst_contents:
raise NothingChanged

if not fast and not mode.is_ipynb:
# Jupyter notebooks will already have been checked above.
check_stability_and_equivalence(src_contents, dst_contents, mode=mode)
check_stability_and_equivalence(
src_contents, dst_contents_pass1, dst_contents, mode=mode
)

return dst_contents


Expand Down Expand Up @@ -967,9 +970,12 @@ def format_cell(src: str, *, fast: bool, mode: Mode) -> str:
masked_src, replacements = mask_cell(src_without_trailing_semicolon)
except SyntaxError:
raise NothingChanged from None
masked_dst = format_str(masked_src, mode=mode)
masked_dst_pass1 = format_str(masked_src, mode=mode)
masked_dst = format_str(masked_dst_pass1, mode=mode)
if not fast:
check_stability_and_equivalence(masked_src, masked_dst, mode=mode)
check_stability_and_equivalence(
masked_src, masked_dst_pass1, masked_dst, mode=mode
)
dst_without_trailing_semicolon = unmask_cell(masked_dst, replacements)
dst = put_trailing_semicolon_back(
dst_without_trailing_semicolon, has_trailing_semicolon
Expand Down
42 changes: 42 additions & 0 deletions tests/data/function_trailing_comma.py
Expand Up @@ -15,6 +15,10 @@ def f(a:int=1,):
}["a"]
if a == {"a": 1,"b": 2,"c": 3,"d": 4,"e": 5,"f": 6,"g": 7,"h": 8,}["a"]:
pass
if {"a": 1,"b": 2,"c": 3,"d": 4,"e": 5,"f": 6,"g": 7,"h": 8,}["a"] == a:
pass
assert a == call({"a": 1,"b": 2,"c": 3,"d": 4,"e": 5,"f": 6,"g": 7,"h": 8,}["a"])
assert call({"a": 1,"b": 2,"c": 3,"d": 4,"e": 5,"f": 6,"g": 7,"h": 8,}["a"]) == a

def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Expand Down Expand Up @@ -100,6 +104,44 @@ def f(
"h": 8,
}["a"]:
pass
if {
"a": 1,
"b": 2,
"c": 3,
"d": 4,
"e": 5,
"f": 6,
"g": 7,
"h": 8,
}["a"] == a:
pass
assert a == call(
{
"a": 1,
"b": 2,
"c": 3,
"d": 4,
"e": 5,
"f": 6,
"g": 7,
"h": 8,
}["a"]
)
assert (
call(
{
"a": 1,
"b": 2,
"c": 3,
"d": 4,
"e": 5,
"f": 6,
"g": 7,
"h": 8,
}["a"]
)
== a
)


def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[
Expand Down
5 changes: 4 additions & 1 deletion tests/util.py
Expand Up @@ -9,7 +9,7 @@
import black
from black.debug import DebugVisitor
from black.mode import TargetVersion
from black.output import err, out
from black.output import err, out, diff

THIS_DIR = Path(__file__).parent
DATA_DIR = THIS_DIR / "data"
Expand Down Expand Up @@ -47,6 +47,9 @@ def _assert_format_equal(expected: str, actual: str) -> None:
except Exception as ve:
err(str(ve))

if actual != expected:
out(diff(expected, actual, "expected", "actual"))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I found this incredibly helpful in debugging the test failures.
It already prints out the ASTs, but for some of the test files, the ASTs are huge. This prints a diff of the actual output as well.


assert actual == expected


Expand Down