Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@

<!-- Changes that affect Black's preview style -->

- Normalize the uppercase `T` prefix of t-strings to lowercase in preview style (e.g.
`T"..."` is now normalized to `t"..."`) (#5185)
- Fix unnecessary parentheses around short RHS expressions in indexed assignments like
`x[key] = expr` (#5095)
- Parenthesize tuple expressions in `yield` statements for consistency with function
Expand Down
2 changes: 2 additions & 0 deletions docs/the_black_code_style/future_style.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ Currently, the following features are included in the preview style:
anyway; let the bracket explode instead. ([see below](labels/hug-comparator))
- `parenthesize_tuple_in_yield`: Add parentheses around tuple expressions in `yield`
statements.
- `normalize_tstring_prefix`: Normalize the uppercase `T` prefix of t-strings to
lowercase (e.g. `T"..."` becomes `t"..."`).

(labels/wrap-comprehension-in)=

Expand Down
4 changes: 2 additions & 2 deletions src/black/linegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ def visit_STRING(self, leaf: Leaf) -> Iterator[Line]:
# We're ignoring docstrings with backslash newline escapes because changing
# indentation of those changes the AST representation of the code.
if self.mode.string_normalization:
docstring = normalize_string_prefix(leaf.value)
docstring = normalize_string_prefix(leaf.value, self.mode)
# We handle string normalization at the end of this method, but since
# what we do right now acts differently depending on quote style (ex.
# see padding logic below), there's a possibility for unstable
Expand Down Expand Up @@ -550,7 +550,7 @@ def visit_STRING(self, leaf: Leaf) -> Iterator[Line]:
leaf.value = prefix + quote + docstring + quote

if self.mode.string_normalization and leaf.type == token.STRING:
leaf.value = normalize_string_prefix(leaf.value)
leaf.value = normalize_string_prefix(leaf.value, self.mode)
leaf.value = normalize_string_quotes(leaf.value)
yield from self.visit_default(leaf)

Expand Down
1 change: 1 addition & 0 deletions src/black/mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ class Preview(Enum):
pyi_blank_line_after_function_docstring = auto()
hug_comparator = auto()
parenthesize_tuple_in_yield = auto()
normalize_tstring_prefix = auto()


UNSTABLE_FEATURES: set[Preview] = {
Expand Down
16 changes: 14 additions & 2 deletions src/black/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@
import sys
from functools import lru_cache
from re import Match, Pattern
from typing import Final
from typing import TYPE_CHECKING, Final

from black._width_table import WIDTH_TABLE
from blib2to3.pytree import Leaf

if TYPE_CHECKING:
from black.mode import Mode


STRING_PREFIX_CHARS: Final = "fturbFTURB" # All possible string prefix characters.
STRING_PREFIX_RE: Final = re.compile(
r"^([" + STRING_PREFIX_CHARS + r"]*)(.*)$", re.DOTALL
Expand Down Expand Up @@ -142,7 +146,7 @@ def assert_is_leaf_string(string: str) -> None:
), f"{set(string[:quote_idx])} is NOT a subset of {set(STRING_PREFIX_CHARS)}."


def normalize_string_prefix(s: str) -> str:
def normalize_string_prefix(s: str, mode: "Mode | None" = None) -> str:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer for mode to be required, just so we don't accidentally forget it anywhere. Black's public API isn't considered stable, so it's not a breaking change.

"""Make all string prefixes lowercase."""
match = STRING_PREFIX_RE.match(s)
assert match is not None, f"failed to match string {s!r}"
Expand All @@ -153,13 +157,21 @@ def normalize_string_prefix(s: str) -> str:
.replace("U", "")
.replace("u", "")
)
if mode is None or _is_preview_tstring_normalization(mode):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you inline the Preview.normalize_tstring_prefix in mode call instead of making a utility function?

new_prefix = new_prefix.replace("T", "t")

# Python syntax guarantees max 2 prefixes and that one of them is "r"
if len(new_prefix) == 2 and new_prefix[0].lower() != "r":
new_prefix = new_prefix[::-1]
return f"{new_prefix}{match.group(2)}"


def _is_preview_tstring_normalization(mode: "Mode") -> bool:
from black.mode import Preview

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this inline import? Why can't we just import black.mode normally?


return Preview.normalize_tstring_prefix in mode


# Re(gex) does actually cache patterns internally but this still improves
# performance on a long list literal of strings by 5-9% since lru_cache's
# caching overhead is much lower.
Expand Down
13 changes: 13 additions & 0 deletions tests/data/cases/preview_pep_750_normalize_tstring_prefix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# flags: --preview --minimum-version=3.14

# Uppercase T prefix is normalized to lowercase t in preview style
x = T"bar {1 + 1}"
x = T'bar {1 + 1}'
rT'\{{\}}'

# output

# Uppercase T prefix is normalized to lowercase t in preview style
x = t"bar {1 + 1}"
x = t"bar {1 + 1}"
rt"\{{\}}"
Loading