Repository: psf/black

Issue:
Add ESP to --preview for stable
A preview flag is added in #2751, which could also contain experimental string processing. So shall we convert ESP there? Or are we still thinking about making it stable very soon?

Discussion:
github-actions[bot]: **diff-shades** reports zero changes comparing this PR (365a83982cbe6e9776f5962de2290cd7cc96d927) to main (9bd4134f3138448eb92af7031d994b2cec7d08ad).

---

[**What is this?**](https://black.readthedocs.io/en/latest/contributing/gauging_changes.html#diff-shades) | [Workflow run](https://github.com/psf/black/actions/runs/1725903178) | [diff-shades documentation](https://github.com/ichard26/diff-shades#readme)
felix-hilden: Wait, is mypy seriously so smart that it recognises that the only element in `Preview` is ESP and so marks the default case as unreachable? Unbelievable 😄 I'll deal with this tomorrow

Patch:
diff --git a/CHANGES.md b/CHANGES.md
index 4b9ceae..c3e2a33 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -30,6 +30,8 @@
 - Fix handling of standalone `match()` or `case()` when there is a trailing newline or a
   comment inside of the parentheses. (#2760)
 - Black now normalizes string prefix order (#2297)
+- Deprecate `--experimental-string-processing` and move the functionality under
+  `--preview` (#2789)
 
 ### Packaging
 
@@ -38,7 +40,8 @@
 
 ### Preview style
 
-- Introduce the `--preview` flag with no style changes (#2752)
+- Introduce the `--preview` flag (#2752)
+- Add `--experimental-string-processing` to the preview style (#2789)
 
 ### Integrations
 
diff --git a/docs/faq.md b/docs/faq.md
index c7d5ec3..94a978d 100644
--- a/docs/faq.md
+++ b/docs/faq.md
@@ -33,7 +33,7 @@ still proposed on the issue tracker. See
 
 Starting in 2022, the formatting output will be stable for the releases made in the same
 year (other than unintentional bugs). It is possible to opt-in to the latest formatting
-styles, using the `--future` flag.
+styles, using the `--preview` flag.
 
 ## Why is my file not formatted?
 
diff --git a/docs/the_black_code_style/current_style.md b/docs/the_black_code_style/current_style.md
index 11fe2c8..1d1e42e 100644
--- a/docs/the_black_code_style/current_style.md
+++ b/docs/the_black_code_style/current_style.md
@@ -10,6 +10,10 @@ with `# fmt: off` and end with `# fmt: on`, or lines that ends with `# fmt: skip
 [YAPF](https://github.com/google/yapf)'s block comments to the same effect, as a
 courtesy for straddling code.
 
+The rest of this document describes the current formatting style. If you're interested
+in trying out where the style is heading, see [future style](./future_style.md) and try
+running `black --preview`.
+
 ### How _Black_ wraps lines
 
 _Black_ ignores previous formatting and applies uniform horizontal and vertical
@@ -260,16 +264,6 @@ If you are adopting _Black_ in a large project with pre-existing string conventi
 you can pass `--skip-string-normalization` on the command line. This is meant as an
 adoption helper, avoid using this for new projects.
 
-(labels/experimental-string)=
-
-As an experimental option (can be enabled by `--experimental-string-processing`),
-_Black_ splits long strings (using parentheses where appropriate) and merges short ones.
-When split, parts of f-strings that don't need formatting are converted to plain
-strings. User-made splits are respected when they do not exceed the line length limit.
-Line continuation backslashes are converted into parenthesized strings. Unnecessary
-parentheses are stripped. Because the functionality is experimental, feedback and issue
-reports are highly encouraged!
-
 _Black_ also processes docstrings. Firstly the indentation of docstrings is corrected
 for both quotations and the text within, although relative indentation in the text is
 preserved. Superfluous trailing whitespace on each line and unnecessary new lines at the
diff --git a/docs/the_black_code_style/future_style.md b/docs/the_black_code_style/future_style.md
index 70ffeef..2ec2c03 100644
--- a/docs/the_black_code_style/future_style.md
+++ b/docs/the_black_code_style/future_style.md
@@ -34,15 +34,18 @@ with \
 Although when the target version is Python 3.9 or higher, _Black_ will use parentheses
 instead since they're allowed in Python 3.9 and higher.
 
-## Improved string processing
-
-Currently, _Black_ does not split long strings to fit the line length limit. Currently,
-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).
+as described in [The Black Code Style](./index.rst). Because the functionality is
+experimental, feedback and issue reports are highly encouraged!
+
+### Improved string processing
+
+_Black_ will split long string literals and merge short ones. Parentheses are used where
+appropriate. When split, parts of f-strings that don't need formatting are converted to
+plain strings. User-made splits are respected when they do not exceed the line length
+limit. Line continuation backslashes are converted into parenthesized strings.
+Unnecessary parentheses are stripped. The stability and status of this feature is
+tracked in [this issue](https://github.com/psf/black/issues/2188).
diff --git a/src/black/__init__.py b/src/black/__init__.py
index 405a010..67c272e 100644
--- a/src/black/__init__.py
+++ b/src/black/__init__.py
@@ -241,10 +241,7 @@ def validate_regex(
     "--experimental-string-processing",
     is_flag=True,
     hidden=True,
-    help=(
-        "Experimental option that performs more normalization on string literals."
-        " Currently disabled because it leads to some crashes."
-    ),
+    help="(DEPRECATED and now included in --preview) Normalize string literals.",
 )
 @click.option(
     "--preview",
diff --git a/src/black/linegen.py b/src/black/linegen.py
index 6008c77..9ee42aa 100644
--- a/src/black/linegen.py
+++ b/src/black/linegen.py
@@ -23,8 +23,7 @@
 from black.strings import normalize_string_prefix, normalize_string_quotes
 from black.trans import Transformer, CannotTransform, StringMerger
 from black.trans import StringSplitter, StringParenWrapper, StringParenStripper
-from black.mode import Mode
-from black.mode import Feature
+from black.mode import Mode, Feature, Preview
 
 from blib2to3.pytree import Node, Leaf
 from blib2to3.pgen2 import token
@@ -338,7 +337,7 @@ def transform_line(
         and not (line.inside_brackets and line.contains_standalone_comments())
     ):
         # Only apply basic string preprocessing, since lines shouldn't be split here.
-        if mode.experimental_string_processing:
+        if Preview.string_processing in mode:
             transformers = [string_merge, string_paren_strip]
         else:
             transformers = []
@@ -381,7 +380,7 @@ def _rhs(
         # via type ... https://github.com/mypyc/mypyc/issues/884
         rhs = type("rhs", (), {"__call__": _rhs})()
 
-        if mode.experimental_string_processing:
+        if Preview.string_processing in mode:
             if line.inside_brackets:
                 transformers = [
                     string_merge,
diff --git a/src/black/mode.py b/src/black/mode.py
index c8c2bd4..b6d1a1f 100644
--- a/src/black/mode.py
+++ b/src/black/mode.py
@@ -7,9 +7,10 @@
 import sys
 
 from dataclasses import dataclass, field
-from enum import Enum
+from enum import Enum, auto
 from operator import attrgetter
 from typing import Dict, Set
+from warnings import warn
 
 if sys.version_info < (3, 8):
     from typing_extensions import Final
@@ -124,6 +125,13 @@ def supports_feature(target_versions: Set[TargetVersion], feature: Feature) -> b
 class Preview(Enum):
     """Individual preview style features."""
 
+    string_processing = auto()
+    hug_simple_powers = auto()
+
+
+class Deprecated(UserWarning):
+    """Visible deprecation warning."""
+
 
 @dataclass
 class Mode:
@@ -136,6 +144,14 @@ class Mode:
     experimental_string_processing: bool = False
     preview: bool = False
 
+    def __post_init__(self) -> None:
+        if self.experimental_string_processing:
+            warn(
+                "`experimental string processing` has been included in `preview`"
+                " and deprecated. Use `preview` instead.",
+                Deprecated,
+            )
+
     def __contains__(self, feature: Preview) -> bool:
         """
         Provide `Preview.FEATURE in Mode` syntax that mirrors the ``preview`` flag.
@@ -143,6 +159,8 @@ def __contains__(self, feature: Preview) -> bool:
         The argument is not checked and features are not differentiated.
         They only exist to make development easier by clarifying intent.
         """
+        if feature is Preview.string_processing:
+            return self.preview or self.experimental_string_processing
         return self.preview
 
     def get_cache_key(self) -> str:


Test patch:
diff --git a/tests/test_black.py b/tests/test_black.py
index 202fe23..19cff23 100644
--- a/tests/test_black.py
+++ b/tests/test_black.py
@@ -150,6 +150,11 @@ def test_empty_ff(self) -> None:
             os.unlink(tmp_file)
         self.assertFormatEqual(expected, actual)
 
+    def test_experimental_string_processing_warns(self) -> None:
+        self.assertWarns(
+            black.mode.Deprecated, black.Mode, experimental_string_processing=True
+        )
+
     def test_piping(self) -> None:
         source, expected = read_data("src/black/__init__", data=False)
         result = BlackRunner().invoke(
@@ -342,7 +347,7 @@ def test_detect_pos_only_arguments(self) -> None:
     @patch("black.dump_to_file", dump_to_stderr)
     def test_string_quotes(self) -> None:
         source, expected = read_data("string_quotes")
-        mode = black.Mode(experimental_string_processing=True)
+        mode = black.Mode(preview=True)
         assert_format(source, expected, mode)
         mode = replace(mode, string_normalization=False)
         not_normalized = fs(source, mode=mode)
diff --git a/tests/test_format.py b/tests/test_format.py
index 00cd07f..40f225c 100644
--- a/tests/test_format.py
+++ b/tests/test_format.py
@@ -55,15 +55,6 @@
     "tupleassign",
 ]
 
-EXPERIMENTAL_STRING_PROCESSING_CASES: List[str] = [
-    "cantfit",
-    "comments7",
-    "long_strings",
-    "long_strings__edge_case",
-    "long_strings__regression",
-    "percent_precedence",
-]
-
 PY310_CASES: List[str] = [
     "pattern_matching_simple",
     "pattern_matching_complex",
@@ -73,7 +64,15 @@
     "parenthesized_context_managers",
 ]
 
-PREVIEW_CASES: List[str] = []
+PREVIEW_CASES: List[str] = [
+    # string processing
+    "cantfit",
+    "comments7",
+    "long_strings",
+    "long_strings__edge_case",
+    "long_strings__regression",
+    "percent_precedence",
+]
 
 SOURCES: List[str] = [
     "src/black/__init__.py",
@@ -136,11 +135,6 @@ def test_simple_format(filename: str) -> None:
     check_file(filename, DEFAULT_MODE)
 
 
-@pytest.mark.parametrize("filename", EXPERIMENTAL_STRING_PROCESSING_CASES)
-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))