Skip to content

πŸ–οΈ Black 2024 Preview StyleΒ #8678

@konstin

Description

@konstin

Black is planning to stabilize a subset of the preview style as stable style for 2024 (psf/black#4042). Ruff implements a subset of the current preview style, and subsequently a subset of the planned stable style.

For each of the unchecked items below, we need to port the test case to ruff, implement the changed behavior under the preview flag and tick off the box below. For two of the changes, we will need to introduce target version support to the formatter (same as in the linter).

Shipped in Ruff Stable

These are mainly bug fixes to Black that we didn't inherit or shipping them right away simplified the implementation and are uncontroversial.

Likely to be stabilized

Styles that with a high likelihood to be promoted to Black stable and are part of the 2024 alpha release.

Not uncontroversial preview features that are likely be stabilized

Unlikely to be stabilized in 2024

Gather feedback

Notes

We might have to remove some preview styles from ruff, but removing the preview clause is enough that we can delay until black's changes are finalized

Reference test file
# ERR dummy_implementations (https://github.com/psf/black/pull/3796)
def dummy(a): ...
def other(b): ...


@overload
def a(arg: int) -> int: ...
@overload
def a(arg: str) -> str: ...
@overload
def a(arg: object) -> NoReturn: ...
def a(arg: Union[int, str, object]) -> Union[int, str]:
    if not isinstance(arg, (int, str)):
        raise TypeError
    return arg

class Proto(Protocol):
    def foo(self, a: int) -> int:
        ...

    def bar(self, b: str) -> str: ...
    def baz(self, c: bytes) -> str:
        ...

# ERR prefer_splitting_right_hand_side_of_assignments (https://github.com/psf/black/pull/3368)
first_item, second_item = (
    some_looooooooong_module.some_looooooooooooooong_function_name(
        first_argument, second_argument, third_argument
    )
)

some_dict["with_a_long_key"] = (
    some_looooooooong_module.some_looooooooooooooong_function_name(
        first_argument, second_argument, third_argument
    )
)

# Make sure it works when the RHS only has one pair of (optional) parens.
first_item, second_item = (
    some_looooooooong_module.SomeClass.some_looooooooooooooong_variable_name
)

some_dict["with_a_long_key"] = (
    some_looooooooong_module.SomeClass.some_looooooooooooooong_variable_name
)

# Make sure chaining assignments work.
first_item, second_item, third_item, forth_item = m["everything"] = (
    some_looooooooong_module.some_looooooooooooooong_function_name(
        first_argument, second_argument, third_argument
    )
)

# ERR(https://github.com/psf/black/issues/3568) hex_codes_in_unicode_sequences (https://github.com/psf/black/pull/2916)
x = "\x1F"
x = "\\x1B"
x = "\\\x1B"
x = "\U0001F60E"
x = "\u0001F60E"
x = r"\u0001F60E"
x = "don't format me"
x = "\xA3"
x = "\u2717"
x = "\uFaCe"
x = "\N{ox}\N{OX}"
x = "\N{lAtIn smaLL letteR x}"
x = "\N{CYRILLIC small LETTER BYELORUSSIAN-UKRAINIAN I}"
x = b"\x1Fdon't byte"
x = rb"\x1Fdon't format"

# ERR allow_empty_first_line_before_new_block_or_comment (https://github.com/psf/black/pull/3967)
def foo():
    """
    Docstring
    """

    # Here we go
    if x:

        # This is also now fine
        a = 123

    else:
        # But not necessary
        a = 123

    if y:

        while True:

            """
            Long comment here
            """
            a = 123

    if z:

        for _ in range(100):
            a = 123
    else:

        try:

            # this should be ok
            a = 123
        except:

            """also this"""
            a = 123


def bar():

    if x:
        a = 123


def baz():

    # OK
    if x:
        a = 123

# ERR parenthesize_long_type_hints (https://github.com/psf/black/pull/3899)
# This has always worked
z= Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong

# "AnnAssign"s now also work
z: Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong
z: (Short
    | Short2
    | Short3
    | Short4)
z: (int)
z: ((int))


z: Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong | Loooooooooooooooooooooooong = 7
z: (Short
    | Short2
    | Short3
    | Short4) = 8
z: (int) = 2.3
z: ((int)) = foo()

# OK module_docstring_newlines (https://github.com/psf/black/pull/3932)
# [Can't do this here since it needs to be the first item in a file, but it's done: https://github.com/astral-sh/ruff/issues/7995]

# OK: respect_magic_trailing_comma_in_return_type (https://github.com/psf/black/pull/3916)
def foo(a,b) -> tuple[int, int, int,]:
    return 2

# ERR: no_blank_line_before_class_docstring (https://github.com/psf/black/pull/3692)
class LineBeforeDocstring:

    """Please move me up"""

# ERR(--target-version py39, see also improved_async_statements_handling): wrap_multiple_context_managers_in_parens (https://github.com/psf/black/pull/3489)
with \
    make_context_manager1() as cm1, \
    make_context_manager2() as cm2, \
    make_context_manager3() as cm3, \
    make_context_manager4() as cm4 \
    :
    pass

# OK: fix_power_op_line_length (https://github.com/psf/black/pull/3942)
a = 1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1
b = 1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1**1
c = 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1 ** 1
d = 1**1 ** 1**1 ** 1**1 ** 1**1 ** 1**1**1 ** 1 ** 1**1 ** 1**1**1**1**1 ** 1 ** 1**1**1 **1**1** 1 ** 1 ** 1
e = π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ
f = π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ**π¨‰Ÿ

a = 1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0
b = 1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0**1.0
c = 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0 ** 1.0
d = 1.0**1.0 ** 1.0**1.0 ** 1.0**1.0 ** 1.0**1.0 ** 1.0**1.0**1.0 ** 1.0 ** 1.0**1.0 ** 1.0**1.0**1.0

# OK: add_trailing_comma_consistently (https://github.com/psf/black/pull/3393)
e = {
    "a": fun(msg, "ts"),
    "longggggggggggggggid": ...,
    "longgggggggggggggggggggkey": ..., "created": ...
    # "longkey": ...
}
f = [
    arg1,
    arg2,
    arg3, arg4
    # comment
]
g = (
    arg1,
    arg2,
    arg3, arg4
    # comment
)
h = {
    arg1,
    arg2,
    arg3, arg4
    # comment
}

# OK(--skip-magic-trailing-comma) skip_magic_trailing_comma_in_subscript (https://github.com/psf/black/pull/3209)
# 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,)

# ERR(.pyi) blank_line_after_nested_stub_class (https://github.com/psf/black/pull/3564)
class Outer:
    class InnerStub: ...
    outer_attr_after_inner_stub: int
    class Inner:
        inner_attr: int
    outer_attr: int

# OK blank_line_between_nested_and_def_stub_file (https://github.com/psf/black/pull/3862)
if sys.version_info > (3, 7):
    class Nested1:
        assignment = 1
        def function_definition(self): ...
    def f1(self) -> str: ...
    class Nested2:
        def function_definition(self): ...
        assignment = 1
    def f2(self) -> str: ...

if sys.version_info > (3, 7):
    def nested1():
        assignment = 1
        def function_definition(self): ...
    def f1(self) -> str: ...
    def nested2():
        def function_definition(self): ...
        assignment = 1
    def f2(self) -> str: ...

# OK accept_raw_docstrings (https://github.com/psf/black/pull/3947)
class C:
    r"""Raw"""

# OK long_case_block_line_splitting (https://github.com/psf/black/pull/4024)
match x:
    case "abcd" | "abcd" | "abcd" :
        pass
    case "abcd" | "abcd" | "abcd" | "abcd" | "abcd" | "abcd" | "abcd" | "abcd" | "abcd" | "abcd" | "abcd" | "abcd" | "abcd" | "abcd" | "abcd":
        pass
    case xxxxxxxxxxxxxxxxxxxxxxx:
        pass

match maybe, multiple:
    case perhaps, 5:
        pass
    case perhaps, 6,:
        pass


match more := (than, one), indeed,:
    case _, (5, 6):
        pass
    case [[5], (6)], [7],:
        pass
    case _:
        pass

# ERR(i think we're correct here though) walrus_subscript (https://github.com/psf/black/pull/3823)
x[(a:=0):]
x[:(a:=0)]
x[a:=0]
x[a := 0]
x[a := 0, b := 1]
x[5, b := 0]
x[a:=0,b:=1]

# ERR(--target-version py39, same as wrap_multiple_context_managers_in_parens effectively) improved_async_statements_handling (https://github.com/psf/black/pull/3609)
async def func() -> (int):
    return 0

@decorated
async def func() -> (int):
    return 0

async for (item) in async_iter:
    pass

async def func():
    async with \
        make_context_manager1() as cm1, \
        make_context_manager2() as cm2, \
        make_context_manager3() as cm3, \
        make_context_manager4() as cm4 \
        :
        pass

    async with some_function(
        argument1, argument2, argument3="some_value"
    ) as some_cm, some_other_function(
        argument1, argument2, argument3="some_value"
    ):
        pass

# ERR(we have different format) single_line_format_skip_with_multiple_comments (https://github.com/psf/black/pull/3959)
foo =  123  # fmt: skip # noqa: E501 # pylint
bar =    (
    123   ,
    (        1      +           5      )  # pylint # fmt:skip
)
baz = "a"    +   "b"  # pylint; fmt: skip; noqa: E501
skip_will_not_work =  "a" +  "b"  # pylint fmt:skip
skip_will_not_work2 =  "a" +  "b"  # some text; fmt:skip happens to be part of it

# ERR parenthesize_conditional_expressions (https://github.com/psf/black/pull/2278)
long_kwargs_single_line = my_function(
    foo="test, this is a sample value",
    bar=some_long_value_name_foo_bar_baz if some_boolean_variable else some_fallback_value_foo_bar_baz,
    baz="hello, this is a another value",
)

multiline_kwargs_indented = my_function(
    foo="test, this is a sample value",
    bar=some_long_value_name_foo_bar_baz
    if some_boolean_variable
    else some_fallback_value_foo_bar_baz,
    baz="hello, this is a another value",
)

imploding_kwargs = my_function(
    foo="test, this is a sample value",
    bar=a
    if foo
    else b,
    baz="hello, this is a another value",
)

imploding_line = (
    1
    if 1 + 1 == 2
    else 0
)

# ERR wrap_long_dict_values_in_parens (https://github.com/psf/black/pull/3440)
my_dict = {
    "something_something":
        r"Lorem ipsum dolor sit amet, an sed convenire eloquentiam \t"
        r"signiferumque, duo ea vocibus consetetur scriptorem. Facer \t"
        r"signiferumque, duo ea vocibus consetetur scriptorem. Facer \t",
}

my_dict = {
    "a key in my dict": a_very_long_variable * and_a_very_long_function_call() / 100000.0
}

my_dict = {
    "a key in my dict": a_very_long_variable * and_a_very_long_function_call() * and_another_long_func() / 100000.0
}

my_dict = {
    "a key in my dict": MyClass.some_attribute.first_call().second_call().third_call(some_args="some value")
}

# ERR multiline_string_handling (https://github.com/psf/black/pull/1879)
textwrap.dedent(
    """\
    This is a
    multiline string
"""
)

Metadata

Metadata

Assignees

No one assigned

    Labels

    formatterRelated to the formatterpreviewRelated to preview mode features

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions