Repository: psf/black

Issue:
Create a --preview flag for disruptive changes between major updates
As discussed in #2394, we'd like to shove disruptive style changes between major updates behind a `--preview` flag. And I think it should be created and available even if such changes have not been implemented yet, so that our CLI is consistent.

Perhaps:
- add the CLI option
- create a separate style docs section for describing the style
- from now on announce these kinds of changes under a "preview" header in our changelog
- document a consistent way of writing preview tests without disrupting stable (I think just duplicating might be fine)

Thoughts, suggestions?

---
@ichard26 shall we add this to the "stable" project?


Discussion:

Resolving patch:
diff --git a/src/black/__init__.py b/src/black/__init__.py
--- a/src/black/__init__.py
+++ b/src/black/__init__.py
@@ -245,6 +245,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,
@@ -398,6 +406,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],
@@ -439,6 +448,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
--- 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)


Test patch:
diff --git a/tests/test_format.py b/tests/test_format.py
--- 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",
@@ -72,7 +72,9 @@
     "parenthesized_context_managers",
 ]
 
-SOURCES = [
+PREVIEW_CASES: List[str] = []
+
+SOURCES: List[str] = [
     "src/black/__init__.py",
     "src/black/__main__.py",
     "src/black/brackets.py",
@@ -138,6 +140,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