diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/attribute_access_on_number_literals.py b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/attribute_access_on_number_literals.py index 1507281ade..f9d13283f0 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/attribute_access_on_number_literals.py +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/attribute_access_on_number_literals.py @@ -1,4 +1,4 @@ -ax = 123456789 .bit_count() +x = 123456789 .bit_count() x = (123456).__abs__() x = .1.is_integer() x = 1. .imag diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments3.py b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments3.py index 1bab9733b1..c81958dc11 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments3.py +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments3.py @@ -1,6 +1,7 @@ # The percent-percent comments are Spyder IDE cells. -#%% + +# %% def func(): x = """ a really long string @@ -44,4 +45,4 @@ def func(): ) -#%% +# %% diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments5.py b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments5.py index c8c38813d5..bda40619f6 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments5.py +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments5.py @@ -62,6 +62,8 @@ def decorated1(): # Preview.empty_lines_before_class_or_def_with_leading_comments. # In the current style, 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(): ... diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments8.py b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments8.py new file mode 100644 index 0000000000..3d5fd9b79c --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments8.py @@ -0,0 +1,6 @@ +# The percent-percent comments are Spyder IDE cells. +# Both `#%%`` and `# %%` are accepted, so `black` standardises +# to the latter. + +#%% +# %% diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments9.py b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments9.py new file mode 100644 index 0000000000..460f063b1b --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments9.py @@ -0,0 +1,139 @@ +# Test for https://github.com/psf/black/issues/246. + +some = statement +# This comment should be split from the statement above by two lines. +def function(): + pass + + +some = statement +# This multiline comments section +# should be split from the statement +# above by two lines. +def function(): + pass + + +some = statement +# This comment should be split from the statement above by two lines. +async def async_function(): + pass + + +some = statement +# This comment should be split from the statement above by two lines. +class MyClass: + pass + + +some = statement +# This should be stick to the statement above + +# This should be split from the above by two lines +class MyClassWithComplexLeadingComments: + pass + + +class ClassWithDocstring: + """A docstring.""" +# Leading comment after a class with just a docstring +class MyClassAfterAnotherClassWithDocstring: + pass + + +some = statement +# leading 1 +@deco1 +# leading 2 +# leading 2 extra +@deco2(with_args=True) +# leading 3 +@deco3 +# leading 4 +def decorated(): + pass + + +some = statement +# leading 1 +@deco1 +# leading 2 +@deco2(with_args=True) + +# leading 3 that already has an empty line +@deco3 +# leading 4 +def decorated_with_split_leading_comments(): + pass + + +some = statement +# leading 1 +@deco1 +# leading 2 +@deco2(with_args=True) +# leading 3 +@deco3 + +# leading 4 that already has an empty line +def decorated_with_split_leading_comments(): + pass + + +def main(): + if a: + # Leading comment before inline function + def inline(): + pass + # Another leading comment + def another_inline(): + pass + else: + # More leading comments + def inline_after_else(): + pass + + +if a: + # Leading comment before "top-level inline" function + def top_level_quote_inline(): + pass + # Another leading comment + def another_top_level_quote_inline_inline(): + pass +else: + # More leading comments + def top_level_quote_inline_after_else(): + pass + + +class MyClass: + # First method has no empty lines between bare class def. + # More comments. + def first_method(self): + pass + + +# Regression test for https://github.com/psf/black/issues/3454. +def foo(): + pass + # Trailing comment that belongs to this function + + +@decorator1 +@decorator2 # fmt: skip +def bar(): + pass + + +# Regression test for https://github.com/psf/black/issues/3454. +def foo(): + pass + # Trailing comment that belongs to this function. + # NOTE this comment only has one empty line below, and the formatter + # should enforce two blank lines. + +@decorator1 +# A standalone comment +def bar(): + pass diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/docstring_preview.py b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/docstring_preview.py new file mode 100644 index 0000000000..9f57ad26da --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/docstring_preview.py @@ -0,0 +1,50 @@ +def docstring_almost_at_line_limit(): + """long docstring................................................................. + """ + + +def docstring_almost_at_line_limit_with_prefix(): + f"""long docstring................................................................ + """ + + +def mulitline_docstring_almost_at_line_limit(): + """long docstring................................................................. + + .................................................................................. + """ + + +def mulitline_docstring_almost_at_line_limit_with_prefix(): + f"""long docstring................................................................ + + .................................................................................. + """ + + +def docstring_at_line_limit(): + """long docstring................................................................""" + + +def docstring_at_line_limit_with_prefix(): + f"""long docstring...............................................................""" + + +def multiline_docstring_at_line_limit(): + """first line----------------------------------------------------------------------- + + second line----------------------------------------------------------------------""" + + +def multiline_docstring_at_line_limit_with_prefix(): + f"""first line---------------------------------------------------------------------- + + second line----------------------------------------------------------------------""" + + +def single_quote_docstring_over_line_limit(): + "We do not want to put the closing quote on a new line as that is invalid (see GH-3141)." + + +def single_quote_docstring_over_line_limit2(): + 'We do not want to put the closing quote on a new line as that is invalid (see GH-3141).' diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/expression.diff b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/expression.diff new file mode 100644 index 0000000000..2eaaeb479f --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/expression.diff @@ -0,0 +1,466 @@ +--- [Deterministic header] ++++ [Deterministic header] +@@ -1,8 +1,8 @@ + ... +-'some_string' +-b'\\xa3' ++"some_string" ++b"\\xa3" + Name + None + True + False + 1 +@@ -21,99 +21,135 @@ + Name1 or (Name2 and Name3) or Name4 + Name1 or Name2 and Name3 or Name4 + v1 << 2 + 1 >> v2 + 1 % finished +-1 + v2 - v3 * 4 ^ 5 ** v6 / 7 // 8 +-((1 + v2) - (v3 * 4)) ^ (((5 ** v6) / 7) // 8) ++1 + v2 - v3 * 4 ^ 5**v6 / 7 // 8 ++((1 + v2) - (v3 * 4)) ^ (((5**v6) / 7) // 8) + not great + ~great + +value + -1 + ~int and not v1 ^ 123 + v2 | True + (~int) and (not ((v1 ^ (123 + v2)) | True)) +-+really ** -confusing ** ~operator ** -precedence +-flags & ~ select.EPOLLIN and waiters.write_task is not None +++(really ** -(confusing ** ~(operator**-precedence))) ++flags & ~select.EPOLLIN and waiters.write_task is not None + lambda arg: None + lambda a=True: a + lambda a, b, c=True: a +-lambda a, b, c=True, *, d=(1 << v2), e='str': a +-lambda a, b, c=True, *vararg, d=(v1 << 2), e='str', **kwargs: a + b ++lambda a, b, c=True, *, d=(1 << v2), e="str": a ++lambda a, b, c=True, *vararg, d=(v1 << 2), e="str", **kwargs: a + b + manylambdas = lambda x=lambda y=lambda z=1: z: y(): x() +-foo = (lambda port_id, ignore_missing: {"port1": port1_resource, "port2": port2_resource}[port_id]) ++foo = lambda port_id, ignore_missing: { ++ "port1": port1_resource, ++ "port2": port2_resource, ++}[port_id] + 1 if True else 2 + str or None if True else str or bytes or None + (str or None) if True else (str or bytes or None) + str or None if (1 if True else 2) else str or bytes or None + (str or None) if (1 if True else 2) else (str or bytes or None) +-((super_long_variable_name or None) if (1 if super_long_test_name else 2) else (str or bytes or None)) +-{'2.7': dead, '3.7': (long_live or die_hard)} +-{'2.7': dead, '3.7': (long_live or die_hard), **{'3.6': verygood}} ++( ++ (super_long_variable_name or None) ++ if (1 if super_long_test_name else 2) ++ else (str or bytes or None) ++) ++{"2.7": dead, "3.7": (long_live or die_hard)} ++{"2.7": dead, "3.7": (long_live or die_hard), **{"3.6": verygood}} + {**a, **b, **c} +-{'2.7', '3.6', '3.7', '3.8', '3.9', ('4.0' if gilectomy else '3.10')} +-({'a': 'b'}, (True or False), (+value), 'string', b'bytes') or None ++{"2.7", "3.6", "3.7", "3.8", "3.9", ("4.0" if gilectomy else "3.10")} ++({"a": "b"}, (True or False), (+value), "string", b"bytes") or None + () + (1,) + (1, 2) + (1, 2, 3) + [] + [1, 2, 3, 4, 5, 6, 7, 8, 9, (10 or A), (11 or B), (12 or C)] +-[1, 2, 3,] ++[ ++ 1, ++ 2, ++ 3, ++] + [*a] + [*range(10)] +-[*a, 4, 5,] +-[4, *a, 5,] +-[this_is_a_very_long_variable_which_will_force_a_delimiter_split, element, another, *more] ++[ ++ *a, ++ 4, ++ 5, ++] ++[ ++ 4, ++ *a, ++ 5, ++] ++[ ++ this_is_a_very_long_variable_which_will_force_a_delimiter_split, ++ element, ++ another, ++ *more, ++] + {i for i in (1, 2, 3)} +-{(i ** 2) for i in (1, 2, 3)} +-{(i ** 2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))} +-{((i ** 2) + j) for i in (1, 2, 3) for j 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) + 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) + j) for i in (1, 2, 3) for j 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) + j) for i in (1, 2, 3) for j in (1, 2, 3)] + {i: 0 for i in (1, 2, 3)} +-{i: j for i, j in ((1, 'a'), (2, 'b'), (3, 'c'))} ++{i: j for i, j in ((1, "a"), (2, "b"), (3, "c"))} + {a: b * 2 for a, b in dictionary.items()} + {a: b * -2 for a, b in dictionary.items()} +-{k: v for k, v in this_is_a_very_long_variable_which_will_cause_a_trailing_comma_which_breaks_the_comprehension} ++{ ++ k: v ++ for k, v in this_is_a_very_long_variable_which_will_cause_a_trailing_comma_which_breaks_the_comprehension ++} + Python3 > Python2 > COBOL + Life is Life + call() + call(arg) +-call(kwarg='hey') +-call(arg, kwarg='hey') +-call(arg, another, kwarg='hey', **kwargs) +-call(this_is_a_very_long_variable_which_will_force_a_delimiter_split, arg, another, kwarg='hey', **kwargs) # note: no trailing comma pre-3.6 ++call(kwarg="hey") ++call(arg, kwarg="hey") ++call(arg, another, kwarg="hey", **kwargs) ++call( ++ this_is_a_very_long_variable_which_will_force_a_delimiter_split, ++ arg, ++ another, ++ kwarg="hey", ++ **kwargs ++) # note: no trailing comma pre-3.6 + call(*gidgets[:2]) + call(a, *gidgets[:2]) + call(**self.screen_kwargs) + call(b, **self.screen_kwargs) + lukasz.langa.pl + call.me(maybe) +-1 .real +-1.0 .real ++(1).real ++(1.0).real + ....__class__ + list[str] + dict[str, int] + tuple[str, ...] ++tuple[str, int, float, dict[str, int]] + tuple[ +- str, int, float, dict[str, int] +-] +-tuple[str, int, float, dict[str, int],] ++ str, ++ int, ++ float, ++ dict[str, int], ++] + very_long_variable_name_filters: t.List[ + t.Tuple[str, t.Union[str, t.List[t.Optional[str]]]], + ] + xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore + sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) + ) + xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore + sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) + ) +-xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[ +- ..., List[SomeClass] +-] = classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)) # type: ignore ++xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( ++ sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) ++) # type: ignore + slice[0] + slice[0:1] + slice[0:1:2] + slice[:] + slice[:-1] +@@ -137,118 +173,199 @@ + numpy[-(c + 1) :, d] + numpy[:, l[-2]] + numpy[:, ::-1] + numpy[np.newaxis, :] + (str or None) if (sys.version_info[0] > (3,)) else (str or bytes or None) +-{'2.7': dead, '3.7': long_live or die_hard} +-{'2.7', '3.6', '3.7', '3.8', '3.9', '4.0' if gilectomy else '3.10'} ++{"2.7": dead, "3.7": long_live or die_hard} ++{"2.7", "3.6", "3.7", "3.8", "3.9", "4.0" if gilectomy else "3.10"} + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or A, 11 or B, 12 or C] + (SomeName) + SomeName + (Good, Bad, Ugly) + (i for i in (1, 2, 3)) +-((i ** 2) for i in (1, 2, 3)) +-((i ** 2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))) +-(((i ** 2) + j) for i in (1, 2, 3) for j 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) + j) for i in (1, 2, 3) for j in (1, 2, 3)) + (*starred,) +-{"id": "1","type": "type","started_at": now(),"ended_at": now() + timedelta(days=10),"priority": 1,"import_session_id": 1,**kwargs} ++{ ++ "id": "1", ++ "type": "type", ++ "started_at": now(), ++ "ended_at": now() + timedelta(days=10), ++ "priority": 1, ++ "import_session_id": 1, ++ **kwargs, ++} + a = (1,) +-b = 1, ++b = (1,) + c = 1 + d = (1,) + a + (2,) + e = (1,).count(1) + f = 1, *range(10) + g = 1, *"ten" +-what_is_up_with_those_new_coord_names = (coord_names + set(vars_to_create)) + set(vars_to_remove) +-what_is_up_with_those_new_coord_names = (coord_names | set(vars_to_create)) - set(vars_to_remove) +-result = session.query(models.Customer.id).filter(models.Customer.account_id == account_id, models.Customer.email == email_address).order_by(models.Customer.id.asc()).all() +-result = session.query(models.Customer.id).filter(models.Customer.account_id == account_id, models.Customer.email == email_address).order_by(models.Customer.id.asc(),).all() ++what_is_up_with_those_new_coord_names = (coord_names + set(vars_to_create)) + set( ++ vars_to_remove ++) ++what_is_up_with_those_new_coord_names = (coord_names | set(vars_to_create)) - set( ++ vars_to_remove ++) ++result = ( ++ session.query(models.Customer.id) ++ .filter( ++ models.Customer.account_id == account_id, models.Customer.email == email_address ++ ) ++ .order_by(models.Customer.id.asc()) ++ .all() ++) ++result = ( ++ session.query(models.Customer.id) ++ .filter( ++ models.Customer.account_id == account_id, models.Customer.email == email_address ++ ) ++ .order_by( ++ models.Customer.id.asc(), ++ ) ++ .all() ++) + Ø = set() + authors.łukasz.say_thanks() + mapping = { + A: 0.25 * (10.0 / 12), + B: 0.1 * (10.0 / 12), + C: 0.1 * (10.0 / 12), + D: 0.1 * (10.0 / 12), + } + ++ + def gen(): + yield from outside_of_generator +- a = (yield) +- b = ((yield)) +- c = (((yield))) ++ a = yield ++ b = yield ++ c = yield ++ + + async def f(): + await some.complicated[0].call(with_args=(True or (1 is not 1))) +-print(* [] or [1]) ++ ++ ++print(*[] or [1]) + print(**{1: 3} if False else {x: x for x in range(3)}) +-print(* lambda x: x) +-assert(not Test),("Short message") +-assert this is ComplexTest and not requirements.fit_in_a_single_line(force=False), "Short message" +-assert(((parens is TooMany))) +-for x, in (1,), (2,), (3,): ... +-for y in (): ... +-for z in (i for i in (1, 2, 3)): ... +-for i in (call()): ... +-for j in (1 + (2 + 3)): ... +-while(this and that): ... +-for addr_family, addr_type, addr_proto, addr_canonname, addr_sockaddr in socket.getaddrinfo('google.com', 'http'): ++print(*lambda x: x) ++assert not Test, "Short message" ++assert this is ComplexTest and not requirements.fit_in_a_single_line( ++ force=False ++), "Short message" ++assert parens is TooMany ++for (x,) in (1,), (2,), (3,): ++ ... ++for y in (): ++ ... ++for z in (i for i in (1, 2, 3)): ++ ... ++for i in call(): ++ ... ++for j in 1 + (2 + 3): ++ ... ++while this and that: ++ ... ++for ( ++ addr_family, ++ addr_type, ++ addr_proto, ++ addr_canonname, ++ addr_sockaddr, ++) in socket.getaddrinfo("google.com", "http"): + pass +-a = aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz +-a = aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp not in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz +-a = aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp is qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz +-a = aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp is not qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz +-if ( +- threading.current_thread() != threading.main_thread() and +- threading.current_thread() != threading.main_thread() or +- signal.getsignal(signal.SIGINT) != signal.default_int_handler +-): +- return True +-if ( +- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | +- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +-): +- return True +-if ( +- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa & +- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +-): +- return True +-if ( +- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +-): +- return True +-if ( +- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - +- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +-): +- return True +-if ( +- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa * +- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +-): +- return True +-if ( +- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa / +- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +-): +- return True +-if ( +- ~ aaaa.a + aaaa.b - aaaa.c * aaaa.d / aaaa.e | aaaa.f & aaaa.g % aaaa.h ^ aaaa.i << aaaa.k >> aaaa.l ** aaaa.m // aaaa.n +-): +- return True +-if ( +- ~ aaaaaaaa.a + aaaaaaaa.b - aaaaaaaa.c @ aaaaaaaa.d / aaaaaaaa.e | aaaaaaaa.f & aaaaaaaa.g % aaaaaaaa.h ^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l ** aaaaaaaa.m // aaaaaaaa.n +-): +- return True +-if ( +- ~ aaaaaaaaaaaaaaaa.a + aaaaaaaaaaaaaaaa.b - aaaaaaaaaaaaaaaa.c * aaaaaaaaaaaaaaaa.d @ aaaaaaaaaaaaaaaa.e | aaaaaaaaaaaaaaaa.f & aaaaaaaaaaaaaaaa.g % aaaaaaaaaaaaaaaa.h ^ aaaaaaaaaaaaaaaa.i << aaaaaaaaaaaaaaaa.k >> aaaaaaaaaaaaaaaa.l ** aaaaaaaaaaaaaaaa.m // aaaaaaaaaaaaaaaa.n +-): +- return True +-aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa - aaaaaaaaaaaaaaaa * (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa) / (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa) ++a = ( ++ aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp ++ in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz ++) ++a = ( ++ aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp ++ not in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz ++) ++a = ( ++ aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp ++ is qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz ++) ++a = ( ++ aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp ++ is not qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz ++) ++if ( ++ threading.current_thread() != threading.main_thread() ++ and threading.current_thread() != threading.main_thread() ++ or signal.getsignal(signal.SIGINT) != signal.default_int_handler ++): ++ return True ++if ( ++ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ++ | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ++): ++ return True ++if ( ++ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ++ & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ++): ++ return True ++if ( ++ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ++ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ++): ++ return True ++if ( ++ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ++ - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ++): ++ return True ++if ( ++ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ++ * aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ++): ++ return True ++if ( ++ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ++ / aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ++): ++ return True ++if ( ++ ~aaaa.a + aaaa.b - aaaa.c * aaaa.d / aaaa.e ++ | aaaa.f & aaaa.g % aaaa.h ^ aaaa.i << aaaa.k >> aaaa.l**aaaa.m // aaaa.n ++): ++ return True ++if ( ++ ~aaaaaaaa.a + aaaaaaaa.b - aaaaaaaa.c @ aaaaaaaa.d / aaaaaaaa.e ++ | aaaaaaaa.f & aaaaaaaa.g % aaaaaaaa.h ++ ^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l**aaaaaaaa.m // aaaaaaaa.n ++): ++ return True ++if ( ++ ~aaaaaaaaaaaaaaaa.a ++ + aaaaaaaaaaaaaaaa.b ++ - aaaaaaaaaaaaaaaa.c * aaaaaaaaaaaaaaaa.d @ aaaaaaaaaaaaaaaa.e ++ | aaaaaaaaaaaaaaaa.f & aaaaaaaaaaaaaaaa.g % aaaaaaaaaaaaaaaa.h ++ ^ aaaaaaaaaaaaaaaa.i ++ << aaaaaaaaaaaaaaaa.k ++ >> aaaaaaaaaaaaaaaa.l**aaaaaaaaaaaaaaaa.m // aaaaaaaaaaaaaaaa.n ++): ++ return True ++( ++ aaaaaaaaaaaaaaaa ++ + aaaaaaaaaaaaaaaa ++ - aaaaaaaaaaaaaaaa ++ * (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa) ++ / (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa) ++) + aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa +-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa >> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa << aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ++( ++ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ++ >> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ++ << aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ++) + bbbb >> bbbb * bbbb +-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ^bbbb.a & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ++( ++ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ++ ^ bbbb.a & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ++ ^ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ++) + last_call() + # standalone comment at ENDMARKER diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/function.py b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/function.py index 1195501740..bc41e08a16 100644 --- a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/function.py +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/function.py @@ -34,7 +34,7 @@ def spaces(a=1, b=(), c=[], d={}, e=True, f=-1, g=1 if False else 2, h="", i=r'' def spaces_types(a: int = 1, b: tuple = (), c: list = [], d: dict = {}, e: bool = True, f: int = -1, g: int = 1 if False else 2, h: str = "", i: str = r''): ... def spaces2(result= _core.Value(None)): assert fut is self._read_fut, (fut, self._read_fut) - + def example(session): result = session.query(models.Customer.id).filter( models.Customer.account_id == account_id, diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/one_element_subscript.py b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/one_element_subscript.py new file mode 100644 index 0000000000..d0fb86aa69 --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/one_element_subscript.py @@ -0,0 +1,12 @@ +# We should not treat the trailing comma +# in a single-element subscript. +a: tuple[int,] +b = tuple[int,] + +# The magic comma still applies to multi-element subscripts. +c: tuple[int, int,] +d = tuple[int, int,] + +# Magic commas still work as expected for non-subscripts. +small_list = [1,] +list_of_types = [tuple[int,],] diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/prefer_rhs_split_reformatted.py b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/prefer_rhs_split_reformatted.py new file mode 100644 index 0000000000..6d91909dfe --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/prefer_rhs_split_reformatted.py @@ -0,0 +1,12 @@ +# Test cases separate from `prefer_rhs_split.py` that contains unformatted source. + +# Left hand side fits in a single line but will still be exploded by the +# magic trailing comma. +first_value, (m1, m2,), third_value = xxxxxx_yyyyyy_zzzzzz_wwwwww_uuuuuuu_vvvvvvvvvvv( + arg1, + arg2, +) + +# Make when when the left side of assignment plus the opening paren "... = (" is +# exactly line length limit + 1, it won't be split like that. +xxxxxxxxx_yyy_zzzzzzzz[xx.xxxxxx(x_yyy_zzzzzz.xxxxx[0]), x_yyy_zzzzzz.xxxxxx(xxxx=1)] = 1 diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_await_parens.py b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_await_parens.py new file mode 100644 index 0000000000..6b8d15f755 --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_await_parens.py @@ -0,0 +1,81 @@ +import asyncio + +# Control example +async def main(): + await asyncio.sleep(1) + +# Remove brackets for short coroutine/task +async def main(): + await (asyncio.sleep(1)) + +async def main(): + await ( + asyncio.sleep(1) + ) + +async def main(): + await (asyncio.sleep(1) + ) + +# Check comments +async def main(): + await ( # Hello + asyncio.sleep(1) + ) + +async def main(): + await ( + asyncio.sleep(1) # Hello + ) + +async def main(): + await ( + asyncio.sleep(1) + ) # Hello + +# Long lines +async def main(): + await asyncio.gather(asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1)) + +# Same as above but with magic trailing comma in function +async def main(): + await asyncio.gather(asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1), asyncio.sleep(1),) + +# Cr@zY Br@ck3Tz +async def main(): + await ( + ((((((((((((( + ((( ((( + ((( ((( + ((( ((( + ((( ((( + ((black(1))) + ))) ))) + ))) ))) + ))) ))) + ))) ))) + ))))))))))))) + ) + +# Keep brackets around non power operations and nested awaits +async def main(): + await (set_of_tasks | other_set) + +async def main(): + await (await asyncio.sleep(1)) + +# It's awaits all the way down... +async def main(): + await (await x) + +async def main(): + await (yield x) + +async def main(): + await (await (asyncio.sleep(1))) + +async def main(): + await (await (await (await (await (asyncio.sleep(1)))))) + +async def main(): + await (yield) diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_except_parens.py b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_except_parens.py new file mode 100644 index 0000000000..270cd1e896 --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_except_parens.py @@ -0,0 +1,35 @@ +# These brackets are redundant, therefore remove. +try: + a.something +except (AttributeError) as err: + raise err + +# This is tuple of exceptions. +# Although this could be replaced with just the exception, +# we do not remove brackets to preserve AST. +try: + a.something +except (AttributeError,) as err: + raise err + +# This is a tuple of exceptions. Do not remove brackets. +try: + a.something +except (AttributeError, ValueError) as err: + raise err + +# Test long variants. +try: + a.something +except (some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error) as err: + raise err + +try: + a.something +except (some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error,) as err: + raise err + +try: + a.something +except (some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error, some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error) as err: + raise err diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_for_brackets.py b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_for_brackets.py new file mode 100644 index 0000000000..8f80fae12e --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_for_brackets.py @@ -0,0 +1,19 @@ +# Only remove tuple brackets after `for` +for (k, v) in d.items(): + print(k, v) + +# Don't touch tuple brackets after `in` +for module in (core, _unicodefun): + if hasattr(module, "_verify_python3_env"): + module._verify_python3_env = lambda: None + +# Brackets remain for long for loop lines +for (why_would_anyone_choose_to_name_a_loop_variable_with_a_name_this_long, i_dont_know_but_we_should_still_check_the_behaviour_if_they_do) in d.items(): + print(k, v) + +for (k, v) in dfkasdjfldsjflkdsjflkdsjfdslkfjldsjfgkjdshgkljjdsfldgkhsdofudsfudsofajdslkfjdslkfjldisfjdffjsdlkfjdlkjjkdflskadjldkfjsalkfjdasj.items(): + print(k, v) + +# Test deeply nested brackets +for (((((k, v))))) in d.items(): + print(k, v) diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_newline_after_code_block_open.py b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_newline_after_code_block_open.py new file mode 100644 index 0000000000..78232d55d0 --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/remove_newline_after_code_block_open.py @@ -0,0 +1,108 @@ +import random + + +def foo1(): + + print("The newline above me should be deleted!") + + +def foo2(): + + + + print("All the newlines above me should be deleted!") + + +def foo3(): + + print("No newline above me!") + + print("There is a newline above me, and that's OK!") + + +def foo4(): + + # There is a comment here + + print("The newline above me should not be deleted!") + + +class Foo: + def bar(self): + + print("The newline above me should be deleted!") + + +for i in range(5): + + print(f"{i}) The line above me should be removed!") + + +for i in range(5): + + + + print(f"{i}) The lines above me should be removed!") + + +for i in range(5): + + for j in range(7): + + print(f"{i}) The lines above me should be removed!") + + +if random.randint(0, 3) == 0: + + print("The new line above me is about to be removed!") + + +if random.randint(0, 3) == 0: + + + + + print("The new lines above me is about to be removed!") + + +if random.randint(0, 3) == 0: + if random.uniform(0, 1) > 0.5: + print("Two lines above me are about to be removed!") + + +while True: + + print("The newline above me should be deleted!") + + +while True: + + + + print("The newlines above me should be deleted!") + + +while True: + + while False: + + print("The newlines above me should be deleted!") + + +with open("/path/to/file.txt", mode="w") as file: + + file.write("The new line above me is about to be removed!") + + +with open("/path/to/file.txt", mode="w") as file: + + + + file.write("The new lines above me is about to be removed!") + + +with open("/path/to/file.txt", mode="r") as read_file: + + with open("/path/to/output_file.txt", mode="w") as write_file: + + write_file.writelines(read_file.readlines()) diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/return_annotation_brackets.py b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/return_annotation_brackets.py new file mode 100644 index 0000000000..3e6b45147f --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/return_annotation_brackets.py @@ -0,0 +1,88 @@ +# Control +def double(a: int) -> int: + return 2*a + +# Remove the brackets +def double(a: int) -> (int): + return 2*a + +# Some newline variations +def double(a: int) -> ( + int): + return 2*a + +def double(a: int) -> (int +): + return 2*a + +def double(a: int) -> ( + int +): + return 2*a + +# Don't lose the comments +def double(a: int) -> ( # Hello + int +): + return 2*a + +def double(a: int) -> ( + int # Hello +): + return 2*a + +# Really long annotations +def foo() -> ( + intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds +): + return 2 + +def foo() -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds: + return 2 + +def foo() -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds | intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds: + return 2 + +def foo(a: int, b: int, c: int,) -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds: + return 2 + +def foo(a: int, b: int, c: int,) -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds | intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds: + return 2 + +# Split args but no need to split return +def foo(a: int, b: int, c: int,) -> int: + return 2 + +# Deeply nested brackets +# with *interesting* spacing +def double(a: int) -> (((((int))))): + return 2*a + +def double(a: int) -> ( + ( ( + ((int) + ) + ) + ) + ): + return 2*a + +def foo() -> ( + ( ( + intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds +) +)): + return 2 + +# Return type with commas +def foo() -> ( + tuple[int, int, int] +): + return 2 + +def foo() -> tuple[loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong]: + return 2 + +# Magic trailing comma example +def foo() -> tuple[int, int, int,]: + return 2 diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/skip_magic_trailing_comma.py b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/skip_magic_trailing_comma.py new file mode 100644 index 0000000000..966eea8f9c --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/skip_magic_trailing_comma.py @@ -0,0 +1,47 @@ +# We should not remove the trailing comma in a single-element subscript. +a: tuple[int,] +b = tuple[int,] + +# But commas in multiple element subscripts should be removed. +c: tuple[int, int,] +d = tuple[int, int,] + +# Remove commas for non-subscripts. +small_list = [1,] +list_of_types = [tuple[int,],] +small_set = {1,} +set_of_types = {tuple[int,],} + +# Except single element tuples +small_tuple = (1,) + +# Trailing commas in multiple chained non-nested parens. +zero( + one, +).two( + three, +).four( + five, +) + +func1(arg1).func2(arg2,).func3(arg3).func4(arg4,).func5(arg5) + +( + a, + b, + c, + d, +) = func1( + arg1 +) and func2(arg2) + +func( + argument1, + ( + one, + two, + ), + argument4, + argument5, + argument6, +) diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/trailing_commas_in_leading_parts.py b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/trailing_commas_in_leading_parts.py new file mode 100644 index 0000000000..3fc4c7d9bc --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/trailing_commas_in_leading_parts.py @@ -0,0 +1,33 @@ +zero(one,).two(three,).four(five,) + +func1(arg1).func2(arg2,).func3(arg3).func4(arg4,).func5(arg5) + +# Inner one-element tuple shouldn't explode +func1(arg1).func2(arg1, (one_tuple,)).func3(arg3) + +(a, b, c, d,) = func1(arg1) and func2(arg2) + + +# Example from https://github.com/psf/black/issues/3229 +def refresh_token(self, device_family, refresh_token, api_key): + return self.orchestration.refresh_token( + data={ + "refreshToken": refresh_token, + }, + api_key=api_key, + )["extensions"]["sdk"]["token"] + + +# Edge case where a bug in a working-in-progress version of +# https://github.com/psf/black/pull/3370 causes an infinite recursion. +assert ( + long_module.long_class.long_func().another_func() + == long_module.long_class.long_func()["some_key"].another_func(arg1) +) + +# Regression test for https://github.com/psf/black/issues/3414. +assert xxxxxxxxx.xxxxxxxxx.xxxxxxxxx( + xxxxxxxxx +).xxxxxxxxxxxxxxxxxx(), ( + "xxx {xxxxxxxxx} xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +) diff --git a/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/whitespace.py b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/whitespace.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/whitespace.py @@ -0,0 +1 @@ + diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comment_after_escaped_newline.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comment_after_escaped_newline.py.snap.expect index 18ac24ab78..e2d0719e53 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comment_after_escaped_newline.py.snap.expect +++ b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comment_after_escaped_newline.py.snap.expect @@ -3,13 +3,10 @@ source: src/source_code/mod.rs assertion_line: 0 expression: formatted --- -def bob(): \ - # pylint: disable=W9016 +def bob(): # pylint: disable=W9016 pass -def bobtwo(): \ - \ - # some comment here +def bobtwo(): # some comment here pass diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments2.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments2.py.snap.expect index 323d64f7ad..5d2a804ac1 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments2.py.snap.expect +++ b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments2.py.snap.expect @@ -61,6 +61,7 @@ else: add_compiler(compilers[(7.0, 32)]) # add_compiler(compilers[(7.1, 64)]) + # Comment before function. def inline_comments_in_brackets_ruin_everything(): if typedargslist: diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments3.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments3.py.snap.expect index 8b37cc3fb0..4aa052ff52 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments3.py.snap.expect +++ b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments3.py.snap.expect @@ -5,7 +5,8 @@ expression: formatted --- # The percent-percent comments are Spyder IDE cells. -#%% + +# %% def func(): x = """ a really long string @@ -49,5 +50,5 @@ def func(): ) -#%% +# %% diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments5.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments5.py.snap.expect index 4af52b1d1d..9ed281954d 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments5.py.snap.expect +++ b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments5.py.snap.expect @@ -67,6 +67,8 @@ def decorated1(): # Preview.empty_lines_before_class_or_def_with_leading_comments. # In the current style, 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(): ... diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments8.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments8.py.snap.expect new file mode 100644 index 0000000000..1c6a7ae745 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments8.py.snap.expect @@ -0,0 +1,12 @@ +--- +source: src/source_code/mod.rs +assertion_line: 0 +expression: formatted +--- +# The percent-percent comments are Spyder IDE cells. +# Both `#%%`` and `# %%` are accepted, so `black` standardises +# to the latter. + +# %% +# %% + diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments9.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments9.py.snap.expect new file mode 100644 index 0000000000..918f7355bc --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__comments9.py.snap.expect @@ -0,0 +1,167 @@ +--- +source: src/source_code/mod.rs +assertion_line: 0 +expression: formatted +--- +# Test for https://github.com/psf/black/issues/246. + +some = statement + + +# This comment should be split from the statement above by two lines. +def function(): + pass + + +some = statement + + +# This multiline comments section +# should be split from the statement +# above by two lines. +def function(): + pass + + +some = statement + + +# This comment should be split from the statement above by two lines. +async def async_function(): + pass + + +some = statement + + +# This comment should be split from the statement above by two lines. +class MyClass: + pass + + +some = statement +# This should be stick to the statement above + + +# This should be split from the above by two lines +class MyClassWithComplexLeadingComments: + pass + + +class ClassWithDocstring: + """A docstring.""" + + +# Leading comment after a class with just a docstring +class MyClassAfterAnotherClassWithDocstring: + pass + + +some = statement + + +# leading 1 +@deco1 +# leading 2 +# leading 2 extra +@deco2(with_args=True) +# leading 3 +@deco3 +# leading 4 +def decorated(): + pass + + +some = statement + + +# leading 1 +@deco1 +# leading 2 +@deco2(with_args=True) + +# leading 3 that already has an empty line +@deco3 +# leading 4 +def decorated_with_split_leading_comments(): + pass + + +some = statement + + +# leading 1 +@deco1 +# leading 2 +@deco2(with_args=True) +# leading 3 +@deco3 + +# leading 4 that already has an empty line +def decorated_with_split_leading_comments(): + pass + + +def main(): + if a: + # Leading comment before inline function + def inline(): + pass + + # Another leading comment + def another_inline(): + pass + + else: + # More leading comments + def inline_after_else(): + pass + + +if a: + # Leading comment before "top-level inline" function + def top_level_quote_inline(): + pass + + # Another leading comment + def another_top_level_quote_inline_inline(): + pass + +else: + # More leading comments + def top_level_quote_inline_after_else(): + pass + + +class MyClass: + # First method has no empty lines between bare class def. + # More comments. + def first_method(self): + pass + + +# Regression test for https://github.com/psf/black/issues/3454. +def foo(): + pass + # Trailing comment that belongs to this function + + +@decorator1 +@decorator2 # fmt: skip +def bar(): + pass + + +# Regression test for https://github.com/psf/black/issues/3454. +def foo(): + pass + # Trailing comment that belongs to this function. + # NOTE this comment only has one empty line below, and the formatter + # should enforce two blank lines. + + +@decorator1 +# A standalone comment +def bar(): + pass + diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__docstring_preview.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__docstring_preview.py.snap.expect new file mode 100644 index 0000000000..a245e99a82 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__docstring_preview.py.snap.expect @@ -0,0 +1,54 @@ +--- +source: src/source_code/mod.rs +assertion_line: 0 +expression: formatted +--- +def docstring_almost_at_line_limit(): + """long docstring.................................................................""" + + +def docstring_almost_at_line_limit_with_prefix(): + f"""long docstring................................................................""" + + +def mulitline_docstring_almost_at_line_limit(): + """long docstring................................................................. + + .................................................................................. + """ + + +def mulitline_docstring_almost_at_line_limit_with_prefix(): + f"""long docstring................................................................ + + .................................................................................. + """ + + +def docstring_at_line_limit(): + """long docstring................................................................""" + + +def docstring_at_line_limit_with_prefix(): + f"""long docstring...............................................................""" + + +def multiline_docstring_at_line_limit(): + """first line----------------------------------------------------------------------- + + second line----------------------------------------------------------------------""" + + +def multiline_docstring_at_line_limit_with_prefix(): + f"""first line---------------------------------------------------------------------- + + second line----------------------------------------------------------------------""" + + +def single_quote_docstring_over_line_limit(): + "We do not want to put the closing quote on a new line as that is invalid (see GH-3141)." + + +def single_quote_docstring_over_line_limit2(): + "We do not want to put the closing quote on a new line as that is invalid (see GH-3141)." + diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__empty_lines.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__empty_lines.py.snap.expect index 800921e04b..8a672658f6 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__empty_lines.py.snap.expect +++ b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__empty_lines.py.snap.expect @@ -8,9 +8,9 @@ expression: formatted # leading comment def f(): - NO = '' - SPACE = ' ' - DOUBLESPACE = ' ' + NO = "" + SPACE = " " + DOUBLESPACE = " " t = leaf.type p = leaf.parent # trailing comment @@ -21,19 +21,14 @@ def f(): if t == token.COMMENT: # another trailing comment return DOUBLESPACE - assert p is not None, f"INTERNAL ERROR: hand-made leaf without parent: {leaf!r}" - prev = leaf.prev_sibling if not prev: prevp = preceding_leaf(p) if not prevp or prevp.type in OPENING_BRACKETS: - - return NO - if prevp.type == token.EQUAL: if prevp.parent and prevp.parent.type in { syms.typedargslist, @@ -54,14 +49,16 @@ def f(): }: return NO + ############################################################################### # SECTION BECAUSE SECTIONS ############################################################################### + def g(): - NO = '' - SPACE = ' ' - DOUBLESPACE = ' ' + NO = "" + SPACE = " " + DOUBLESPACE = " " t = leaf.type p = leaf.parent @@ -75,7 +72,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: diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__expression.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__expression.py.snap.expect index fdc93bc737..ff1b33cc9b 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__expression.py.snap.expect +++ b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__expression.py.snap.expect @@ -4,8 +4,8 @@ assertion_line: 0 expression: formatted --- ... -'some_string' -b'\\xa3' +"some_string" +b"\\xa3" Name None True @@ -28,83 +28,119 @@ Name1 or Name2 and Name3 or Name4 v1 << 2 1 >> v2 1 % finished -1 + v2 - v3 * 4 ^ 5 ** v6 / 7 // 8 -((1 + v2) - (v3 * 4)) ^ (((5 ** v6) / 7) // 8) +1 + v2 - v3 * 4 ^ 5**v6 / 7 // 8 +((1 + v2) - (v3 * 4)) ^ (((5**v6) / 7) // 8) not great ~great +value -1 ~int and not v1 ^ 123 + v2 | True (~int) and (not ((v1 ^ (123 + v2)) | True)) -+really ** -confusing ** ~operator ** -precedence -flags & ~ select.EPOLLIN and waiters.write_task is not None ++(really ** -(confusing ** ~(operator**-precedence))) +flags & ~select.EPOLLIN and waiters.write_task is not None lambda arg: None lambda a=True: a lambda a, b, c=True: a -lambda a, b, c=True, *, d=(1 << v2), e='str': a -lambda a, b, c=True, *vararg, d=(v1 << 2), e='str', **kwargs: a + b +lambda a, b, c=True, *, d=(1 << v2), e="str": a +lambda a, b, c=True, *vararg, d=(v1 << 2), e="str", **kwargs: a + b manylambdas = lambda x=lambda y=lambda z=1: z: y(): x() -foo = (lambda port_id, ignore_missing: {"port1": port1_resource, "port2": port2_resource}[port_id]) +foo = lambda port_id, ignore_missing: { + "port1": port1_resource, + "port2": port2_resource, +}[port_id] 1 if True else 2 str or None if True else str or bytes or None (str or None) if True else (str or bytes or None) str or None if (1 if True else 2) else str or bytes or None (str or None) if (1 if True else 2) else (str or bytes or None) -((super_long_variable_name or None) if (1 if super_long_test_name else 2) else (str or bytes or None)) -{'2.7': dead, '3.7': (long_live or die_hard)} -{'2.7': dead, '3.7': (long_live or die_hard), **{'3.6': verygood}} +( + (super_long_variable_name or None) + if (1 if super_long_test_name else 2) + else (str or bytes or None) +) +{"2.7": dead, "3.7": (long_live or die_hard)} +{"2.7": dead, "3.7": (long_live or die_hard), **{"3.6": verygood}} {**a, **b, **c} -{'2.7', '3.6', '3.7', '3.8', '3.9', ('4.0' if gilectomy else '3.10')} -({'a': 'b'}, (True or False), (+value), 'string', b'bytes') or None +{"2.7", "3.6", "3.7", "3.8", "3.9", ("4.0" if gilectomy else "3.10")} +({"a": "b"}, (True or False), (+value), "string", b"bytes") or None () (1,) (1, 2) (1, 2, 3) [] [1, 2, 3, 4, 5, 6, 7, 8, 9, (10 or A), (11 or B), (12 or C)] -[1, 2, 3,] +[ + 1, + 2, + 3, +] [*a] [*range(10)] -[*a, 4, 5,] -[4, *a, 5,] -[this_is_a_very_long_variable_which_will_force_a_delimiter_split, element, another, *more] +[ + *a, + 4, + 5, +] +[ + 4, + *a, + 5, +] +[ + this_is_a_very_long_variable_which_will_force_a_delimiter_split, + element, + another, + *more, +] {i for i in (1, 2, 3)} -{(i ** 2) for i in (1, 2, 3)} -{(i ** 2) for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))} -{((i ** 2) + j) for i in (1, 2, 3) for j 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) + 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) + j) for i in (1, 2, 3) for j 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) + j) for i in (1, 2, 3) for j in (1, 2, 3)] {i: 0 for i in (1, 2, 3)} -{i: j for i, j in ((1, 'a'), (2, 'b'), (3, 'c'))} +{i: j for i, j in ((1, "a"), (2, "b"), (3, "c"))} {a: b * 2 for a, b in dictionary.items()} {a: b * -2 for a, b in dictionary.items()} -{k: v for k, v in this_is_a_very_long_variable_which_will_cause_a_trailing_comma_which_breaks_the_comprehension} +{ + k: v + for k, v in this_is_a_very_long_variable_which_will_cause_a_trailing_comma_which_breaks_the_comprehension +} Python3 > Python2 > COBOL Life is Life call() call(arg) -call(kwarg='hey') -call(arg, kwarg='hey') -call(arg, another, kwarg='hey', **kwargs) -call(this_is_a_very_long_variable_which_will_force_a_delimiter_split, arg, another, kwarg='hey', **kwargs) # note: no trailing comma pre-3.6 +call(kwarg="hey") +call(arg, kwarg="hey") +call(arg, another, kwarg="hey", **kwargs) +call( + this_is_a_very_long_variable_which_will_force_a_delimiter_split, + arg, + another, + kwarg="hey", + **kwargs +) # note: no trailing comma pre-3.6 call(*gidgets[:2]) call(a, *gidgets[:2]) call(**self.screen_kwargs) call(b, **self.screen_kwargs) lukasz.langa.pl call.me(maybe) -1 .real -1.0 .real +(1).real +(1.0).real ....__class__ list[str] dict[str, int] tuple[str, ...] +tuple[str, int, float, dict[str, int]] tuple[ - str, int, float, dict[str, int] + str, + int, + float, + dict[str, int], ] -tuple[str, int, float, dict[str, int],] very_long_variable_name_filters: t.List[ t.Tuple[str, t.Union[str, t.List[t.Optional[str]]]], ] @@ -114,9 +150,9 @@ xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: igno xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( # type: ignore sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) ) -xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[ - ..., List[SomeClass] -] = classmethod(sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__)) # type: ignore +xxxx_xxx_xxxx_xxxxx_xxxx_xxx: Callable[..., List[SomeClass]] = classmethod( + sync(async_xxxx_xxx_xxxx_xxxxx_xxxx_xxx.__func__) +) # type: ignore slice[0] slice[0:1] slice[0:1:2] @@ -144,29 +180,57 @@ 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) + j) for i in (1, 2, 3) for j 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) + j) for i in (1, 2, 3) for j in (1, 2, 3)) (*starred,) -{"id": "1","type": "type","started_at": now(),"ended_at": now() + timedelta(days=10),"priority": 1,"import_session_id": 1,**kwargs} +{ + "id": "1", + "type": "type", + "started_at": now(), + "ended_at": now() + timedelta(days=10), + "priority": 1, + "import_session_id": 1, + **kwargs, +} a = (1,) -b = 1, +b = (1,) c = 1 d = (1,) + a + (2,) e = (1,).count(1) f = 1, *range(10) g = 1, *"ten" -what_is_up_with_those_new_coord_names = (coord_names + set(vars_to_create)) + set(vars_to_remove) -what_is_up_with_those_new_coord_names = (coord_names | set(vars_to_create)) - set(vars_to_remove) -result = session.query(models.Customer.id).filter(models.Customer.account_id == account_id, models.Customer.email == email_address).order_by(models.Customer.id.asc()).all() -result = session.query(models.Customer.id).filter(models.Customer.account_id == account_id, models.Customer.email == email_address).order_by(models.Customer.id.asc(),).all() +what_is_up_with_those_new_coord_names = (coord_names + set(vars_to_create)) + set( + vars_to_remove +) +what_is_up_with_those_new_coord_names = (coord_names | set(vars_to_create)) - set( + vars_to_remove +) +result = ( + session.query(models.Customer.id) + .filter( + models.Customer.account_id == account_id, models.Customer.email == email_address + ) + .order_by(models.Customer.id.asc()) + .all() +) +result = ( + session.query(models.Customer.id) + .filter( + models.Customer.account_id == account_id, models.Customer.email == email_address + ) + .order_by( + models.Customer.id.asc(), + ) + .all() +) Ø = set() authors.łukasz.say_thanks() mapping = { @@ -176,85 +240,138 @@ mapping = { D: 0.1 * (10.0 / 12), } + def gen(): yield from outside_of_generator - a = (yield) - b = ((yield)) - c = (((yield))) + a = yield + b = yield + c = yield + async def f(): await some.complicated[0].call(with_args=(True or (1 is not 1))) -print(* [] or [1]) + + +print(*[] or [1]) print(**{1: 3} if False else {x: x for x in range(3)}) -print(* lambda x: x) -assert(not Test),("Short message") -assert this is ComplexTest and not requirements.fit_in_a_single_line(force=False), "Short message" -assert(((parens is TooMany))) -for x, in (1,), (2,), (3,): ... -for y in (): ... -for z in (i for i in (1, 2, 3)): ... -for i in (call()): ... -for j in (1 + (2 + 3)): ... -while(this and that): ... -for addr_family, addr_type, addr_proto, addr_canonname, addr_sockaddr in socket.getaddrinfo('google.com', 'http'): +print(*lambda x: x) +assert not Test, "Short message" +assert this is ComplexTest and not requirements.fit_in_a_single_line( + force=False +), "Short message" +assert parens is TooMany +for (x,) in (1,), (2,), (3,): + ... +for y in (): + ... +for z in (i for i in (1, 2, 3)): + ... +for i in call(): + ... +for j in 1 + (2 + 3): + ... +while this and that: + ... +for ( + addr_family, + addr_type, + addr_proto, + addr_canonname, + addr_sockaddr, +) in socket.getaddrinfo("google.com", "http"): pass -a = aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz -a = aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp not in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz -a = aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp is qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz -a = aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp is not qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz +a = ( + aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp + in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz +) +a = ( + aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp + not in qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz +) +a = ( + aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp + is qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz +) +a = ( + aaaa.bbbb.cccc.dddd.eeee.ffff.gggg.hhhh.iiii.jjjj.kkkk.llll.mmmm.nnnn.oooo.pppp + is not qqqq.rrrr.ssss.tttt.uuuu.vvvv.xxxx.yyyy.zzzz +) if ( - threading.current_thread() != threading.main_thread() and - threading.current_thread() != threading.main_thread() or - signal.getsignal(signal.SIGINT) != signal.default_int_handler + threading.current_thread() != threading.main_thread() + and threading.current_thread() != threading.main_thread() + or signal.getsignal(signal.SIGINT) != signal.default_int_handler ): return True if ( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ): return True if ( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ): return True if ( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ): return True if ( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ): return True if ( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa * aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + * aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ): return True if ( - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa / aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + / aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ): return True if ( - ~ aaaa.a + aaaa.b - aaaa.c * aaaa.d / aaaa.e | aaaa.f & aaaa.g % aaaa.h ^ aaaa.i << aaaa.k >> aaaa.l ** aaaa.m // aaaa.n + ~aaaa.a + aaaa.b - aaaa.c * aaaa.d / aaaa.e + | aaaa.f & aaaa.g % aaaa.h ^ aaaa.i << aaaa.k >> aaaa.l**aaaa.m // aaaa.n ): return True if ( - ~ aaaaaaaa.a + aaaaaaaa.b - aaaaaaaa.c @ aaaaaaaa.d / aaaaaaaa.e | aaaaaaaa.f & aaaaaaaa.g % aaaaaaaa.h ^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l ** aaaaaaaa.m // aaaaaaaa.n + ~aaaaaaaa.a + aaaaaaaa.b - aaaaaaaa.c @ aaaaaaaa.d / aaaaaaaa.e + | aaaaaaaa.f & aaaaaaaa.g % aaaaaaaa.h + ^ aaaaaaaa.i << aaaaaaaa.k >> aaaaaaaa.l**aaaaaaaa.m // aaaaaaaa.n ): return True if ( - ~ aaaaaaaaaaaaaaaa.a + aaaaaaaaaaaaaaaa.b - aaaaaaaaaaaaaaaa.c * aaaaaaaaaaaaaaaa.d @ aaaaaaaaaaaaaaaa.e | aaaaaaaaaaaaaaaa.f & aaaaaaaaaaaaaaaa.g % aaaaaaaaaaaaaaaa.h ^ aaaaaaaaaaaaaaaa.i << aaaaaaaaaaaaaaaa.k >> aaaaaaaaaaaaaaaa.l ** aaaaaaaaaaaaaaaa.m // aaaaaaaaaaaaaaaa.n + ~aaaaaaaaaaaaaaaa.a + + aaaaaaaaaaaaaaaa.b + - aaaaaaaaaaaaaaaa.c * aaaaaaaaaaaaaaaa.d @ aaaaaaaaaaaaaaaa.e + | aaaaaaaaaaaaaaaa.f & aaaaaaaaaaaaaaaa.g % aaaaaaaaaaaaaaaa.h + ^ aaaaaaaaaaaaaaaa.i + << aaaaaaaaaaaaaaaa.k + >> aaaaaaaaaaaaaaaa.l**aaaaaaaaaaaaaaaa.m // aaaaaaaaaaaaaaaa.n ): return True -aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa - aaaaaaaaaaaaaaaa * (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa) / (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa) +( + aaaaaaaaaaaaaaaa + + aaaaaaaaaaaaaaaa + - aaaaaaaaaaaaaaaa + * (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa) + / (aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa) +) aaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaa -aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa >> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa << aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + >> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + << aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +) bbbb >> bbbb * bbbb -aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ^bbbb.a & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +( + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + ^ bbbb.a & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + ^ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +) last_call() # standalone comment at ENDMARKER diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtonoff.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtonoff.py.snap.expect index f324c0856b..8278fc83cc 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtonoff.py.snap.expect +++ b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__fmtonoff.py.snap.expect @@ -20,6 +20,7 @@ f"trigger 3.6 mode" # Comment 2 + # fmt: off def func_no_args(): a; b; c diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__function.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__function.py.snap.expect index 5809e62081..20b3f4f84f 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__function.py.snap.expect +++ b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__function.py.snap.expect @@ -9,52 +9,97 @@ import sys from third_party import X, Y, Z -from library import some_connection, \ - some_decorator -f'trigger 3.6 mode' +from library import some_connection, some_decorator + +f"trigger 3.6 mode" + + def func_no_args(): - a; b; c - if True: raise RuntimeError - if False: ... - for i in range(10): - print(i) - continue - exec("new-style exec", {}, {}) - return None + a + b + c + if True: + raise RuntimeError + if False: + ... + for i in range(10): + print(i) + continue + exec("new-style exec", {}, {}) + return None + + 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 asyncio.sleep(1) + "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) + + @asyncio.coroutine -@some_decorator( -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: - return text[number:-1] -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 -def spaces_types(a: int = 1, b: tuple = (), c: list = [], d: dict = {}, e: bool = True, f: int = -1, g: int = 1 if False else 2, h: str = "", i: str = r''): ... -def spaces2(result= _core.Value(None)): - assert fut is self._read_fut, (fut, self._read_fut) +@some_decorator(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: + return text[number:-1] + + +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 + + +def spaces_types( + a: int = 1, + b: tuple = (), + c: list = [], + d: dict = {}, + e: bool = True, + f: int = -1, + g: int = 1 if False else 2, + h: str = "", + i: str = r"", +): + ... + + +def spaces2(result=_core.Value(None)): + assert fut is self._read_fut, (fut, self._read_fut) + def example(session): - result = session.query(models.Customer.id).filter( - models.Customer.account_id == account_id, - models.Customer.email == email_address, - ).order_by( - models.Customer.id.asc() - ).all() + result = ( + session.query(models.Customer.id) + .filter( + models.Customer.account_id == account_id, + models.Customer.email == email_address, + ) + .order_by(models.Customer.id.asc()) + .all() + ) + + def long_lines(): if True: typedargslist.extend( - gen_annotated_params(ast_args.kwonlyargs, ast_args.kw_defaults, parameters, implicit_default=True) + gen_annotated_params( + ast_args.kwonlyargs, + ast_args.kw_defaults, + parameters, + implicit_default=True, + ) ) typedargslist.extend( gen_annotated_params( - ast_args.kwonlyargs, ast_args.kw_defaults, parameters, implicit_default=True, + ast_args.kwonlyargs, + ast_args.kw_defaults, + parameters, + implicit_default=True, # trailing standalone comment ) ) @@ -77,18 +122,23 @@ def long_lines(): \n? ) $ - """, re.MULTILINE | re.VERBOSE + """, + re.MULTILINE | re.VERBOSE, ) + + def trailing_comma(): mapping = { - A: 0.25 * (10.0 / 12), - B: 0.1 * (10.0 / 12), - C: 0.1 * (10.0 / 12), - D: 0.1 * (10.0 / 12), -} + A: 0.25 * (10.0 / 12), + B: 0.1 * (10.0 / 12), + C: 0.1 * (10.0 / 12), + D: 0.1 * (10.0 / 12), + } + + def f( - a, - **kwargs, + a, + **kwargs, ) -> A: return ( yield from A( @@ -97,5 +147,8 @@ def f( **kwargs, ) ) -def __await__(): return (yield) + + +def __await__(): + return (yield) diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__function2.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__function2.py.snap.expect index 55fe1f05fc..c5249d27ba 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__function2.py.snap.expect +++ b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__function2.py.snap.expect @@ -4,13 +4,13 @@ assertion_line: 0 expression: formatted --- def f( - a, - **kwargs, + a, + **kwargs, ) -> A: with cache_dir(): if something: - result = ( - CliRunner().invoke(black.main, [str(src1), str(src2), "--diff", "--check"]) + result = CliRunner().invoke( + black.main, [str(src1), str(src2), "--diff", "--check"] ) limited.append(-limited.pop()) # negate top return A( @@ -18,24 +18,34 @@ def f( very_long_argument_name2=-very.long.value.for_the_argument, **kwargs, ) + + def g(): "Docstring." + def inner(): pass + print("Inner defs should breathe a little.") + + def h(): def inner(): pass + print("Inner defs should breathe a little.") if os.name == "posix": import termios + def i_should_be_followed_by_only_one_newline(): pass + elif os.name == "nt": try: import msvcrt + def i_should_be_followed_by_only_one_newline(): pass @@ -49,11 +59,13 @@ elif False: class IHopeYouAreHavingALovelyDay: def __call__(self): print("i_should_be_followed_by_only_one_newline") + else: def foo(): pass + with hmm_but_this_should_get_two_preceding_newlines(): pass diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__function_trailing_comma.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__function_trailing_comma.py.snap.expect index 97403631df..47edd1f2d7 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__function_trailing_comma.py.snap.expect +++ b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__function_trailing_comma.py.snap.expect @@ -3,29 +3,72 @@ source: src/source_code/mod.rs assertion_line: 0 expression: formatted --- -def f(a,): - d = {'key': 'value',} +def f( + a, +): + d = { + "key": "value", + } tup = (1,) -def f2(a,b,): - d = {'key': 'value', 'key2': 'value2',} - tup = (1,2,) -def f(a:int=1,): - call(arg={'explode': 'this',}) - call2(arg=[1,2,3],) +def f2( + a, + b, +): + d = { + "key": "value", + "key2": "value2", + } + tup = ( + 1, + 2, + ) + + +def f( + a: int = 1, +): + call( + arg={ + "explode": "this", + } + ) + call2( + arg=[1, 2, 3], + ) x = { "a": 1, "b": 2, }["a"] - if a == {"a": 1,"b": 2,"c": 3,"d": 4,"e": 5,"f": 6,"g": 7,"h": 8,}["a"]: + if ( + a + == { + "a": 1, + "b": 2, + "c": 3, + "d": 4, + "e": 5, + "f": 6, + "g": 7, + "h": 8, + }["a"] + ): pass -def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[ - "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -]: - json = {"k": {"k2": {"k3": [1,]}}} +def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> ( + Set["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"] +): + json = { + "k": { + "k2": { + "k3": [ + 1, + ] + } + } + } # The type annotation shouldn't get a trailing comma since that would change its type. @@ -36,21 +79,24 @@ def some_function_with_a_really_long_name() -> ( pass -def some_method_with_a_really_long_name(very_long_parameter_so_yeah: str, another_long_parameter: int) -> ( - another_case_of_returning_a_deeply_nested_import_of_a_type_i_suppose_cause_why_not +def some_method_with_a_really_long_name( + very_long_parameter_so_yeah: str, another_long_parameter: int +) -> another_case_of_returning_a_deeply_nested_import_of_a_type_i_suppose_cause_why_not: + pass + + +def func() -> ( + also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black( + this_shouldn_t_get_a_trailing_comma_too + ) ): pass def func() -> ( - also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black(this_shouldn_t_get_a_trailing_comma_too) -): - pass - - -def func() -> ((also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black( + also_super_long_type_annotation_that_may_cause_an_AST_related_crash_in_black( this_shouldn_t_get_a_trailing_comma_too - )) + ) ): pass @@ -62,6 +108,13 @@ some_module.some_function( # Inner trailing comma causes outer to explode some_module.some_function( - argument1, (one, two,), argument4, argument5, argument6 + argument1, + ( + one, + two, + ), + argument4, + argument5, + argument6, ) diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__one_element_subscript.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__one_element_subscript.py.snap.expect new file mode 100644 index 0000000000..b93a2f09cd --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__one_element_subscript.py.snap.expect @@ -0,0 +1,28 @@ +--- +source: src/source_code/mod.rs +assertion_line: 0 +expression: formatted +--- +# We should not treat the trailing comma +# in a single-element subscript. +a: tuple[int,] +b = tuple[int,] + +# The magic comma still applies to multi-element subscripts. +c: tuple[ + int, + int, +] +d = tuple[ + int, + int, +] + +# Magic commas still work as expected for non-subscripts. +small_list = [ + 1, +] +list_of_types = [ + tuple[int,], +] + diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__power_op_spacing.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__power_op_spacing.py.snap.expect index 0a16bc7b9f..7a8512a70c 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__power_op_spacing.py.snap.expect +++ b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__power_op_spacing.py.snap.expect @@ -5,11 +5,11 @@ expression: formatted --- def function(**kwargs): t = a**2 + b**3 - return t ** 2 + return t**2 def function_replace_spaces(**kwargs): - t = a **2 + b** 3 + c ** 4 + t = a**2 + b**3 + c**4 def function_dont_replace_spaces(): diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__prefer_rhs_split_reformatted.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__prefer_rhs_split_reformatted.py.snap.expect new file mode 100644 index 0000000000..6730e959d4 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__prefer_rhs_split_reformatted.py.snap.expect @@ -0,0 +1,27 @@ +--- +source: src/source_code/mod.rs +assertion_line: 0 +expression: formatted +--- +# Test cases separate from `prefer_rhs_split.py` that contains unformatted source. + +# Left hand side fits in a single line but will still be exploded by the +# magic trailing comma. +( + first_value, + ( + m1, + m2, + ), + third_value, +) = xxxxxx_yyyyyy_zzzzzz_wwwwww_uuuuuuu_vvvvvvvvvvv( + arg1, + arg2, +) + +# Make when when the left side of assignment plus the opening paren "... = (" is +# exactly line length limit + 1, it won't be split like that. +xxxxxxxxx_yyy_zzzzzzzz[ + xx.xxxxxx(x_yyy_zzzzzz.xxxxx[0]), x_yyy_zzzzzz.xxxxxx(xxxx=1) +] = 1 + diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_await_parens.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_await_parens.py.snap.expect new file mode 100644 index 0000000000..fabcb9ed80 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_await_parens.py.snap.expect @@ -0,0 +1,99 @@ +--- +source: src/source_code/mod.rs +assertion_line: 0 +expression: formatted +--- +import asyncio + + +# Control example +async def main(): + await asyncio.sleep(1) + + +# Remove brackets for short coroutine/task +async def main(): + await asyncio.sleep(1) + + +async def main(): + await asyncio.sleep(1) + + +async def main(): + await asyncio.sleep(1) + + +# Check comments +async def main(): + await asyncio.sleep(1) # Hello + + +async def main(): + await asyncio.sleep(1) # Hello + + +async def main(): + await asyncio.sleep(1) # Hello + + +# Long lines +async def main(): + await asyncio.gather( + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + ) + + +# Same as above but with magic trailing comma in function +async def main(): + await asyncio.gather( + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + asyncio.sleep(1), + ) + + +# Cr@zY Br@ck3Tz +async def main(): + await black(1) + + +# Keep brackets around non power operations and nested awaits +async def main(): + await (set_of_tasks | other_set) + + +async def main(): + await (await asyncio.sleep(1)) + + +# It's awaits all the way down... +async def main(): + await (await x) + + +async def main(): + await (yield x) + + +async def main(): + await (await asyncio.sleep(1)) + + +async def main(): + await (await (await (await (await asyncio.sleep(1))))) + + +async def main(): + await (yield) + diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_except_parens.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_except_parens.py.snap.expect new file mode 100644 index 0000000000..ab395b583e --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_except_parens.py.snap.expect @@ -0,0 +1,48 @@ +--- +source: src/source_code/mod.rs +assertion_line: 0 +expression: formatted +--- +# These brackets are redundant, therefore remove. +try: + a.something +except AttributeError as err: + raise err + +# This is tuple of exceptions. +# Although this could be replaced with just the exception, +# we do not remove brackets to preserve AST. +try: + a.something +except (AttributeError,) as err: + raise err + +# This is a tuple of exceptions. Do not remove brackets. +try: + a.something +except (AttributeError, ValueError) as err: + raise err + +# Test long variants. +try: + a.something +except ( + some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error +) as err: + raise err + +try: + a.something +except ( + some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error, +) as err: + raise err + +try: + a.something +except ( + some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error, + some.really.really.really.looooooooooooooooooooooooooooooooong.module.over89.chars.Error, +) as err: + raise err + diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_for_brackets.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_for_brackets.py.snap.expect new file mode 100644 index 0000000000..43077b0550 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_for_brackets.py.snap.expect @@ -0,0 +1,33 @@ +--- +source: src/source_code/mod.rs +assertion_line: 0 +expression: formatted +--- +# Only remove tuple brackets after `for` +for k, v in d.items(): + print(k, v) + +# Don't touch tuple brackets after `in` +for module in (core, _unicodefun): + if hasattr(module, "_verify_python3_env"): + module._verify_python3_env = lambda: None + +# Brackets remain for long for loop lines +for ( + why_would_anyone_choose_to_name_a_loop_variable_with_a_name_this_long, + i_dont_know_but_we_should_still_check_the_behaviour_if_they_do, +) in d.items(): + print(k, v) + +for ( + k, + v, +) in ( + dfkasdjfldsjflkdsjflkdsjfdslkfjldsjfgkjdshgkljjdsfldgkhsdofudsfudsofajdslkfjdslkfjldisfjdffjsdlkfjdlkjjkdflskadjldkfjsalkfjdasj.items() +): + print(k, v) + +# Test deeply nested brackets +for k, v in d.items(): + print(k, v) + diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_newline_after_code_block_open.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_newline_after_code_block_open.py.snap.expect new file mode 100644 index 0000000000..96d7e17ceb --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_newline_after_code_block_open.py.snap.expect @@ -0,0 +1,84 @@ +--- +source: src/source_code/mod.rs +assertion_line: 0 +expression: formatted +--- +import random + + +def foo1(): + print("The newline above me should be deleted!") + + +def foo2(): + print("All the newlines above me should be deleted!") + + +def foo3(): + print("No newline above me!") + + print("There is a newline above me, and that's OK!") + + +def foo4(): + # There is a comment here + + print("The newline above me should not be deleted!") + + +class Foo: + def bar(self): + print("The newline above me should be deleted!") + + +for i in range(5): + print(f"{i}) The line above me should be removed!") + + +for i in range(5): + print(f"{i}) The lines above me should be removed!") + + +for i in range(5): + for j in range(7): + print(f"{i}) The lines above me should be removed!") + + +if random.randint(0, 3) == 0: + print("The new line above me is about to be removed!") + + +if random.randint(0, 3) == 0: + print("The new lines above me is about to be removed!") + + +if random.randint(0, 3) == 0: + if random.uniform(0, 1) > 0.5: + print("Two lines above me are about to be removed!") + + +while True: + print("The newline above me should be deleted!") + + +while True: + print("The newlines above me should be deleted!") + + +while True: + while False: + print("The newlines above me should be deleted!") + + +with open("/path/to/file.txt", mode="w") as file: + file.write("The new line above me is about to be removed!") + + +with open("/path/to/file.txt", mode="w") as file: + file.write("The new lines above me is about to be removed!") + + +with open("/path/to/file.txt", mode="r") as read_file: + with open("/path/to/output_file.txt", mode="w") as write_file: + write_file.writelines(read_file.readlines()) + diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_parens.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_parens.py.snap.expect index 171ab1dfe2..f49f5db22d 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_parens.py.snap.expect +++ b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__remove_parens.py.snap.expect @@ -3,59 +3,89 @@ source: src/source_code/mod.rs assertion_line: 0 expression: formatted --- -x = (1) -x = (1.2) +x = 1 +x = 1.2 data = ( "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ).encode() + async def show_status(): while True: try: if report_host: data = ( - f"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - ).encode() + f"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + ).encode() except Exception as e: pass + def example(): - return (("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")) + return "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" def example1(): - return ((1111111111111111111111111111111111111111111111111111111111111111111111111111111111111)) + return 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111 def example1point5(): - return ((((((1111111111111111111111111111111111111111111111111111111111111111111111111111111111111)))))) + return 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111 def example2(): - return (("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")) + return ( + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + ) def example3(): - return ((1111111111111111111111111111111111111111111111111111111111111111111111111111111)) + return ( + 1111111111111111111111111111111111111111111111111111111111111111111111111111111 + ) def example4(): - return ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((True)))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) + return True def example5(): - return ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) + return () def example6(): - return ((((((((({a:a for a in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]}))))))))) + return {a: a for a in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]} def example7(): - return ((((((((({a:a for a in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20000000000000000000]}))))))))) + return { + a: a + for a in [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20000000000000000000, + ] + } def example8(): - return (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((None))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) + return None diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__return_annotation_brackets.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__return_annotation_brackets.py.snap.expect new file mode 100644 index 0000000000..b1744fd7d9 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__return_annotation_brackets.py.snap.expect @@ -0,0 +1,126 @@ +--- +source: src/source_code/mod.rs +assertion_line: 0 +expression: formatted +--- +# Control +def double(a: int) -> int: + return 2 * a + + +# Remove the brackets +def double(a: int) -> int: + return 2 * a + + +# Some newline variations +def double(a: int) -> int: + return 2 * a + + +def double(a: int) -> int: + return 2 * a + + +def double(a: int) -> int: + return 2 * a + + +# Don't lose the comments +def double(a: int) -> int: # Hello + return 2 * a + + +def double(a: int) -> int: # Hello + return 2 * a + + +# Really long annotations +def foo() -> ( + intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds +): + return 2 + + +def foo() -> ( + intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds +): + return 2 + + +def foo() -> ( + intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds + | intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds +): + return 2 + + +def foo( + a: int, + b: int, + c: int, +) -> intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds: + return 2 + + +def foo( + a: int, + b: int, + c: int, +) -> ( + intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds + | intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds +): + return 2 + + +# Split args but no need to split return +def foo( + a: int, + b: int, + c: int, +) -> int: + return 2 + + +# Deeply nested brackets +# with *interesting* spacing +def double(a: int) -> int: + return 2 * a + + +def double(a: int) -> int: + return 2 * a + + +def foo() -> ( + intsdfsafafafdfdsasdfsfsdfasdfafdsafdfdsfasdskdsdsfdsafdsafsdfdasfffsfdsfdsafafhdskfhdsfjdslkfdlfsdkjhsdfjkdshfkljds +): + return 2 + + +# Return type with commas +def foo() -> tuple[int, int, int]: + return 2 + + +def foo() -> ( + tuple[ + loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, + loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, + loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, + ] +): + return 2 + + +# Magic trailing comma example +def foo() -> ( + tuple[ + int, + int, + int, + ] +): + return 2 + diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__skip_magic_trailing_comma.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__skip_magic_trailing_comma.py.snap.expect new file mode 100644 index 0000000000..508ed6e633 --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__skip_magic_trailing_comma.py.snap.expect @@ -0,0 +1,31 @@ +--- +source: src/source_code/mod.rs +assertion_line: 0 +expression: formatted +--- +# We should not remove the trailing comma in a single-element subscript. +a: tuple[int,] +b = tuple[int,] + +# But commas in multiple element subscripts should be removed. +c: tuple[int, int] +d = tuple[int, int] + +# Remove commas for non-subscripts. +small_list = [1] +list_of_types = [tuple[int,]] +small_set = {1} +set_of_types = {tuple[int,]} + +# Except single element tuples +small_tuple = (1,) + +# Trailing commas in multiple chained non-nested parens. +zero(one).two(three).four(five) + +func1(arg1).func2(arg2).func3(arg3).func4(arg4).func5(arg5) + +(a, b, c, d) = func1(arg1) and func2(arg2) + +func(argument1, (one, two), argument4, argument5, argument6) + diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__trailing_commas_in_leading_parts.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__trailing_commas_in_leading_parts.py.snap.expect new file mode 100644 index 0000000000..233dde6f9d --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__trailing_commas_in_leading_parts.py.snap.expect @@ -0,0 +1,56 @@ +--- +source: src/source_code/mod.rs +assertion_line: 0 +expression: formatted +--- +zero( + one, +).two( + three, +).four( + five, +) + +func1(arg1).func2( + arg2, +).func3(arg3).func4( + arg4, +).func5(arg5) + +# Inner one-element tuple shouldn't explode +func1(arg1).func2(arg1, (one_tuple,)).func3(arg3) + +( + a, + b, + c, + d, +) = func1( + arg1 +) and func2(arg2) + + +# Example from https://github.com/psf/black/issues/3229 +def refresh_token(self, device_family, refresh_token, api_key): + return self.orchestration.refresh_token( + data={ + "refreshToken": refresh_token, + }, + api_key=api_key, + )["extensions"]["sdk"]["token"] + + +# Edge case where a bug in a working-in-progress version of +# https://github.com/psf/black/pull/3370 causes an infinite recursion. +assert ( + long_module.long_class.long_func().another_func() + == long_module.long_class.long_func()["some_key"].another_func(arg1) +) + +# Regression test for https://github.com/psf/black/issues/3414. +assert xxxxxxxxx.xxxxxxxxx.xxxxxxxxx( + xxxxxxxxx +).xxxxxxxxxxxxxxxxxx(), ( + "xxx {xxxxxxxxx} xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +) + diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__tupleassign.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__tupleassign.py.snap.expect index b62974315a..59b201d8ac 100644 --- a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__tupleassign.py.snap.expect +++ b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__tupleassign.py.snap.expect @@ -4,10 +4,15 @@ assertion_line: 0 expression: formatted --- # This is a standalone comment. -sdfjklsdfsjldkflkjsf, sdfjsdfjlksdljkfsdlkf, sdfsdjfklsdfjlksdljkf, sdsfsdfjskdflsfsdf = 1, 2, 3 +( + sdfjklsdfsjldkflkjsf, + sdfjsdfjlksdljkfsdlkf, + sdfsdjfklsdfjlksdljkf, + sdsfsdfjskdflsfsdf, +) = (1, 2, 3) # This is as well. -this_will_be_wrapped_in_parens, = struct.unpack(b"12345678901234567890") +(this_will_be_wrapped_in_parens,) = struct.unpack(b"12345678901234567890") (a,) = call() diff --git a/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__whitespace.py.snap.expect b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__whitespace.py.snap.expect new file mode 100644 index 0000000000..6093b15f4e --- /dev/null +++ b/crates/ruff_python_formatter/src/snapshots/expect/ruff_python_formatter__tests__simple_cases__whitespace.py.snap.expect @@ -0,0 +1,7 @@ +--- +source: src/source_code/mod.rs +assertion_line: 0 +expression: formatted +--- + + diff --git a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__bracketmatch.py.snap b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__bracketmatch.py.snap index 17711c4ac5..37c5cb0d6a 100644 --- a/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__bracketmatch.py.snap +++ b/crates/ruff_python_formatter/src/snapshots/ruff_python_formatter__tests__simple_cases__bracketmatch.py.snap @@ -7,3 +7,4 @@ for ((x in {}) or {})["a"] in x: pass pem_spam = lambda l, spam={"x": 3}: not spam.get(l.strip()) lambda x=lambda y={1: 3}: y["x" : lambda y: {1: 2}]: x +