mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-28 12:55:05 +00:00
Implement multiline dictionary and list hugging for preview style (#8293)
## Summary This PR implement's Black's new single-argument hugging for lists, sets, and dictionaries under preview style. For example, this: ```python foo( [ 1, 2, 3, ] ) ``` Would instead now be formatted as: ```python foo([ 1, 2, 3, ]) ``` A couple notes: - This doesn't apply when the argument has a magic trailing comma. - This _does_ apply when the argument is starred or double-starred. - We don't apply this when there are comments before or after the argument, though Black does in some cases (and moves the comments outside the call parentheses). It doesn't say it in the originating PR (https://github.com/psf/black/pull/3964), but I think this also applies to parenthesized expressions? At least, it does in my testing of preview vs. stable, though it's possible that behavior predated the linked PR. See: #8279. ## Test Plan Before: | project | similarity index | total files | changed files | |----------------|------------------:|------------------:|------------------:| | cpython | 0.75804 | 1799 | 1648 | | django | 0.99984 | 2772 | 34 | | home-assistant | 0.99963 | 10596 | 146 | | poetry | 0.99925 | 317 | 12 | | transformers | 0.99967 | 2657 | 322 | | twine | 1.00000 | 33 | 0 | | typeshed | 0.99980 | 3669 | 18 | | warehouse | 0.99977 | 654 | 13 | | zulip | 0.99970 | 1459 | 21 | After: | project | similarity index | total files | changed files | |----------------|------------------:|------------------:|------------------:| | cpython | 0.75804 | 1799 | 1648 | | django | 0.99984 | 2772 | 34 | | home-assistant | 0.99963 | 10596 | 146 | | poetry | 0.96215 | 317 | 34 | | transformers | 0.99967 | 2657 | 322 | | twine | 1.00000 | 33 | 0 | | typeshed | 0.99980 | 3669 | 18 | | warehouse | 0.99977 | 654 | 13 | | zulip | 0.99970 | 1459 | 21 |
This commit is contained in:
parent
f06c5dc896
commit
019d9aebe9
11 changed files with 1969 additions and 15 deletions
|
@ -1,11 +1,13 @@
|
|||
use ruff_formatter::write;
|
||||
use ruff_formatter::{write, FormatContext};
|
||||
use ruff_python_ast::{ArgOrKeyword, Arguments, Expr};
|
||||
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
use crate::comments::SourceComment;
|
||||
use crate::expression::expr_generator_exp::GeneratorExpParentheses;
|
||||
use crate::expression::is_expression_huggable;
|
||||
use crate::expression::parentheses::{empty_parenthesized, parenthesized, Parentheses};
|
||||
use crate::other::commas;
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -104,6 +106,7 @@ impl FormatNodeRule<Arguments> for FormatArguments {
|
|||
// )
|
||||
// ```
|
||||
parenthesized("(", &group(&all_arguments), ")")
|
||||
.with_indent(!is_argument_huggable(item, f.context()))
|
||||
.with_dangling_comments(dangling_comments)
|
||||
]
|
||||
)
|
||||
|
@ -143,3 +146,68 @@ fn is_single_argument_parenthesized(argument: &Expr, call_end: TextSize, source:
|
|||
|
||||
false
|
||||
}
|
||||
/// Returns `true` if the arguments can hug directly to the enclosing parentheses in the call, as
|
||||
/// in Black's `hug_parens_with_braces_and_square_brackets` preview style behavior.
|
||||
///
|
||||
/// For example, in preview style, given:
|
||||
/// ```python
|
||||
/// func([1, 2, 3,])
|
||||
/// ```
|
||||
///
|
||||
/// We want to format it as:
|
||||
/// ```python
|
||||
/// func([
|
||||
/// 1,
|
||||
/// 2,
|
||||
/// 3,
|
||||
/// ])
|
||||
/// ```
|
||||
///
|
||||
/// As opposed to:
|
||||
/// ```python
|
||||
/// func(
|
||||
/// [
|
||||
/// 1,
|
||||
/// 2,
|
||||
/// 3,
|
||||
/// ]
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// Hugging should only be applied to single-argument collections, like lists, or starred versions
|
||||
/// of those collections.
|
||||
fn is_argument_huggable(item: &Arguments, context: &PyFormatContext) -> bool {
|
||||
let options = context.options();
|
||||
if !options.preview().is_enabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the lone argument or `**kwargs` keyword.
|
||||
let arg = match (item.args.as_slice(), item.keywords.as_slice()) {
|
||||
([arg], []) => arg,
|
||||
([], [keyword]) if keyword.arg.is_none() && !context.comments().has(keyword) => {
|
||||
&keyword.value
|
||||
}
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
// If the expression itself isn't huggable, then we can't hug it.
|
||||
if !is_expression_huggable(arg, options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the expression has leading or trailing comments, then we can't hug it.
|
||||
let comments = context.comments().leading_dangling_trailing(arg);
|
||||
if comments.has_leading() || comments.has_trailing() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the expression has a trailing comma, then we can't hug it.
|
||||
if options.magic_trailing_comma().is_respect()
|
||||
&& commas::has_magic_trailing_comma(TextRange::new(arg.end(), item.end()), options, context)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue