Normalize string quotes (#75)
* Normalize string quotes Convert single-quoted strings to double-quoted. Convert triple single-quoted strings to triple double-quoted. Do not touch any strings where conversion would increase the number of backslashes. Fixes #51. * reformat Black itself
This commit is contained in:
parent
4dfec562ed
commit
80bd2b3134
253
black.py
253
black.py
@ -47,9 +47,9 @@
|
||||
Priority = int
|
||||
Index = int
|
||||
LN = Union[Leaf, Node]
|
||||
SplitFunc = Callable[['Line', bool], Iterator['Line']]
|
||||
SplitFunc = Callable[["Line", bool], Iterator["Line"]]
|
||||
out = partial(click.secho, bold=True, err=True)
|
||||
err = partial(click.secho, fg='red', err=True)
|
||||
err = partial(click.secho, fg="red", err=True)
|
||||
|
||||
|
||||
class NothingChanged(UserWarning):
|
||||
@ -94,15 +94,15 @@ class FormatOff(FormatError):
|
||||
|
||||
@click.command()
|
||||
@click.option(
|
||||
'-l',
|
||||
'--line-length',
|
||||
"-l",
|
||||
"--line-length",
|
||||
type=int,
|
||||
default=DEFAULT_LINE_LENGTH,
|
||||
help='How many character per line to allow.',
|
||||
help="How many character per line to allow.",
|
||||
show_default=True,
|
||||
)
|
||||
@click.option(
|
||||
'--check',
|
||||
"--check",
|
||||
is_flag=True,
|
||||
help=(
|
||||
"Don't write back the files, just return the status. Return code 0 "
|
||||
@ -111,13 +111,13 @@ class FormatOff(FormatError):
|
||||
),
|
||||
)
|
||||
@click.option(
|
||||
'--fast/--safe',
|
||||
"--fast/--safe",
|
||||
is_flag=True,
|
||||
help='If --fast given, skip temporary sanity checks. [default: --safe]',
|
||||
help="If --fast given, skip temporary sanity checks. [default: --safe]",
|
||||
)
|
||||
@click.version_option(version=__version__)
|
||||
@click.argument(
|
||||
'src',
|
||||
"src",
|
||||
nargs=-1,
|
||||
type=click.Path(
|
||||
exists=True, file_okay=True, dir_okay=True, readable=True, allow_dash=True
|
||||
@ -136,17 +136,17 @@ def main(
|
||||
elif p.is_file():
|
||||
# if a file was explicitly given, we don't care about its extension
|
||||
sources.append(p)
|
||||
elif s == '-':
|
||||
sources.append(Path('-'))
|
||||
elif s == "-":
|
||||
sources.append(Path("-"))
|
||||
else:
|
||||
err(f'invalid path: {s}')
|
||||
err(f"invalid path: {s}")
|
||||
if len(sources) == 0:
|
||||
ctx.exit(0)
|
||||
elif len(sources) == 1:
|
||||
p = sources[0]
|
||||
report = Report(check=check)
|
||||
try:
|
||||
if not p.is_file() and str(p) == '-':
|
||||
if not p.is_file() and str(p) == "-":
|
||||
changed = format_stdin_to_stdout(
|
||||
line_length=line_length, fast=fast, write_back=not check
|
||||
)
|
||||
@ -202,7 +202,7 @@ async def schedule_formatting(
|
||||
report = Report(check=not write_back)
|
||||
for src, task in tasks.items():
|
||||
if not task.done():
|
||||
report.failed(src, 'timed out, cancelling')
|
||||
report.failed(src, "timed out, cancelling")
|
||||
task.cancel()
|
||||
cancelled.append(task)
|
||||
elif task.cancelled():
|
||||
@ -214,7 +214,7 @@ async def schedule_formatting(
|
||||
if cancelled:
|
||||
await asyncio.gather(*cancelled, loop=loop, return_exceptions=True)
|
||||
else:
|
||||
out('All done! ✨ 🍰 ✨')
|
||||
out("All done! ✨ 🍰 ✨")
|
||||
click.echo(str(report))
|
||||
return report.return_code
|
||||
|
||||
@ -272,7 +272,7 @@ def format_file_contents(
|
||||
valid by calling :func:`assert_equivalent` and :func:`assert_stable` on it.
|
||||
`line_length` is passed to :func:`format_str`.
|
||||
"""
|
||||
if src_contents.strip() == '':
|
||||
if src_contents.strip() == "":
|
||||
raise NothingChanged
|
||||
|
||||
dst_contents = format_str(src_contents, line_length=line_length)
|
||||
@ -319,8 +319,8 @@ def format_str(src_contents: str, line_length: int) -> FileContent:
|
||||
def lib2to3_parse(src_txt: str) -> Node:
|
||||
"""Given a string with source, return the lib2to3 Node."""
|
||||
grammar = pygram.python_grammar_no_print_statement
|
||||
if src_txt[-1] != '\n':
|
||||
nl = '\r\n' if '\r\n' in src_txt[:1024] else '\n'
|
||||
if src_txt[-1] != "\n":
|
||||
nl = "\r\n" if "\r\n" in src_txt[:1024] else "\n"
|
||||
src_txt += nl
|
||||
for grammar in GRAMMARS:
|
||||
drv = driver.Driver(grammar, pytree.convert)
|
||||
@ -350,7 +350,7 @@ def lib2to3_unparse(node: Node) -> str:
|
||||
return code
|
||||
|
||||
|
||||
T = TypeVar('T')
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class Visitor(Generic[T]):
|
||||
@ -370,7 +370,7 @@ def visit(self, node: LN) -> Iterator[T]:
|
||||
name = token.tok_name[node.type]
|
||||
else:
|
||||
name = type_repr(node.type)
|
||||
yield from getattr(self, f'visit_{name}', self.visit_default)(node)
|
||||
yield from getattr(self, f"visit_{name}", self.visit_default)(node)
|
||||
|
||||
def visit_default(self, node: LN) -> Iterator[T]:
|
||||
"""Default `visit_*()` implementation. Recurses to children of `node`."""
|
||||
@ -384,24 +384,24 @@ class DebugVisitor(Visitor[T]):
|
||||
tree_depth: int = 0
|
||||
|
||||
def visit_default(self, node: LN) -> Iterator[T]:
|
||||
indent = ' ' * (2 * self.tree_depth)
|
||||
indent = " " * (2 * self.tree_depth)
|
||||
if isinstance(node, Node):
|
||||
_type = type_repr(node.type)
|
||||
out(f'{indent}{_type}', fg='yellow')
|
||||
out(f"{indent}{_type}", fg="yellow")
|
||||
self.tree_depth += 1
|
||||
for child in node.children:
|
||||
yield from self.visit(child)
|
||||
|
||||
self.tree_depth -= 1
|
||||
out(f'{indent}/{_type}', fg='yellow', bold=False)
|
||||
out(f"{indent}/{_type}", fg="yellow", bold=False)
|
||||
else:
|
||||
_type = token.tok_name.get(node.type, str(node.type))
|
||||
out(f'{indent}{_type}', fg='blue', nl=False)
|
||||
out(f"{indent}{_type}", fg="blue", nl=False)
|
||||
if node.prefix:
|
||||
# We don't have to handle prefixes for `Node` objects since
|
||||
# that delegates to the first child anyway.
|
||||
out(f' {node.prefix!r}', fg='green', bold=False, nl=False)
|
||||
out(f' {node.value!r}', fg='blue', bold=False)
|
||||
out(f" {node.prefix!r}", fg="green", bold=False, nl=False)
|
||||
out(f" {node.value!r}", fg="blue", bold=False)
|
||||
|
||||
@classmethod
|
||||
def show(cls, code: str) -> None:
|
||||
@ -415,7 +415,7 @@ def show(cls, code: str) -> None:
|
||||
|
||||
KEYWORDS = set(keyword.kwlist)
|
||||
WHITESPACE = {token.DEDENT, token.INDENT, token.NEWLINE}
|
||||
FLOW_CONTROL = {'return', 'raise', 'break', 'continue'}
|
||||
FLOW_CONTROL = {"return", "raise", "break", "continue"}
|
||||
STATEMENT = {
|
||||
syms.if_stmt,
|
||||
syms.while_stmt,
|
||||
@ -427,7 +427,7 @@ def show(cls, code: str) -> None:
|
||||
syms.classdef,
|
||||
}
|
||||
STANDALONE_COMMENT = 153
|
||||
LOGIC_OPERATORS = {'and', 'or'}
|
||||
LOGIC_OPERATORS = {"and", "or"}
|
||||
COMPARATORS = {
|
||||
token.LESS,
|
||||
token.GREATER,
|
||||
@ -500,14 +500,14 @@ def mark(self, leaf: Leaf) -> None:
|
||||
self.delimiters[id(self.previous)] = STRING_PRIORITY
|
||||
elif (
|
||||
leaf.type == token.NAME
|
||||
and leaf.value == 'for'
|
||||
and leaf.value == "for"
|
||||
and leaf.parent
|
||||
and leaf.parent.type in {syms.comp_for, syms.old_comp_for}
|
||||
):
|
||||
self.delimiters[id(self.previous)] = COMPREHENSION_PRIORITY
|
||||
elif (
|
||||
leaf.type == token.NAME
|
||||
and leaf.value == 'if'
|
||||
and leaf.value == "if"
|
||||
and leaf.parent
|
||||
and leaf.parent.type in {syms.comp_if, syms.old_comp_if}
|
||||
):
|
||||
@ -612,7 +612,7 @@ def is_class(self) -> bool:
|
||||
return (
|
||||
bool(self)
|
||||
and self.leaves[0].type == token.NAME
|
||||
and self.leaves[0].value == 'class'
|
||||
and self.leaves[0].value == "class"
|
||||
)
|
||||
|
||||
@property
|
||||
@ -628,12 +628,12 @@ def is_def(self) -> bool:
|
||||
except IndexError:
|
||||
second_leaf = None
|
||||
return (
|
||||
(first_leaf.type == token.NAME and first_leaf.value == 'def')
|
||||
(first_leaf.type == token.NAME and first_leaf.value == "def")
|
||||
or (
|
||||
first_leaf.type == token.ASYNC
|
||||
and second_leaf is not None
|
||||
and second_leaf.type == token.NAME
|
||||
and second_leaf.value == 'def'
|
||||
and second_leaf.value == "def"
|
||||
)
|
||||
)
|
||||
|
||||
@ -655,7 +655,7 @@ def is_yield(self) -> bool:
|
||||
return (
|
||||
bool(self)
|
||||
and self.leaves[0].type == token.NAME
|
||||
and self.leaves[0].value == 'yield'
|
||||
and self.leaves[0].value == "yield"
|
||||
)
|
||||
|
||||
@property
|
||||
@ -722,7 +722,7 @@ def maybe_increment_for_loop_variable(self, leaf: Leaf) -> bool:
|
||||
To avoid splitting on the comma in this situation, increase the depth of
|
||||
tokens between `for` and `in`.
|
||||
"""
|
||||
if leaf.type == token.NAME and leaf.value == 'for':
|
||||
if leaf.type == token.NAME and leaf.value == "for":
|
||||
self.has_for = True
|
||||
self.bracket_tracker.depth += 1
|
||||
self._for_loop_variable = True
|
||||
@ -732,7 +732,7 @@ def maybe_increment_for_loop_variable(self, leaf: Leaf) -> bool:
|
||||
|
||||
def maybe_decrement_after_for_loop_variable(self, leaf: Leaf) -> bool:
|
||||
"""See `maybe_increment_for_loop_variable` above for explanation."""
|
||||
if self._for_loop_variable and leaf.type == token.NAME and leaf.value == 'in':
|
||||
if self._for_loop_variable and leaf.type == token.NAME and leaf.value == "in":
|
||||
self.bracket_tracker.depth -= 1
|
||||
self._for_loop_variable = False
|
||||
return True
|
||||
@ -745,7 +745,7 @@ def append_comment(self, comment: Leaf) -> bool:
|
||||
comment.type == STANDALONE_COMMENT
|
||||
and self.bracket_tracker.any_open_brackets()
|
||||
):
|
||||
comment.prefix = ''
|
||||
comment.prefix = ""
|
||||
return False
|
||||
|
||||
if comment.type != token.COMMENT:
|
||||
@ -754,7 +754,7 @@ def append_comment(self, comment: Leaf) -> bool:
|
||||
after = len(self.leaves) - 1
|
||||
if after == -1:
|
||||
comment.type = STANDALONE_COMMENT
|
||||
comment.prefix = ''
|
||||
comment.prefix = ""
|
||||
return False
|
||||
|
||||
else:
|
||||
@ -786,17 +786,17 @@ def remove_trailing_comma(self) -> None:
|
||||
def __str__(self) -> str:
|
||||
"""Render the line."""
|
||||
if not self:
|
||||
return '\n'
|
||||
return "\n"
|
||||
|
||||
indent = ' ' * self.depth
|
||||
indent = " " * self.depth
|
||||
leaves = iter(self.leaves)
|
||||
first = next(leaves)
|
||||
res = f'{first.prefix}{indent}{first.value}'
|
||||
res = f"{first.prefix}{indent}{first.value}"
|
||||
for leaf in leaves:
|
||||
res += str(leaf)
|
||||
for _, comment in self.comments:
|
||||
res += str(comment)
|
||||
return res + '\n'
|
||||
return res + "\n"
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
"""Return True if the line has leaves or comments."""
|
||||
@ -832,9 +832,9 @@ def __str__(self) -> str:
|
||||
`depth` is not used for indentation in this case.
|
||||
"""
|
||||
if not self:
|
||||
return '\n'
|
||||
return "\n"
|
||||
|
||||
res = ''
|
||||
res = ""
|
||||
for leaf in self.leaves:
|
||||
res += str(leaf)
|
||||
return res
|
||||
@ -888,9 +888,9 @@ def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]:
|
||||
if current_line.leaves:
|
||||
# Consume the first leaf's extra newlines.
|
||||
first_leaf = current_line.leaves[0]
|
||||
before = first_leaf.prefix.count('\n')
|
||||
before = first_leaf.prefix.count("\n")
|
||||
before = min(before, max_allowed)
|
||||
first_leaf.prefix = ''
|
||||
first_leaf.prefix = ""
|
||||
else:
|
||||
before = 0
|
||||
depth = current_line.depth
|
||||
@ -1009,6 +1009,8 @@ def visit_default(self, node: LN) -> Iterator[Line]:
|
||||
|
||||
else:
|
||||
normalize_prefix(node, inside_brackets=any_open_brackets)
|
||||
if node.type == token.STRING:
|
||||
normalize_string_quotes(node)
|
||||
if node.type not in WHITESPACE:
|
||||
self.current_line.append(node)
|
||||
yield from super().visit_default(node)
|
||||
@ -1098,14 +1100,14 @@ def visit_unformatted(self, node: LN) -> Iterator[Line]:
|
||||
def __attrs_post_init__(self) -> None:
|
||||
"""You are in a twisty little maze of passages."""
|
||||
v = self.visit_stmt
|
||||
self.visit_if_stmt = partial(v, keywords={'if', 'else', 'elif'})
|
||||
self.visit_while_stmt = partial(v, keywords={'while', 'else'})
|
||||
self.visit_for_stmt = partial(v, keywords={'for', 'else'})
|
||||
self.visit_try_stmt = partial(v, keywords={'try', 'except', 'else', 'finally'})
|
||||
self.visit_except_clause = partial(v, keywords={'except'})
|
||||
self.visit_funcdef = partial(v, keywords={'def'})
|
||||
self.visit_with_stmt = partial(v, keywords={'with'})
|
||||
self.visit_classdef = partial(v, keywords={'class'})
|
||||
self.visit_if_stmt = partial(v, keywords={"if", "else", "elif"})
|
||||
self.visit_while_stmt = partial(v, keywords={"while", "else"})
|
||||
self.visit_for_stmt = partial(v, keywords={"for", "else"})
|
||||
self.visit_try_stmt = partial(v, keywords={"try", "except", "else", "finally"})
|
||||
self.visit_except_clause = partial(v, keywords={"except"})
|
||||
self.visit_funcdef = partial(v, keywords={"def"})
|
||||
self.visit_with_stmt = partial(v, keywords={"with"})
|
||||
self.visit_classdef = partial(v, keywords={"class"})
|
||||
self.visit_async_funcdef = self.visit_async_stmt
|
||||
self.visit_decorated = self.visit_decorators
|
||||
|
||||
@ -1119,9 +1121,9 @@ def __attrs_post_init__(self) -> None:
|
||||
|
||||
def whitespace(leaf: Leaf) -> str: # noqa C901
|
||||
"""Return whitespace prefix if needed for the given `leaf`."""
|
||||
NO = ''
|
||||
SPACE = ' '
|
||||
DOUBLESPACE = ' '
|
||||
NO = ""
|
||||
SPACE = " "
|
||||
DOUBLESPACE = " "
|
||||
t = leaf.type
|
||||
p = leaf.parent
|
||||
v = leaf.value
|
||||
@ -1185,7 +1187,7 @@ def whitespace(leaf: Leaf) -> str: # noqa C901
|
||||
and prevp.parent.type == syms.shift_expr
|
||||
and prevp.prev_sibling
|
||||
and prevp.prev_sibling.type == token.NAME
|
||||
and prevp.prev_sibling.value == 'print' # type: ignore
|
||||
and prevp.prev_sibling.value == "print" # type: ignore
|
||||
):
|
||||
# Python 2 print chevron
|
||||
return NO
|
||||
@ -1342,7 +1344,7 @@ def whitespace(leaf: Leaf) -> str: # noqa C901
|
||||
return NO
|
||||
|
||||
elif t == token.NAME:
|
||||
if v == 'import':
|
||||
if v == "import":
|
||||
return SPACE
|
||||
|
||||
if prev and prev.type == token.DOT:
|
||||
@ -1416,17 +1418,17 @@ def generate_comments(leaf: Leaf) -> Iterator[Leaf]:
|
||||
if not p:
|
||||
return
|
||||
|
||||
if '#' not in p:
|
||||
if "#" not in p:
|
||||
return
|
||||
|
||||
consumed = 0
|
||||
nlines = 0
|
||||
for index, line in enumerate(p.split('\n')):
|
||||
for index, line in enumerate(p.split("\n")):
|
||||
consumed += len(line) + 1 # adding the length of the split '\n'
|
||||
line = line.lstrip()
|
||||
if not line:
|
||||
nlines += 1
|
||||
if not line.startswith('#'):
|
||||
if not line.startswith("#"):
|
||||
continue
|
||||
|
||||
if index == 0 and leaf.type != token.ENDMARKER:
|
||||
@ -1434,12 +1436,12 @@ def generate_comments(leaf: Leaf) -> Iterator[Leaf]:
|
||||
else:
|
||||
comment_type = STANDALONE_COMMENT
|
||||
comment = make_comment(line)
|
||||
yield Leaf(comment_type, comment, prefix='\n' * nlines)
|
||||
yield Leaf(comment_type, comment, prefix="\n" * nlines)
|
||||
|
||||
if comment in {'# fmt: on', '# yapf: enable'}:
|
||||
if comment in {"# fmt: on", "# yapf: enable"}:
|
||||
raise FormatOn(consumed)
|
||||
|
||||
if comment in {'# fmt: off', '# yapf: disable'}:
|
||||
if comment in {"# fmt: off", "# yapf: disable"}:
|
||||
raise FormatOff(consumed)
|
||||
|
||||
nlines = 0
|
||||
@ -1455,13 +1457,13 @@ def make_comment(content: str) -> str:
|
||||
"""
|
||||
content = content.rstrip()
|
||||
if not content:
|
||||
return '#'
|
||||
return "#"
|
||||
|
||||
if content[0] == '#':
|
||||
if content[0] == "#":
|
||||
content = content[1:]
|
||||
if content and content[0] not in ' !:#':
|
||||
content = ' ' + content
|
||||
return '#' + content
|
||||
if content and content[0] not in " !:#":
|
||||
content = " " + content
|
||||
return "#" + content
|
||||
|
||||
|
||||
def split_line(
|
||||
@ -1481,10 +1483,10 @@ def split_line(
|
||||
yield line
|
||||
return
|
||||
|
||||
line_str = str(line).strip('\n')
|
||||
line_str = str(line).strip("\n")
|
||||
if (
|
||||
len(line_str) <= line_length
|
||||
and '\n' not in line_str # multiline strings
|
||||
and "\n" not in line_str # multiline strings
|
||||
and not line.contains_standalone_comments
|
||||
):
|
||||
yield line
|
||||
@ -1504,7 +1506,7 @@ def split_line(
|
||||
result: List[Line] = []
|
||||
try:
|
||||
for l in split_func(line, py36):
|
||||
if str(l).strip('\n') == line_str:
|
||||
if str(l).strip("\n") == line_str:
|
||||
raise CannotSplit("Split function returned an unchanged result")
|
||||
|
||||
result.extend(
|
||||
@ -1703,7 +1705,7 @@ def append_to_line(leaf: Leaf) -> Iterator[Line]:
|
||||
and current_line.leaves[-1].type != token.COMMA
|
||||
and trailing_comma_safe
|
||||
):
|
||||
current_line.append(Leaf(token.COMMA, ','))
|
||||
current_line.append(Leaf(token.COMMA, ","))
|
||||
yield current_line
|
||||
|
||||
|
||||
@ -1749,8 +1751,8 @@ def is_import(leaf: Leaf) -> bool:
|
||||
return bool(
|
||||
t == token.NAME
|
||||
and (
|
||||
(v == 'import' and p and p.type == syms.import_name)
|
||||
or (v == 'from' and p and p.type == syms.import_from)
|
||||
(v == "import" and p and p.type == syms.import_name)
|
||||
or (v == "from" and p and p.type == syms.import_from)
|
||||
)
|
||||
)
|
||||
|
||||
@ -1762,15 +1764,52 @@ def normalize_prefix(leaf: Leaf, *, inside_brackets: bool) -> None:
|
||||
Note: don't use backslashes for formatting or you'll lose your voting rights.
|
||||
"""
|
||||
if not inside_brackets:
|
||||
spl = leaf.prefix.split('#')
|
||||
if '\\' not in spl[0]:
|
||||
nl_count = spl[-1].count('\n')
|
||||
spl = leaf.prefix.split("#")
|
||||
if "\\" not in spl[0]:
|
||||
nl_count = spl[-1].count("\n")
|
||||
if len(spl) > 1:
|
||||
nl_count -= 1
|
||||
leaf.prefix = '\n' * nl_count
|
||||
leaf.prefix = "\n" * nl_count
|
||||
return
|
||||
|
||||
leaf.prefix = ''
|
||||
leaf.prefix = ""
|
||||
|
||||
|
||||
def normalize_string_quotes(leaf: Leaf) -> None:
|
||||
value = leaf.value.lstrip("furbFURB")
|
||||
if value[:3] == '"""':
|
||||
return
|
||||
|
||||
elif value[:3] == "'''":
|
||||
orig_quote = "'''"
|
||||
new_quote = '"""'
|
||||
elif value[0] == '"':
|
||||
orig_quote = '"'
|
||||
new_quote = "'"
|
||||
else:
|
||||
orig_quote = "'"
|
||||
new_quote = '"'
|
||||
first_quote_pos = leaf.value.find(orig_quote)
|
||||
if first_quote_pos == -1:
|
||||
return # There's an internal error
|
||||
|
||||
body = leaf.value[first_quote_pos + len(orig_quote):-len(orig_quote)]
|
||||
new_body = body.replace(f"\\{orig_quote}", orig_quote).replace(
|
||||
new_quote, f"\\{new_quote}"
|
||||
)
|
||||
if new_quote == '"""' and new_body[-1] == '"':
|
||||
# edge case:
|
||||
new_body = new_body[:-1] + '\\"'
|
||||
orig_escape_count = body.count("\\")
|
||||
new_escape_count = new_body.count("\\")
|
||||
if new_escape_count > orig_escape_count:
|
||||
return # Do not introduce more escaping
|
||||
|
||||
if new_escape_count == orig_escape_count and orig_quote == '"':
|
||||
return # Prefer double quotes
|
||||
|
||||
prefix = leaf.value[:first_quote_pos]
|
||||
leaf.value = f"{prefix}{new_quote}{new_body}{new_quote}"
|
||||
|
||||
|
||||
def is_python36(node: Node) -> bool:
|
||||
@ -1783,7 +1822,7 @@ def is_python36(node: Node) -> bool:
|
||||
for n in node.pre_order():
|
||||
if n.type == token.STRING:
|
||||
value_head = n.value[:2] # type: ignore
|
||||
if value_head in {'f"', 'F"', "f'", "F'", 'rf', 'fr', 'RF', 'FR'}:
|
||||
if value_head in {'f"', 'F"', "f'", "F'", "rf", "fr", "RF", "FR"}:
|
||||
return True
|
||||
|
||||
elif (
|
||||
@ -1798,9 +1837,9 @@ def is_python36(node: Node) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
PYTHON_EXTENSIONS = {'.py'}
|
||||
PYTHON_EXTENSIONS = {".py"}
|
||||
BLACKLISTED_DIRECTORIES = {
|
||||
'build', 'buck-out', 'dist', '_build', '.git', '.hg', '.mypy_cache', '.tox', '.venv'
|
||||
"build", "buck-out", "dist", "_build", ".git", ".hg", ".mypy_cache", ".tox", ".venv"
|
||||
}
|
||||
|
||||
|
||||
@ -1830,16 +1869,16 @@ class Report:
|
||||
def done(self, src: Path, changed: bool) -> None:
|
||||
"""Increment the counter for successful reformatting. Write out a message."""
|
||||
if changed:
|
||||
reformatted = 'would reformat' if self.check else 'reformatted'
|
||||
out(f'{reformatted} {src}')
|
||||
reformatted = "would reformat" if self.check else "reformatted"
|
||||
out(f"{reformatted} {src}")
|
||||
self.change_count += 1
|
||||
else:
|
||||
out(f'{src} already well formatted, good job.', bold=False)
|
||||
out(f"{src} already well formatted, good job.", bold=False)
|
||||
self.same_count += 1
|
||||
|
||||
def failed(self, src: Path, message: str) -> None:
|
||||
"""Increment the counter for failed reformatting. Write out a message."""
|
||||
err(f'error: cannot format {src}: {message}')
|
||||
err(f"error: cannot format {src}: {message}")
|
||||
self.failure_count += 1
|
||||
|
||||
@property
|
||||
@ -1876,19 +1915,19 @@ def __str__(self) -> str:
|
||||
failed = "failed to reformat"
|
||||
report = []
|
||||
if self.change_count:
|
||||
s = 's' if self.change_count > 1 else ''
|
||||
s = "s" if self.change_count > 1 else ""
|
||||
report.append(
|
||||
click.style(f'{self.change_count} file{s} {reformatted}', bold=True)
|
||||
click.style(f"{self.change_count} file{s} {reformatted}", bold=True)
|
||||
)
|
||||
if self.same_count:
|
||||
s = 's' if self.same_count > 1 else ''
|
||||
report.append(f'{self.same_count} file{s} {unchanged}')
|
||||
s = "s" if self.same_count > 1 else ""
|
||||
report.append(f"{self.same_count} file{s} {unchanged}")
|
||||
if self.failure_count:
|
||||
s = 's' if self.failure_count > 1 else ''
|
||||
s = "s" if self.failure_count > 1 else ""
|
||||
report.append(
|
||||
click.style(f'{self.failure_count} file{s} {failed}', fg='red')
|
||||
click.style(f"{self.failure_count} file{s} {failed}", fg="red")
|
||||
)
|
||||
return ', '.join(report) + '.'
|
||||
return ", ".join(report) + "."
|
||||
|
||||
|
||||
def assert_equivalent(src: str, dst: str) -> None:
|
||||
@ -1935,17 +1974,17 @@ def _v(node: ast.AST, depth: int = 0) -> Iterator[str]:
|
||||
try:
|
||||
dst_ast = ast.parse(dst)
|
||||
except Exception as exc:
|
||||
log = dump_to_file(''.join(traceback.format_tb(exc.__traceback__)), dst)
|
||||
log = dump_to_file("".join(traceback.format_tb(exc.__traceback__)), dst)
|
||||
raise AssertionError(
|
||||
f"INTERNAL ERROR: Black produced invalid code: {exc}. "
|
||||
f"Please report a bug on https://github.com/ambv/black/issues. "
|
||||
f"This invalid output might be helpful: {log}"
|
||||
) from None
|
||||
|
||||
src_ast_str = '\n'.join(_v(src_ast))
|
||||
dst_ast_str = '\n'.join(_v(dst_ast))
|
||||
src_ast_str = "\n".join(_v(src_ast))
|
||||
dst_ast_str = "\n".join(_v(dst_ast))
|
||||
if src_ast_str != dst_ast_str:
|
||||
log = dump_to_file(diff(src_ast_str, dst_ast_str, 'src', 'dst'))
|
||||
log = dump_to_file(diff(src_ast_str, dst_ast_str, "src", "dst"))
|
||||
raise AssertionError(
|
||||
f"INTERNAL ERROR: Black produced code that is not equivalent to "
|
||||
f"the source. "
|
||||
@ -1959,8 +1998,8 @@ def assert_stable(src: str, dst: str, line_length: int) -> None:
|
||||
newdst = format_str(dst, line_length=line_length)
|
||||
if dst != newdst:
|
||||
log = dump_to_file(
|
||||
diff(src, dst, 'source', 'first pass'),
|
||||
diff(dst, newdst, 'first pass', 'second pass'),
|
||||
diff(src, dst, "source", "first pass"),
|
||||
diff(dst, newdst, "first pass", "second pass"),
|
||||
)
|
||||
raise AssertionError(
|
||||
f"INTERNAL ERROR: Black produced different code on the second pass "
|
||||
@ -1975,11 +2014,11 @@ def dump_to_file(*output: str) -> str:
|
||||
import tempfile
|
||||
|
||||
with tempfile.NamedTemporaryFile(
|
||||
mode='w', prefix='blk_', suffix='.log', delete=False
|
||||
mode="w", prefix="blk_", suffix=".log", delete=False
|
||||
) as f:
|
||||
for lines in output:
|
||||
f.write(lines)
|
||||
f.write('\n')
|
||||
f.write("\n")
|
||||
return f.name
|
||||
|
||||
|
||||
@ -1987,9 +2026,9 @@ def diff(a: str, b: str, a_name: str, b_name: str) -> str:
|
||||
"""Return a unified diff string between strings `a` and `b`."""
|
||||
import difflib
|
||||
|
||||
a_lines = [line + '\n' for line in a.split('\n')]
|
||||
b_lines = [line + '\n' for line in b.split('\n')]
|
||||
return ''.join(
|
||||
a_lines = [line + "\n" for line in a.split("\n")]
|
||||
b_lines = [line + "\n" for line in b.split("\n")]
|
||||
return "".join(
|
||||
difflib.unified_diff(a_lines, b_lines, fromfile=a_name, tofile=b_name, n=5)
|
||||
)
|
||||
|
||||
@ -2023,5 +2062,5 @@ def shutdown(loop: BaseEventLoop) -> None:
|
||||
loop.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
56
setup.py
56
setup.py
@ -11,48 +11,48 @@
|
||||
|
||||
|
||||
def get_long_description():
|
||||
readme_md = CURRENT_DIR / 'README.md'
|
||||
with open(readme_md, encoding='utf8') as ld_file:
|
||||
readme_md = CURRENT_DIR / "README.md"
|
||||
with open(readme_md, encoding="utf8") as ld_file:
|
||||
return ld_file.read()
|
||||
|
||||
|
||||
def get_version():
|
||||
black_py = CURRENT_DIR / 'black.py'
|
||||
_version_re = re.compile(r'__version__\s+=\s+(?P<version>.*)')
|
||||
with open(black_py, 'r', encoding='utf8') as f:
|
||||
version = _version_re.search(f.read()).group('version')
|
||||
black_py = CURRENT_DIR / "black.py"
|
||||
_version_re = re.compile(r"__version__\s+=\s+(?P<version>.*)")
|
||||
with open(black_py, "r", encoding="utf8") as f:
|
||||
version = _version_re.search(f.read()).group("version")
|
||||
return str(ast.literal_eval(version))
|
||||
|
||||
|
||||
setup(
|
||||
name='black',
|
||||
name="black",
|
||||
version=get_version(),
|
||||
description="The uncompromising code formatter.",
|
||||
long_description=get_long_description(),
|
||||
long_description_content_type="text/markdown",
|
||||
keywords='automation formatter yapf autopep8 pyfmt gofmt rustfmt',
|
||||
author='Łukasz Langa',
|
||||
author_email='lukasz@langa.pl',
|
||||
url='https://github.com/ambv/black',
|
||||
license='MIT',
|
||||
py_modules=['black'],
|
||||
packages=['blib2to3', 'blib2to3.pgen2'],
|
||||
package_data={'blib2to3': ['*.txt']},
|
||||
keywords="automation formatter yapf autopep8 pyfmt gofmt rustfmt",
|
||||
author="Łukasz Langa",
|
||||
author_email="lukasz@langa.pl",
|
||||
url="https://github.com/ambv/black",
|
||||
license="MIT",
|
||||
py_modules=["black"],
|
||||
packages=["blib2to3", "blib2to3.pgen2"],
|
||||
package_data={"blib2to3": ["*.txt"]},
|
||||
python_requires=">=3.6",
|
||||
zip_safe=False,
|
||||
install_requires=['click', 'attrs>=17.4.0'],
|
||||
test_suite='tests.test_black',
|
||||
install_requires=["click", "attrs>=17.4.0"],
|
||||
test_suite="tests.test_black",
|
||||
classifiers=[
|
||||
'Development Status :: 3 - Alpha',
|
||||
'Environment :: Console',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3 :: Only',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
'Topic :: Software Development :: Quality Assurance',
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Environment :: Console",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3.6",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
"Topic :: Software Development :: Quality Assurance",
|
||||
],
|
||||
entry_points={'console_scripts': ['black=black:main']},
|
||||
entry_points={"console_scripts": ["black=black:main"]},
|
||||
)
|
||||
|
@ -43,7 +43,7 @@ def function(default=None):
|
||||
|
||||
|
||||
# Explains why we use global state.
|
||||
GLOBAL_STATE = {'a': a(1), 'b': a(2), 'c': a(3)}
|
||||
GLOBAL_STATE = {"a": a(1), "b": a(2), "c": a(3)}
|
||||
|
||||
|
||||
# Another comment!
|
||||
@ -76,7 +76,7 @@ async def wat():
|
||||
result = await x.method1()
|
||||
# Comment after ending a block.
|
||||
if result:
|
||||
print('A OK', file=sys.stdout)
|
||||
print("A OK", file=sys.stdout)
|
||||
# Comment between things.
|
||||
print()
|
||||
|
||||
|
@ -125,23 +125,23 @@ def inline_comments_in_brackets_ruin_everything():
|
||||
|
||||
__all__ = [
|
||||
# Super-special typing primitives.
|
||||
'Any',
|
||||
'Callable',
|
||||
'ClassVar',
|
||||
"Any",
|
||||
"Callable",
|
||||
"ClassVar",
|
||||
# ABCs (from collections.abc).
|
||||
'AbstractSet', # collections.abc.Set.
|
||||
'ByteString',
|
||||
'Container',
|
||||
"AbstractSet", # collections.abc.Set.
|
||||
"ByteString",
|
||||
"Container",
|
||||
# Concrete collection types.
|
||||
'Counter',
|
||||
'Deque',
|
||||
'Dict',
|
||||
'DefaultDict',
|
||||
'List',
|
||||
'Set',
|
||||
'FrozenSet',
|
||||
'NamedTuple', # Not really a type.
|
||||
'Generator',
|
||||
"Counter",
|
||||
"Deque",
|
||||
"Dict",
|
||||
"DefaultDict",
|
||||
"List",
|
||||
"Set",
|
||||
"FrozenSet",
|
||||
"NamedTuple", # Not really a type.
|
||||
"Generator",
|
||||
]
|
||||
|
||||
# Comment before function.
|
||||
@ -212,7 +212,7 @@ def inline_comments_in_brackets_ruin_everything():
|
||||
]
|
||||
lcomp3 = [
|
||||
# This one is actually too long to fit in a single line.
|
||||
element.split('\n', 1)[0]
|
||||
element.split("\n", 1)[0]
|
||||
# yup
|
||||
for element in collection.select_elements()
|
||||
# right
|
||||
@ -228,7 +228,7 @@ def inline_comments_in_brackets_ruin_everything():
|
||||
# let's return
|
||||
return Node(
|
||||
syms.simple_stmt,
|
||||
[Node(statement, result), Leaf(token.NEWLINE, '\n')], # FIXME: \r\n?
|
||||
[Node(statement, result), Leaf(token.NEWLINE, "\n")], # FIXME: \r\n?
|
||||
)
|
||||
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
def func():
|
||||
lcomp3 = [
|
||||
# This one is actually too long to fit in a single line.
|
||||
element.split('\n', 1)[0]
|
||||
element.split("\n", 1)[0]
|
||||
# yup
|
||||
for element in collection.select_elements()
|
||||
# right
|
||||
|
@ -61,7 +61,7 @@ def test_fails_invalid_post_data(
|
||||
|
||||
def foo(list_a, list_b):
|
||||
results = (
|
||||
User.query.filter(User.foo == 'bar').filter( # Because foo.
|
||||
User.query.filter(User.foo == "bar").filter( # Because foo.
|
||||
db.or_(User.field_a.astext.in_(list_a), User.field_b.astext.in_(list_b))
|
||||
).filter(
|
||||
User.xyz.is_(None)
|
||||
|
@ -3,19 +3,19 @@ class C:
|
||||
def test(self) -> None:
|
||||
with patch("black.out", print):
|
||||
self.assertEqual(
|
||||
unstyle(str(report)), '1 file reformatted, 1 file failed to reformat.'
|
||||
unstyle(str(report)), "1 file reformatted, 1 file failed to reformat."
|
||||
)
|
||||
self.assertEqual(
|
||||
unstyle(str(report)),
|
||||
'1 file reformatted, 1 file left unchanged, 1 file failed to reformat.',
|
||||
"1 file reformatted, 1 file left unchanged, 1 file failed to reformat.",
|
||||
)
|
||||
self.assertEqual(
|
||||
unstyle(str(report)),
|
||||
'2 files reformatted, 1 file left unchanged, '
|
||||
'1 file failed to reformat.',
|
||||
"2 files reformatted, 1 file left unchanged, "
|
||||
"1 file failed to reformat.",
|
||||
)
|
||||
self.assertEqual(
|
||||
unstyle(str(report)),
|
||||
'2 files reformatted, 2 files left unchanged, '
|
||||
'2 files failed to reformat.',
|
||||
"2 files reformatted, 2 files left unchanged, "
|
||||
"2 files failed to reformat.",
|
||||
)
|
||||
|
@ -64,7 +64,7 @@ def g():
|
||||
return DOUBLESPACE
|
||||
|
||||
# Another comment because more comments
|
||||
assert p is not None, f"INTERNAL ERROR: hand-made leaf without parent: {leaf!r}"
|
||||
assert p is not None, f'INTERNAL ERROR: hand-made leaf without parent: {leaf!r}'
|
||||
|
||||
prev = leaf.prev_sibling
|
||||
if not prev:
|
||||
@ -90,9 +90,9 @@ def g():
|
||||
|
||||
|
||||
def f():
|
||||
NO = ''
|
||||
SPACE = ' '
|
||||
DOUBLESPACE = ' '
|
||||
NO = ""
|
||||
SPACE = " "
|
||||
DOUBLESPACE = " "
|
||||
|
||||
t = leaf.type
|
||||
p = leaf.parent # trailing comment
|
||||
@ -139,9 +139,9 @@ def f():
|
||||
|
||||
|
||||
def g():
|
||||
NO = ''
|
||||
SPACE = ' '
|
||||
DOUBLESPACE = ' '
|
||||
NO = ""
|
||||
SPACE = " "
|
||||
DOUBLESPACE = " "
|
||||
|
||||
t = leaf.type
|
||||
p = leaf.parent
|
||||
|
@ -157,8 +157,8 @@ async def f():
|
||||
|
||||
|
||||
...
|
||||
'some_string'
|
||||
b'\\xa3'
|
||||
"some_string"
|
||||
b"\\xa3"
|
||||
Name
|
||||
None
|
||||
True
|
||||
@ -193,18 +193,18 @@ async def f():
|
||||
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
|
||||
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)
|
||||
{'2.7': dead, '3.7': (long_live or die_hard)}
|
||||
{'2.7': dead, '3.7': (long_live or die_hard), **{'3.6': verygood}}
|
||||
{"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)
|
||||
@ -214,14 +214,14 @@ async def f():
|
||||
[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 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()}
|
||||
{
|
||||
@ -232,14 +232,14 @@ async def f():
|
||||
Life is Life
|
||||
call()
|
||||
call(arg)
|
||||
call(kwarg='hey')
|
||||
call(arg, kwarg='hey')
|
||||
call(arg, another, kwarg='hey', **kwargs)
|
||||
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',
|
||||
kwarg="hey",
|
||||
**kwargs
|
||||
) # note: no trailing comma pre-3.6
|
||||
call(*gidgets[:2])
|
||||
@ -283,15 +283,15 @@ async def f():
|
||||
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)
|
||||
a = (1,)
|
||||
|
@ -15,10 +15,10 @@ def func_no_args():
|
||||
for i in range(10):
|
||||
print(i)
|
||||
continue
|
||||
exec("new-style exec", {}, {})
|
||||
exec('new-style exec', {}, {})
|
||||
return None
|
||||
async def coroutine(arg, exec=False):
|
||||
"Single-line docstring. Multiline is harder to reformat."
|
||||
'Single-line docstring. Multiline is harder to reformat.'
|
||||
async with some_connection() as conn:
|
||||
await conn.do_what_i_mean('SELECT bobby, tables FROM xkcd', timeout=2)
|
||||
await asyncio.sleep(1)
|
||||
@ -27,7 +27,7 @@ async def coroutine(arg, exec=False):
|
||||
with_args=True,
|
||||
many_args=[1,2,3]
|
||||
)
|
||||
def function_signature_stress_test(number:int,no_annotation=None,text:str="default",* ,debug:bool=False,**kwargs) -> str:
|
||||
def function_signature_stress_test(number:int,no_annotation=None,text:str='default',* ,debug:bool=False,**kwargs) -> str:
|
||||
return text[number:-1]
|
||||
# fmt: on
|
||||
def spaces(a=1, b=(), c=[], d={}, e=True, f=-1, g=1 if False else 2, h="", i=r''):
|
||||
@ -83,7 +83,7 @@ def long_lines():
|
||||
|
||||
from library import some_connection, some_decorator
|
||||
|
||||
f'trigger 3.6 mode'
|
||||
f"trigger 3.6 mode"
|
||||
# fmt: off
|
||||
def func_no_args():
|
||||
a; b; c
|
||||
@ -92,10 +92,10 @@ def func_no_args():
|
||||
for i in range(10):
|
||||
print(i)
|
||||
continue
|
||||
exec("new-style exec", {}, {})
|
||||
exec('new-style exec', {}, {})
|
||||
return None
|
||||
async def coroutine(arg, exec=False):
|
||||
"Single-line docstring. Multiline is harder to reformat."
|
||||
'Single-line docstring. Multiline is harder to reformat.'
|
||||
async with some_connection() as conn:
|
||||
await conn.do_what_i_mean('SELECT bobby, tables FROM xkcd', timeout=2)
|
||||
await asyncio.sleep(1)
|
||||
@ -104,12 +104,12 @@ async def coroutine(arg, exec=False):
|
||||
with_args=True,
|
||||
many_args=[1,2,3]
|
||||
)
|
||||
def function_signature_stress_test(number:int,no_annotation=None,text:str="default",* ,debug:bool=False,**kwargs) -> str:
|
||||
def function_signature_stress_test(number:int,no_annotation=None,text:str='default',* ,debug:bool=False,**kwargs) -> str:
|
||||
return text[number:-1]
|
||||
# fmt: on
|
||||
|
||||
|
||||
def spaces(a=1, b=(), c=[], d={}, e=True, f=-1, g=1 if False else 2, h="", i=r''):
|
||||
def spaces(a=1, b=(), c=[], d={}, e=True, f=-1, g=1 if False else 2, h="", i=r""):
|
||||
offset = attr.ib(default=attr.Factory(lambda: _r.uniform(10000, 200000)))
|
||||
assert task._cancel_stack[:len(old_stack)] == old_stack
|
||||
|
||||
@ -123,7 +123,7 @@ def spaces_types(
|
||||
f: int = -1,
|
||||
g: int = 1 if False else 2,
|
||||
h: str = "",
|
||||
i: str = r'',
|
||||
i: str = r"",
|
||||
):
|
||||
...
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
f'f-string without formatted values is just a string'
|
||||
f'{{NOT a formatted value}}'
|
||||
f'some f-string with {a} {few():.2f} {formatted.values!r}'
|
||||
f"f-string without formatted values is just a string"
|
||||
f"{{NOT a formatted value}}"
|
||||
f"some f-string with {a} {few():.2f} {formatted.values!r}"
|
||||
f"{f'{nested} inner'} outer"
|
||||
f'space between opening braces: { {a for a in (1, 2, 3)}}'
|
||||
f"space between opening braces: { {a for a in (1, 2, 3)}}"
|
||||
|
@ -80,7 +80,7 @@ def long_lines():
|
||||
|
||||
from library import some_connection, some_decorator
|
||||
|
||||
f'trigger 3.6 mode'
|
||||
f"trigger 3.6 mode"
|
||||
|
||||
|
||||
def func_no_args():
|
||||
@ -103,7 +103,7 @@ def func_no_args():
|
||||
async def coroutine(arg, exec=False):
|
||||
"Single-line docstring. Multiline is harder to reformat."
|
||||
async with some_connection() as conn:
|
||||
await conn.do_what_i_mean('SELECT bobby, tables FROM xkcd', timeout=2)
|
||||
await conn.do_what_i_mean("SELECT bobby, tables FROM xkcd", timeout=2)
|
||||
await asyncio.sleep(1)
|
||||
|
||||
|
||||
@ -120,7 +120,7 @@ def function_signature_stress_test(
|
||||
return text[number:-1]
|
||||
|
||||
|
||||
def spaces(a=1, b=(), c=[], d={}, e=True, f=-1, g=1 if False else 2, h="", i=r''):
|
||||
def spaces(a=1, b=(), c=[], d={}, e=True, f=-1, g=1 if False else 2, h="", i=r""):
|
||||
offset = attr.ib(default=attr.Factory(lambda: _r.uniform(10000, 200000)))
|
||||
assert task._cancel_stack[:len(old_stack)] == old_stack
|
||||
|
||||
@ -134,7 +134,7 @@ def spaces_types(
|
||||
f: int = -1,
|
||||
g: int = 1 if False else 2,
|
||||
h: str = "",
|
||||
i: str = r'',
|
||||
i: str = r"",
|
||||
):
|
||||
...
|
||||
|
||||
|
37
tests/string_quotes.py
Normal file
37
tests/string_quotes.py
Normal file
@ -0,0 +1,37 @@
|
||||
"Hello"
|
||||
"Don't do that"
|
||||
'Here is a "'
|
||||
'What\'s the deal here?'
|
||||
"What's the deal \"here\"?"
|
||||
"And \"here\"?"
|
||||
"""Strings with "" in them"""
|
||||
'''Strings with "" in them'''
|
||||
'''Here's a "'''
|
||||
'''Here's a " '''
|
||||
'''Just a normal triple
|
||||
quote'''
|
||||
f"just a normal {f} string"
|
||||
f'''This is a triple-quoted {f}-string'''
|
||||
f'MOAR {" ".join([])}'
|
||||
f"MOAR {' '.join([])}"
|
||||
r"raw string ftw"
|
||||
|
||||
# output
|
||||
|
||||
"Hello"
|
||||
"Don't do that"
|
||||
'Here is a "'
|
||||
"What's the deal here?"
|
||||
'What\'s the deal "here"?'
|
||||
'And "here"?'
|
||||
"""Strings with "" in them"""
|
||||
"""Strings with "" in them"""
|
||||
'''Here's a "'''
|
||||
"""Here's a " """
|
||||
"""Just a normal triple
|
||||
quote"""
|
||||
f"just a normal {f} string"
|
||||
f"""This is a triple-quoted {f}-string"""
|
||||
f'MOAR {" ".join([])}'
|
||||
f"MOAR {' '.join([])}"
|
||||
r"raw string ftw"
|
@ -17,25 +17,25 @@
|
||||
fs = partial(black.format_str, line_length=ll)
|
||||
THIS_FILE = Path(__file__)
|
||||
THIS_DIR = THIS_FILE.parent
|
||||
EMPTY_LINE = '# EMPTY LINE WITH WHITESPACE' + ' (this comment will be removed)'
|
||||
EMPTY_LINE = "# EMPTY LINE WITH WHITESPACE" + " (this comment will be removed)"
|
||||
|
||||
|
||||
def dump_to_stderr(*output: str) -> str:
|
||||
return '\n' + '\n'.join(output) + '\n'
|
||||
return "\n" + "\n".join(output) + "\n"
|
||||
|
||||
|
||||
def read_data(name: str) -> Tuple[str, str]:
|
||||
"""read_data('test_name') -> 'input', 'output'"""
|
||||
if not name.endswith(('.py', '.out')):
|
||||
name += '.py'
|
||||
if not name.endswith((".py", ".out")):
|
||||
name += ".py"
|
||||
_input: List[str] = []
|
||||
_output: List[str] = []
|
||||
with open(THIS_DIR / name, 'r', encoding='utf8') as test:
|
||||
with open(THIS_DIR / name, "r", encoding="utf8") as test:
|
||||
lines = test.readlines()
|
||||
result = _input
|
||||
for line in lines:
|
||||
line = line.replace(EMPTY_LINE, '')
|
||||
if line.rstrip() == '# output':
|
||||
line = line.replace(EMPTY_LINE, "")
|
||||
if line.rstrip() == "# output":
|
||||
result = _output
|
||||
continue
|
||||
|
||||
@ -43,23 +43,23 @@ def read_data(name: str) -> Tuple[str, str]:
|
||||
if _input and not _output:
|
||||
# If there's no output marker, treat the entire file as already pre-formatted.
|
||||
_output = _input[:]
|
||||
return ''.join(_input).strip() + '\n', ''.join(_output).strip() + '\n'
|
||||
return "".join(_input).strip() + "\n", "".join(_output).strip() + "\n"
|
||||
|
||||
|
||||
class BlackTestCase(unittest.TestCase):
|
||||
maxDiff = None
|
||||
|
||||
def assertFormatEqual(self, expected: str, actual: str) -> None:
|
||||
if actual != expected and not os.environ.get('SKIP_AST_PRINT'):
|
||||
if actual != expected and not os.environ.get("SKIP_AST_PRINT"):
|
||||
bdv: black.DebugVisitor[Any]
|
||||
black.out('Expected tree:', fg='green')
|
||||
black.out("Expected tree:", fg="green")
|
||||
try:
|
||||
exp_node = black.lib2to3_parse(expected)
|
||||
bdv = black.DebugVisitor()
|
||||
list(bdv.visit(exp_node))
|
||||
except Exception as ve:
|
||||
black.err(str(ve))
|
||||
black.out('Actual tree:', fg='red')
|
||||
black.out("Actual tree:", fg="red")
|
||||
try:
|
||||
exp_node = black.lib2to3_parse(actual)
|
||||
bdv = black.DebugVisitor()
|
||||
@ -70,7 +70,7 @@ def assertFormatEqual(self, expected: str, actual: str) -> None:
|
||||
|
||||
@patch("black.dump_to_file", dump_to_stderr)
|
||||
def test_self(self) -> None:
|
||||
source, expected = read_data('test_black')
|
||||
source, expected = read_data("test_black")
|
||||
actual = fs(source)
|
||||
self.assertFormatEqual(expected, actual)
|
||||
black.assert_equivalent(source, actual)
|
||||
@ -79,19 +79,19 @@ def test_self(self) -> None:
|
||||
|
||||
@patch("black.dump_to_file", dump_to_stderr)
|
||||
def test_black(self) -> None:
|
||||
source, expected = read_data('../black')
|
||||
source, expected = read_data("../black")
|
||||
actual = fs(source)
|
||||
self.assertFormatEqual(expected, actual)
|
||||
black.assert_equivalent(source, actual)
|
||||
black.assert_stable(source, actual, line_length=ll)
|
||||
self.assertFalse(ff(THIS_DIR / '..' / 'black.py'))
|
||||
self.assertFalse(ff(THIS_DIR / ".." / "black.py"))
|
||||
|
||||
def test_piping(self) -> None:
|
||||
source, expected = read_data('../black')
|
||||
source, expected = read_data("../black")
|
||||
hold_stdin, hold_stdout = sys.stdin, sys.stdout
|
||||
try:
|
||||
sys.stdin, sys.stdout = StringIO(source), StringIO()
|
||||
sys.stdin.name = '<stdin>'
|
||||
sys.stdin.name = "<stdin>"
|
||||
black.format_stdin_to_stdout(line_length=ll, fast=True, write_back=True)
|
||||
sys.stdout.seek(0)
|
||||
actual = sys.stdout.read()
|
||||
@ -103,16 +103,16 @@ def test_piping(self) -> None:
|
||||
|
||||
@patch("black.dump_to_file", dump_to_stderr)
|
||||
def test_setup(self) -> None:
|
||||
source, expected = read_data('../setup')
|
||||
source, expected = read_data("../setup")
|
||||
actual = fs(source)
|
||||
self.assertFormatEqual(expected, actual)
|
||||
black.assert_equivalent(source, actual)
|
||||
black.assert_stable(source, actual, line_length=ll)
|
||||
self.assertFalse(ff(THIS_DIR / '..' / 'setup.py'))
|
||||
self.assertFalse(ff(THIS_DIR / ".." / "setup.py"))
|
||||
|
||||
@patch("black.dump_to_file", dump_to_stderr)
|
||||
def test_function(self) -> None:
|
||||
source, expected = read_data('function')
|
||||
source, expected = read_data("function")
|
||||
actual = fs(source)
|
||||
self.assertFormatEqual(expected, actual)
|
||||
black.assert_equivalent(source, actual)
|
||||
@ -120,7 +120,7 @@ def test_function(self) -> None:
|
||||
|
||||
@patch("black.dump_to_file", dump_to_stderr)
|
||||
def test_expression(self) -> None:
|
||||
source, expected = read_data('expression')
|
||||
source, expected = read_data("expression")
|
||||
actual = fs(source)
|
||||
self.assertFormatEqual(expected, actual)
|
||||
black.assert_equivalent(source, actual)
|
||||
@ -128,7 +128,15 @@ def test_expression(self) -> None:
|
||||
|
||||
@patch("black.dump_to_file", dump_to_stderr)
|
||||
def test_fstring(self) -> None:
|
||||
source, expected = read_data('fstring')
|
||||
source, expected = read_data("fstring")
|
||||
actual = fs(source)
|
||||
self.assertFormatEqual(expected, actual)
|
||||
black.assert_equivalent(source, actual)
|
||||
black.assert_stable(source, actual, line_length=ll)
|
||||
|
||||
@patch("black.dump_to_file", dump_to_stderr)
|
||||
def test_string_quotes(self) -> None:
|
||||
source, expected = read_data("string_quotes")
|
||||
actual = fs(source)
|
||||
self.assertFormatEqual(expected, actual)
|
||||
black.assert_equivalent(source, actual)
|
||||
@ -136,7 +144,7 @@ def test_fstring(self) -> None:
|
||||
|
||||
@patch("black.dump_to_file", dump_to_stderr)
|
||||
def test_comments(self) -> None:
|
||||
source, expected = read_data('comments')
|
||||
source, expected = read_data("comments")
|
||||
actual = fs(source)
|
||||
self.assertFormatEqual(expected, actual)
|
||||
black.assert_equivalent(source, actual)
|
||||
@ -144,7 +152,7 @@ def test_comments(self) -> None:
|
||||
|
||||
@patch("black.dump_to_file", dump_to_stderr)
|
||||
def test_comments2(self) -> None:
|
||||
source, expected = read_data('comments2')
|
||||
source, expected = read_data("comments2")
|
||||
actual = fs(source)
|
||||
self.assertFormatEqual(expected, actual)
|
||||
black.assert_equivalent(source, actual)
|
||||
@ -152,7 +160,7 @@ def test_comments2(self) -> None:
|
||||
|
||||
@patch("black.dump_to_file", dump_to_stderr)
|
||||
def test_comments3(self) -> None:
|
||||
source, expected = read_data('comments3')
|
||||
source, expected = read_data("comments3")
|
||||
actual = fs(source)
|
||||
self.assertFormatEqual(expected, actual)
|
||||
black.assert_equivalent(source, actual)
|
||||
@ -160,7 +168,7 @@ def test_comments3(self) -> None:
|
||||
|
||||
@patch("black.dump_to_file", dump_to_stderr)
|
||||
def test_comments4(self) -> None:
|
||||
source, expected = read_data('comments4')
|
||||
source, expected = read_data("comments4")
|
||||
actual = fs(source)
|
||||
self.assertFormatEqual(expected, actual)
|
||||
black.assert_equivalent(source, actual)
|
||||
@ -168,7 +176,7 @@ def test_comments4(self) -> None:
|
||||
|
||||
@patch("black.dump_to_file", dump_to_stderr)
|
||||
def test_cantfit(self) -> None:
|
||||
source, expected = read_data('cantfit')
|
||||
source, expected = read_data("cantfit")
|
||||
actual = fs(source)
|
||||
self.assertFormatEqual(expected, actual)
|
||||
black.assert_equivalent(source, actual)
|
||||
@ -176,7 +184,7 @@ def test_cantfit(self) -> None:
|
||||
|
||||
@patch("black.dump_to_file", dump_to_stderr)
|
||||
def test_import_spacing(self) -> None:
|
||||
source, expected = read_data('import_spacing')
|
||||
source, expected = read_data("import_spacing")
|
||||
actual = fs(source)
|
||||
self.assertFormatEqual(expected, actual)
|
||||
black.assert_equivalent(source, actual)
|
||||
@ -184,7 +192,7 @@ def test_import_spacing(self) -> None:
|
||||
|
||||
@patch("black.dump_to_file", dump_to_stderr)
|
||||
def test_composition(self) -> None:
|
||||
source, expected = read_data('composition')
|
||||
source, expected = read_data("composition")
|
||||
actual = fs(source)
|
||||
self.assertFormatEqual(expected, actual)
|
||||
black.assert_equivalent(source, actual)
|
||||
@ -192,7 +200,7 @@ def test_composition(self) -> None:
|
||||
|
||||
@patch("black.dump_to_file", dump_to_stderr)
|
||||
def test_empty_lines(self) -> None:
|
||||
source, expected = read_data('empty_lines')
|
||||
source, expected = read_data("empty_lines")
|
||||
actual = fs(source)
|
||||
self.assertFormatEqual(expected, actual)
|
||||
black.assert_equivalent(source, actual)
|
||||
@ -200,7 +208,7 @@ def test_empty_lines(self) -> None:
|
||||
|
||||
@patch("black.dump_to_file", dump_to_stderr)
|
||||
def test_python2(self) -> None:
|
||||
source, expected = read_data('python2')
|
||||
source, expected = read_data("python2")
|
||||
actual = fs(source)
|
||||
self.assertFormatEqual(expected, actual)
|
||||
# black.assert_equivalent(source, actual)
|
||||
@ -208,7 +216,7 @@ def test_python2(self) -> None:
|
||||
|
||||
@patch("black.dump_to_file", dump_to_stderr)
|
||||
def test_fmtonoff(self) -> None:
|
||||
source, expected = read_data('fmtonoff')
|
||||
source, expected = read_data("fmtonoff")
|
||||
actual = fs(source)
|
||||
self.assertFormatEqual(expected, actual)
|
||||
black.assert_equivalent(source, actual)
|
||||
@ -226,68 +234,68 @@ def err(msg: str, **kwargs: Any) -> None:
|
||||
err_lines.append(msg)
|
||||
|
||||
with patch("black.out", out), patch("black.err", err):
|
||||
report.done(Path('f1'), changed=False)
|
||||
report.done(Path("f1"), changed=False)
|
||||
self.assertEqual(len(out_lines), 1)
|
||||
self.assertEqual(len(err_lines), 0)
|
||||
self.assertEqual(out_lines[-1], 'f1 already well formatted, good job.')
|
||||
self.assertEqual(unstyle(str(report)), '1 file left unchanged.')
|
||||
self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
|
||||
self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
|
||||
self.assertEqual(report.return_code, 0)
|
||||
report.done(Path('f2'), changed=True)
|
||||
report.done(Path("f2"), changed=True)
|
||||
self.assertEqual(len(out_lines), 2)
|
||||
self.assertEqual(len(err_lines), 0)
|
||||
self.assertEqual(out_lines[-1], 'reformatted f2')
|
||||
self.assertEqual(out_lines[-1], "reformatted f2")
|
||||
self.assertEqual(
|
||||
unstyle(str(report)), '1 file reformatted, 1 file left unchanged.'
|
||||
unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
|
||||
)
|
||||
self.assertEqual(report.return_code, 0)
|
||||
report.check = True
|
||||
self.assertEqual(report.return_code, 1)
|
||||
report.check = False
|
||||
report.failed(Path('e1'), 'boom')
|
||||
report.failed(Path("e1"), "boom")
|
||||
self.assertEqual(len(out_lines), 2)
|
||||
self.assertEqual(len(err_lines), 1)
|
||||
self.assertEqual(err_lines[-1], 'error: cannot format e1: boom')
|
||||
self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
|
||||
self.assertEqual(
|
||||
unstyle(str(report)),
|
||||
'1 file reformatted, 1 file left unchanged, '
|
||||
'1 file failed to reformat.',
|
||||
"1 file reformatted, 1 file left unchanged, "
|
||||
"1 file failed to reformat.",
|
||||
)
|
||||
self.assertEqual(report.return_code, 123)
|
||||
report.done(Path('f3'), changed=True)
|
||||
report.done(Path("f3"), changed=True)
|
||||
self.assertEqual(len(out_lines), 3)
|
||||
self.assertEqual(len(err_lines), 1)
|
||||
self.assertEqual(out_lines[-1], 'reformatted f3')
|
||||
self.assertEqual(out_lines[-1], "reformatted f3")
|
||||
self.assertEqual(
|
||||
unstyle(str(report)),
|
||||
'2 files reformatted, 1 file left unchanged, '
|
||||
'1 file failed to reformat.',
|
||||
"2 files reformatted, 1 file left unchanged, "
|
||||
"1 file failed to reformat.",
|
||||
)
|
||||
self.assertEqual(report.return_code, 123)
|
||||
report.failed(Path('e2'), 'boom')
|
||||
report.failed(Path("e2"), "boom")
|
||||
self.assertEqual(len(out_lines), 3)
|
||||
self.assertEqual(len(err_lines), 2)
|
||||
self.assertEqual(err_lines[-1], 'error: cannot format e2: boom')
|
||||
self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
|
||||
self.assertEqual(
|
||||
unstyle(str(report)),
|
||||
'2 files reformatted, 1 file left unchanged, '
|
||||
'2 files failed to reformat.',
|
||||
"2 files reformatted, 1 file left unchanged, "
|
||||
"2 files failed to reformat.",
|
||||
)
|
||||
self.assertEqual(report.return_code, 123)
|
||||
report.done(Path('f4'), changed=False)
|
||||
report.done(Path("f4"), changed=False)
|
||||
self.assertEqual(len(out_lines), 4)
|
||||
self.assertEqual(len(err_lines), 2)
|
||||
self.assertEqual(out_lines[-1], 'f4 already well formatted, good job.')
|
||||
self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
|
||||
self.assertEqual(
|
||||
unstyle(str(report)),
|
||||
'2 files reformatted, 2 files left unchanged, '
|
||||
'2 files failed to reformat.',
|
||||
"2 files reformatted, 2 files left unchanged, "
|
||||
"2 files failed to reformat.",
|
||||
)
|
||||
self.assertEqual(report.return_code, 123)
|
||||
report.check = True
|
||||
self.assertEqual(
|
||||
unstyle(str(report)),
|
||||
'2 files would be reformatted, 2 files would be left unchanged, '
|
||||
'2 files would fail to reformat.',
|
||||
"2 files would be reformatted, 2 files would be left unchanged, "
|
||||
"2 files would fail to reformat.",
|
||||
)
|
||||
|
||||
def test_is_python36(self) -> None:
|
||||
@ -297,20 +305,20 @@ def test_is_python36(self) -> None:
|
||||
self.assertTrue(black.is_python36(node))
|
||||
node = black.lib2to3_parse("def f(*, arg): f'string'\n")
|
||||
self.assertTrue(black.is_python36(node))
|
||||
source, expected = read_data('function')
|
||||
source, expected = read_data("function")
|
||||
node = black.lib2to3_parse(source)
|
||||
self.assertTrue(black.is_python36(node))
|
||||
node = black.lib2to3_parse(expected)
|
||||
self.assertTrue(black.is_python36(node))
|
||||
source, expected = read_data('expression')
|
||||
source, expected = read_data("expression")
|
||||
node = black.lib2to3_parse(source)
|
||||
self.assertFalse(black.is_python36(node))
|
||||
node = black.lib2to3_parse(expected)
|
||||
self.assertFalse(black.is_python36(node))
|
||||
|
||||
def test_debug_visitor(self) -> None:
|
||||
source, _ = read_data('debug_visitor.py')
|
||||
expected, _ = read_data('debug_visitor.out')
|
||||
source, _ = read_data("debug_visitor.py")
|
||||
expected, _ = read_data("debug_visitor.out")
|
||||
out_lines = []
|
||||
err_lines = []
|
||||
|
||||
@ -322,8 +330,8 @@ def err(msg: str, **kwargs: Any) -> None:
|
||||
|
||||
with patch("black.out", out), patch("black.err", err):
|
||||
black.DebugVisitor.show(source)
|
||||
actual = '\n'.join(out_lines) + '\n'
|
||||
log_name = ''
|
||||
actual = "\n".join(out_lines) + "\n"
|
||||
log_name = ""
|
||||
if expected != actual:
|
||||
log_name = black.dump_to_file(*out_lines)
|
||||
self.assertEqual(
|
||||
@ -333,5 +341,5 @@ def err(msg: str, **kwargs: Any) -> None:
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
Loading…
Reference in New Issue
Block a user