mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-19 10:01:15 +00:00
Add formatter support for call and class definition Arguments
(#6274)
## Summary This PR leverages the `Arguments` AST node introduced in #6259 in the formatter, which ensures that we correctly handle trailing comments in calls, like: ```python f( 1, # comment ) pass ``` (Previously, this was treated as a leading comment on `pass`.) This also allows us to unify the argument handling across calls and class definitions. ## Test Plan A bunch of new fixture tests, plus improved Black compatibility.
This commit is contained in:
parent
b095b7204b
commit
4c53bfe896
19 changed files with 640 additions and 252 deletions
|
@ -3,8 +3,8 @@ use std::cmp::Ordering;
|
|||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::whitespace::indentation;
|
||||
use ruff_python_ast::{
|
||||
self as ast, Comprehension, Expr, ExprAttribute, ExprBinOp, ExprIfExp, ExprSlice, ExprStarred,
|
||||
MatchCase, Parameters, Ranged,
|
||||
self as ast, Arguments, Comprehension, Expr, ExprAttribute, ExprBinOp, ExprIfExp, ExprSlice,
|
||||
ExprStarred, MatchCase, Parameters, Ranged,
|
||||
};
|
||||
use ruff_python_trivia::{
|
||||
indentation_at_offset, PythonWhitespace, SimpleToken, SimpleTokenKind, SimpleTokenizer,
|
||||
|
@ -46,6 +46,7 @@ pub(super) fn place_comment<'a>(
|
|||
AnyNodeRef::Parameters(arguments) => {
|
||||
handle_parameters_separator_comment(comment, arguments, locator)
|
||||
}
|
||||
AnyNodeRef::Arguments(arguments) => handle_arguments_comment(comment, arguments),
|
||||
AnyNodeRef::Comprehension(comprehension) => {
|
||||
handle_comprehension_comment(comment, comprehension, locator)
|
||||
}
|
||||
|
@ -80,6 +81,9 @@ pub(super) fn place_comment<'a>(
|
|||
AnyNodeRef::StmtFunctionDef(_) | AnyNodeRef::StmtAsyncFunctionDef(_) => {
|
||||
handle_leading_function_with_decorators_comment(comment)
|
||||
}
|
||||
AnyNodeRef::StmtClassDef(class_def) => {
|
||||
handle_leading_class_with_decorators_comment(comment, class_def)
|
||||
}
|
||||
AnyNodeRef::StmtImportFrom(import_from) => handle_import_from_comment(comment, import_from),
|
||||
_ => CommentPlacement::Default(comment),
|
||||
}
|
||||
|
@ -843,6 +847,32 @@ fn handle_leading_function_with_decorators_comment(comment: DecoratedComment) ->
|
|||
}
|
||||
}
|
||||
|
||||
/// Handle comments between decorators and the decorated node.
|
||||
///
|
||||
/// For example, given:
|
||||
/// ```python
|
||||
/// @dataclass
|
||||
/// # comment
|
||||
/// class Foo(Bar):
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// The comment should be attached to the enclosing [`ast::StmtClassDef`] as a dangling node,
|
||||
/// as opposed to being treated as a leading comment on `Bar` or similar.
|
||||
fn handle_leading_class_with_decorators_comment<'a>(
|
||||
comment: DecoratedComment<'a>,
|
||||
class_def: &'a ast::StmtClassDef,
|
||||
) -> CommentPlacement<'a> {
|
||||
if comment.start() < class_def.name.start() {
|
||||
if let Some(decorator) = class_def.decorator_list.last() {
|
||||
if decorator.end() < comment.start() {
|
||||
return CommentPlacement::dangling(class_def, comment);
|
||||
}
|
||||
}
|
||||
}
|
||||
CommentPlacement::Default(comment)
|
||||
}
|
||||
|
||||
/// Handles comments between `**` and the variable name in dict unpacking
|
||||
/// It attaches these to the appropriate value node
|
||||
///
|
||||
|
@ -1105,6 +1135,64 @@ fn find_only_token_in_range(
|
|||
token
|
||||
}
|
||||
|
||||
/// Attach an enclosed end-of-line comment to a set of [`Arguments`].
|
||||
///
|
||||
/// For example, given:
|
||||
/// ```python
|
||||
/// foo( # comment
|
||||
/// bar,
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// The comment will be attached to the [`Arguments`] node as a dangling comment, to ensure
|
||||
/// that it remains on the same line as open parenthesis.
|
||||
fn handle_arguments_comment<'a>(
|
||||
comment: DecoratedComment<'a>,
|
||||
arguments: &'a Arguments,
|
||||
) -> CommentPlacement<'a> {
|
||||
// The comment needs to be on the same line, but before the first argument. For example, we want
|
||||
// to treat this as a dangling comment:
|
||||
// ```python
|
||||
// foo( # comment
|
||||
// bar,
|
||||
// baz,
|
||||
// qux,
|
||||
// )
|
||||
// ```
|
||||
// However, this should _not_ be treated as a dangling comment:
|
||||
// ```python
|
||||
// foo(bar, # comment
|
||||
// baz,
|
||||
// qux,
|
||||
// )
|
||||
// ```
|
||||
// Thus, we check whether the comment is an end-of-line comment _between_ the start of the
|
||||
// statement and the first argument. If so, the only possible position is immediately following
|
||||
// the open parenthesis.
|
||||
if comment.line_position().is_end_of_line() {
|
||||
let first_argument = match (arguments.args.as_slice(), arguments.keywords.as_slice()) {
|
||||
([first_arg, ..], [first_keyword, ..]) => {
|
||||
if first_arg.start() < first_keyword.start() {
|
||||
Some(first_arg.range())
|
||||
} else {
|
||||
Some(first_keyword.range())
|
||||
}
|
||||
}
|
||||
([first_arg, ..], []) => Some(first_arg.range()),
|
||||
([], [first_keyword, ..]) => Some(first_keyword.range()),
|
||||
([], []) => None,
|
||||
};
|
||||
|
||||
if let Some(first_argument) = first_argument {
|
||||
if arguments.start() < comment.start() && comment.end() < first_argument.start() {
|
||||
return CommentPlacement::dangling(comment.enclosing_node(), comment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CommentPlacement::Default(comment)
|
||||
}
|
||||
|
||||
/// Attach an enclosed end-of-line comment to a [`StmtImportFrom`].
|
||||
///
|
||||
/// For example, given:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue