diff --git a/CHANGES.md b/CHANGES.md index 2fe14cd5246..3ecb01486b4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -62,6 +62,7 @@ release: +- Print warning when toml config contains an invalid key (#4165) - Fix symlink handling, properly catch and ignore symlinks that point outside of root (#4161) - Fix cache mtime logic that resulted in false positive cache hits (#4128) diff --git a/src/black/__init__.py b/src/black/__init__.py index e3cbaab5f1d..961ed9479a8 100644 --- a/src/black/__init__.py +++ b/src/black/__init__.py @@ -142,6 +142,7 @@ def read_pyproject_toml( if not config: return None else: + spellcheck_pyproject_toml_keys(ctx, list(config), value) # Sanitize the values to be Click friendly. For more information please see: # https://github.com/psf/black/issues/1458 # https://github.com/pallets/click/issues/1567 @@ -181,6 +182,22 @@ def read_pyproject_toml( return value +def spellcheck_pyproject_toml_keys( + ctx: click.Context, config_keys: List[str], config_file_path: str +) -> None: + invalid_keys: List[str] = [] + available_config_options = {param.name for param in ctx.command.params} + for key in config_keys: + if key not in available_config_options: + invalid_keys.append(key) + if invalid_keys: + keys_str = ", ".join(map(repr, invalid_keys)) + out( + f"Invalid config keys detected: {keys_str} (in {config_file_path})", + fg="red", + ) + + def target_version_option_callback( c: click.Context, p: Union[click.Option, click.Parameter], v: Tuple[str, ...] ) -> List[TargetVersion]: diff --git a/tests/data/incorrect_spelling.toml b/tests/data/incorrect_spelling.toml new file mode 100644 index 00000000000..560c9e27be2 --- /dev/null +++ b/tests/data/incorrect_spelling.toml @@ -0,0 +1,5 @@ +[tool.black] +ine_length = 50 +target-ersion = ['py37'] +exclude='\.pyi?$' +include='\.py?$' \ No newline at end of file diff --git a/tests/test_black.py b/tests/test_black.py index 2b5fab5d28d..a979a95b674 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -106,6 +106,7 @@ class FakeContext(click.Context): def __init__(self) -> None: self.default_map: Dict[str, Any] = {} self.params: Dict[str, Any] = {} + self.command: click.Command = black.main # Dummy root, since most of the tests don't care about it self.obj: Dict[str, Any] = {"root": PROJECT_ROOT} @@ -1538,6 +1539,22 @@ def test_parse_pyproject_toml(self) -> None: self.assertEqual(config["exclude"], r"\.pyi?$") self.assertEqual(config["include"], r"\.py?$") + def test_spellcheck_pyproject_toml(self) -> None: + test_toml_file = THIS_DIR / "data" / "incorrect_spelling.toml" + result = BlackRunner().invoke( + black.main, + [ + "--code=print('hello world')", + "--verbose", + f"--config={str(test_toml_file)}", + ], + ) + + assert ( + r"Invalid config keys detected: 'ine_length', 'target_ersion' (in" + rf" {test_toml_file})" in result.stderr + ) + def test_parse_pyproject_toml_project_metadata(self) -> None: for test_toml, expected in [ ("only_black_pyproject.toml", ["py310"]),