diff --git a/CHANGES.md b/CHANGES.md index 32059a30548..4b9ceae81dc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -36,6 +36,10 @@ - All upper version bounds on dependencies have been removed (#2718) - `typing-extensions` is no longer a required dependency in Python 3.10+ (#2772) +### Preview style + +- Introduce the `--preview` flag with no style changes (#2752) + ### Integrations - Update GitHub action to support containerized runs (#2748) diff --git a/docs/contributing/the_basics.md b/docs/contributing/the_basics.md index 9a639731073..23fbb8a3d7e 100644 --- a/docs/contributing/the_basics.md +++ b/docs/contributing/the_basics.md @@ -55,7 +55,9 @@ go back and workout what to add to the `CHANGES.md` for each release. If a change would affect the advertised code style, please modify the documentation (The _Black_ code style) to reflect that change. Patches that fix unintended bugs in -formatting don't need to be mentioned separately though. +formatting don't need to be mentioned separately though. If the change is implemented +with the `--preview` flag, please include the change in the future style document +instead and write the changelog entry under a dedicated "Preview changes" heading. ### Docs Testing diff --git a/docs/the_black_code_style/future_style.md b/docs/the_black_code_style/future_style.md index a7676090553..70ffeefc76a 100644 --- a/docs/the_black_code_style/future_style.md +++ b/docs/the_black_code_style/future_style.md @@ -40,3 +40,9 @@ Currently, _Black_ does not split long strings to fit the line length limit. Cur there is [an experimental option](labels/experimental-string) to enable splitting strings. We plan to enable this option by default once it is fully stable. This is tracked in [this issue](https://github.com/psf/black/issues/2188). + +## Preview style + +Experimental, potentially disruptive style changes are gathered under the `--preview` +CLI flag. At the end of each year, these changes may be adopted into the default style, +as described in [The Black Code Style](./index.rst). diff --git a/docs/the_black_code_style/index.rst b/docs/the_black_code_style/index.rst index d53703277e4..3952a174223 100644 --- a/docs/the_black_code_style/index.rst +++ b/docs/the_black_code_style/index.rst @@ -32,7 +32,7 @@ versions of *Black*: improved formatting enabled by newer Python language syntax as well as due to improvements in the formatting logic. -- The ``--future`` flag is exempt from this policy. There are no guarantees +- The ``--preview`` flag is exempt from this policy. There are no guarantees around the stability of the output with that flag passed into *Black*. This flag is intended for allowing experimentation with the proposed changes to the *Black* code style. diff --git a/src/black/__init__.py b/src/black/__init__.py index fa918ce2931..405a01082e7 100644 --- a/src/black/__init__.py +++ b/src/black/__init__.py @@ -246,6 +246,14 @@ def validate_regex( " Currently disabled because it leads to some crashes." ), ) +@click.option( + "--preview", + is_flag=True, + help=( + "Enable potentially disruptive style changes that will be added to Black's main" + " functionality in the next major release." + ), +) @click.option( "--check", is_flag=True, @@ -399,6 +407,7 @@ def main( skip_string_normalization: bool, skip_magic_trailing_comma: bool, experimental_string_processing: bool, + preview: bool, quiet: bool, verbose: bool, required_version: Optional[str], @@ -469,6 +478,7 @@ def main( string_normalization=not skip_string_normalization, magic_trailing_comma=not skip_magic_trailing_comma, experimental_string_processing=experimental_string_processing, + preview=preview, ) if code is not None: diff --git a/src/black/mode.py b/src/black/mode.py index 5e04525cfc9..c8c2bd4eb26 100644 --- a/src/black/mode.py +++ b/src/black/mode.py @@ -121,6 +121,10 @@ def supports_feature(target_versions: Set[TargetVersion], feature: Feature) -> b return all(feature in VERSION_TO_FEATURES[version] for version in target_versions) +class Preview(Enum): + """Individual preview style features.""" + + @dataclass class Mode: target_versions: Set[TargetVersion] = field(default_factory=set) @@ -130,6 +134,16 @@ class Mode: is_ipynb: bool = False magic_trailing_comma: bool = True experimental_string_processing: bool = False + preview: bool = False + + def __contains__(self, feature: Preview) -> bool: + """ + Provide `Preview.FEATURE in Mode` syntax that mirrors the ``preview`` flag. + + The argument is not checked and features are not differentiated. + They only exist to make development easier by clarifying intent. + """ + return self.preview def get_cache_key(self) -> str: if self.target_versions: @@ -147,5 +161,6 @@ def get_cache_key(self) -> str: str(int(self.is_ipynb)), str(int(self.magic_trailing_comma)), str(int(self.experimental_string_processing)), + str(int(self.preview)), ] return ".".join(parts) diff --git a/tests/test_format.py b/tests/test_format.py index db39678cdfe..00cd07f36f7 100644 --- a/tests/test_format.py +++ b/tests/test_format.py @@ -1,5 +1,5 @@ from dataclasses import replace -from typing import Any, Iterator +from typing import Any, Iterator, List from unittest.mock import patch import pytest @@ -14,7 +14,7 @@ read_data, ) -SIMPLE_CASES = [ +SIMPLE_CASES: List[str] = [ "beginning_backslash", "bracketmatch", "class_blank_parentheses", @@ -55,7 +55,7 @@ "tupleassign", ] -EXPERIMENTAL_STRING_PROCESSING_CASES = [ +EXPERIMENTAL_STRING_PROCESSING_CASES: List[str] = [ "cantfit", "comments7", "long_strings", @@ -64,7 +64,7 @@ "percent_precedence", ] -PY310_CASES = [ +PY310_CASES: List[str] = [ "pattern_matching_simple", "pattern_matching_complex", "pattern_matching_extras", @@ -73,7 +73,9 @@ "parenthesized_context_managers", ] -SOURCES = [ +PREVIEW_CASES: List[str] = [] + +SOURCES: List[str] = [ "src/black/__init__.py", "src/black/__main__.py", "src/black/brackets.py", @@ -139,6 +141,11 @@ def test_experimental_format(filename: str) -> None: check_file(filename, black.Mode(experimental_string_processing=True)) +@pytest.mark.parametrize("filename", PREVIEW_CASES) +def test_preview_format(filename: str) -> None: + check_file(filename, black.Mode(preview=True)) + + @pytest.mark.parametrize("filename", SOURCES) def test_source_is_formatted(filename: str) -> None: path = THIS_DIR.parent / filename