Merge branch 'main' into black23

This commit is contained in:
Jelle Zijlstra 2022-12-17 09:43:09 -08:00 committed by GitHub
commit 45fd512519
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 487 additions and 112 deletions

View File

@ -22,7 +22,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10"]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
steps:
- uses: actions/checkout@v3

View File

@ -59,7 +59,7 @@ jobs:
- uses: actions/checkout@v3
- name: Build wheels via cibuildwheel
uses: pypa/cibuildwheel@v2.11.2
uses: pypa/cibuildwheel@v2.11.3
env:
CIBW_ARCHS_MACOS: "${{ matrix.macos_arch }}"

View File

@ -1,57 +0,0 @@
name: Test 3.11 without aiohttp extensions
on:
push:
paths-ignore:
- "docs/**"
- "*.md"
pull_request:
paths-ignore:
- "docs/**"
- "*.md"
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
jobs:
main:
# We want to run on external PRs, but not on our own internal PRs as they'll be run
# by the push to the branch. Without this if check, checks are duplicated since
# internal PRs match both the push and pull_request events.
if:
github.event_name == 'push' || github.event.pull_request.head.repo.full_name !=
github.repository
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
python-version: ["3.11.0-rc - 3.11"]
os: [ubuntu-latest, macOS-latest, windows-latest]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install tox
run: |
python -m pip install --upgrade pip
python -m pip install --upgrade tox
- name: Run tests via tox
run: |
python -m tox -e 311
- name: Format ourselves
run: |
python -m pip install .
python -m black --check src/

View File

@ -31,7 +31,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "pypy-3.7", "pypy-3.8"]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "pypy-3.7", "pypy-3.8"]
os: [ubuntu-latest, macOS-latest, windows-latest]
steps:

View File

@ -38,7 +38,7 @@ repos:
- flake8-simplify
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.971
rev: v0.991
hooks:
- id: mypy
exclude: ^docs/conf.py

View File

@ -10,11 +10,18 @@
<!-- Changes that affect Black's stable style -->
- Fix a crash when a colon line is marked between `# fmt: off` and `# fmt: on` (#3439)
### Preview style
<!-- Changes that affect Black's preview style -->
- Fix a crash in preview style with assert + parenthesized string (#3415)
- Do not put the closing quotes in a docstring on a separate line, even if the line is
too long (#3430)
- Long values in dict literals are now wrapped in parentheses; correspondingly
unnecessary parentheses around short values in dict literals are now removed; long
string lambda values are now wrapped in parentheses (#3440)
### Configuration
@ -24,6 +31,9 @@
<!-- Changes to how Black is packaged, such as dependency requirements -->
- Upgrade mypyc from `0.971` to `0.991` so mypycified _Black_ can be built on armv7
(#3380)
### Parser
<!-- Changes to the parser or to version autodetection -->
@ -36,6 +46,9 @@
<!-- Changes to Black's terminal output and error messages -->
- Verbose logging now shows the values of `pyproject.toml` configuration variables
(#3392)
### _Blackd_
<!-- Changes to blackd -->
@ -44,6 +57,8 @@
<!-- For example, Docker, GitHub Actions, pre-commit, editors -->
- Move 3.11 CI to normal flow now all dependencies support 3.11 (#3446)
### Documentation
<!-- Major changes to documentation and policies. Small docs changes
@ -60,6 +75,8 @@
present) or as a single newline character (if a newline is present) (#3348)
- Implicitly concatenated strings used as function args are now wrapped inside
parentheses (#3307)
- For assignment statements, prefer splitting the right hand side if the left hand side
fits on a single line (#3368)
- Correctly handle trailing commas that are inside a line's leading non-nested parens
(#3370)

View File

@ -39,7 +39,7 @@ Try it out now using the [Black Playground](https://black.vercel.app). Watch the
### Installation
_Black_ can be installed by running `pip install black`. It requires Python 3.7+ to run.
If you want to format Jupyter Notebooks, install with `pip install 'black[jupyter]'`.
If you want to format Jupyter Notebooks, install with `pip install "black[jupyter]"`.
If you can't wait for the latest _hotness_ and want to install from GitHub, use:

View File

@ -17,7 +17,7 @@ Also, you can try out _Black_ online for minimal fuss on the
## Installation
_Black_ can be installed by running `pip install black`. It requires Python 3.7+ to run.
If you want to format Jupyter Notebooks, install with `pip install 'black[jupyter]'`.
If you want to format Jupyter Notebooks, install with `pip install "black[jupyter]"`.
If you can't wait for the latest _hotness_ and want to install from GitHub, use:

View File

@ -6,4 +6,4 @@ Sphinx==5.3.0
docutils==0.19
sphinxcontrib-programoutput==0.17
sphinx_copybutton==0.5.1
furo==2022.9.29
furo==2022.12.7

View File

@ -119,7 +119,7 @@ sources = ["src"]
enable-by-default = false
dependencies = [
"hatch-mypyc>=0.13.0",
"mypy==0.971",
"mypy==0.991",
# Required stubs to be removed when the packages support PEP 561 themselves
"types-typed-ast>=1.4.2",
]

View File

@ -511,6 +511,9 @@ def main( # noqa: C901
out("Using configuration from project root.", fg="blue")
else:
out(f"Using configuration in '{config}'.", fg="blue")
if ctx.default_map:
for param, value in ctx.default_map.items():
out(f"{param}: {value}")
error_msg = "Oh no! 💥 💔 💥"
if (

View File

@ -226,7 +226,7 @@ def generate_ignored_nodes(leaf: Leaf, comment: ProtoComment) -> Iterator[LN]:
# fix for fmt: on in children
if children_contains_fmt_on(container):
for child in container.children:
for index, child in enumerate(container.children):
if isinstance(child, Leaf) and is_fmt_on(child):
if child.type in CLOSING_BRACKETS:
# This means `# fmt: on` is placed at a different bracket level
@ -235,6 +235,16 @@ def generate_ignored_nodes(leaf: Leaf, comment: ProtoComment) -> Iterator[LN]:
# The alternative is to fail the formatting.
yield child
return
if (
child.type == token.INDENT
and index < len(container.children) - 1
and children_contains_fmt_on(
container.children[index + 1]
)
):
# This means `# fmt: on` is placed right after an indentation
# level, and we shouldn't swallow the previous INDENT token.
return
if children_contains_fmt_on(child):
return
yield child

View File

@ -64,7 +64,7 @@ def jupyter_dependencies_are_installed(*, verbose: bool, quiet: bool) -> bool:
if verbose or not quiet:
msg = (
"Skipping .ipynb files as Jupyter dependencies are not installed.\n"
"You can fix this by running ``pip install black[jupyter]``"
'You can fix this by running ``pip install "black[jupyter]"``'
)
out(msg)
return False

View File

@ -2,6 +2,7 @@
Generating lines of code.
"""
import sys
from dataclasses import dataclass
from enum import Enum, auto
from functools import partial, wraps
from typing import Collection, Iterator, List, Optional, Set, Union, cast
@ -24,6 +25,7 @@
from black.mode import Feature, Mode, Preview
from black.nodes import (
ASSIGNMENTS,
BRACKETS,
CLOSING_BRACKETS,
OPENING_BRACKETS,
RARROW,
@ -179,6 +181,23 @@ def visit_stmt(
yield from self.visit(child)
def visit_dictsetmaker(self, node: Node) -> Iterator[Line]:
if Preview.wrap_long_dict_values_in_parens in self.mode:
for i, child in enumerate(node.children):
if i == 0:
continue
if node.children[i - 1].type == token.COLON:
if child.type == syms.atom and child.children[0].type == token.LPAR:
if maybe_make_parens_invisible_in_atom(
child,
parent=node,
remove_brackets_around_comma=False,
):
wrap_in_parentheses(node, child, visible=False)
else:
wrap_in_parentheses(node, child, visible=False)
yield from self.visit_default(node)
def visit_funcdef(self, node: Node) -> Iterator[Line]:
"""Visit function definition."""
if Preview.annotation_parens not in self.mode:
@ -381,19 +400,18 @@ def visit_STRING(self, leaf: Leaf) -> Iterator[Line]:
# We need to find the length of the last line of the docstring
# to find if we can add the closing quotes to the line without
# exceeding the maximum line length.
# If docstring is one line, then we need to add the length
# of the indent, prefix, and starting quotes. Ending quotes are
# handled later.
# If docstring is one line, we don't put the closing quotes on a
# separate line because it looks ugly (#3320).
lines = docstring.splitlines()
last_line_length = len(lines[-1]) if docstring else 0
if len(lines) == 1:
last_line_length += len(indent) + len(prefix) + quote_len
# If adding closing quotes would cause the last line to exceed
# the maximum line length then put a line break before the
# closing quotes
if last_line_length + quote_len > self.mode.line_length:
if (
len(lines) > 1
and last_line_length + quote_len > self.mode.line_length
):
leaf.value = prefix + quote + docstring + "\n" + indent + quote
else:
leaf.value = prefix + quote + docstring + quote
@ -604,6 +622,17 @@ def left_hand_split(line: Line, _features: Collection[Feature] = ()) -> Iterator
yield result
@dataclass
class _RHSResult:
"""Intermediate split result from a right hand split."""
head: Line
body: Line
tail: Line
opening_bracket: Leaf
closing_bracket: Leaf
def right_hand_split(
line: Line,
line_length: int,
@ -618,6 +647,22 @@ def right_hand_split(
Note: running this function modifies `bracket_depth` on the leaves of `line`.
"""
rhs_result = _first_right_hand_split(line, omit=omit)
yield from _maybe_split_omitting_optional_parens(
rhs_result, line, line_length, features=features, omit=omit
)
def _first_right_hand_split(
line: Line,
omit: Collection[LeafID] = (),
) -> _RHSResult:
"""Split the line into head, body, tail starting with the last bracket pair.
Note: this function should not have side effects. It's relied upon by
_maybe_split_omitting_optional_parens to get an opinion whether to prefer
splitting on the right side of an assignment statement.
"""
tail_leaves: List[Leaf] = []
body_leaves: List[Leaf] = []
head_leaves: List[Leaf] = []
@ -653,37 +698,71 @@ def right_hand_split(
tail_leaves, line, opening_bracket, component=_BracketSplitComponent.tail
)
bracket_split_succeeded_or_raise(head, body, tail)
return _RHSResult(head, body, tail, opening_bracket, closing_bracket)
def _maybe_split_omitting_optional_parens(
rhs: _RHSResult,
line: Line,
line_length: int,
features: Collection[Feature] = (),
omit: Collection[LeafID] = (),
) -> Iterator[Line]:
if (
Feature.FORCE_OPTIONAL_PARENTHESES not in features
# the opening bracket is an optional paren
and opening_bracket.type == token.LPAR
and not opening_bracket.value
and rhs.opening_bracket.type == token.LPAR
and not rhs.opening_bracket.value
# the closing bracket is an optional paren
and closing_bracket.type == token.RPAR
and not closing_bracket.value
and rhs.closing_bracket.type == token.RPAR
and not rhs.closing_bracket.value
# it's not an import (optional parens are the only thing we can split on
# in this case; attempting a split without them is a waste of time)
and not line.is_import
# there are no standalone comments in the body
and not body.contains_standalone_comments(0)
and not rhs.body.contains_standalone_comments(0)
# and we can actually remove the parens
and can_omit_invisible_parens(body, line_length)
and can_omit_invisible_parens(rhs.body, line_length)
):
omit = {id(closing_bracket), *omit}
omit = {id(rhs.closing_bracket), *omit}
try:
yield from right_hand_split(line, line_length, features=features, omit=omit)
return
# The _RHSResult Omitting Optional Parens.
rhs_oop = _first_right_hand_split(line, omit=omit)
if not (
Preview.prefer_splitting_right_hand_side_of_assignments in line.mode
# the split is right after `=`
and len(rhs.head.leaves) >= 2
and rhs.head.leaves[-2].type == token.EQUAL
# the left side of assignement contains brackets
and any(leaf.type in BRACKETS for leaf in rhs.head.leaves[:-1])
# the left side of assignment is short enough (the -1 is for the ending
# optional paren)
and is_line_short_enough(rhs.head, line_length=line_length - 1)
# the left side of assignment won't explode further because of magic
# trailing comma
and rhs.head.magic_trailing_comma is None
# the split by omitting optional parens isn't preferred by some other
# reason
and not _prefer_split_rhs_oop(rhs_oop, line_length=line_length)
):
yield from _maybe_split_omitting_optional_parens(
rhs_oop, line, line_length, features=features, omit=omit
)
return
except CannotSplit as e:
if not (
can_be_split(body)
or is_line_short_enough(body, line_length=line_length)
can_be_split(rhs.body)
or is_line_short_enough(rhs.body, line_length=line_length)
):
raise CannotSplit(
"Splitting failed, body is still too long and can't be split."
) from e
elif head.contains_multiline_strings() or tail.contains_multiline_strings():
elif (
rhs.head.contains_multiline_strings()
or rhs.tail.contains_multiline_strings()
):
raise CannotSplit(
"The current optional pair of parentheses is bound to fail to"
" satisfy the splitting algorithm because the head or the tail"
@ -691,13 +770,42 @@ def right_hand_split(
" line."
) from e
ensure_visible(opening_bracket)
ensure_visible(closing_bracket)
for result in (head, body, tail):
ensure_visible(rhs.opening_bracket)
ensure_visible(rhs.closing_bracket)
for result in (rhs.head, rhs.body, rhs.tail):
if result:
yield result
def _prefer_split_rhs_oop(rhs_oop: _RHSResult, line_length: int) -> bool:
"""
Returns whether we should prefer the result from a split omitting optional parens.
"""
has_closing_bracket_after_assign = False
for leaf in reversed(rhs_oop.head.leaves):
if leaf.type == token.EQUAL:
break
if leaf.type in CLOSING_BRACKETS:
has_closing_bracket_after_assign = True
break
return (
# contains matching brackets after the `=` (done by checking there is a
# closing bracket)
has_closing_bracket_after_assign
or (
# the split is actually from inside the optional parens (done by checking
# the first line still contains the `=`)
any(leaf.type == token.EQUAL for leaf in rhs_oop.head.leaves)
# the first line is short enough
and is_line_short_enough(rhs_oop.head, line_length=line_length)
)
# contains unsplittable type ignore
or rhs_oop.head.contains_unsplittable_type_ignore()
or rhs_oop.body.contains_unsplittable_type_ignore()
or rhs_oop.tail.contains_unsplittable_type_ignore()
)
def bracket_split_succeeded_or_raise(head: Line, body: Line, tail: Line) -> None:
"""Raise :exc:`CannotSplit` if the last left- or right-hand split failed.
@ -1287,8 +1395,10 @@ def run_transformer(
result.extend(transform_line(transformed_line, mode=mode, features=features))
features_set = set(features)
if (
transform.__class__.__name__ != "rhs"
Feature.FORCE_OPTIONAL_PARENTHESES in features_set
or transform.__class__.__name__ != "rhs"
or not line.bracket_tracker.invisible
or any(bracket.value for bracket in line.bracket_tracker.invisible)
or line.contains_multiline_strings()
@ -1305,7 +1415,7 @@ def run_transformer(
line_copy = line.clone()
append_leaves(line_copy, line, line.leaves)
features_fop = set(features) | {Feature.FORCE_OPTIONAL_PARENTHESES}
features_fop = features_set | {Feature.FORCE_OPTIONAL_PARENTHESES}
second_opinion = run_transformer(
line_copy, transform, mode, features_fop, line_str=line_str
)

View File

@ -151,7 +151,11 @@ class Preview(Enum):
annotation_parens = auto()
long_docstring_quotes_on_newline = auto()
# NOTE: string_processing requires wrap_long_dict_values_in_parens
# for https://github.com/psf/black/issues/3117 to be fixed.
string_processing = auto()
skip_magic_trailing_comma_in_subscript = auto()
wrap_long_dict_values_in_parens = auto()
class Deprecated(UserWarning):

View File

@ -1638,6 +1638,8 @@ class StringParenWrapper(BaseStringSplitter, CustomSplitMapMixin):
* The line is a dictionary key assignment where some valid key is being
assigned the value of some string.
OR
* The line is an lambda expression and the value is a string.
OR
* The line starts with an "atom" string that prefers to be wrapped in
parens. It's preferred to be wrapped when the string is surrounded by
commas (or is the first/last child).
@ -1683,7 +1685,7 @@ def do_splitter_match(self, line: Line) -> TMatchResult:
or self._else_match(LL)
or self._assert_match(LL)
or self._assign_match(LL)
or self._dict_match(LL)
or self._dict_or_lambda_match(LL)
or self._prefer_paren_wrap_match(LL)
)
@ -1841,22 +1843,23 @@ def _assign_match(LL: List[Leaf]) -> Optional[int]:
return None
@staticmethod
def _dict_match(LL: List[Leaf]) -> Optional[int]:
def _dict_or_lambda_match(LL: List[Leaf]) -> Optional[int]:
"""
Returns:
string_idx such that @LL[string_idx] is equal to our target (i.e.
matched) string, if this line matches the dictionary key assignment
statement requirements listed in the 'Requirements' section of this
classes' docstring.
statement or lambda expression requirements listed in the
'Requirements' section of this classes' docstring.
OR
None, otherwise.
"""
# If this line is apart of a dictionary key assignment...
if syms.dictsetmaker in [parent_type(LL[0]), parent_type(LL[0].parent)]:
# If this line is a part of a dictionary key assignment or lambda expression...
parent_types = [parent_type(LL[0]), parent_type(LL[0].parent)]
if syms.dictsetmaker in parent_types or syms.lambdef in parent_types:
is_valid_index = is_valid_index_factory(LL)
for i, leaf in enumerate(LL):
# We MUST find a colon...
# We MUST find a colon, it can either be dict's or lambda's colon...
if leaf.type == token.COLON:
idx = i + 2 if is_empty_par(LL[i + 1]) else i + 1
@ -1951,6 +1954,25 @@ def do_transform(self, line: Line, string_idx: int) -> Iterator[TResult[Line]]:
f" (left_leaves={left_leaves}, right_leaves={right_leaves})"
)
old_rpar_leaf = right_leaves.pop()
elif right_leaves and right_leaves[-1].type == token.RPAR:
# Special case for lambda expressions as dict's value, e.g.:
# my_dict = {
# "key": lambda x: f"formatted: {x},
# }
# After wrapping the dict's value with parentheses, the string is
# followed by a RPAR but its opening bracket is lambda's, not
# the string's:
# "key": (lambda x: f"formatted: {x}),
opening_bracket = right_leaves[-1].opening_bracket
if opening_bracket is not None and opening_bracket in left_leaves:
index = left_leaves.index(opening_bracket)
if (
index > 0
and index < len(left_leaves) - 1
and left_leaves[index - 1].type == token.COLON
and left_leaves[index + 1].value == "lambda"
):
right_leaves.pop()
append_leaves(string_line, line, right_leaves)

View File

@ -54,13 +54,11 @@ def single_quote_docstring_over_line_limit2():
def docstring_almost_at_line_limit():
"""long docstring.................................................................
"""
"""long docstring................................................................."""
def docstring_almost_at_line_limit_with_prefix():
f"""long docstring................................................................
"""
f"""long docstring................................................................"""
def mulitline_docstring_almost_at_line_limit():

View File

@ -0,0 +1,90 @@
my_dict = {
"something_something":
r"Lorem ipsum dolor sit amet, an sed convenire eloquentiam \t"
r"signiferumque, duo ea vocibus consetetur scriptorem. Facer \t"
r"signiferumque, duo ea vocibus consetetur scriptorem. Facer \t",
}
my_dict = {
"a key in my dict": a_very_long_variable * and_a_very_long_function_call() / 100000.0
}
my_dict = {
"a key in my dict": a_very_long_variable * and_a_very_long_function_call() * and_another_long_func() / 100000.0
}
my_dict = {
"a key in my dict": MyClass.some_attribute.first_call().second_call().third_call(some_args="some value")
}
{
'xxxxxx':
xxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxx(
xxxxxxxxxxxxxx={
'x':
xxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxx(
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=(
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(
xxxxxxxxxxxxx=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx={
'x': x.xx,
'x': x.x,
}))))
}),
}
# output
my_dict = {
"something_something": (
r"Lorem ipsum dolor sit amet, an sed convenire eloquentiam \t"
r"signiferumque, duo ea vocibus consetetur scriptorem. Facer \t"
r"signiferumque, duo ea vocibus consetetur scriptorem. Facer \t"
),
}
my_dict = {
"a key in my dict": (
a_very_long_variable * and_a_very_long_function_call() / 100000.0
)
}
my_dict = {
"a key in my dict": (
a_very_long_variable
* and_a_very_long_function_call()
* and_another_long_func()
/ 100000.0
)
}
my_dict = {
"a key in my dict": (
MyClass.some_attribute.first_call()
.second_call()
.third_call(some_args="some value")
)
}
{
"xxxxxx": xxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxx(
xxxxxxxxxxxxxx={
"x": xxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxx(
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=(
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(
xxxxxxxxxxxxx=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx={
"x": x.xx,
"x": x.x,
}
)
)
)
)
}
),
}

View File

@ -278,6 +278,15 @@ def foo():
"........................................................................... \\N{LAO KO LA}"
)
msg = lambda x: f"this is a very very very long lambda value {x} that doesn't fit on a single line"
dict_with_lambda_values = {
"join": lambda j: (
f"{j.__class__.__name__}({some_function_call(j.left)}, "
f"{some_function_call(j.right)})"
),
}
# output
@ -362,9 +371,8 @@ def foo():
"A %s %s"
% ("formatted", "string"): (
"This is a really really really long string that has to go inside of a"
" dictionary. It is %s bad (#%d)."
)
% ("soooo", 2),
" dictionary. It is %s bad (#%d)." % ("soooo", 2)
),
}
D5 = { # Test for https://github.com/psf/black/issues/3261
@ -806,3 +814,17 @@ def foo():
"..........................................................................."
" \\N{LAO KO LA}"
)
msg = (
lambda x: (
f"this is a very very very long lambda value {x} that doesn't fit on a single"
" line"
)
)
dict_with_lambda_values = {
"join": lambda j: (
f"{j.__class__.__name__}({some_function_call(j.left)}, "
f"{some_function_call(j.right)})"
),
}

View File

@ -524,6 +524,13 @@ async def foo(self):
},
)
# Regression test for https://github.com/psf/black/issues/3117.
some_dict = {
"something_something":
r"Lorem ipsum dolor sit amet, an sed convenire eloquentiam \t"
r"signiferumque, duo ea vocibus consetetur scriptorem. Facer \t",
}
# output
@ -976,9 +983,9 @@ def xxxxxxx_xxxxxx(xxxx):
)
value.__dict__[
key
] = "test" # set some Thrift field to non-None in the struct aa bb cc dd ee
value.__dict__[key] = (
"test" # set some Thrift field to non-None in the struct aa bb cc dd ee
)
RE_ONE_BACKSLASH = {
"asdf_hjkl_jkl": re.compile(
@ -1178,3 +1185,11 @@ async def foo(self):
),
},
)
# Regression test for https://github.com/psf/black/issues/3117.
some_dict = {
"something_something": (
r"Lorem ipsum dolor sit amet, an sed convenire eloquentiam \t"
r"signiferumque, duo ea vocibus consetetur scriptorem. Facer \t"
),
}

View File

@ -0,0 +1,85 @@
first_item, second_item = (
some_looooooooong_module.some_looooooooooooooong_function_name(
first_argument, second_argument, third_argument
)
)
some_dict["with_a_long_key"] = (
some_looooooooong_module.some_looooooooooooooong_function_name(
first_argument, second_argument, third_argument
)
)
# Make sure it works when the RHS only has one pair of (optional) parens.
first_item, second_item = (
some_looooooooong_module.SomeClass.some_looooooooooooooong_variable_name
)
some_dict["with_a_long_key"] = (
some_looooooooong_module.SomeClass.some_looooooooooooooong_variable_name
)
# Make sure chaining assignments work.
first_item, second_item, third_item, forth_item = m["everything"] = (
some_looooooooong_module.some_looooooooooooooong_function_name(
first_argument, second_argument, third_argument
)
)
# Make sure when the RHS's first split at the non-optional paren fits,
# we split there instead of the outer RHS optional paren.
first_item, second_item = some_looooooooong_module.some_loooooog_function_name(
first_argument, second_argument, third_argument
)
(
first_item,
second_item,
third_item,
forth_item,
fifth_item,
last_item_very_loooooong,
) = some_looooooooong_module.some_looooooooooooooong_function_name(
first_argument, second_argument, third_argument
)
(
first_item,
second_item,
third_item,
forth_item,
fifth_item,
last_item_very_loooooong,
) = everyting = some_loooooog_function_name(
first_argument, second_argument, third_argument
)
# Make sure unsplittable type ignore won't be moved.
some_kind_of_table[some_key] = util.some_function( # type: ignore # noqa: E501
some_arg
).intersection(pk_cols)
some_kind_of_table[
some_key
] = lambda obj: obj.some_long_named_method() # type: ignore # noqa: E501
some_kind_of_table[
some_key # type: ignore # noqa: E501
] = lambda obj: obj.some_long_named_method()
# Make when when the left side of assignement plus the opening paren "... = (" is
# exactly line length limit + 1, it won't be split like that.
xxxxxxxxx_yyy_zzzzzzzz[
xx.xxxxxx(x_yyy_zzzzzz.xxxxx[0]), x_yyy_zzzzzz.xxxxxx(xxxx=1)
] = 1
# Right side of assignment contains un-nested pairs of inner parens.
some_kind_of_instance.some_kind_of_map[a_key] = (
isinstance(some_var, SomeClass)
and table.something_and_something != table.something_else
) or (
isinstance(some_other_var, BaseClass) and table.something != table.some_other_thing
)

View File

@ -0,0 +1,38 @@
# Test cases separate from `prefer_rhs_split.py` that contains unformatted source.
# Left hand side fits in a single line but will still be exploded by the
# magic trailing comma.
first_value, (m1, m2,), third_value = xxxxxx_yyyyyy_zzzzzz_wwwwww_uuuuuuu_vvvvvvvvvvv(
arg1,
arg2,
)
# Make when when the left side of assignement plus the opening paren "... = (" is
# exactly line length limit + 1, it won't be split like that.
xxxxxxxxx_yyy_zzzzzzzz[xx.xxxxxx(x_yyy_zzzzzz.xxxxx[0]), x_yyy_zzzzzz.xxxxxx(xxxx=1)] = 1
# output
# Test cases separate from `prefer_rhs_split.py` that contains unformatted source.
# Left hand side fits in a single line but will still be exploded by the
# magic trailing comma.
(
first_value,
(
m1,
m2,
),
third_value,
) = xxxxxx_yyyyyy_zzzzzz_wwwwww_uuuuuuu_vvvvvvvvvvv(
arg1,
arg2,
)
# Make when when the left side of assignement plus the opening paren "... = (" is
# exactly line length limit + 1, it won't be split like that.
xxxxxxxxx_yyy_zzzzzzzz[
xx.xxxxxx(x_yyy_zzzzzz.xxxxx[0]), x_yyy_zzzzzz.xxxxxx(xxxx=1)
] = 1

View File

@ -64,7 +64,7 @@ async def call(param):
print ( "This will be formatted" )
# Regression test for https://github.com/psf/black/issues/2985
# Regression test for https://github.com/psf/black/issues/2985.
class Named(t.Protocol):
# fmt: off
@property
@ -75,6 +75,15 @@ def this_will_be_formatted ( self, **kwargs ) -> Named: ...
# fmt: on
# Regression test for https://github.com/psf/black/issues/3436.
if x:
return x
# fmt: off
elif unformatted:
# fmt: on
will_be_formatted ()
# output
@ -144,7 +153,7 @@ async def call(param):
print("This will be formatted")
# Regression test for https://github.com/psf/black/issues/2985
# Regression test for https://github.com/psf/black/issues/2985.
class Named(t.Protocol):
# fmt: off
@property
@ -156,3 +165,12 @@ def this_will_be_formatted(self, **kwargs) -> Named:
...
# fmt: on
# Regression test for https://github.com/psf/black/issues/3436.
if x:
return x
# fmt: off
elif unformatted:
# fmt: on
will_be_formatted()

View File

@ -17,7 +17,7 @@ def test_ipynb_diff_with_no_change_single() -> None:
result = runner.invoke(main, [str(path)])
expected_output = (
"Skipping .ipynb files as Jupyter dependencies are not installed.\n"
"You can fix this by running ``pip install black[jupyter]``\n"
'You can fix this by running ``pip install "black[jupyter]"``\n'
)
assert expected_output in result.output
@ -32,6 +32,6 @@ def test_ipynb_diff_with_no_change_dir(tmp_path: pathlib.Path) -> None:
result = runner.invoke(main, [str(tmp_path)])
expected_output = (
"Skipping .ipynb files as Jupyter dependencies are not installed.\n"
"You can fix this by running ``pip install black[jupyter]``\n"
'You can fix this by running ``pip install "black[jupyter]"``\n'
)
assert expected_output in result.output