Skip to content

Commit

Permalink
Merge branch 'main' into psfgh-3545
Browse files Browse the repository at this point in the history
  • Loading branch information
JelleZijlstra authored Apr 24, 2024
2 parents 5ceaf93 + 2f88085 commit cbf5730
Show file tree
Hide file tree
Showing 20 changed files with 1,109 additions and 119 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ repos:
hooks:
- id: flake8
additional_dependencies:
- flake8-bugbear
- flake8-bugbear==24.2.6
- flake8-comprehensions
- flake8-simplify
exclude: ^src/blib2to3/
Expand Down
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@

<!-- Include any especially major or disruptive changes here -->

- Add support for the new Python 3.12 f-string syntax introduced by PEP 701 (#3822)

### Stable style

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

- Fix crash involving indented dummy functions containing newlines (#4318)

### Preview style

<!-- Changes that affect Black's preview style -->
Expand Down Expand Up @@ -46,6 +50,8 @@

<!-- For example, Docker, GitHub Actions, pre-commit, editors -->

- Github Action now works even when `git archive` is skipped (#4313)

### Documentation

<!-- Major changes to documentation and policies. Small docs changes
Expand Down
7 changes: 6 additions & 1 deletion action/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

BLACK_VERSION_RE = re.compile(r"^black([^A-Z0-9._-]+.*)$", re.IGNORECASE)
EXTRAS_RE = re.compile(r"\[.*\]")
EXPORT_SUBST_FAIL_RE = re.compile(r"\$Format:.*\$")


def determine_version_specifier() -> str:
Expand Down Expand Up @@ -135,7 +136,11 @@ def find_black_version_in_array(array: object) -> Union[str, None]:
# expected format is one of:
# - 23.1.0
# - 23.1.0-51-g448bba7
if describe_name.count("-") < 2:
# - $Format:%(describe:tags=true,match=*[0-9]*)$ (if export-subst fails)
if (
describe_name.count("-") < 2
and EXPORT_SUBST_FAIL_RE.match(describe_name) is None
):
# the action's commit matches a tag exactly, install exact version from PyPI
req = f"black{extra_deps}=={describe_name}"
else:
Expand Down
2 changes: 1 addition & 1 deletion docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Used by ReadTheDocs; pinned requirements for stability.

myst-parser==2.0.0
Sphinx==7.2.6
Sphinx==7.3.7
# Older versions break Sphinx even though they're declared to be supported.
docutils==0.20.1
sphinxcontrib-programoutput==0.17
Expand Down
31 changes: 13 additions & 18 deletions src/black/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,7 @@
from black.mode import FUTURE_FLAG_TO_FEATURE, VERSION_TO_FEATURES, Feature
from black.mode import Mode as Mode # re-exported
from black.mode import Preview, TargetVersion, supports_feature
from black.nodes import (
STARS,
is_number_token,
is_simple_decorator_expression,
is_string_token,
syms,
)
from black.nodes import STARS, is_number_token, is_simple_decorator_expression, syms
from black.output import color_diff, diff, dump_to_file, err, ipynb_diff, out
from black.parsing import ( # noqa F401
ASTSafetyError,
Expand All @@ -91,7 +85,6 @@
sanitized_lines,
)
from black.report import Changed, NothingChanged, Report
from black.trans import iter_fexpr_spans
from blib2to3.pgen2 import token
from blib2to3.pytree import Leaf, Node

Expand Down Expand Up @@ -1265,7 +1258,10 @@ def _format_str_once(
elt = EmptyLineTracker(mode=mode)
split_line_features = {
feature
for feature in {Feature.TRAILING_COMMA_IN_CALL, Feature.TRAILING_COMMA_IN_DEF}
for feature in {
Feature.TRAILING_COMMA_IN_CALL,
Feature.TRAILING_COMMA_IN_DEF,
}
if supports_feature(versions, feature)
}
block: Optional[LinesBlock] = None
Expand Down Expand Up @@ -1337,15 +1333,14 @@ def get_features_used( # noqa: C901
}

for n in node.pre_order():
if is_string_token(n):
value_head = n.value[:2]
if value_head in {'f"', 'F"', "f'", "F'", "rf", "fr", "RF", "FR"}:
features.add(Feature.F_STRINGS)
if Feature.DEBUG_F_STRINGS not in features:
for span_beg, span_end in iter_fexpr_spans(n.value):
if n.value[span_beg : span_end - 1].rstrip().endswith("="):
features.add(Feature.DEBUG_F_STRINGS)
break
if n.type == token.FSTRING_START:
features.add(Feature.F_STRINGS)
elif (
n.type == token.RBRACE
and n.parent is not None
and any(child.type == token.EQUAL for child in n.parent.children)
):
features.add(Feature.DEBUG_F_STRINGS)

elif is_number_token(n):
if "_" in n.value:
Expand Down
70 changes: 57 additions & 13 deletions src/black/linegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
WHITESPACE,
Visitor,
ensure_visible,
fstring_to_string,
get_annotation_type,
is_arith_like,
is_async_stmt_or_funcdef,
Expand Down Expand Up @@ -152,11 +153,6 @@ def visit_default(self, node: LN) -> Iterator[Line]:

if any_open_brackets:
node.prefix = ""
if self.mode.string_normalization and node.type == token.STRING:
node.value = normalize_string_prefix(node.value)
node.value = normalize_string_quotes(node.value)
if node.type == token.NUMBER:
normalize_numeric_literal(node)
if node.type not in WHITESPACE:
self.current_line.append(node)
yield from super().visit_default(node)
Expand Down Expand Up @@ -313,8 +309,11 @@ def visit_simple_stmt(self, node: Node) -> Iterator[Line]:
yield from self.line(-1)

else:
if not node.parent or not is_stub_suite(node.parent):
yield from self.line()
if node.parent and is_stub_suite(node.parent):
node.prefix = ""
yield from self.visit_default(node)
return
yield from self.line()
yield from self.visit_default(node)

def visit_async_stmt(self, node: Node) -> Iterator[Line]:
Expand Down Expand Up @@ -420,12 +419,11 @@ def visit_STRING(self, leaf: Leaf) -> Iterator[Line]:
# indentation of those changes the AST representation of the code.
if self.mode.string_normalization:
docstring = normalize_string_prefix(leaf.value)
# visit_default() does handle string normalization for us, but
# since this method acts differently depending on quote style (ex.
# 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
# formatting as visit_default() is called *after*. To avoid a
# situation where this function formats a docstring differently on
# the second pass, normalize it early.
# formatting. To avoid a situation where this function formats a
# docstring differently on the second pass, normalize it early.
docstring = normalize_string_quotes(docstring)
else:
docstring = leaf.value
Expand Down Expand Up @@ -499,6 +497,13 @@ def visit_STRING(self, leaf: Leaf) -> Iterator[Line]:
else:
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_quotes(leaf.value)
yield from self.visit_default(leaf)

def visit_NUMBER(self, leaf: Leaf) -> Iterator[Line]:
normalize_numeric_literal(leaf)
yield from self.visit_default(leaf)

def visit_atom(self, node: Node) -> Iterator[Line]:
Expand All @@ -514,6 +519,45 @@ def visit_atom(self, node: Node) -> Iterator[Line]:

yield from self.visit_default(node)

def visit_fstring(self, node: Node) -> Iterator[Line]:
# currently we don't want to format and split f-strings at all.
string_leaf = fstring_to_string(node)
node.replace(string_leaf)
yield from self.visit_STRING(string_leaf)

# TODO: Uncomment Implementation to format f-string children
# fstring_start = node.children[0]
# fstring_end = node.children[-1]
# assert isinstance(fstring_start, Leaf)
# assert isinstance(fstring_end, Leaf)

# quote_char = fstring_end.value[0]
# quote_idx = fstring_start.value.index(quote_char)
# prefix, quote = (
# fstring_start.value[:quote_idx],
# fstring_start.value[quote_idx:]
# )

# if not is_docstring(node, self.mode):
# prefix = normalize_string_prefix(prefix)

# assert quote == fstring_end.value

# is_raw_fstring = "r" in prefix or "R" in prefix
# middles = [
# leaf
# for leaf in node.leaves()
# if leaf.type == token.FSTRING_MIDDLE
# ]

# if self.mode.string_normalization:
# middles, quote = normalize_fstring_quotes(quote, middles, is_raw_fstring)

# fstring_start.value = prefix + quote
# fstring_end.value = quote

# yield from self.visit_default(node)

def __post_init__(self) -> None:
"""You are in a twisty little maze of passages."""
self.current_line = Line(mode=self.mode)
Expand Down Expand Up @@ -1388,7 +1432,7 @@ def normalize_invisible_parens( # noqa: C901
# of case will be not parsed as a Python keyword.
break

elif not (isinstance(child, Leaf) and is_multiline_string(child)):
elif not is_multiline_string(child):
wrap_in_parentheses(node, child, visible=False)

comma_check = child.type == token.COMMA
Expand Down
7 changes: 6 additions & 1 deletion src/black/lines.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,12 @@ def append(
Inline comments are put aside.
"""
has_value = leaf.type in BRACKETS or bool(leaf.value.strip())
has_value = (
leaf.type in BRACKETS
# empty fstring-middles must not be truncated
or leaf.type == token.FSTRING_MIDDLE
or bool(leaf.value.strip())
)
if not has_value:
return

Expand Down
2 changes: 2 additions & 0 deletions src/black/mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class Feature(Enum):
DEBUG_F_STRINGS = 16
PARENTHESIZED_CONTEXT_MANAGERS = 17
TYPE_PARAMS = 18
FSTRING_PARSING = 19
FORCE_OPTIONAL_PARENTHESES = 50

# __future__ flags
Expand Down Expand Up @@ -156,6 +157,7 @@ class Feature(Enum):
Feature.EXCEPT_STAR,
Feature.VARIADIC_GENERICS,
Feature.TYPE_PARAMS,
Feature.FSTRING_PARSING,
},
}

Expand Down
Loading

0 comments on commit cbf5730

Please sign in to comment.