ruff/crates/ruff_python_formatter/src/statement
Brent Westbrook 827d8ae5d4
Allow newlines after function headers without docstrings (#21110)
Summary
--

This is a first step toward fixing #9745. After reviewing our open
issues and several Black issues and PRs, I personally found the function
case the most compelling, especially with very long argument lists:

```py
def func(
	self,
	arg1: int,
	arg2: bool,
	arg3: bool,
	arg4: float,
	arg5: bool,
) -> tuple[...]:
	if arg2 and arg3:
		raise ValueError
```

or many annotations:

```py
def function(
    self, data: torch.Tensor | tuple[torch.Tensor, ...], other_argument: int
) -> torch.Tensor | tuple[torch.Tensor, ...]:
    do_something(data)
    return something
```

I think docstrings help the situation substantially both because syntax
highlighting will usually give a very clear separation between the
annotations and the docstring and because we already allow a blank line
_after_ the docstring:

```py
def function(
    self, data: torch.Tensor | tuple[torch.Tensor, ...], other_argument: int
) -> torch.Tensor | tuple[torch.Tensor, ...]:
    """
	A function doing something.

	And a longer description of the things it does.
	"""

    do_something(data)
    return something
```

There are still other comments on #9745, such as [this one] with 9
upvotes, where users specifically request blank lines in all block
types, or at least including conditionals and loops. I'm sympathetic to
that case as well, even if personally I don't find an [example] like
this:

```py
if blah:

    # Do some stuff that is logically related
    data = get_data()

    # Do some different stuff that is logically related
    results = calculate_results()

    return results
```

to be much more readable than:

```py
if blah:
    # Do some stuff that is logically related
    data = get_data()

    # Do some different stuff that is logically related
    results = calculate_results()

    return results
```

I'm probably just used to the latter from the formatters I've used, but
I do prefer it. I also think that functions are the least susceptible to
the accidental introduction of a newline after refactoring described in
Micha's [comment] on #8893.

I actually considered further restricting this change to functions with
multiline headers. I don't think very short functions like:

```py
def foo():

    return 1
```

benefit nearly as much from the allowed newline, but I just went with
any function without a docstring for now. I guess a marginal case like:

```py
def foo(a_long_parameter: ALongType, b_long_parameter: BLongType) -> CLongType:

    return 1
```

might be a good argument for not restricting it.

I caused a couple of syntax errors before adding special handling for
the ellipsis-only case, so I suspect that there are some other
interesting edge cases that may need to be handled better.

Test Plan
--

Existing tests, plus a few simple new ones. As noted above, I suspect
that we may need a few more for edge cases I haven't considered.

[this one]:
https://github.com/astral-sh/ruff/issues/9745#issuecomment-2876771400
[example]:
https://github.com/psf/black/issues/902#issuecomment-1562154809
[comment]:
https://github.com/astral-sh/ruff/issues/8893#issuecomment-1867259744
2025-10-31 14:53:40 -04:00
..
clause.rs Allow newlines after function headers without docstrings (#21110) 2025-10-31 14:53:40 -04:00
mod.rs Preserve trailing statement semicolons when using `fmt: skip` (#8273) 2023-10-30 00:07:14 +00:00
stmt_ann_assign.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
stmt_assert.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
stmt_assign.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
stmt_aug_assign.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
stmt_break.rs Implement RUF028 to detect useless formatter suppression comments (#9899) 2024-02-28 19:21:06 +00:00
stmt_class_def.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
stmt_continue.rs Implement RUF028 to detect useless formatter suppression comments (#9899) 2024-02-28 19:21:06 +00:00
stmt_delete.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
stmt_expr.rs Implement RUF028 to detect useless formatter suppression comments (#9899) 2024-02-28 19:21:06 +00:00
stmt_for.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
stmt_function_def.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
stmt_global.rs Remove `AstNode` and `AnyNode` (#15479) 2025-01-17 17:11:00 -05:00
stmt_if.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
stmt_import.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
stmt_import_from.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
stmt_ipy_escape_command.rs Implement RUF028 to detect useless formatter suppression comments (#9899) 2024-02-28 19:21:06 +00:00
stmt_match.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
stmt_nonlocal.rs Remove `AstNode` and `AnyNode` (#15479) 2025-01-17 17:11:00 -05:00
stmt_pass.rs Implement RUF028 to detect useless formatter suppression comments (#9899) 2024-02-28 19:21:06 +00:00
stmt_raise.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
stmt_return.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
stmt_try.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
stmt_type_alias.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
stmt_while.rs [ty] AST garbage collection (#18482) 2025-06-13 08:40:11 -04:00
stmt_with.rs Switch to Rust 2024 edition (#18129) 2025-05-16 13:25:28 +02:00
suite.rs Allow newlines after function headers without docstrings (#21110) 2025-10-31 14:53:40 -04:00