From 80bd2b3134b4f01da4e279d040a224326b3577e5 Mon Sep 17 00:00:00 2001 From: Zsolt Dollenstein Date: Sat, 31 Mar 2018 19:21:25 +0100 Subject: [PATCH] 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 --- black.py | 253 ++++++++++++++++++++++++----------------- setup.py | 56 ++++----- tests/comments.py | 4 +- tests/comments2.py | 34 +++--- tests/comments3.py | 2 +- tests/comments4.py | 2 +- tests/composition.py | 12 +- tests/empty_lines.py | 14 +-- tests/expression.py | 36 +++--- tests/fmtonoff.py | 18 +-- tests/fstring.py | 8 +- tests/function.py | 8 +- tests/string_quotes.py | 37 ++++++ tests/test_black.py | 132 +++++++++++---------- 14 files changed, 350 insertions(+), 266 deletions(-) create mode 100644 tests/string_quotes.py diff --git a/black.py b/black.py index 82fe5d1..dc03e0a 100644 --- a/black.py +++ b/black.py @@ -47,9 +47,9 @@ LeafID = int 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 @@ GRAMMARS = [ 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 @@ class Visitor(Generic[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 @@ class DebugVisitor(Visitor[T]): 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 @@ STATEMENT = { syms.classdef, } STANDALONE_COMMENT = 153 -LOGIC_OPERATORS = {'and', 'or'} +LOGIC_OPERATORS = {"and", "or"} COMPARATORS = { token.LESS, token.GREATER, @@ -500,14 +500,14 @@ class BracketTracker: 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 @@ class Line: 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 @@ class Line: 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 @@ class Line: 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 @@ class Line: 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 @@ class Line: 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 @@ class Line: 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 @@ class Line: after = len(self.leaves) - 1 if after == -1: comment.type = STANDALONE_COMMENT - comment.prefix = '' + comment.prefix = "" return False else: @@ -786,17 +786,17 @@ class Line: 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 @@ class UnformattedLines(Line): `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 @@ class EmptyLineTracker: 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 @@ class LineGenerator(Visitor[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 @@ class LineGenerator(Visitor[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 @@ ALWAYS_NO_SPACE = CLOSING_BRACKETS | {token.COMMA, STANDALONE_COMMENT} 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 delimiter_split(line: Line, py36: bool = False) -> 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 @@ class Report: 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 assert_equivalent(src: str, dst: str) -> None: 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() diff --git a/setup.py b/setup.py index 0f9141d..13969df 100644 --- a/setup.py +++ b/setup.py @@ -11,48 +11,48 @@ CURRENT_DIR = Path(__file__).parent 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.*)') - 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.*)") + 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"]}, ) diff --git a/tests/comments.py b/tests/comments.py index 3a39afd..8ce9ffe 100644 --- a/tests/comments.py +++ b/tests/comments.py @@ -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() diff --git a/tests/comments2.py b/tests/comments2.py index e90c079..49ef2dc 100644 --- a/tests/comments2.py +++ b/tests/comments2.py @@ -125,23 +125,23 @@ instruction() __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 @@ short ] 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 @@ short # 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? ) diff --git a/tests/comments3.py b/tests/comments3.py index b57f8f3..c1f0e75 100644 --- a/tests/comments3.py +++ b/tests/comments3.py @@ -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 diff --git a/tests/comments4.py b/tests/comments4.py index e74bf50..241b6ce 100644 --- a/tests/comments4.py +++ b/tests/comments4.py @@ -61,7 +61,7 @@ class C: 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) diff --git a/tests/composition.py b/tests/composition.py index 7b462ac..fb27b3e 100644 --- a/tests/composition.py +++ b/tests/composition.py @@ -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.", ) diff --git a/tests/empty_lines.py b/tests/empty_lines.py index ec04337..d001db4 100644 --- a/tests/empty_lines.py +++ b/tests/empty_lines.py @@ -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 diff --git a/tests/expression.py b/tests/expression.py index 79e7c7e..1e8fa5c 100644 --- a/tests/expression.py +++ b/tests/expression.py @@ -157,8 +157,8 @@ last_call() ... -'some_string' -b'\\xa3' +"some_string" +b"\\xa3" Name None True @@ -193,18 +193,18 @@ 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 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 @@ str or None if (1 if True else 2) else str or bytes or None [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 @@ Python3 > Python2 > COBOL 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 @@ 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) a = (1,) diff --git a/tests/fmtonoff.py b/tests/fmtonoff.py index 3e3db11..4bacfcf 100644 --- a/tests/fmtonoff.py +++ b/tests/fmtonoff.py @@ -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 @@ from third_party import X, Y, Z 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"", ): ... diff --git a/tests/fstring.py b/tests/fstring.py index 6b821be..b288cbc 100644 --- a/tests/fstring.py +++ b/tests/fstring.py @@ -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)}}" diff --git a/tests/function.py b/tests/function.py index 387e441..007cc98 100644 --- a/tests/function.py +++ b/tests/function.py @@ -80,7 +80,7 @@ from third_party import X, Y, Z 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"", ): ... diff --git a/tests/string_quotes.py b/tests/string_quotes.py new file mode 100644 index 0000000..1080aaf --- /dev/null +++ b/tests/string_quotes.py @@ -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" diff --git a/tests/test_black.py b/tests/test_black.py index 1c22e54..30ecaf6 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -17,25 +17,25 @@ ff = partial(black.format_file_in_place, line_length=ll, fast=True) 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 @@ class BlackTestCase(unittest.TestCase): @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 @@ class BlackTestCase(unittest.TestCase): @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 = '' + sys.stdin.name = "" 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 @@ class BlackTestCase(unittest.TestCase): @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 @@ class BlackTestCase(unittest.TestCase): @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 @@ class BlackTestCase(unittest.TestCase): @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 @@ class BlackTestCase(unittest.TestCase): @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 @@ class BlackTestCase(unittest.TestCase): @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 @@ class BlackTestCase(unittest.TestCase): @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 @@ class BlackTestCase(unittest.TestCase): @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 @@ class BlackTestCase(unittest.TestCase): @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 @@ class BlackTestCase(unittest.TestCase): @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 @@ class BlackTestCase(unittest.TestCase): @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 @@ class BlackTestCase(unittest.TestCase): @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 @@ class BlackTestCase(unittest.TestCase): @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 @@ class BlackTestCase(unittest.TestCase): @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 @@ class BlackTestCase(unittest.TestCase): 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 @@ class BlackTestCase(unittest.TestCase): 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 @@ class BlackTestCase(unittest.TestCase): 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 @@ class BlackTestCase(unittest.TestCase): ) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() -- 2.39.2