diff --git a/CHANGES.md b/CHANGES.md index 864f0a54410..6f4e6ed5bca 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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_ diff --git a/src/black/__init__.py b/src/black/__init__.py index fdbaf040d64..6d8bf920811 100644 --- a/src/black/__init__.py +++ b/src/black/__init__.py @@ -879,7 +879,7 @@ 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. @@ -887,16 +887,14 @@ def check_stability_and_equivalence( 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) - 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`. @@ -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) + 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 @@ -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 diff --git a/tests/data/function_trailing_comma.py b/tests/data/function_trailing_comma.py index 02078219e82..956840789ec 100644 --- a/tests/data/function_trailing_comma.py +++ b/tests/data/function_trailing_comma.py @@ -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" @@ -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[ diff --git a/tests/util.py b/tests/util.py index 84e98bb0fbd..11a4f7f5dae 100644 --- a/tests/util.py +++ b/tests/util.py @@ -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" @@ -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")) + assert actual == expected