Add --skip-magic-trailing-comma (#1824)
This commit is contained in:
parent
de510478d9
commit
692c0f50d9
19
README.md
19
README.md
@ -97,6 +97,10 @@ Options:
|
||||
|
||||
-S, --skip-string-normalization
|
||||
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
|
||||
status. Return code 0 means nothing would
|
||||
change. Return code 1 means some files
|
||||
@ -127,18 +131,19 @@ Options:
|
||||
paths are excluded. Use forward slashes for
|
||||
directories on all platforms (Windows, too).
|
||||
Exclusions are calculated first, inclusions
|
||||
later. [default: /(\.eggs|\.git|\.hg|\.mypy
|
||||
_cache|\.nox|\.tox|\.venv|\.svn|_build|buck-
|
||||
out|build|dist)/]
|
||||
later. [default: /(\.direnv|\.eggs|\.git|\.
|
||||
hg|\.mypy_cache|\.nox|\.tox|\.venv|\.svn|_bu
|
||||
ild|buck-out|build|dist)/]
|
||||
|
||||
--force-exclude TEXT Like --exclude, but files and directories
|
||||
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. Useful to make sure Black will
|
||||
respect --force-exclude option on some
|
||||
editors that rely on using stdin.
|
||||
|
||||
-q, --quiet Don't emit non-error messages to stderr.
|
||||
Errors are still emitted; silence those with
|
||||
|
@ -54,6 +54,9 @@ The headers controlling how source code is formatted are:
|
||||
- `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
|
||||
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
|
||||
`--fast` command line flag.
|
||||
- `X-Python-Variant`: if set to `pyi`, `blackd` will act as _Black_ does when passed the
|
||||
|
@ -52,6 +52,10 @@ Options:
|
||||
|
||||
-S, --skip-string-normalization
|
||||
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
|
||||
status. Return code 0 means nothing would
|
||||
change. Return code 1 means some files
|
||||
@ -82,13 +86,19 @@ Options:
|
||||
paths are excluded. Use forward slashes for
|
||||
directories on all platforms (Windows, too).
|
||||
Exclusions are calculated first, inclusions
|
||||
later. [default: /(\.eggs|\.git|\.hg|\.mypy
|
||||
_cache|\.nox|\.tox|\.venv|\.svn|_build|buck-
|
||||
out|build|dist)/]
|
||||
later. [default: /(\.direnv|\.eggs|\.git|\.
|
||||
hg|\.mypy_cache|\.nox|\.tox|\.venv|\.svn|_bu
|
||||
ild|buck-out|build|dist)/]
|
||||
|
||||
--force-exclude TEXT Like --exclude, but files and directories
|
||||
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. Useful to make sure Black will respect
|
||||
|
@ -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
|
||||
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"
|
||||
|
||||
_Black_ normalizes string quotes as well as string prefixes, making them lowercase. One
|
||||
|
@ -260,6 +260,7 @@ class Mode:
|
||||
target_versions: Set[TargetVersion] = field(default_factory=set)
|
||||
line_length: int = DEFAULT_LINE_LENGTH
|
||||
string_normalization: bool = True
|
||||
magic_trailing_comma: bool = True
|
||||
experimental_string_processing: bool = False
|
||||
is_pyi: bool = False
|
||||
|
||||
@ -397,6 +398,12 @@ def target_version_option_callback(
|
||||
is_flag=True,
|
||||
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(
|
||||
"--experimental-string-processing",
|
||||
is_flag=True,
|
||||
@ -524,6 +531,7 @@ def main(
|
||||
fast: bool,
|
||||
pyi: bool,
|
||||
skip_string_normalization: bool,
|
||||
skip_magic_trailing_comma: bool,
|
||||
experimental_string_processing: bool,
|
||||
quiet: bool,
|
||||
verbose: bool,
|
||||
@ -546,6 +554,7 @@ def main(
|
||||
line_length=line_length,
|
||||
is_pyi=pyi,
|
||||
string_normalization=not skip_string_normalization,
|
||||
magic_trailing_comma=not skip_magic_trailing_comma,
|
||||
experimental_string_processing=experimental_string_processing,
|
||||
)
|
||||
if config and verbose:
|
||||
@ -1022,13 +1031,12 @@ def f(
|
||||
versions = detect_target_versions(src_node)
|
||||
normalize_fmt_off(src_node)
|
||||
lines = LineGenerator(
|
||||
mode=mode,
|
||||
remove_u_prefix="unicode_literals" in future_imports
|
||||
or supports_feature(versions, Feature.UNICODE_LITERALS),
|
||||
is_pyi=mode.is_pyi,
|
||||
normalize_strings=mode.string_normalization,
|
||||
)
|
||||
elt = EmptyLineTracker(is_pyi=mode.is_pyi)
|
||||
empty_line = Line()
|
||||
empty_line = Line(mode=mode)
|
||||
after = 0
|
||||
split_line_features = {
|
||||
feature
|
||||
@ -1464,6 +1472,7 @@ def get_open_lsqb(self) -> Optional[Leaf]:
|
||||
class Line:
|
||||
"""Holds leaves and comments. Can be printed with `str(line)`."""
|
||||
|
||||
mode: Mode
|
||||
depth: int = 0
|
||||
leaves: List[Leaf] = field(default_factory=list)
|
||||
# keys ordered like `leaves`
|
||||
@ -1496,8 +1505,11 @@ def append(self, leaf: Leaf, preformatted: bool = False) -> None:
|
||||
)
|
||||
if self.inside_brackets or not preformatted:
|
||||
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
|
||||
elif self.has_magic_trailing_comma(leaf, ensure_removable=True):
|
||||
self.remove_trailing_comma()
|
||||
if not self.append_comment(leaf):
|
||||
self.leaves.append(leaf)
|
||||
|
||||
@ -1673,10 +1685,14 @@ def contains_unsplittable_type_ignore(self) -> bool:
|
||||
def contains_multiline_strings(self) -> bool:
|
||||
return any(is_multiline_string(leaf) for leaf in self.leaves)
|
||||
|
||||
def maybe_should_explode(self, closing: Leaf) -> bool:
|
||||
"""Return True if this line should explode (always be split), that is when:
|
||||
- there's a trailing comma here; and
|
||||
- it's not a one-tuple.
|
||||
def has_magic_trailing_comma(
|
||||
self, closing: Leaf, ensure_removable: bool = False
|
||||
) -> bool:
|
||||
"""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 (
|
||||
closing.type in CLOSING_BRACKETS
|
||||
@ -1685,9 +1701,15 @@ def maybe_should_explode(self, closing: Leaf) -> bool:
|
||||
):
|
||||
return False
|
||||
|
||||
if closing.type in {token.RBRACE, token.RSQB}:
|
||||
if closing.type == token.RBRACE:
|
||||
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:
|
||||
return True
|
||||
|
||||
@ -1765,6 +1787,7 @@ def is_complex_subscript(self, leaf: Leaf) -> bool:
|
||||
|
||||
def clone(self) -> "Line":
|
||||
return Line(
|
||||
mode=self.mode,
|
||||
depth=self.depth,
|
||||
inside_brackets=self.inside_brackets,
|
||||
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.
|
||||
"""
|
||||
|
||||
is_pyi: bool = False
|
||||
normalize_strings: bool = True
|
||||
current_line: Line = field(default_factory=Line)
|
||||
mode: Mode
|
||||
remove_u_prefix: bool = False
|
||||
current_line: Line = field(init=False)
|
||||
|
||||
def line(self, indent: int = 0) -> Iterator[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.
|
||||
|
||||
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
|
||||
|
||||
def visit_default(self, node: LN) -> Iterator[Line]:
|
||||
@ -1965,7 +1987,7 @@ def visit_default(self, node: LN) -> Iterator[Line]:
|
||||
yield from self.line()
|
||||
|
||||
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_quotes(node)
|
||||
if node.type == token.NUMBER:
|
||||
@ -2017,7 +2039,7 @@ def visit_stmt(
|
||||
|
||||
def visit_suite(self, node: Node) -> Iterator[Line]:
|
||||
"""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])
|
||||
else:
|
||||
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."""
|
||||
is_suite_like = node.parent and node.parent.type in STATEMENT
|
||||
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)
|
||||
else:
|
||||
yield from self.line(+1)
|
||||
@ -2034,7 +2056,11 @@ def visit_simple_stmt(self, node: Node) -> Iterator[Line]:
|
||||
yield from self.line(-1)
|
||||
|
||||
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.visit_default(node)
|
||||
|
||||
@ -2110,6 +2136,8 @@ def visit_STRING(self, leaf: Leaf) -> Iterator[Line]:
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""You are in a twisty little maze of passages."""
|
||||
self.current_line = Line(mode=self.mode)
|
||||
|
||||
v = self.visit_stmt
|
||||
Ø: Set[str] = set()
|
||||
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.
|
||||
string_value = LL[string_idx].value
|
||||
string_line = Line(
|
||||
mode=line.mode,
|
||||
depth=line.depth + 1,
|
||||
inside_brackets=True,
|
||||
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
|
||||
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:
|
||||
result.inside_brackets = True
|
||||
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:
|
||||
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
|
||||
trailing_comma_safe = True
|
||||
|
||||
@ -5027,7 +5058,9 @@ def append_to_line(leaf: Leaf) -> Iterator[Line]:
|
||||
except ValueError:
|
||||
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)
|
||||
|
||||
for leaf in line.leaves:
|
||||
@ -5051,7 +5084,9 @@ def append_to_line(leaf: Leaf) -> Iterator[Line]:
|
||||
if leaf_priority == delimiter_priority:
|
||||
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 (
|
||||
trailing_comma_safe
|
||||
@ -5072,7 +5107,9 @@ def standalone_comment_split(
|
||||
if not line.contains_standalone_comments(0):
|
||||
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]:
|
||||
"""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:
|
||||
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)
|
||||
|
||||
for leaf in line.leaves:
|
||||
@ -5767,7 +5806,7 @@ def should_split_body_explode(line: Line, opening_bracket: Leaf) -> bool:
|
||||
return False
|
||||
|
||||
return max_priority == COMMA_PRIORITY and (
|
||||
trailing_comma
|
||||
(line.mode.magic_trailing_comma and trailing_comma)
|
||||
# always explode imports
|
||||
or opening_bracket.parent.type in {syms.atom, syms.import_from}
|
||||
)
|
||||
|
@ -32,6 +32,7 @@
|
||||
LINE_LENGTH_HEADER = "X-Line-Length"
|
||||
PYTHON_VARIANT_HEADER = "X-Python-Variant"
|
||||
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"
|
||||
DIFF_HEADER = "X-Diff"
|
||||
|
||||
@ -40,6 +41,7 @@
|
||||
LINE_LENGTH_HEADER,
|
||||
PYTHON_VARIANT_HEADER,
|
||||
SKIP_STRING_NORMALIZATION_HEADER,
|
||||
SKIP_MAGIC_TRAILING_COMMA,
|
||||
FAST_OR_SAFE_HEADER,
|
||||
DIFF_HEADER,
|
||||
]
|
||||
@ -114,6 +116,9 @@ async def handle(request: web.Request, executor: Executor) -> web.Response:
|
||||
skip_string_normalization = bool(
|
||||
request.headers.get(SKIP_STRING_NORMALIZATION_HEADER, False)
|
||||
)
|
||||
skip_magic_trailing_comma = bool(
|
||||
request.headers.get(SKIP_MAGIC_TRAILING_COMMA, False)
|
||||
)
|
||||
fast = False
|
||||
if request.headers.get(FAST_OR_SAFE_HEADER, "safe") == "fast":
|
||||
fast = True
|
||||
@ -122,6 +127,7 @@ async def handle(request: web.Request, executor: Executor) -> web.Response:
|
||||
is_pyi=pyi,
|
||||
line_length=line_length,
|
||||
string_normalization=not skip_string_normalization,
|
||||
magic_trailing_comma=not skip_magic_trailing_comma,
|
||||
)
|
||||
req_bytes = await request.content.read()
|
||||
charset = request.charset if request.charset is not None else "utf8"
|
||||
|
404
tests/data/expression_skip_magic_trailing_comma.diff
Normal file
404
tests/data/expression_skip_magic_trailing_comma.diff
Normal 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
|
@ -395,6 +395,31 @@ def test_numeric_literals_ignoring_underscores(self) -> None:
|
||||
black.assert_equivalent(source, actual)
|
||||
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)
|
||||
def test_python2_print_function(self) -> None:
|
||||
source, expected = read_data("python2_print_function")
|
||||
|
Loading…
Reference in New Issue
Block a user