Add --skip-magic-trailing-comma (#1824)

This commit is contained in:
Shantanu 2021-01-17 16:59:06 -08:00 committed by GitHub
parent de510478d9
commit 692c0f50d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 531 additions and 36 deletions

View File

@ -97,6 +97,10 @@ Options:
-S, --skip-string-normalization -S, --skip-string-normalization
Don't normalize string quotes or prefixes. Don't normalize string quotes or prefixes.
-C, --skip-magic-trailing-comma
Don't use trailing commas as a reason to
split lines.
--check Don't write the files back, just return the --check Don't write the files back, just return the
status. Return code 0 means nothing would status. Return code 0 means nothing would
change. Return code 1 means some files change. Return code 1 means some files
@ -127,18 +131,19 @@ Options:
paths are excluded. Use forward slashes for paths are excluded. Use forward slashes for
directories on all platforms (Windows, too). directories on all platforms (Windows, too).
Exclusions are calculated first, inclusions Exclusions are calculated first, inclusions
later. [default: /(\.eggs|\.git|\.hg|\.mypy later. [default: /(\.direnv|\.eggs|\.git|\.
_cache|\.nox|\.tox|\.venv|\.svn|_build|buck- hg|\.mypy_cache|\.nox|\.tox|\.venv|\.svn|_bu
out|build|dist)/] ild|buck-out|build|dist)/]
--force-exclude TEXT Like --exclude, but files and directories --force-exclude TEXT Like --exclude, but files and directories
matching this regex will be excluded even matching this regex will be excluded even
when they are passed explicitly as arguments. when they are passed explicitly as
arguments.
--stdin-filename TEXT The name of the file when passing it through --stdin-filename TEXT The name of the file when passing it through
stdin. Useful to make sure Black will respect stdin. Useful to make sure Black will
--force-exclude option on some editors that respect --force-exclude option on some
rely on using stdin. editors that rely on using stdin.
-q, --quiet Don't emit non-error messages to stderr. -q, --quiet Don't emit non-error messages to stderr.
Errors are still emitted; silence those with Errors are still emitted; silence those with

View File

@ -54,6 +54,9 @@ The headers controlling how source code is formatted are:
- `X-Skip-String-Normalization`: corresponds to the `--skip-string-normalization` - `X-Skip-String-Normalization`: corresponds to the `--skip-string-normalization`
command line flag. If present and its value is not the empty string, no string command line flag. If present and its value is not the empty string, no string
normalization will be performed. normalization will be performed.
- `X-Skip-Magic-Trailing-Comma`: corresponds to the `--skip-magic-trailing-comma`
command line flag. If present and its value is not the empty string, trailing commas
will not be used as a reason to split lines.
- `X-Fast-Or-Safe`: if set to `fast`, `blackd` will act as _Black_ does when passed the - `X-Fast-Or-Safe`: if set to `fast`, `blackd` will act as _Black_ does when passed the
`--fast` command line flag. `--fast` command line flag.
- `X-Python-Variant`: if set to `pyi`, `blackd` will act as _Black_ does when passed the - `X-Python-Variant`: if set to `pyi`, `blackd` will act as _Black_ does when passed the

View File

@ -52,6 +52,10 @@ Options:
-S, --skip-string-normalization -S, --skip-string-normalization
Don't normalize string quotes or prefixes. Don't normalize string quotes or prefixes.
-C, --skip-magic-trailing-comma
Don't use trailing commas as a reason to
split lines.
--check Don't write the files back, just return the --check Don't write the files back, just return the
status. Return code 0 means nothing would status. Return code 0 means nothing would
change. Return code 1 means some files change. Return code 1 means some files
@ -82,13 +86,19 @@ Options:
paths are excluded. Use forward slashes for paths are excluded. Use forward slashes for
directories on all platforms (Windows, too). directories on all platforms (Windows, too).
Exclusions are calculated first, inclusions Exclusions are calculated first, inclusions
later. [default: /(\.eggs|\.git|\.hg|\.mypy later. [default: /(\.direnv|\.eggs|\.git|\.
_cache|\.nox|\.tox|\.venv|\.svn|_build|buck- hg|\.mypy_cache|\.nox|\.tox|\.venv|\.svn|_bu
out|build|dist)/] ild|buck-out|build|dist)/]
--force-exclude TEXT Like --exclude, but files and directories --force-exclude TEXT Like --exclude, but files and directories
matching this regex will be excluded even matching this regex will be excluded even
when they are passed explicitly as arguments. when they are passed explicitly as
arguments.
--stdin-filename TEXT The name of the file when passing it through
stdin. Useful to make sure Black will
respect --force-exclude option on some
editors that rely on using stdin.
--stdin-filename TEXT The name of the file when passing it through --stdin-filename TEXT The name of the file when passing it through
stdin. Useful to make sure Black will respect stdin. Useful to make sure Black will respect

View File

@ -438,6 +438,9 @@ into one item per line.
How do you make it stop? Just delete that trailing comma and _Black_ will collapse your How do you make it stop? Just delete that trailing comma and _Black_ will collapse your
collection into one line if it fits. collection into one line if it fits.
If you must, you can recover the behaviour of early versions of Black with the option
`--skip-magic-trailing-comma` / `-C`.
### r"strings" and R"strings" ### r"strings" and R"strings"
_Black_ normalizes string quotes as well as string prefixes, making them lowercase. One _Black_ normalizes string quotes as well as string prefixes, making them lowercase. One

View File

@ -260,6 +260,7 @@ class Mode:
target_versions: Set[TargetVersion] = field(default_factory=set) target_versions: Set[TargetVersion] = field(default_factory=set)
line_length: int = DEFAULT_LINE_LENGTH line_length: int = DEFAULT_LINE_LENGTH
string_normalization: bool = True string_normalization: bool = True
magic_trailing_comma: bool = True
experimental_string_processing: bool = False experimental_string_processing: bool = False
is_pyi: bool = False is_pyi: bool = False
@ -397,6 +398,12 @@ def target_version_option_callback(
is_flag=True, is_flag=True,
help="Don't normalize string quotes or prefixes.", help="Don't normalize string quotes or prefixes.",
) )
@click.option(
"-C",
"--skip-magic-trailing-comma",
is_flag=True,
help="Don't use trailing commas as a reason to split lines.",
)
@click.option( @click.option(
"--experimental-string-processing", "--experimental-string-processing",
is_flag=True, is_flag=True,
@ -524,6 +531,7 @@ def main(
fast: bool, fast: bool,
pyi: bool, pyi: bool,
skip_string_normalization: bool, skip_string_normalization: bool,
skip_magic_trailing_comma: bool,
experimental_string_processing: bool, experimental_string_processing: bool,
quiet: bool, quiet: bool,
verbose: bool, verbose: bool,
@ -546,6 +554,7 @@ def main(
line_length=line_length, line_length=line_length,
is_pyi=pyi, is_pyi=pyi,
string_normalization=not skip_string_normalization, string_normalization=not skip_string_normalization,
magic_trailing_comma=not skip_magic_trailing_comma,
experimental_string_processing=experimental_string_processing, experimental_string_processing=experimental_string_processing,
) )
if config and verbose: if config and verbose:
@ -1022,13 +1031,12 @@ def f(
versions = detect_target_versions(src_node) versions = detect_target_versions(src_node)
normalize_fmt_off(src_node) normalize_fmt_off(src_node)
lines = LineGenerator( lines = LineGenerator(
mode=mode,
remove_u_prefix="unicode_literals" in future_imports remove_u_prefix="unicode_literals" in future_imports
or supports_feature(versions, Feature.UNICODE_LITERALS), or supports_feature(versions, Feature.UNICODE_LITERALS),
is_pyi=mode.is_pyi,
normalize_strings=mode.string_normalization,
) )
elt = EmptyLineTracker(is_pyi=mode.is_pyi) elt = EmptyLineTracker(is_pyi=mode.is_pyi)
empty_line = Line() empty_line = Line(mode=mode)
after = 0 after = 0
split_line_features = { split_line_features = {
feature feature
@ -1464,6 +1472,7 @@ def get_open_lsqb(self) -> Optional[Leaf]:
class Line: class Line:
"""Holds leaves and comments. Can be printed with `str(line)`.""" """Holds leaves and comments. Can be printed with `str(line)`."""
mode: Mode
depth: int = 0 depth: int = 0
leaves: List[Leaf] = field(default_factory=list) leaves: List[Leaf] = field(default_factory=list)
# keys ordered like `leaves` # keys ordered like `leaves`
@ -1496,8 +1505,11 @@ def append(self, leaf: Leaf, preformatted: bool = False) -> None:
) )
if self.inside_brackets or not preformatted: if self.inside_brackets or not preformatted:
self.bracket_tracker.mark(leaf) self.bracket_tracker.mark(leaf)
if self.maybe_should_explode(leaf): if self.mode.magic_trailing_comma:
if self.has_magic_trailing_comma(leaf):
self.should_explode = True self.should_explode = True
elif self.has_magic_trailing_comma(leaf, ensure_removable=True):
self.remove_trailing_comma()
if not self.append_comment(leaf): if not self.append_comment(leaf):
self.leaves.append(leaf) self.leaves.append(leaf)
@ -1673,10 +1685,14 @@ def contains_unsplittable_type_ignore(self) -> bool:
def contains_multiline_strings(self) -> bool: def contains_multiline_strings(self) -> bool:
return any(is_multiline_string(leaf) for leaf in self.leaves) return any(is_multiline_string(leaf) for leaf in self.leaves)
def maybe_should_explode(self, closing: Leaf) -> bool: def has_magic_trailing_comma(
"""Return True if this line should explode (always be split), that is when: self, closing: Leaf, ensure_removable: bool = False
- there's a trailing comma here; and ) -> bool:
- it's not a one-tuple. """Return True if we have a magic trailing comma, that is when:
- there's a trailing comma here
- it's not a one-tuple
Additionally, if ensure_removable:
- it's not from square bracket indexing
""" """
if not ( if not (
closing.type in CLOSING_BRACKETS closing.type in CLOSING_BRACKETS
@ -1685,9 +1701,15 @@ def maybe_should_explode(self, closing: Leaf) -> bool:
): ):
return False return False
if closing.type in {token.RBRACE, token.RSQB}: if closing.type == token.RBRACE:
return True return True
if closing.type == token.RSQB:
if not ensure_removable:
return True
comma = self.leaves[-1]
return bool(comma.parent and comma.parent.type == syms.listmaker)
if self.is_import: if self.is_import:
return True return True
@ -1765,6 +1787,7 @@ def is_complex_subscript(self, leaf: Leaf) -> bool:
def clone(self) -> "Line": def clone(self) -> "Line":
return Line( return Line(
mode=self.mode,
depth=self.depth, depth=self.depth,
inside_brackets=self.inside_brackets, inside_brackets=self.inside_brackets,
should_explode=self.should_explode, should_explode=self.should_explode,
@ -1923,10 +1946,9 @@ class LineGenerator(Visitor[Line]):
in ways that will no longer stringify to valid Python code on the tree. in ways that will no longer stringify to valid Python code on the tree.
""" """
is_pyi: bool = False mode: Mode
normalize_strings: bool = True
current_line: Line = field(default_factory=Line)
remove_u_prefix: bool = False remove_u_prefix: bool = False
current_line: Line = field(init=False)
def line(self, indent: int = 0) -> Iterator[Line]: def line(self, indent: int = 0) -> Iterator[Line]:
"""Generate a line. """Generate a line.
@ -1941,7 +1963,7 @@ def line(self, indent: int = 0) -> Iterator[Line]:
return # Line is empty, don't emit. Creating a new one unnecessary. return # Line is empty, don't emit. Creating a new one unnecessary.
complete_line = self.current_line complete_line = self.current_line
self.current_line = Line(depth=complete_line.depth + indent) self.current_line = Line(mode=self.mode, depth=complete_line.depth + indent)
yield complete_line yield complete_line
def visit_default(self, node: LN) -> Iterator[Line]: def visit_default(self, node: LN) -> Iterator[Line]:
@ -1965,7 +1987,7 @@ def visit_default(self, node: LN) -> Iterator[Line]:
yield from self.line() yield from self.line()
normalize_prefix(node, inside_brackets=any_open_brackets) normalize_prefix(node, inside_brackets=any_open_brackets)
if self.normalize_strings and node.type == token.STRING: if self.mode.string_normalization and node.type == token.STRING:
normalize_string_prefix(node, remove_u_prefix=self.remove_u_prefix) normalize_string_prefix(node, remove_u_prefix=self.remove_u_prefix)
normalize_string_quotes(node) normalize_string_quotes(node)
if node.type == token.NUMBER: if node.type == token.NUMBER:
@ -2017,7 +2039,7 @@ def visit_stmt(
def visit_suite(self, node: Node) -> Iterator[Line]: def visit_suite(self, node: Node) -> Iterator[Line]:
"""Visit a suite.""" """Visit a suite."""
if self.is_pyi and is_stub_suite(node): if self.mode.is_pyi and is_stub_suite(node):
yield from self.visit(node.children[2]) yield from self.visit(node.children[2])
else: else:
yield from self.visit_default(node) yield from self.visit_default(node)
@ -2026,7 +2048,7 @@ def visit_simple_stmt(self, node: Node) -> Iterator[Line]:
"""Visit a statement without nested statements.""" """Visit a statement without nested statements."""
is_suite_like = node.parent and node.parent.type in STATEMENT is_suite_like = node.parent and node.parent.type in STATEMENT
if is_suite_like: if is_suite_like:
if self.is_pyi and is_stub_body(node): if self.mode.is_pyi and is_stub_body(node):
yield from self.visit_default(node) yield from self.visit_default(node)
else: else:
yield from self.line(+1) yield from self.line(+1)
@ -2034,7 +2056,11 @@ def visit_simple_stmt(self, node: Node) -> Iterator[Line]:
yield from self.line(-1) yield from self.line(-1)
else: else:
if not self.is_pyi or not node.parent or not is_stub_suite(node.parent): if (
not self.mode.is_pyi
or not node.parent
or not is_stub_suite(node.parent)
):
yield from self.line() yield from self.line()
yield from self.visit_default(node) yield from self.visit_default(node)
@ -2110,6 +2136,8 @@ def visit_STRING(self, leaf: Leaf) -> Iterator[Line]:
def __post_init__(self) -> None: def __post_init__(self) -> None:
"""You are in a twisty little maze of passages.""" """You are in a twisty little maze of passages."""
self.current_line = Line(mode=self.mode)
v = self.visit_stmt v = self.visit_stmt
Ø: Set[str] = set() Ø: Set[str] = set()
self.visit_assert_stmt = partial(v, keywords={"assert"}, parens={"assert", ","}) self.visit_assert_stmt = partial(v, keywords={"assert"}, parens={"assert", ","})
@ -4350,6 +4378,7 @@ def do_transform(self, line: Line, string_idx: int) -> Iterator[TResult[Line]]:
# `StringSplitter` will break it down further if necessary. # `StringSplitter` will break it down further if necessary.
string_value = LL[string_idx].value string_value = LL[string_idx].value
string_line = Line( string_line = Line(
mode=line.mode,
depth=line.depth + 1, depth=line.depth + 1,
inside_brackets=True, inside_brackets=True,
should_explode=line.should_explode, should_explode=line.should_explode,
@ -4943,7 +4972,7 @@ def bracket_split_build_line(
If `is_body` is True, the result line is one-indented inside brackets and as such If `is_body` is True, the result line is one-indented inside brackets and as such
has its first leaf's prefix normalized and a trailing comma added when expected. has its first leaf's prefix normalized and a trailing comma added when expected.
""" """
result = Line(depth=original.depth) result = Line(mode=original.mode, depth=original.depth)
if is_body: if is_body:
result.inside_brackets = True result.inside_brackets = True
result.depth += 1 result.depth += 1
@ -5015,7 +5044,9 @@ def delimiter_split(line: Line, features: Collection[Feature] = ()) -> Iterator[
if bt.delimiter_count_with_priority(delimiter_priority) == 1: if bt.delimiter_count_with_priority(delimiter_priority) == 1:
raise CannotSplit("Splitting a single attribute from its owner looks wrong") raise CannotSplit("Splitting a single attribute from its owner looks wrong")
current_line = Line(depth=line.depth, inside_brackets=line.inside_brackets) current_line = Line(
mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets
)
lowest_depth = sys.maxsize lowest_depth = sys.maxsize
trailing_comma_safe = True trailing_comma_safe = True
@ -5027,7 +5058,9 @@ def append_to_line(leaf: Leaf) -> Iterator[Line]:
except ValueError: except ValueError:
yield current_line yield current_line
current_line = Line(depth=line.depth, inside_brackets=line.inside_brackets) current_line = Line(
mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets
)
current_line.append(leaf) current_line.append(leaf)
for leaf in line.leaves: for leaf in line.leaves:
@ -5051,7 +5084,9 @@ def append_to_line(leaf: Leaf) -> Iterator[Line]:
if leaf_priority == delimiter_priority: if leaf_priority == delimiter_priority:
yield current_line yield current_line
current_line = Line(depth=line.depth, inside_brackets=line.inside_brackets) current_line = Line(
mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets
)
if current_line: if current_line:
if ( if (
trailing_comma_safe trailing_comma_safe
@ -5072,7 +5107,9 @@ def standalone_comment_split(
if not line.contains_standalone_comments(0): if not line.contains_standalone_comments(0):
raise CannotSplit("Line does not have any standalone comments") raise CannotSplit("Line does not have any standalone comments")
current_line = Line(depth=line.depth, inside_brackets=line.inside_brackets) current_line = Line(
mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets
)
def append_to_line(leaf: Leaf) -> Iterator[Line]: def append_to_line(leaf: Leaf) -> Iterator[Line]:
"""Append `leaf` to current line or to new line if appending impossible.""" """Append `leaf` to current line or to new line if appending impossible."""
@ -5082,7 +5119,9 @@ def append_to_line(leaf: Leaf) -> Iterator[Line]:
except ValueError: except ValueError:
yield current_line yield current_line
current_line = Line(depth=line.depth, inside_brackets=line.inside_brackets) current_line = Line(
line.mode, depth=line.depth, inside_brackets=line.inside_brackets
)
current_line.append(leaf) current_line.append(leaf)
for leaf in line.leaves: for leaf in line.leaves:
@ -5767,7 +5806,7 @@ def should_split_body_explode(line: Line, opening_bracket: Leaf) -> bool:
return False return False
return max_priority == COMMA_PRIORITY and ( return max_priority == COMMA_PRIORITY and (
trailing_comma (line.mode.magic_trailing_comma and trailing_comma)
# always explode imports # always explode imports
or opening_bracket.parent.type in {syms.atom, syms.import_from} or opening_bracket.parent.type in {syms.atom, syms.import_from}
) )

View File

@ -32,6 +32,7 @@
LINE_LENGTH_HEADER = "X-Line-Length" LINE_LENGTH_HEADER = "X-Line-Length"
PYTHON_VARIANT_HEADER = "X-Python-Variant" PYTHON_VARIANT_HEADER = "X-Python-Variant"
SKIP_STRING_NORMALIZATION_HEADER = "X-Skip-String-Normalization" SKIP_STRING_NORMALIZATION_HEADER = "X-Skip-String-Normalization"
SKIP_MAGIC_TRAILING_COMMA = "X-Skip-Magic-Trailing-Comma"
FAST_OR_SAFE_HEADER = "X-Fast-Or-Safe" FAST_OR_SAFE_HEADER = "X-Fast-Or-Safe"
DIFF_HEADER = "X-Diff" DIFF_HEADER = "X-Diff"
@ -40,6 +41,7 @@
LINE_LENGTH_HEADER, LINE_LENGTH_HEADER,
PYTHON_VARIANT_HEADER, PYTHON_VARIANT_HEADER,
SKIP_STRING_NORMALIZATION_HEADER, SKIP_STRING_NORMALIZATION_HEADER,
SKIP_MAGIC_TRAILING_COMMA,
FAST_OR_SAFE_HEADER, FAST_OR_SAFE_HEADER,
DIFF_HEADER, DIFF_HEADER,
] ]
@ -114,6 +116,9 @@ async def handle(request: web.Request, executor: Executor) -> web.Response:
skip_string_normalization = bool( skip_string_normalization = bool(
request.headers.get(SKIP_STRING_NORMALIZATION_HEADER, False) request.headers.get(SKIP_STRING_NORMALIZATION_HEADER, False)
) )
skip_magic_trailing_comma = bool(
request.headers.get(SKIP_MAGIC_TRAILING_COMMA, False)
)
fast = False fast = False
if request.headers.get(FAST_OR_SAFE_HEADER, "safe") == "fast": if request.headers.get(FAST_OR_SAFE_HEADER, "safe") == "fast":
fast = True fast = True
@ -122,6 +127,7 @@ async def handle(request: web.Request, executor: Executor) -> web.Response:
is_pyi=pyi, is_pyi=pyi,
line_length=line_length, line_length=line_length,
string_normalization=not skip_string_normalization, string_normalization=not skip_string_normalization,
magic_trailing_comma=not skip_magic_trailing_comma,
) )
req_bytes = await request.content.read() req_bytes = await request.content.read()
charset = request.charset if request.charset is not None else "utf8" charset = request.charset if request.charset is not None else "utf8"

View File

@ -0,0 +1,404 @@
--- [Deterministic header]
+++ [Deterministic header]
@@ -1,8 +1,8 @@
...
-'some_string'
-b'\\xa3'
+"some_string"
+b"\\xa3"
Name
None
True
False
1
@@ -29,63 +29,84 @@
~great
+value
-1
~int and not v1 ^ 123 + v2 | True
(~int) and (not ((v1 ^ (123 + v2)) | True))
-+really ** -confusing ** ~operator ** -precedence
-flags & ~ select.EPOLLIN and waiters.write_task is not None
++(really ** -(confusing ** ~(operator ** -precedence)))
+flags & ~select.EPOLLIN and waiters.write_task is not None
lambda arg: None
lambda a=True: a
lambda a, b, c=True: a
-lambda a, b, c=True, *, d=(1 << v2), e='str': a
-lambda a, b, c=True, *vararg, d=(v1 << 2), e='str', **kwargs: a + b
+lambda a, b, c=True, *, d=(1 << v2), e="str": a
+lambda a, b, c=True, *vararg, d=(v1 << 2), e="str", **kwargs: a + b
manylambdas = lambda x=lambda y=lambda z=1: z: y(): x()
-foo = (lambda port_id, ignore_missing: {"port1": port1_resource, "port2": port2_resource}[port_id])
+foo = lambda port_id, ignore_missing: {
+ "port1": port1_resource,
+ "port2": port2_resource,
+}[port_id]
1 if True else 2
str or None if True else str or bytes or None
(str or None) if True else (str or bytes or None)
str or None if (1 if True else 2) else str or bytes or None
(str or None) if (1 if True else 2) else (str or bytes or None)
-((super_long_variable_name or None) if (1 if super_long_test_name else 2) else (str or bytes or None))
-{'2.7': dead, '3.7': (long_live or die_hard)}
-{'2.7': dead, '3.7': (long_live or die_hard), **{'3.6': verygood}}
+(
+ (super_long_variable_name or None)
+ if (1 if super_long_test_name else 2)
+ else (str or bytes or None)
+)
+{"2.7": dead, "3.7": (long_live or die_hard)}
+{"2.7": dead, "3.7": (long_live or die_hard), **{"3.6": verygood}}
{**a, **b, **c}
-{'2.7', '3.6', '3.7', '3.8', '3.9', ('4.0' if gilectomy else '3.10')}
-({'a': 'b'}, (True or False), (+value), 'string', b'bytes') or None
+{"2.7", "3.6", "3.7", "3.8", "3.9", ("4.0" if gilectomy else "3.10")}
+({"a": "b"}, (True or False), (+value), "string", b"bytes") or None
()
(1,)
(1, 2)
(1, 2, 3)
[]
[1, 2, 3, 4, 5, 6, 7, 8, 9, (10 or A), (11 or B), (12 or C)]
-[1, 2, 3,]
+[1, 2, 3]
[*a]
[*range(10)]
-[*a, 4, 5,]
-[4, *a, 5,]
-[this_is_a_very_long_variable_which_will_force_a_delimiter_split, element, another, *more]
+[*a, 4, 5]
+[4, *a, 5]
+[
+ this_is_a_very_long_variable_which_will_force_a_delimiter_split,
+ element,
+ another,
+ *more,
+]
{i for i in (1, 2, 3)}
{(i ** 2) for i in (1, 2, 3)}
-{(i ** 2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))}
+{(i ** 2) for i, _ in ((1, "a"), (2, "b"), (3, "c"))}
{((i ** 2) + j) for i in (1, 2, 3) for j in (1, 2, 3)}
[i for i in (1, 2, 3)]
[(i ** 2) for i in (1, 2, 3)]
-[(i ** 2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))]
+[(i ** 2) for i, _ in ((1, "a"), (2, "b"), (3, "c"))]
[((i ** 2) + j) for i in (1, 2, 3) for j in (1, 2, 3)]
{i: 0 for i in (1, 2, 3)}
-{i: j for i, j in ((1, 'a'), (2, 'b'), (3, 'c'))}
+{i: j for i, j in ((1, "a"), (2, "b"), (3, "c"))}
{a: b * 2 for a, b in dictionary.items()}
{a: b * -2 for a, b in dictionary.items()}
-{k: v for k, v in this_is_a_very_long_variable_which_will_cause_a_trailing_comma_which_breaks_the_comprehension}
+{
+ k: v
+ for k, v in this_is_a_very_long_variable_which_will_cause_a_trailing_comma_which_breaks_the_comprehension
+}
Python3 > Python2 > COBOL
Life is Life
call()
call(arg)
-call(kwarg='hey')
-call(arg, kwarg='hey')
-call(arg, another, kwarg='hey', **kwargs)
-call(this_is_a_very_long_variable_which_will_force_a_delimiter_split, arg, another, kwarg='hey', **kwargs) # note: no trailing comma pre-3.6
+call(kwarg="hey")
+call(arg, kwarg="hey")
+call(arg, another, kwarg="hey", **kwargs)
+call(
+ this_is_a_very_long_variable_which_will_force_a_delimiter_split,
+ arg,
+ another,
+ kwarg="hey",
+ **kwargs
+) # note: no trailing comma pre-3.6
call(*gidgets[:2])
call(a, *gidgets[:2])
call(**self.screen_kwargs)
call(b, **self.screen_kwargs)
lukasz.langa.pl
@@ -94,26 +115,24 @@
1.0 .real
....__class__
list[str]
dict[str, int]
tuple[str, ...]
-tuple[
- str, int, float, dict[str, int]
-]
+tuple[str, int, float, dict[str, int]]
tuple[str, int, float, dict[str, int],]
very_long_variable_name_filters: t.List[
t.Tuple[str, t.Union[str, t.List[t.Optional[str]]]],
]
xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore
sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)
)
xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore
sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)
)
-xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[
- ..., List[SomeClass]
-] = classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)) # type: ignore
+xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod(
+ sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)
+) # type: ignore
slice[0]
slice[0:1]
slice[0:1:2]
slice[:]
slice[:-1]
@@ -137,113 +156,178 @@
numpy[-(c + 1) :, d]
numpy[:, l[-2]]
numpy[:, ::-1]
numpy[np.newaxis, :]
(str or None) if (sys.version_info[0] > (3,)) else (str or bytes or None)
-{'2.7': dead, '3.7': long_live or die_hard}
-{'2.7', '3.6', '3.7', '3.8', '3.9', '4.0' if gilectomy else '3.10'}
+{"2.7": dead, "3.7": long_live or die_hard}
+{"2.7", "3.6", "3.7", "3.8", "3.9", "4.0" if gilectomy else "3.10"}
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or A, 11 or B, 12 or C]
(SomeName)
SomeName
(Good, Bad, Ugly)
(i for i in (1, 2, 3))
((i ** 2) for i in (1, 2, 3))
-((i ** 2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c')))
+((i ** 2) for i, _ in ((1, "a"), (2, "b"), (3, "c")))
(((i ** 2) + j) for i in (1, 2, 3) for j in (1, 2, 3))
(*starred,)
-{"id": "1","type": "type","started_at": now(),"ended_at": now() + timedelta(days=10),"priority": 1,"import_session_id": 1,**kwargs}
+{
+ "id": "1",
+ "type": "type",
+ "started_at": now(),
+ "ended_at": now() + timedelta(days=10),
+ "priority": 1,
+ "import_session_id": 1,
+ **kwargs,
+}
a = (1,)
-b = 1,
+b = (1,)
c = 1
d = (1,) + a + (2,)
e = (1,).count(1)
f = 1, *range(10)
g = 1, *"ten"
-what_is_up_with_those_new_coord_names = (coord_names + set(vars_to_create)) + set(vars_to_remove)
-what_is_up_with_those_new_coord_names = (coord_names | set(vars_to_create)) - set(vars_to_remove)
-result = session.query(models.Customer.id).filter(models.Customer.account_id == account_id, models.Customer.email == email_address).order_by(models.Customer.id.asc()).all()
-result = session.query(models.Customer.id).filter(models.Customer.account_id == account_id, models.Customer.email == email_address).order_by(models.Customer.id.asc(),).all()
+what_is_up_with_those_new_coord_names = (coord_names + set(vars_to_create)) + set(
+ vars_to_remove
+)
+what_is_up_with_those_new_coord_names = (coord_names | set(vars_to_create)) - set(
+ vars_to_remove
+)
+result = (
+ session.query(models.Customer.id)
+ .filter(
+ models.Customer.account_id == account_id, models.Customer.email == email_address
+ )
+ .order_by(models.Customer.id.asc())
+ .all()
+)
+result = (
+ session.query(models.Customer.id)
+ .filter(
+ models.Customer.account_id == account_id, models.Customer.email == email_address
+ )
+ .order_by(models.Customer.id.asc())
+ .all()
+)
Ø = set()
authors.łukasz.say_thanks()
mapping = {
A: 0.25 * (10.0 / 12),
B: 0.1 * (10.0 / 12),
C: 0.1 * (10.0 / 12),
D: 0.1 * (10.0 / 12),
}
+
def gen():
yield from outside_of_generator
- a = (yield)
- b = ((yield))
- c = (((yield)))
+ a = yield
+ b = yield
+ c = yield
+
async def f():
await some.complicated[0].call(with_args=(True or (1 is not 1)))
-print(* [] or [1])
+
+
+print(*[] or [1])
print(**{1: 3} if False else {x: x for x in range(3)})
-print(* lambda x: x)
-assert(not Test),("Short message")
-assert this is ComplexTest and not requirements.fit_in_a_single_line(force=False), "Short message"
-assert(((parens is TooMany)))
-for x, in (1,), (2,), (3,): ...
-for y in (): ...
-for z in (i for i in (1, 2, 3)): ...
-for i in (call()): ...
-for j in (1 + (2 + 3)): ...
-while(this and that): ...
-for addr_family, addr_type, addr_proto, addr_canonname, addr_sockaddr in socket.getaddrinfo('google.com', 'http'):
+print(*lambda x: x)
+assert not Test, "Short message"
+assert this is ComplexTest and not requirements.fit_in_a_single_line(
+ force=False
+), "Short message"
+assert parens is TooMany
+for (x,) in (1,), (2,), (3,):
+ ...
+for y in ():
+ ...
+for z in (i for i in (1, 2, 3)):
+ ...
+for i in call():
+ ...
+for j in 1 + (2 + 3):
+ ...
+while this and that:
+ ...
+for (
+ addr_family,
+ addr_type,
+ addr_proto,
+ addr_canonname,
+ addr_sockaddr,
+) in socket.getaddrinfo("google.com", "http"):
pass
-a = aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz
-a = aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp not in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz
-a = aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp is qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz
-a = aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp is not qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz
-if (
- threading.current_thread() != threading.main_thread() and
- threading.current_thread() != threading.main_thread() or
- signal.getsignal(signal.SIGINT) != signal.default_int_handler
-):
- return True
-if (
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa |
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-):
- return True
-if (
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa &
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-):
- return True
-if (
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-):
- return True
-if (
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-):
- return True
-if (
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa *
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-):
- return True
-if (
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa /
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-):
- return True
-if (
- ~ aaaa.a + aaaa.b - aaaa.c * aaaa.d / aaaa.e | aaaa.f & aaaa.g % aaaa.h ^ aaaa.i << aaaa.k >> aaaa.l ** aaaa.m // aaaa.n
-):
- return True
-if (
- ~ aaaaaaaa.a + aaaaaaaa.b - aaaaaaaa.c @ aaaaaaaa.d / aaaaaaaa.e | aaaaaaaa.f & aaaaaaaa.g % aaaaaaaa.h ^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l ** aaaaaaaa.m // aaaaaaaa.n
-):
- return True
-if (
- ~ aaaaaaaaaaaaaaaa.a + aaaaaaaaaaaaaaaa.b - aaaaaaaaaaaaaaaa.c * aaaaaaaaaaaaaaaa.d @ aaaaaaaaaaaaaaaa.e | aaaaaaaaaaaaaaaa.f & aaaaaaaaaaaaaaaa.g % aaaaaaaaaaaaaaaa.h ^ aaaaaaaaaaaaaaaa.i << aaaaaaaaaaaaaaaa.k >> aaaaaaaaaaaaaaaa.l ** aaaaaaaaaaaaaaaa.m // aaaaaaaaaaaaaaaa.n
+a = (
+ aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp
+ in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz
+)
+a = (
+ aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp
+ not in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz
+)
+a = (
+ aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp
+ is qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz
+)
+a = (
+ aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp
+ is not qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz
+)
+if (
+ threading.current_thread() != threading.main_thread()
+ and threading.current_thread() != threading.main_thread()
+ or signal.getsignal(signal.SIGINT) != signal.default_int_handler
+):
+ return True
+if (
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+):
+ return True
+if (
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+):
+ return True
+if (
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+):
+ return True
+if (
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+):
+ return True
+if (
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ * aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+):
+ return True
+if (
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ / aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+):
+ return True
+if (
+ ~aaaa.a + aaaa.b - aaaa.c * aaaa.d / aaaa.e
+ | aaaa.f & aaaa.g % aaaa.h ^ aaaa.i << aaaa.k >> aaaa.l ** aaaa.m // aaaa.n
+):
+ return True
+if (
+ ~aaaaaaaa.a + aaaaaaaa.b - aaaaaaaa.c @ aaaaaaaa.d / aaaaaaaa.e
+ | aaaaaaaa.f & aaaaaaaa.g % aaaaaaaa.h
+ ^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l ** aaaaaaaa.m // aaaaaaaa.n
+):
+ return True
+if (
+ ~aaaaaaaaaaaaaaaa.a
+ + aaaaaaaaaaaaaaaa.b
+ - aaaaaaaaaaaaaaaa.c * aaaaaaaaaaaaaaaa.d @ aaaaaaaaaaaaaaaa.e
+ | aaaaaaaaaaaaaaaa.f & aaaaaaaaaaaaaaaa.g % aaaaaaaaaaaaaaaa.h
+ ^ aaaaaaaaaaaaaaaa.i
+ << aaaaaaaaaaaaaaaa.k
+ >> aaaaaaaaaaaaaaaa.l ** aaaaaaaaaaaaaaaa.m // aaaaaaaaaaaaaaaa.n
):
return True
last_call()
# standalone comment at ENDMARKER

View File

@ -395,6 +395,31 @@ def test_numeric_literals_ignoring_underscores(self) -> None:
black.assert_equivalent(source, actual) black.assert_equivalent(source, actual)
black.assert_stable(source, actual, mode) black.assert_stable(source, actual, mode)
def test_skip_magic_trailing_comma(self) -> None:
source, _ = read_data("expression.py")
expected, _ = read_data("expression_skip_magic_trailing_comma.diff")
tmp_file = Path(black.dump_to_file(source))
diff_header = re.compile(
rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
)
try:
result = BlackRunner().invoke(black.main, ["-C", "--diff", str(tmp_file)])
self.assertEqual(result.exit_code, 0)
finally:
os.unlink(tmp_file)
actual = result.output
actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
actual = actual.rstrip() + "\n" # the diff output has a trailing space
if expected != actual:
dump = black.dump_to_file(actual)
msg = (
"Expected diff isn't equal to the actual. If you made changes to"
" expression.py and this is an anticipated difference, overwrite"
f" tests/data/expression_skip_magic_trailing_comma.diff with {dump}"
)
self.assertEqual(expected, actual, msg)
@patch("black.dump_to_file", dump_to_stderr) @patch("black.dump_to_file", dump_to_stderr)
def test_python2_print_function(self) -> None: def test_python2_print_function(self) -> None:
source, expected = read_data("python2_print_function") source, expected = read_data("python2_print_function")