From 52fda8b0e9e52e94aae6cb3170c9b1b492a2d8b4 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Langa?= Date: Tue, 24 Apr 2018 11:50:31 -0700 Subject: [PATCH 01/16] Support sticky standalone comments (comments preceding defs, classes, and decorators) Fixes #56 Fixes #154 --- README.md | 11 ++++++----- black.py | 10 ++++++---- tests/comments2.py | 2 -- tests/comments5.py | 40 ++++++++++++++++++++++++++++++++++++++++ tests/comments6.py | 8 -------- tests/empty_lines.py | 10 ++++++++++ tests/fmtonoff.py | 2 -- tests/test_black.py | 8 -------- 8 files changed, 62 insertions(+), 29 deletions(-) delete mode 100644 tests/comments6.py diff --git a/README.md b/README.md index 05bff36..e2f91ef 100644 --- a/README.md +++ b/README.md @@ -230,11 +230,9 @@ are always reformatted to fit minimal space, this whitespace is lost. It will also insert proper spacing before and after function definitions. It's one line before and after inner functions and two lines before and -after module-level functions. *Black* will put those empty lines also -between the function definition and any standalone comments that -immediately precede the given function. If you want to comment on the -entire function, use a docstring or put a leading comment in the function -body. +after module-level functions. *Black* will not put empty lines between +function/class definitions and standalone comments that immediately precede +the given function/class. ### Trailing commas @@ -532,6 +530,9 @@ More details can be found in [CONTRIBUTING](CONTRIBUTING.md). * fixed comment indentation when a standalone comment closes a block (#16, #32) +* fixed standalone comments receiving extra empty lines if immediately preceding + a class, def, or decorator (#56, #154) + * fixed `--diff` not showing entire path (#130) * fixed parsing of complex expressions after star and double stars in diff --git a/black.py b/black.py index a03b9aa..11386d2 100644 --- a/black.py +++ b/black.py @@ -1040,12 +1040,14 @@ class EmptyLineTracker: # Don't insert empty lines before the first line in the file. return 0, 0 - if self.previous_line and self.previous_line.is_decorator: - # Don't insert empty lines between decorators. + if self.previous_line.is_decorator: return 0, 0 - if is_decorator and self.previous_line and self.previous_line.is_comment: - # Don't insert empty lines between decorator comments. + if ( + self.previous_line.is_comment + and self.previous_line.depth == current_line.depth + and before == 0 + ): return 0, 0 newlines = 2 diff --git a/tests/comments2.py b/tests/comments2.py index 73fff32..44a4711 100644 --- a/tests/comments2.py +++ b/tests/comments2.py @@ -161,8 +161,6 @@ else: # add_compiler(compilers[(7.1, 64)]) # Comment before function. - - def inline_comments_in_brackets_ruin_everything(): if typedargslist: parameters.children = [ diff --git a/tests/comments5.py b/tests/comments5.py index 703922d..d83b6b8 100644 --- a/tests/comments5.py +++ b/tests/comments5.py @@ -27,5 +27,45 @@ try: except OSError: print("problems") +import sys + + +# leading function comment +def wat(): + ... + # trailing function comment + + +# SECTION COMMENT + + +# leading 1 +@deco1 +# leading 2 +@deco2(with_args=True) +# leading 3 +@deco3 +def decorated1(): + ... + + +# leading 1 +@deco1 +# leading 2 +@deco2(with_args=True) +# leading function comment +def decorated1(): + ... + + +# Note: crappy but inevitable. The current design of EmptyLineTracker doesn't +# allow this to work correctly. The user will have to split those lines by +# hand. +some_instruction +# This comment should be split from `some_instruction` by two lines but isn't. +def g(): + ... + + if __name__ == "__main__": main() diff --git a/tests/comments6.py b/tests/comments6.py deleted file mode 100644 index 0565015..0000000 --- a/tests/comments6.py +++ /dev/null @@ -1,8 +0,0 @@ -@property -# TODO: X -@property -# TODO: Y -# TODO: Z -@property -def foo(): - pass diff --git a/tests/empty_lines.py b/tests/empty_lines.py index 0edeb01..5b7ce92 100644 --- a/tests/empty_lines.py +++ b/tests/empty_lines.py @@ -1,3 +1,7 @@ +"""Docstring.""" + + +# leading comment def f(): NO = '' SPACE = ' ' @@ -44,9 +48,11 @@ def f(): syms.dictsetmaker, }: return NO + ############################################################################### # SECTION BECAUSE SECTIONS ############################################################################### + def g(): NO = '' SPACE = ' ' @@ -89,6 +95,10 @@ def g(): # output +"""Docstring.""" + + +# leading comment def f(): NO = "" SPACE = " " diff --git a/tests/fmtonoff.py b/tests/fmtonoff.py index 16c3925..a7b9bc7 100644 --- a/tests/fmtonoff.py +++ b/tests/fmtonoff.py @@ -119,8 +119,6 @@ many_args=[1,2,3] 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""): offset = attr.ib(default=attr.Factory(lambda: _r.uniform(10000, 200000))) assert task._cancel_stack[:len(old_stack)] == old_stack diff --git a/tests/test_black.py b/tests/test_black.py index 9c029df..dd3beed 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -626,14 +626,6 @@ class BlackTestCase(unittest.TestCase): ) self.assertEqual(result.exit_code, 1) - @patch("black.dump_to_file", dump_to_stderr) - def test_comment_in_decorator(self) -> None: - source, expected = read_data("comments6") - actual = fs(source) - self.assertFormatEqual(expected, actual) - black.assert_equivalent(source, actual) - black.assert_stable(source, actual, line_length=ll) - if __name__ == "__main__": unittest.main() -- 2.39.5 From 92957a41e3f909c7b813b448f65cd437cf0139f2 Mon Sep 17 00:00:00 2001 From: Jonas Obrist Date: Wed, 25 Apr 2018 03:56:50 +0900 Subject: [PATCH 02/16] Make cache work with non-default line lenghts (#163) --- README.md | 6 +++--- black.py | 25 +++++++++++++++---------- tests/test_black.py | 44 ++++++++++++++++++++++++++++---------------- 3 files changed, 46 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index e2f91ef..02cf2f9 100644 --- a/README.md +++ b/README.md @@ -458,9 +458,9 @@ location of the file depends on the black version and the system on which black is run. The file is non-portable. The standard location on common operating systems is: -* Windows: `C:\\Users\\AppData\Local\black\black\Cache\\cache.pickle` -* macOS: `/Users//Library/Caches/black//cache.pickle` -* Linux: `/home//.cache/black//cache.pickle` +* Windows: `C:\\Users\\AppData\Local\black\black\Cache\\cache..pickle` +* macOS: `/Users//Library/Caches/black//cache..pickle` +* Linux: `/home//.cache/black//cache..pickle` ## Testimonials diff --git a/black.py b/black.py index 11386d2..aeb7402 100644 --- a/black.py +++ b/black.py @@ -233,7 +233,7 @@ def reformat_one( else: cache: Cache = {} if write_back != WriteBack.DIFF: - cache = read_cache() + cache = read_cache(line_length) src = src.resolve() if src in cache and cache[src] == get_cache_info(src): changed = Changed.CACHED @@ -245,7 +245,7 @@ def reformat_one( ): changed = Changed.YES if write_back != WriteBack.DIFF and changed is not Changed.NO: - write_cache(cache, [src]) + write_cache(cache, [src], line_length) report.done(src, changed) except Exception as exc: report.failed(src, str(exc)) @@ -269,7 +269,7 @@ async def schedule_formatting( """ cache: Cache = {} if write_back != WriteBack.DIFF: - cache = read_cache() + cache = read_cache(line_length) sources, cached = filter_cached(cache, sources) for src in cached: report.done(src, Changed.CACHED) @@ -312,7 +312,7 @@ async def schedule_formatting( if cancelled: await asyncio.gather(*cancelled, loop=loop, return_exceptions=True) if write_back != WriteBack.DIFF and formatted: - write_cache(cache, formatted) + write_cache(cache, formatted, line_length) def format_file_in_place( @@ -2473,18 +2473,22 @@ def sub_twice(regex: Pattern[str], replacement: str, original: str) -> str: CACHE_DIR = Path(user_cache_dir("black", version=__version__)) -CACHE_FILE = CACHE_DIR / "cache.pickle" -def read_cache() -> Cache: +def get_cache_file(line_length: int) -> Path: + return CACHE_DIR / f"cache.{line_length}.pickle" + + +def read_cache(line_length: int) -> Cache: """Read the cache if it exists and is well formed. If it is not well formed, the call to write_cache later should resolve the issue. """ - if not CACHE_FILE.exists(): + cache_file = get_cache_file(line_length) + if not cache_file.exists(): return {} - with CACHE_FILE.open("rb") as fobj: + with cache_file.open("rb") as fobj: try: cache: Cache = pickle.load(fobj) except pickle.UnpicklingError: @@ -2517,13 +2521,14 @@ def filter_cached( return todo, done -def write_cache(cache: Cache, sources: List[Path]) -> None: +def write_cache(cache: Cache, sources: List[Path], line_length: int) -> None: """Update the cache file.""" + cache_file = get_cache_file(line_length) try: if not CACHE_DIR.exists(): CACHE_DIR.mkdir(parents=True) new_cache = {**cache, **{src.resolve(): get_cache_info(src) for src in sources}} - with CACHE_FILE.open("wb") as fobj: + with cache_file.open("wb") as fobj: pickle.dump(new_cache, fobj, protocol=pickle.HIGHEST_PROTOCOL) except OSError: pass diff --git a/tests/test_black.py b/tests/test_black.py index dd3beed..94cbe35 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -57,8 +57,7 @@ def cache_dir(exists: bool = True) -> Iterator[Path]: cache_dir = Path(workspace) if not exists: cache_dir = cache_dir / "new" - cache_file = cache_dir / "cache.pkl" - with patch("black.CACHE_DIR", cache_dir), patch("black.CACHE_FILE", cache_file): + with patch("black.CACHE_DIR", cache_dir): yield cache_dir @@ -492,15 +491,16 @@ class BlackTestCase(unittest.TestCase): def test_cache_broken_file(self) -> None: with cache_dir() as workspace: - with black.CACHE_FILE.open("w") as fobj: + cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH) + with cache_file.open("w") as fobj: fobj.write("this is not a pickle") - self.assertEqual(black.read_cache(), {}) + self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH), {}) src = (workspace / "test.py").resolve() with src.open("w") as fobj: fobj.write("print('hello')") result = CliRunner().invoke(black.main, [str(src)]) self.assertEqual(result.exit_code, 0) - cache = black.read_cache() + cache = black.read_cache(black.DEFAULT_LINE_LENGTH) self.assertIn(src, cache) def test_cache_single_file_already_cached(self) -> None: @@ -508,7 +508,7 @@ class BlackTestCase(unittest.TestCase): src = (workspace / "test.py").resolve() with src.open("w") as fobj: fobj.write("print('hello')") - black.write_cache({}, [src]) + black.write_cache({}, [src], black.DEFAULT_LINE_LENGTH) result = CliRunner().invoke(black.main, [str(src)]) self.assertEqual(result.exit_code, 0) with src.open("r") as fobj: @@ -525,14 +525,14 @@ class BlackTestCase(unittest.TestCase): two = (workspace / "two.py").resolve() with two.open("w") as fobj: fobj.write("print('hello')") - black.write_cache({}, [one]) + black.write_cache({}, [one], black.DEFAULT_LINE_LENGTH) result = CliRunner().invoke(black.main, [str(workspace)]) self.assertEqual(result.exit_code, 0) with one.open("r") as fobj: self.assertEqual(fobj.read(), "print('hello')") with two.open("r") as fobj: self.assertEqual(fobj.read(), 'print("hello")\n') - cache = black.read_cache() + cache = black.read_cache(black.DEFAULT_LINE_LENGTH) self.assertIn(one, cache) self.assertIn(two, cache) @@ -543,24 +543,26 @@ class BlackTestCase(unittest.TestCase): fobj.write("print('hello')") result = CliRunner().invoke(black.main, [str(src), "--diff"]) self.assertEqual(result.exit_code, 0) - self.assertFalse(black.CACHE_FILE.exists()) + cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH) + self.assertFalse(cache_file.exists()) def test_no_cache_when_stdin(self) -> None: with cache_dir(): result = CliRunner().invoke(black.main, ["-"], input="print('hello')") self.assertEqual(result.exit_code, 0) - self.assertFalse(black.CACHE_FILE.exists()) + cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH) + self.assertFalse(cache_file.exists()) def test_read_cache_no_cachefile(self) -> None: with cache_dir(): - self.assertEqual(black.read_cache(), {}) + self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH), {}) def test_write_cache_read_cache(self) -> None: with cache_dir() as workspace: src = (workspace / "test.py").resolve() src.touch() - black.write_cache({}, [src]) - cache = black.read_cache() + black.write_cache({}, [src], black.DEFAULT_LINE_LENGTH) + cache = black.read_cache(black.DEFAULT_LINE_LENGTH) self.assertIn(src, cache) self.assertEqual(cache[src], black.get_cache_info(src)) @@ -583,7 +585,7 @@ class BlackTestCase(unittest.TestCase): def test_write_cache_creates_directory_if_needed(self) -> None: with cache_dir(exists=False) as workspace: self.assertFalse(workspace.exists()) - black.write_cache({}, []) + black.write_cache({}, [], black.DEFAULT_LINE_LENGTH) self.assertTrue(workspace.exists()) @event_loop(close=False) @@ -599,14 +601,14 @@ class BlackTestCase(unittest.TestCase): fobj.write('print("hello")\n') result = CliRunner().invoke(black.main, [str(workspace)]) self.assertEqual(result.exit_code, 123) - cache = black.read_cache() + cache = black.read_cache(black.DEFAULT_LINE_LENGTH) self.assertNotIn(failing, cache) self.assertIn(clean, cache) def test_write_cache_write_fail(self) -> None: with cache_dir(), patch.object(Path, "open") as mock: mock.side_effect = OSError - black.write_cache({}, []) + black.write_cache({}, [], black.DEFAULT_LINE_LENGTH) def test_check_diff_use_together(self) -> None: with cache_dir(): @@ -626,6 +628,16 @@ class BlackTestCase(unittest.TestCase): ) self.assertEqual(result.exit_code, 1) + def test_read_cache_line_lengths(self) -> None: + with cache_dir() as workspace: + path = (workspace / "file.py").resolve() + path.touch() + black.write_cache({}, [path], 1) + one = black.read_cache(1) + self.assertIn(path, one) + two = black.read_cache(2) + self.assertNotIn(path, two) + if __name__ == "__main__": unittest.main() -- 2.39.5 From 9d671bdbe13ab68cea1bba15001c43e90cf2c1a6 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Langa?= Date: Tue, 24 Apr 2018 12:19:56 -0700 Subject: [PATCH 03/16] Split ternary expressions Fixes #141 --- README.md | 2 ++ black.py | 9 +++++++++ tests/expression.diff | 12 +++++++++--- tests/expression.py | 6 ++++++ 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 02cf2f9..4663176 100644 --- a/README.md +++ b/README.md @@ -540,6 +540,8 @@ More details can be found in [CONTRIBUTING](CONTRIBUTING.md). * fixed invalid splitting on comma in lambda arguments (#133) +* fixed missing splits of ternary expressions (#141) + ### 18.4a2 * fixed parsing of unaligned standalone comments (#99, #112) diff --git a/black.py b/black.py index aeb7402..5bf466a 100644 --- a/black.py +++ b/black.py @@ -583,6 +583,7 @@ UNPACKING_PARENTS = { } COMPREHENSION_PRIORITY = 20 COMMA_PRIORITY = 10 +TERNARY_PRIORITY = 7 LOGIC_PRIORITY = 5 STRING_PRIORITY = 4 COMPARATOR_PRIORITY = 3 @@ -1602,6 +1603,14 @@ def is_split_before_delimiter(leaf: Leaf, previous: Leaf = None) -> int: ): return COMPREHENSION_PRIORITY + if ( + leaf.type == token.NAME + and leaf.value in {"if", "else"} + and leaf.parent + and leaf.parent.type == syms.test + ): + return TERNARY_PRIORITY + if leaf.type == token.NAME and leaf.value in LOGIC_OPERATORS and leaf.parent: return LOGIC_PRIORITY diff --git a/tests/expression.diff b/tests/expression.diff index 9da0048..da48a13 100644 --- a/tests/expression.diff +++ b/tests/expression.diff @@ -11,7 +11,7 @@ True False 1 -@@ -29,60 +29,78 @@ +@@ -29,61 +29,83 @@ ~great +value -1 @@ -37,8 +37,14 @@ (str or None) if True else (str or bytes or None) str or None if (1 if True else 2) else str or bytes or None (str or None) if (1 if True else 2) else (str or bytes or None) +-((super_long_variable_name or None) if (1 if super_long_test_name else 2) else (str or bytes or None)) -{'2.7': dead, '3.7': (long_live or die_hard)} -{'2.7': dead, '3.7': (long_live or die_hard), **{'3.6': verygood}} ++( ++ (super_long_variable_name or None) ++ if (1 if super_long_test_name else 2) ++ else (str or bytes or None) ++) +{"2.7": dead, "3.7": (long_live or die_hard)} +{"2.7": dead, "3.7": (long_live or die_hard), **{"3.6": verygood}} {**a, **b, **c} @@ -110,7 +116,7 @@ call(**self.screen_kwargs) call(b, **self.screen_kwargs) lukasz.langa.pl -@@ -91,11 +109,11 @@ +@@ -92,11 +114,11 @@ 1.0 .real ....__class__ list[str] @@ -123,7 +129,7 @@ ] slice[0] slice[0:1] -@@ -122,88 +140,122 @@ +@@ -123,88 +145,122 @@ numpy[-(c + 1):, d] numpy[:, l[-2]] numpy[:, ::-1] diff --git a/tests/expression.py b/tests/expression.py index c67505f..2c4d8dd 100644 --- a/tests/expression.py +++ b/tests/expression.py @@ -43,6 +43,7 @@ str or None if True else str or bytes or None (str or None) if True else (str or bytes or None) str or None if (1 if True else 2) else str or bytes or None (str or None) if (1 if True else 2) else (str or bytes or None) +((super_long_variable_name or None) if (1 if super_long_test_name else 2) else (str or bytes or None)) {'2.7': dead, '3.7': (long_live or die_hard)} {'2.7': dead, '3.7': (long_live or die_hard), **{'3.6': verygood}} {**a, **b, **c} @@ -260,6 +261,11 @@ str or None if True else str or bytes or None (str or None) if True else (str or bytes or None) str or None if (1 if True else 2) else str or bytes or None (str or None) if (1 if True else 2) else (str or bytes or None) +( + (super_long_variable_name or None) + if (1 if super_long_test_name else 2) + else (str or bytes or None) +) {"2.7": dead, "3.7": (long_live or die_hard)} {"2.7": dead, "3.7": (long_live or die_hard), **{"3.6": verygood}} {**a, **b, **c} -- 2.39.5 From b250aed47bf11240b460bf4ecb57ba3f28078fd1 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Langa?= Date: Tue, 24 Apr 2018 12:40:46 -0700 Subject: [PATCH 04/16] Do not enforce empty lines after control flow statements Fixes #90 --- README.md | 8 ++++---- black.py | 10 ---------- tests/expression.diff | 18 ++++-------------- tests/expression.py | 8 -------- tests/function.py | 2 -- 5 files changed, 8 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 4663176..a589d0d 100644 --- a/README.md +++ b/README.md @@ -218,10 +218,7 @@ bother you if you overdo it by a few km/h". *Black* avoids spurious vertical whitespace. This is in the spirit of PEP 8 which says that in-function vertical whitespace should only be -used sparingly. One exception is control flow statements: *Black* will -always emit an extra empty line after ``return``, ``raise``, ``break``, -``continue``, and ``yield``. This is to make changes in control flow -more prominent to readers of your code. +used sparingly. *Black* will allow single empty lines inside functions, and single and double empty lines on module level left by the original editors, except @@ -528,6 +525,9 @@ More details can be found in [CONTRIBUTING](CONTRIBUTING.md). * generalized star expression handling, including double stars; this fixes multiplication making expressions "unsafe" for trailing commas (#132) +* Black no longer enforces putting empty lines behind control flow statements + (#90) + * fixed comment indentation when a standalone comment closes a block (#16, #32) * fixed standalone comments receiving extra empty lines if immediately preceding diff --git a/black.py b/black.py index 5bf466a..95489f3 100644 --- a/black.py +++ b/black.py @@ -1056,9 +1056,6 @@ class EmptyLineTracker: newlines -= 1 return newlines, 0 - if current_line.is_flow_control: - return before, 1 - if ( self.previous_line and self.previous_line.is_import @@ -1067,13 +1064,6 @@ class EmptyLineTracker: ): return (before or 1), 0 - if ( - self.previous_line - and self.previous_line.is_yield - and (not current_line.is_yield or depth != self.previous_line.depth) - ): - return (before or 1), 0 - return before, 0 diff --git a/tests/expression.diff b/tests/expression.diff index da48a13..11c1355 100644 --- a/tests/expression.diff +++ b/tests/expression.diff @@ -129,7 +129,7 @@ ] slice[0] slice[0:1] -@@ -123,88 +145,122 @@ +@@ -123,88 +145,114 @@ numpy[-(c + 1):, d] numpy[:, l[-2]] numpy[:, ::-1] @@ -190,10 +190,9 @@ + def gen(): yield from outside_of_generator -+ a = (yield) -+ ++ async def f(): await some.complicated[0].call(with_args=(True or (1 is not 1))) -print(* [] or [1]) @@ -242,8 +241,6 @@ -if ( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa / - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa --): -- return True +print(*lambda x: x) +for (x,) in (1,), (2,), (3,): + ... @@ -263,42 +260,35 @@ + or signal.getsignal(signal.SIGINT) != signal.default_int_handler +): + return True -+ +if ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +): + return True -+ +if ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +): + return True -+ +if ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +): + return True -+ +if ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +): + return True -+ +if ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + * aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +): + return True -+ +if ( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + / aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -+): -+ return True -+ + ): + return True last_call() # standalone comment at ENDMARKER diff --git a/tests/expression.py b/tests/expression.py index 2c4d8dd..274c150 100644 --- a/tests/expression.py +++ b/tests/expression.py @@ -409,7 +409,6 @@ mapping = { def gen(): yield from outside_of_generator - a = (yield) @@ -438,42 +437,35 @@ if ( or signal.getsignal(signal.SIGINT) != signal.default_int_handler ): return True - if ( aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ): return True - if ( aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ): return True - if ( aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ): return True - if ( aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ): return True - if ( aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa * aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ): return True - if ( aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa / aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ): return True - last_call() # standalone comment at ENDMARKER diff --git a/tests/function.py b/tests/function.py index 08f9414..9a12bf6 100644 --- a/tests/function.py +++ b/tests/function.py @@ -102,13 +102,11 @@ def func_no_args(): c if True: raise RuntimeError - if False: ... for i in range(10): print(i) continue - exec("new-style exec", {}, {}) return None -- 2.39.5 From 09f5ee3a19f4274bb848324867bd8e68724cf851 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Langa?= Date: Tue, 24 Apr 2018 13:44:28 -0700 Subject: [PATCH 05/16] Split imports like isort Fixes #127 Partially addresses #152 --- README.md | 9 +++++++++ black.py | 20 ++++++++++++++++++++ tests/import_spacing.py | 21 +++++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/README.md b/README.md index a589d0d..489bda5 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,13 @@ between two distinct sections of the code that otherwise share the same indentation level (like the arguments list and the docstring in the example above). +If a line of "from" imports cannot fit in the allotted length, it's always split +into one per line. Imports tend to change often and this minimizes diffs, as well +as enables readers of code to easily find which commit introduced a particular +import. This exception also makes *Black* compatible with +[isort](https://pypi.org/p/isort/). Use `multi_line_output=3` and +`include_trailing_comma=True` in your isort config. + ### Line length @@ -528,6 +535,8 @@ More details can be found in [CONTRIBUTING](CONTRIBUTING.md). * Black no longer enforces putting empty lines behind control flow statements (#90) +* Black now splits imports like "Mode 3 + trailing comma" of isort (#127) + * fixed comment indentation when a standalone comment closes a block (#16, #32) * fixed standalone comments receiving extra empty lines if immediately preceding diff --git a/black.py b/black.py index 95489f3..21e3743 100644 --- a/black.py +++ b/black.py @@ -1712,6 +1712,8 @@ def split_line( split_funcs: List[SplitFunc] if line.is_def: split_funcs = [left_hand_split] + elif line.is_import: + split_funcs = [explode_split] elif line.inside_brackets: split_funcs = [delimiter_split, standalone_comment_split, right_hand_split] else: @@ -1978,6 +1980,24 @@ def standalone_comment_split(line: Line, py36: bool = False) -> Iterator[Line]: yield current_line +def explode_split( + line: Line, py36: bool = False, omit: Collection[LeafID] = () +) -> Iterator[Line]: + """Split by RHS and immediately split contents by a delimiter.""" + new_lines = list(right_hand_split(line, py36, omit)) + if len(new_lines) != 3: + yield from new_lines + return + + yield new_lines[0] + try: + yield from delimiter_split(new_lines[1], py36) + except CannotSplit: + yield new_lines[1] + + yield new_lines[2] + + def is_import(leaf: Leaf) -> bool: """Return True if the given leaf starts an import statement.""" p = leaf.parent diff --git a/tests/import_spacing.py b/tests/import_spacing.py index 4091148..f095ba1 100644 --- a/tests/import_spacing.py +++ b/tests/import_spacing.py @@ -17,6 +17,10 @@ from ..runners import * # comment here from ..queues import * from ..streams import * +from some_library import ( + Just, Enough, Libraries, To, Fit, In, This, Nice, Split, Which, We, No, Longer, Use +) + from .a.b.c.subprocess import * from . import (tasks) from . import (A, B, C) @@ -59,6 +63,23 @@ from ..runners import * # comment here from ..queues import * from ..streams import * +from some_library import ( + Just, + Enough, + Libraries, + To, + Fit, + In, + This, + Nice, + Split, + Which, + We, + No, + Longer, + Use, +) + from .a.b.c.subprocess import * from . import tasks from . import A, B, C -- 2.39.5 From 1445bea97edee472e053f00010cb378f4b51e1c4 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Langa?= Date: Tue, 24 Apr 2018 13:52:12 -0700 Subject: [PATCH 06/16] 18.4a3 --- README.md | 3 ++- black.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 489bda5..09779d8 100644 --- a/README.md +++ b/README.md @@ -522,7 +522,7 @@ More details can be found in [CONTRIBUTING](CONTRIBUTING.md). ## Change Log -### 18.4a3 (unreleased) +### 18.4a3 * added a "cache"; files already reformatted that haven't changed on disk won't be reformatted again (#109) @@ -551,6 +551,7 @@ More details can be found in [CONTRIBUTING](CONTRIBUTING.md). * fixed missing splits of ternary expressions (#141) + ### 18.4a2 * fixed parsing of unaligned standalone comments (#99, #112) diff --git a/black.py b/black.py index 21e3743..e899f7f 100644 --- a/black.py +++ b/black.py @@ -43,8 +43,9 @@ from blib2to3 import pygram, pytree from blib2to3.pgen2 import driver, token from blib2to3.pgen2.parse import ParseError -__version__ = "18.4a2" +__version__ = "18.4a3" DEFAULT_LINE_LENGTH = 88 + # types syms = pygram.python_symbols FileContent = str -- 2.39.5 From b62bd4de75694dfe775ff4f6f7f0a567fda549a4 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Langa?= Date: Tue, 24 Apr 2018 14:27:21 -0700 Subject: [PATCH 07/16] Add `explode_split` to documentation --- black.py | 3 ++- docs/reference/reference_functions.rst | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/black.py b/black.py index e899f7f..5458198 100644 --- a/black.py +++ b/black.py @@ -1984,13 +1984,14 @@ def standalone_comment_split(line: Line, py36: bool = False) -> Iterator[Line]: def explode_split( line: Line, py36: bool = False, omit: Collection[LeafID] = () ) -> Iterator[Line]: - """Split by RHS and immediately split contents by a delimiter.""" + """Split by rightmost bracket and immediately split contents by a delimiter.""" new_lines = list(right_hand_split(line, py36, omit)) if len(new_lines) != 3: yield from new_lines return yield new_lines[0] + try: yield from delimiter_split(new_lines[1], py36) except CannotSplit: diff --git a/docs/reference/reference_functions.rst b/docs/reference/reference_functions.rst index 40d9665..d0ded95 100644 --- a/docs/reference/reference_functions.rst +++ b/docs/reference/reference_functions.rst @@ -60,6 +60,8 @@ Split functions .. autofunction:: black.delimiter_split +.. autofunction:: black.explode_split + .. autofunction:: black.left_hand_split .. autofunction:: black.right_hand_split -- 2.39.5 From 25abcea6c5b7edff49841f46f4de1ccd14c05f4b Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Langa?= Date: Thu, 26 Apr 2018 17:10:40 -0700 Subject: [PATCH 08/16] Reword inspiration Fixes #167 --- CONTRIBUTING.md | 4 ++-- README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7008ecb..f674224 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,8 +7,8 @@ yet? ## Bird's eye view -In terms of inspiration, *Black* is about as configurable as *gofmt* and -*rustfmt* are. This is deliberate. +In terms of inspiration, *Black* is about as configurable as *gofmt*. +This is deliberate. Bug reports and fixes are always welcome! Please follow the issue template on GitHub for best results. diff --git a/README.md b/README.md index 09779d8..6cb50eb 100644 --- a/README.md +++ b/README.md @@ -506,8 +506,8 @@ MIT ## Contributing to Black -In terms of inspiration, *Black* is about as configurable as *gofmt* and -*rustfmt* are. This is deliberate. +In terms of inspiration, *Black* is about as configurable as *gofmt*. +This is deliberate. Bug reports and fixes are always welcome! However, before you suggest a new feature or configuration knob, ask yourself why you want it. If it -- 2.39.5 From 0f3ecb7e500f9668a7f9ec74a43d8d565df6e2ea Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Langa?= Date: Fri, 27 Apr 2018 14:02:10 -0700 Subject: [PATCH 09/16] 18.4a4 hotfix: don't populate the cache on --check Fixes #175 --- README.md | 5 +++++ black.py | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6cb50eb..bc8977b 100644 --- a/README.md +++ b/README.md @@ -522,6 +522,11 @@ More details can be found in [CONTRIBUTING](CONTRIBUTING.md). ## Change Log +### 18.4a4 + +* don't populate the cache on `--check` (#175) + + ### 18.4a3 * added a "cache"; files already reformatted that haven't changed on disk diff --git a/black.py b/black.py index 5458198..4c5f0f0 100644 --- a/black.py +++ b/black.py @@ -43,7 +43,7 @@ from blib2to3 import pygram, pytree from blib2to3.pgen2 import driver, token from blib2to3.pgen2.parse import ParseError -__version__ = "18.4a3" +__version__ = "18.4a4" DEFAULT_LINE_LENGTH = 88 # types @@ -245,7 +245,7 @@ def reformat_one( ) ): changed = Changed.YES - if write_back != WriteBack.DIFF and changed is not Changed.NO: + if write_back == WriteBack.YES and changed is not Changed.NO: write_cache(cache, [src], line_length) report.done(src, changed) except Exception as exc: @@ -312,7 +312,7 @@ async def schedule_formatting( if cancelled: await asyncio.gather(*cancelled, loop=loop, return_exceptions=True) - if write_back != WriteBack.DIFF and formatted: + if write_back == WriteBack.YES and formatted: write_cache(cache, formatted, line_length) -- 2.39.5 From 6243540ae739e323c936769f6f4ace0272707405 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Langa?= Date: Sat, 28 Apr 2018 13:47:59 -0700 Subject: [PATCH 10/16] .gititnore += .vscode --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5d81454..a796585 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .coverage _build .DS_Store +.vscode docs/_static/pypi.svg -- 2.39.5 From 188572118253f410eaf24d9945e369f744760f42 Mon Sep 17 00:00:00 2001 From: Zsolt Dollenstein Date: Sun, 29 Apr 2018 00:50:08 -0700 Subject: [PATCH 11/16] fix type errors in setup.py (#179) --- setup.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 4e9a5c9..67a1e76 100644 --- a/setup.py +++ b/setup.py @@ -10,17 +10,18 @@ from pathlib import Path # noqa E402 CURRENT_DIR = Path(__file__).parent -def get_long_description(): +def get_long_description() -> str: readme_md = CURRENT_DIR / "README.md" with open(readme_md, encoding="utf8") as ld_file: return ld_file.read() -def get_version(): +def get_version() -> str: 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") + match = _version_re.search(f.read()) + version = match.group("version") if match is not None else '"unknown"' return str(ast.literal_eval(version)) -- 2.39.5 From 9f096d55365cb63548eef97e254c2793ae2776a0 Mon Sep 17 00:00:00 2001 From: Zsolt Dollenstein Date: Mon, 30 Apr 2018 22:49:30 -0700 Subject: [PATCH 12/16] Format subscriptions in a PEP-8 compliant way (#178) Fixes #157 --- .flake8 | 2 +- README.md | 13 +++++++ black.py | 87 +++++++++++++++++++++++++++++++++++++------ tests/expression.diff | 2 +- tests/expression.py | 12 +++--- tests/fmtonoff.py | 2 +- tests/function.py | 2 +- tests/slices.py | 31 +++++++++++++++ tests/test_black.py | 8 ++++ 9 files changed, 137 insertions(+), 22 deletions(-) create mode 100644 tests/slices.py diff --git a/.flake8 b/.flake8 index 5838390..fae93b0 100644 --- a/.flake8 +++ b/.flake8 @@ -2,7 +2,7 @@ # Keep in sync with setup.cfg which is used for source packages. [flake8] -ignore = E266, E501, W503 +ignore = E203, E266, E501, W503 max-line-length = 80 max-complexity = 15 select = B,C,E,F,W,T4,B9 diff --git a/README.md b/README.md index bc8977b..31f92c9 100644 --- a/README.md +++ b/README.md @@ -302,6 +302,19 @@ This behaviour may raise ``W503 line break before binary operator`` warnings in style guide enforcement tools like Flake8. Since ``W503`` is not PEP 8 compliant, you should tell Flake8 to ignore these warnings. +### Slices + +PEP 8 [recommends](https://www.python.org/dev/peps/pep-0008/#whitespace-in-expressions-and-statements) +to treat ``:`` in slices as a binary operator with the lowest priority, and to +leave an equal amount of space on either side, except if a parameter is omitted +(e.g. ``ham[1 + 1 :]``). It also states that for extended slices, both ``:`` +operators have to have the same amount of spacing, except if a parameter is +omitted (``ham[1 + 1 ::]``). *Black* enforces these rules consistently. + +This behaviour may raise ``E203 whitespace before ':'`` warnings in style guide +enforcement tools like Flake8. Since ``E203`` is not PEP 8 compliant, you should +tell Flake8 to ignore these warnings. + ### Parentheses Some parentheses are optional in the Python grammar. Any expression can diff --git a/black.py b/black.py index 4c5f0f0..5e087d1 100644 --- a/black.py +++ b/black.py @@ -89,11 +89,11 @@ class FormatError(Exception): self.consumed = consumed def trim_prefix(self, leaf: Leaf) -> None: - leaf.prefix = leaf.prefix[self.consumed:] + leaf.prefix = leaf.prefix[self.consumed :] def leaf_from_consumed(self, leaf: Leaf) -> Leaf: """Returns a new Leaf from the consumed part of the prefix.""" - unformatted_prefix = leaf.prefix[:self.consumed] + unformatted_prefix = leaf.prefix[: self.consumed] return Leaf(token.NEWLINE, unformatted_prefix) @@ -582,6 +582,23 @@ UNPACKING_PARENTS = { syms.listmaker, syms.testlist_gexp, } +TEST_DESCENDANTS = { + syms.test, + syms.lambdef, + syms.or_test, + syms.and_test, + syms.not_test, + syms.comparison, + syms.star_expr, + syms.expr, + syms.xor_expr, + syms.and_expr, + syms.shift_expr, + syms.arith_expr, + syms.trailer, + syms.term, + syms.power, +} COMPREHENSION_PRIORITY = 20 COMMA_PRIORITY = 10 TERNARY_PRIORITY = 7 @@ -698,6 +715,10 @@ class BracketTracker: return False + def get_open_lsqb(self) -> Optional[Leaf]: + """Return the most recent opening square bracket (if any).""" + return self.bracket_match.get((self.depth - 1, token.RSQB)) + @dataclass class Line: @@ -726,7 +747,9 @@ class Line: if self.leaves and not preformatted: # Note: at this point leaf.prefix should be empty except for # imports, for which we only preserve newlines. - leaf.prefix += whitespace(leaf) + leaf.prefix += whitespace( + leaf, complex_subscript=self.is_complex_subscript(leaf) + ) if self.inside_brackets or not preformatted: self.bracket_tracker.mark(leaf) self.maybe_remove_trailing_comma(leaf) @@ -859,7 +882,7 @@ class Line: else: return False - for leaf in self.leaves[_opening_index + 1:]: + for leaf in self.leaves[_opening_index + 1 :]: if leaf is closing: break @@ -920,6 +943,24 @@ class Line: self.comments[i] = (comma_index - 1, comment) self.leaves.pop() + def is_complex_subscript(self, leaf: Leaf) -> bool: + """Return True iff `leaf` is part of a slice with non-trivial exprs.""" + open_lsqb = ( + leaf if leaf.type == token.LSQB else self.bracket_tracker.get_open_lsqb() + ) + if open_lsqb is None: + return False + + subscript_start = open_lsqb.next_sibling + if ( + isinstance(subscript_start, Node) + and subscript_start.type == syms.subscriptlist + ): + subscript_start = child_towards(subscript_start, leaf) + return subscript_start is not None and any( + n.type in TEST_DESCENDANTS for n in subscript_start.pre_order() + ) + def __str__(self) -> str: """Render the line.""" if not self: @@ -1303,8 +1344,12 @@ BRACKETS = OPENING_BRACKETS | CLOSING_BRACKETS 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`.""" +def whitespace(leaf: Leaf, *, complex_subscript: bool) -> str: # noqa C901 + """Return whitespace prefix if needed for the given `leaf`. + + `complex_subscript` signals whether the given leaf is part of a subscription + which has non-trivial arguments, like arithmetic expressions or function calls. + """ NO = "" SPACE = " " DOUBLESPACE = " " @@ -1318,7 +1363,10 @@ def whitespace(leaf: Leaf) -> str: # noqa C901 return DOUBLESPACE assert p is not None, f"INTERNAL ERROR: hand-made leaf without parent: {leaf!r}" - if t == token.COLON and p.type not in {syms.subscript, syms.subscriptlist}: + if ( + t == token.COLON + and p.type not in {syms.subscript, syms.subscriptlist, syms.sliceop} + ): return NO prev = leaf.prev_sibling @@ -1328,7 +1376,13 @@ def whitespace(leaf: Leaf) -> str: # noqa C901 return NO if t == token.COLON: - return SPACE if prevp.type == token.COMMA else NO + if prevp.type == token.COLON: + return NO + + elif prevp.type != token.COMMA and not complex_subscript: + return NO + + return SPACE if prevp.type == token.EQUAL: if prevp.parent: @@ -1349,7 +1403,7 @@ def whitespace(leaf: Leaf) -> str: # noqa C901 elif prevp.type == token.COLON: if prevp.parent and prevp.parent.type in {syms.subscript, syms.sliceop}: - return NO + return SPACE if complex_subscript else NO elif ( prevp.parent @@ -1455,7 +1509,7 @@ def whitespace(leaf: Leaf) -> str: # noqa C901 if prev and prev.type == token.LPAR: return NO - elif p.type == syms.subscript: + elif p.type in {syms.subscript, syms.sliceop}: # indexing if not prev: assert p.parent is not None, "subscripts are always parented" @@ -1464,7 +1518,7 @@ def whitespace(leaf: Leaf) -> str: # noqa C901 return NO - else: + elif not complex_subscript: return NO elif p.type == syms.atom: @@ -1534,6 +1588,14 @@ def preceding_leaf(node: Optional[LN]) -> Optional[Leaf]: return None +def child_towards(ancestor: Node, descendant: LN) -> Optional[LN]: + """Return the child of `ancestor` that contains `descendant`.""" + node: Optional[LN] = descendant + while node and node.parent != ancestor: + node = node.parent + return node + + def is_split_after_delimiter(leaf: Leaf, previous: Leaf = None) -> int: """Return the priority of the `leaf` delimiter, given a line break after it. @@ -1994,6 +2056,7 @@ def explode_split( try: yield from delimiter_split(new_lines[1], py36) + except CannotSplit: yield new_lines[1] @@ -2061,7 +2124,7 @@ def normalize_string_quotes(leaf: Leaf) -> None: unescaped_new_quote = re.compile(rf"(([^\\]|^)(\\\\)*){new_quote}") escaped_new_quote = re.compile(rf"([^\\]|^)\\(\\\\)*{new_quote}") escaped_orig_quote = re.compile(rf"([^\\]|^)\\(\\\\)*{orig_quote}") - body = leaf.value[first_quote_pos + len(orig_quote):-len(orig_quote)] + body = leaf.value[first_quote_pos + len(orig_quote) : -len(orig_quote)] if "r" in prefix.casefold(): if unescaped_new_quote.search(body): # There's at least one unescaped new_quote in this raw string diff --git a/tests/expression.diff b/tests/expression.diff index 11c1355..309a480 100644 --- a/tests/expression.diff +++ b/tests/expression.diff @@ -130,7 +130,7 @@ slice[0] slice[0:1] @@ -123,88 +145,114 @@ - numpy[-(c + 1):, d] + numpy[-(c + 1) :, d] numpy[:, l[-2]] numpy[:, ::-1] numpy[np.newaxis, :] diff --git a/tests/expression.py b/tests/expression.py index 274c150..d170a66 100644 --- a/tests/expression.py +++ b/tests/expression.py @@ -105,7 +105,7 @@ slice[:] slice[:-1] slice[1:] slice[::-1] -slice[d::d + 1] +slice[d :: d + 1] slice[:c, c - 1] numpy[:, 0:1] numpy[:, :-1] @@ -119,8 +119,8 @@ numpy[4:, 2:] numpy[:, (0, 1, 2, 5)] numpy[0, [0]] numpy[:, [i]] -numpy[1:c + 1, c] -numpy[-(c + 1):, d] +numpy[1 : c + 1, c] +numpy[-(c + 1) :, d] numpy[:, l[-2]] numpy[:, ::-1] numpy[np.newaxis, :] @@ -341,7 +341,7 @@ slice[:] slice[:-1] slice[1:] slice[::-1] -slice[d::d + 1] +slice[d :: d + 1] slice[:c, c - 1] numpy[:, 0:1] numpy[:, :-1] @@ -355,8 +355,8 @@ numpy[4:, 2:] numpy[:, (0, 1, 2, 5)] numpy[0, [0]] numpy[:, [i]] -numpy[1:c + 1, c] -numpy[-(c + 1):, d] +numpy[1 : c + 1, c] +numpy[-(c + 1) :, d] numpy[:, l[-2]] numpy[:, ::-1] numpy[np.newaxis, :] diff --git a/tests/fmtonoff.py b/tests/fmtonoff.py index a7b9bc7..0ff6672 100644 --- a/tests/fmtonoff.py +++ b/tests/fmtonoff.py @@ -121,7 +121,7 @@ def function_signature_stress_test(number:int,no_annotation=None,text:str='defau # fmt: on 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 + assert task._cancel_stack[: len(old_stack)] == old_stack def spaces_types( diff --git a/tests/function.py b/tests/function.py index 9a12bf6..4ec9057 100644 --- a/tests/function.py +++ b/tests/function.py @@ -133,7 +133,7 @@ def function_signature_stress_test( 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 + assert task._cancel_stack[: len(old_stack)] == old_stack def spaces_types( diff --git a/tests/slices.py b/tests/slices.py new file mode 100644 index 0000000..7a42678 --- /dev/null +++ b/tests/slices.py @@ -0,0 +1,31 @@ +slice[a.b : c.d] +slice[d :: d + 1] +slice[d + 1 :: d] +slice[d::d] +slice[0] +slice[-1] +slice[:-1] +slice[::-1] +slice[:c, c - 1] +slice[c, c + 1, d::] +slice[ham[c::d] :: 1] +slice[ham[cheese ** 2 : -1] : 1 : 1, ham[1:2]] +slice[:-1:] +slice[lambda: None : lambda: None] +slice[lambda x, y, *args, really=2, **kwargs: None :, None::] +slice[1 or 2 : True and False] +slice[not so_simple : 1 < val <= 10] +slice[(1 for i in range(42)) : x] +slice[:: [i for i in range(42)]] + + +async def f(): + slice[await x : [i async for i in arange(42)] : 42] + + +# These are from PEP-8: +ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:] +ham[lower:upper], ham[lower:upper:], ham[lower::step] +# ham[lower+offset : upper+offset] +ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)] +ham[lower + offset : upper + offset] diff --git a/tests/test_black.py b/tests/test_black.py index 94cbe35..02926d1 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -229,6 +229,14 @@ class BlackTestCase(unittest.TestCase): black.assert_equivalent(source, actual) black.assert_stable(source, actual, line_length=ll) + @patch("black.dump_to_file", dump_to_stderr) + def test_slices(self) -> None: + source, expected = read_data("slices") + 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_comments(self) -> None: source, expected = read_data("comments") -- 2.39.5 From e84dee52d91d84d58b7a77096ba9cce761317366 Mon Sep 17 00:00:00 2001 From: David Szotten Date: Thu, 3 May 2018 04:25:23 +0100 Subject: [PATCH 13/16] Should this be "_cede_ control" (#187) --- README.md | 2 +- docs/index.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 31f92c9..ba5e7dd 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ *Black* is the uncompromising Python code formatter. By using it, you -agree to cease control over minutiae of hand-formatting. In return, +agree to cede control over minutiae of hand-formatting. In return, *Black* gives you speed, determinism, and freedom from `pycodestyle` nagging about formatting. You will save time and mental energy for more important matters. diff --git a/docs/index.rst b/docs/index.rst index df59f71..a0b176b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,7 +4,7 @@ The uncompromising code formatter ================================= -By using *Black*, you agree to cease control over minutiae of +By using *Black*, you agree to cede control over minutiae of hand-formatting. In return, *Black* gives you speed, determinism, and freedom from `pycodestyle` nagging about formatting. You will save time and mental energy for more important matters. -- 2.39.5 From adf4ebd2d9540dd0812e2db0e5735a5d28d31d81 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 4 May 2018 22:52:17 +0200 Subject: [PATCH 14/16] Remove unnecessary shebang lines (#189) Since black.py is not marked as executable, the shebang in black.py serves no purpose. black should be invoked through its entry point any way. token.py is an internal module without a __name__ == '__main__' block or other executable code. It contains just list of constants and small helper functions. Signed-off-by: Christian Heimes --- black.py | 2 -- blib2to3/pgen2/token.py | 2 -- 2 files changed, 4 deletions(-) mode change 100755 => 100644 blib2to3/pgen2/token.py diff --git a/black.py b/black.py index 5e087d1..fdefb74 100644 --- a/black.py +++ b/black.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - import asyncio import pickle from asyncio.base_events import BaseEventLoop diff --git a/blib2to3/pgen2/token.py b/blib2to3/pgen2/token.py old mode 100755 new mode 100644 index 1a67955..c37b0d5 --- a/blib2to3/pgen2/token.py +++ b/blib2to3/pgen2/token.py @@ -1,5 +1,3 @@ -#! /usr/bin/env python3 - """Token constants (from "token.h").""" # Taken from Python (r53757) and modified to include some tokens -- 2.39.5 From 21ccf44b27f1f3c391412273322cbce3b02a0b84 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Langa?= Date: Thu, 3 May 2018 00:48:37 -0700 Subject: [PATCH 15/16] More detailed isort configuration explanation --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ba5e7dd..d3f296c 100644 --- a/README.md +++ b/README.md @@ -180,8 +180,9 @@ If a line of "from" imports cannot fit in the allotted length, it's always split into one per line. Imports tend to change often and this minimizes diffs, as well as enables readers of code to easily find which commit introduced a particular import. This exception also makes *Black* compatible with -[isort](https://pypi.org/p/isort/). Use `multi_line_output=3` and -`include_trailing_comma=True` in your isort config. +[isort](https://pypi.org/p/isort/). Use `multi_line_output=3`, +`include_trailing_comma=True`, `force_grid_wrap=0`, and `line_length=88` in your +isort config. ### Line length -- 2.39.5 From 8325f893b4793e123b486e7a341188d1ab5b77d1 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 7 May 2018 19:11:21 +0200 Subject: [PATCH 16/16] Add more files/directories to .gitignore (#191) Ignore .tox, black.egg-info and __pycache__ directories. Signed-off-by: Christian Heimes --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index a796585..c59b667 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ _build .DS_Store .vscode docs/_static/pypi.svg +.tox +__pycache__ +black.egg-info -- 2.39.5