mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 05:15:12 +00:00
Join implicit concatenated strings when they fit on a line (#13663)
This commit is contained in:
parent
e402e27a09
commit
73ee72b665
50 changed files with 3907 additions and 363 deletions
|
@ -20,7 +20,7 @@ use crate::expression::parentheses::{
|
|||
};
|
||||
use crate::expression::OperatorPrecedence;
|
||||
use crate::prelude::*;
|
||||
use crate::string::FormatImplicitConcatenatedString;
|
||||
use crate::string::implicit::FormatImplicitConcatenatedString;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(super) enum BinaryLike<'a> {
|
||||
|
|
|
@ -5,7 +5,8 @@ use crate::expression::parentheses::{
|
|||
in_parentheses_only_group, NeedsParentheses, OptionalParentheses,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use crate::string::{FormatImplicitConcatenatedString, StringLikeExtensions};
|
||||
use crate::string::implicit::FormatImplicitConcatenatedStringFlat;
|
||||
use crate::string::{implicit::FormatImplicitConcatenatedString, StringLikeExtensions};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatExprBytesLiteral;
|
||||
|
@ -14,9 +15,19 @@ impl FormatNodeRule<ExprBytesLiteral> for FormatExprBytesLiteral {
|
|||
fn fmt_fields(&self, item: &ExprBytesLiteral, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
let ExprBytesLiteral { value, .. } = item;
|
||||
|
||||
match value.as_slice() {
|
||||
[bytes_literal] => bytes_literal.format().fmt(f),
|
||||
_ => in_parentheses_only_group(&FormatImplicitConcatenatedString::new(item)).fmt(f),
|
||||
if let [bytes_literal] = value.as_slice() {
|
||||
bytes_literal.format().fmt(f)
|
||||
} else {
|
||||
// Always join byte literals that aren't parenthesized and thus, always on a single line.
|
||||
if !f.context().node_level().is_parenthesized() {
|
||||
if let Some(format_flat) =
|
||||
FormatImplicitConcatenatedStringFlat::new(item.into(), f.context())
|
||||
{
|
||||
return format_flat.fmt(f);
|
||||
}
|
||||
}
|
||||
|
||||
in_parentheses_only_group(&FormatImplicitConcatenatedString::new(item)).fmt(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,8 @@ use crate::expression::parentheses::{
|
|||
};
|
||||
use crate::other::f_string_part::FormatFStringPart;
|
||||
use crate::prelude::*;
|
||||
use crate::string::{FormatImplicitConcatenatedString, Quoting, StringLikeExtensions};
|
||||
use crate::string::implicit::FormatImplicitConcatenatedStringFlat;
|
||||
use crate::string::{implicit::FormatImplicitConcatenatedString, Quoting, StringLikeExtensions};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatExprFString;
|
||||
|
@ -16,13 +17,23 @@ impl FormatNodeRule<ExprFString> for FormatExprFString {
|
|||
fn fmt_fields(&self, item: &ExprFString, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
let ExprFString { value, .. } = item;
|
||||
|
||||
match value.as_slice() {
|
||||
[f_string_part] => FormatFStringPart::new(
|
||||
if let [f_string_part] = value.as_slice() {
|
||||
FormatFStringPart::new(
|
||||
f_string_part,
|
||||
f_string_quoting(item, &f.context().locator()),
|
||||
)
|
||||
.fmt(f),
|
||||
_ => in_parentheses_only_group(&FormatImplicitConcatenatedString::new(item)).fmt(f),
|
||||
.fmt(f)
|
||||
} else {
|
||||
// Always join fstrings that aren't parenthesized and thus, are always on a single line.
|
||||
if !f.context().node_level().is_parenthesized() {
|
||||
if let Some(format_flat) =
|
||||
FormatImplicitConcatenatedStringFlat::new(item.into(), f.context())
|
||||
{
|
||||
return format_flat.fmt(f);
|
||||
}
|
||||
}
|
||||
|
||||
in_parentheses_only_group(&FormatImplicitConcatenatedString::new(item)).fmt(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +46,7 @@ impl NeedsParentheses for ExprFString {
|
|||
) -> OptionalParentheses {
|
||||
if self.value.is_implicit_concatenated() {
|
||||
OptionalParentheses::Multiline
|
||||
}
|
||||
// TODO(dhruvmanila): Ideally what we want here is a new variant which
|
||||
// is something like:
|
||||
// - If the expression fits by just adding the parentheses, then add them and
|
||||
|
@ -53,7 +65,7 @@ impl NeedsParentheses for ExprFString {
|
|||
// ```
|
||||
// This isn't decided yet, refer to the relevant discussion:
|
||||
// https://github.com/astral-sh/ruff/discussions/9785
|
||||
} else if StringLike::FString(self).is_multiline(context.source()) {
|
||||
else if StringLike::FString(self).is_multiline(context.source()) {
|
||||
OptionalParentheses::Never
|
||||
} else {
|
||||
OptionalParentheses::BestFit
|
||||
|
|
|
@ -6,7 +6,8 @@ use crate::expression::parentheses::{
|
|||
};
|
||||
use crate::other::string_literal::StringLiteralKind;
|
||||
use crate::prelude::*;
|
||||
use crate::string::{FormatImplicitConcatenatedString, StringLikeExtensions};
|
||||
use crate::string::implicit::FormatImplicitConcatenatedStringFlat;
|
||||
use crate::string::{implicit::FormatImplicitConcatenatedString, StringLikeExtensions};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatExprStringLiteral {
|
||||
|
@ -26,16 +27,20 @@ impl FormatNodeRule<ExprStringLiteral> for FormatExprStringLiteral {
|
|||
fn fmt_fields(&self, item: &ExprStringLiteral, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
let ExprStringLiteral { value, .. } = item;
|
||||
|
||||
match value.as_slice() {
|
||||
[string_literal] => string_literal.format().with_options(self.kind).fmt(f),
|
||||
_ => {
|
||||
// This is just a sanity check because [`DocstringStmt::try_from_statement`]
|
||||
// ensures that the docstring is a *single* string literal.
|
||||
assert!(!self.kind.is_docstring());
|
||||
|
||||
in_parentheses_only_group(&FormatImplicitConcatenatedString::new(item))
|
||||
if let [string_literal] = value.as_slice() {
|
||||
string_literal.format().with_options(self.kind).fmt(f)
|
||||
} else {
|
||||
// Always join strings that aren't parenthesized and thus, always on a single line.
|
||||
if !f.context().node_level().is_parenthesized() {
|
||||
if let Some(mut format_flat) =
|
||||
FormatImplicitConcatenatedStringFlat::new(item.into(), f.context())
|
||||
{
|
||||
format_flat.set_docstring(self.kind.is_docstring());
|
||||
return format_flat.fmt(f);
|
||||
}
|
||||
}
|
||||
.fmt(f),
|
||||
|
||||
in_parentheses_only_group(&FormatImplicitConcatenatedString::new(item)).fmt(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ use crate::expression::parentheses::{
|
|||
use crate::prelude::*;
|
||||
use crate::preview::{
|
||||
is_empty_parameters_no_unnecessary_parentheses_around_return_value_enabled,
|
||||
is_f_string_implicit_concatenated_string_literal_quotes_enabled,
|
||||
is_hug_parens_with_braces_and_square_brackets_enabled,
|
||||
};
|
||||
|
||||
|
@ -405,38 +406,39 @@ impl Format<PyFormatContext<'_>> for MaybeParenthesizeExpression<'_> {
|
|||
needs_parentheses => needs_parentheses,
|
||||
};
|
||||
|
||||
let unparenthesized = expression.format().with_options(Parentheses::Never);
|
||||
|
||||
match needs_parentheses {
|
||||
OptionalParentheses::Multiline => match parenthesize {
|
||||
|
||||
Parenthesize::IfBreaksParenthesized | Parenthesize::IfBreaksParenthesizedNested if !is_empty_parameters_no_unnecessary_parentheses_around_return_value_enabled(f.context()) => {
|
||||
parenthesize_if_expands(&expression.format().with_options(Parentheses::Never))
|
||||
.fmt(f)
|
||||
}
|
||||
Parenthesize::IfRequired => {
|
||||
expression.format().with_options(Parentheses::Never).fmt(f)
|
||||
parenthesize_if_expands(&unparenthesized).fmt(f)
|
||||
}
|
||||
|
||||
Parenthesize::IfRequired => unparenthesized.fmt(f),
|
||||
|
||||
Parenthesize::Optional | Parenthesize::IfBreaks | Parenthesize::IfBreaksParenthesized | Parenthesize::IfBreaksParenthesizedNested => {
|
||||
if can_omit_optional_parentheses(expression, f.context()) {
|
||||
optional_parentheses(&expression.format().with_options(Parentheses::Never))
|
||||
.fmt(f)
|
||||
optional_parentheses(&unparenthesized).fmt(f)
|
||||
} else {
|
||||
parenthesize_if_expands(
|
||||
&expression.format().with_options(Parentheses::Never),
|
||||
)
|
||||
.fmt(f)
|
||||
parenthesize_if_expands(&unparenthesized).fmt(f)
|
||||
}
|
||||
}
|
||||
},
|
||||
OptionalParentheses::BestFit => match parenthesize {
|
||||
Parenthesize::IfBreaksParenthesized | Parenthesize::IfBreaksParenthesizedNested if !is_empty_parameters_no_unnecessary_parentheses_around_return_value_enabled(f.context()) =>
|
||||
parenthesize_if_expands(&unparenthesized).fmt(f),
|
||||
|
||||
Parenthesize::IfBreaksParenthesized | Parenthesize::IfBreaksParenthesizedNested => {
|
||||
parenthesize_if_expands(&expression.format().with_options(Parentheses::Never))
|
||||
.fmt(f)
|
||||
// Can-omit layout is relevant for `"abcd".call`. We don't want to add unnecessary
|
||||
// parentheses in this case.
|
||||
if can_omit_optional_parentheses(expression, f.context()) {
|
||||
optional_parentheses(&unparenthesized).fmt(f)
|
||||
} else {
|
||||
parenthesize_if_expands(&unparenthesized).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
Parenthesize::Optional | Parenthesize::IfRequired => {
|
||||
expression.format().with_options(Parentheses::Never).fmt(f)
|
||||
}
|
||||
Parenthesize::Optional | Parenthesize::IfRequired => unparenthesized.fmt(f),
|
||||
|
||||
Parenthesize::IfBreaks => {
|
||||
if node_comments.has_trailing() {
|
||||
|
@ -446,7 +448,7 @@ impl Format<PyFormatContext<'_>> for MaybeParenthesizeExpression<'_> {
|
|||
let group_id = f.group_id("optional_parentheses");
|
||||
let f = &mut WithNodeLevel::new(NodeLevel::Expression(Some(group_id)), f);
|
||||
|
||||
best_fit_parenthesize(&expression.format().with_options(Parentheses::Never))
|
||||
best_fit_parenthesize(&unparenthesized)
|
||||
.with_group_id(Some(group_id))
|
||||
.fmt(f)
|
||||
}
|
||||
|
@ -454,13 +456,13 @@ impl Format<PyFormatContext<'_>> for MaybeParenthesizeExpression<'_> {
|
|||
},
|
||||
OptionalParentheses::Never => match parenthesize {
|
||||
Parenthesize::IfBreaksParenthesized | Parenthesize::IfBreaksParenthesizedNested if !is_empty_parameters_no_unnecessary_parentheses_around_return_value_enabled(f.context()) => {
|
||||
parenthesize_if_expands(&expression.format().with_options(Parentheses::Never))
|
||||
parenthesize_if_expands(&unparenthesized)
|
||||
.with_indent(!is_expression_huggable(expression, f.context()))
|
||||
.fmt(f)
|
||||
}
|
||||
|
||||
Parenthesize::Optional | Parenthesize::IfBreaks | Parenthesize::IfRequired | Parenthesize::IfBreaksParenthesized | Parenthesize::IfBreaksParenthesizedNested => {
|
||||
expression.format().with_options(Parentheses::Never).fmt(f)
|
||||
unparenthesized.fmt(f)
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -768,15 +770,26 @@ impl<'input> CanOmitOptionalParenthesesVisitor<'input> {
|
|||
Expr::StringLiteral(ast::ExprStringLiteral { value, .. })
|
||||
if value.is_implicit_concatenated() =>
|
||||
{
|
||||
self.update_max_precedence(OperatorPrecedence::String);
|
||||
if !is_f_string_implicit_concatenated_string_literal_quotes_enabled(self.context) {
|
||||
self.update_max_precedence(OperatorPrecedence::String);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. })
|
||||
if value.is_implicit_concatenated() =>
|
||||
{
|
||||
self.update_max_precedence(OperatorPrecedence::String);
|
||||
if !is_f_string_implicit_concatenated_string_literal_quotes_enabled(self.context) {
|
||||
self.update_max_precedence(OperatorPrecedence::String);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
Expr::FString(ast::ExprFString { value, .. }) if value.is_implicit_concatenated() => {
|
||||
self.update_max_precedence(OperatorPrecedence::String);
|
||||
if !is_f_string_implicit_concatenated_string_literal_quotes_enabled(self.context) {
|
||||
self.update_max_precedence(OperatorPrecedence::String);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue