Skip to content

Commit

Permalink
Add --skip-magic-trailing-comma (#1824)
Browse files Browse the repository at this point in the history
  • Loading branch information
hauntsaninja committed Jan 18, 2021
1 parent de51047 commit 692c0f5
Show file tree
Hide file tree
Showing 8 changed files with 531 additions and 36 deletions.
19 changes: 12 additions & 7 deletions README.md
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions docs/blackd.md
Expand Up @@ -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
Expand Down
18 changes: 14 additions & 4 deletions docs/installation_and_usage.md
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions docs/the_black_code_style.md
Expand Up @@ -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
Expand Down
89 changes: 64 additions & 25 deletions src/black/__init__.py
Expand Up @@ -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

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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`
Expand Down Expand Up @@ -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):
self.should_explode = True
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)

Expand Down Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand All @@ -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]:
Expand All @@ -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:
Expand Down Expand Up @@ -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)
Expand All @@ -2026,15 +2048,19 @@ 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)
yield from self.visit_default(node)
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)

Expand Down Expand Up @@ -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", ","})
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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:
Expand All @@ -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
Expand All @@ -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."""
Expand All @@ -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:
Expand Down Expand Up @@ -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}
)
Expand Down
6 changes: 6 additions & 0 deletions src/blackd/__init__.py
Expand Up @@ -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"

Expand All @@ -40,6 +41,7 @@
LINE_LENGTH_HEADER,
PYTHON_VARIANT_HEADER,
SKIP_STRING_NORMALIZATION_HEADER,
SKIP_MAGIC_TRAILING_COMMA,
FAST_OR_SAFE_HEADER,
DIFF_HEADER,
]
Expand Down Expand Up @@ -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
Expand All @@ -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"
Expand Down

0 comments on commit 692c0f5

Please sign in to comment.