diff --git a/src/fixit/format.py b/src/fixit/format.py index 9a332c35..facd8f7b 100644 --- a/src/fixit/format.py +++ b/src/fixit/format.py @@ -48,9 +48,16 @@ def format(self, module: Module, path: Path) -> FileContent: import ufmt.util mode = ufmt.util.make_black_config(path) - content = black.format_file_contents( - module.bytes.decode("utf-8"), fast=False, mode=mode - ) + try: + content = black.format_file_contents( + module.bytes.decode("utf-8"), fast=False, mode=mode + ) + except black.NothingChanged: + # black signals "this is already formatted" by raising rather than + # by returning the content unchanged. That is a successful no-op, + # and letting it propagate discards the autofix that produced this + # module. + return module.bytes return content.encode("utf-8") diff --git a/src/fixit/tests/__init__.py b/src/fixit/tests/__init__.py index ef740dcb..2d842fe6 100644 --- a/src/fixit/tests/__init__.py +++ b/src/fixit/tests/__init__.py @@ -9,6 +9,7 @@ from fixit.testing import add_lint_rule_tests_to_module from .config import ConfigTest from .engine import EngineTest +from .format import FormatTest from .ftypes import TypesTest from .rule import RuleTest, RunnerTest from .smoke import SmokeTest diff --git a/src/fixit/tests/format.py b/src/fixit/tests/format.py new file mode 100644 index 00000000..d8d03a81 --- /dev/null +++ b/src/fixit/tests/format.py @@ -0,0 +1,34 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from pathlib import Path +from unittest import TestCase + +import libcst + +from ..format import format_module +from ..ftypes import Config + +FAKE_PATH = Path("fake.py") + + +class FormatTest(TestCase): + def test_black_already_formatted(self) -> None: + # black reports "already formatted" by raising rather than by returning + # the content unchanged. Letting that escape loses the autofix that + # produced the module, so the formatter has to treat it as a no-op. + config = Config(path=FAKE_PATH, formatter="black") + for content, expected in ( + (b"x = 1\n", b"x = 1\n"), + (b"x=1\n", b"x = 1\n"), + ): + with self.subTest(content=content): + module = libcst.parse_module(content) + self.assertEqual(expected, format_module(module, FAKE_PATH, config)) + + def test_no_formatter_returns_module_bytes(self) -> None: + config = Config(path=FAKE_PATH) + module = libcst.parse_module(b"x=1\n") + self.assertEqual(b"x=1\n", format_module(module, FAKE_PATH, config))