Ruff 2025 style guide (#13906)

Closes #13371
This commit is contained in:
Micha Reiser 2025-01-03 14:16:10 +01:00
parent a95deec00f
commit 424b720c19
62 changed files with 1799 additions and 3890 deletions

View file

@ -37,7 +37,6 @@ for node_line in node_lines:
# `FStringLiteralElement`, `FStringFormatSpec` and `FStringExpressionElement` are handled by the `FString` # `FStringLiteralElement`, `FStringFormatSpec` and `FStringExpressionElement` are handled by the `FString`
# implementation. # implementation.
if node in ( if node in (
"FString",
"FStringLiteralElement", "FStringLiteralElement",
"FStringExpressionElement", "FStringExpressionElement",
"FStringFormatSpec", "FStringFormatSpec",

View file

@ -1,11 +1,9 @@
[ [
{ {
"quote_style": "preserve", "quote_style": "preserve"
"preview": "enabled"
}, },
{ {
"quote_style": "preserve", "quote_style": "preserve",
"preview": "enabled",
"target_version": "py312" "target_version": "py312"
} }
] ]

View file

@ -1,6 +1,5 @@
[ [
{ {
"preview": "enabled",
"source_type": "Stub" "source_type": "Stub"
} }
] ]

View file

@ -1,15 +1,14 @@
use ruff_python_ast::{AnyNodeRef, ExprFString, StringLike}; use ruff_python_ast::{AnyNodeRef, ExprFString, StringLike};
use ruff_text_size::TextSlice;
use crate::expression::parentheses::{ use crate::expression::parentheses::{
in_parentheses_only_group, NeedsParentheses, OptionalParentheses, in_parentheses_only_group, NeedsParentheses, OptionalParentheses,
}; };
use crate::other::f_string::{FStringLayout, FormatFString}; use crate::other::f_string::FStringLayout;
use crate::prelude::*; use crate::prelude::*;
use crate::string::implicit::{ use crate::string::implicit::{
FormatImplicitConcatenatedString, FormatImplicitConcatenatedStringFlat, FormatImplicitConcatenatedString, FormatImplicitConcatenatedStringFlat,
}; };
use crate::string::{Quoting, StringLikeExtensions}; use crate::string::StringLikeExtensions;
#[derive(Default)] #[derive(Default)]
pub struct FormatExprFString; pub struct FormatExprFString;
@ -23,7 +22,7 @@ impl FormatNodeRule<ExprFString> for FormatExprFString {
// [`ruff_python_ast::FStringValue::single`] constructor. // [`ruff_python_ast::FStringValue::single`] constructor.
let f_string = f_string_part.as_f_string().unwrap(); let f_string = f_string_part.as_f_string().unwrap();
FormatFString::new(f_string, f_string_quoting(item, f.context().source())).fmt(f) f_string.format().fmt(f)
} else { } else {
// Always join fstrings that aren't parenthesized and thus, are always on a single line. // Always join fstrings that aren't parenthesized and thus, are always on a single line.
if !f.context().node_level().is_parenthesized() { if !f.context().node_level().is_parenthesized() {
@ -58,28 +57,3 @@ impl NeedsParentheses for ExprFString {
} }
} }
} }
pub(crate) fn f_string_quoting(f_string: &ExprFString, source: &str) -> Quoting {
let unprefixed = source
.slice(f_string)
.trim_start_matches(|c| c != '"' && c != '\'');
let triple_quoted = unprefixed.starts_with(r#"""""#) || unprefixed.starts_with(r"'''");
if f_string
.value
.elements()
.filter_map(|element| element.as_expression())
.any(|expression| {
let string_content = source.slice(expression);
if triple_quoted {
string_content.contains(r#"""""#) || string_content.contains("'''")
} else {
string_content.contains(['"', '\''])
}
})
{
Quoting::Preserve
} else {
Quoting::CanChange
}
}

View file

@ -8,7 +8,6 @@ use crate::expression::parentheses::{
}; };
use crate::expression::CallChainLayout; use crate::expression::CallChainLayout;
use crate::prelude::*; use crate::prelude::*;
use crate::preview::is_empty_parameters_no_unnecessary_parentheses_around_return_value_enabled;
#[derive(Default)] #[derive(Default)]
pub struct FormatExprSubscript { pub struct FormatExprSubscript {
@ -108,13 +107,14 @@ impl NeedsParentheses for ExprSubscript {
if function.returns.as_deref().is_some_and(|returns| { if function.returns.as_deref().is_some_and(|returns| {
AnyNodeRef::ptr_eq(returns.into(), self.into()) AnyNodeRef::ptr_eq(returns.into(), self.into())
}) { }) {
if is_empty_parameters_no_unnecessary_parentheses_around_return_value_enabled(context) && if function.parameters.is_empty()
function.parameters.is_empty() && !context.comments().has(&*function.parameters) { && !context.comments().has(&*function.parameters)
{
// Apply the `optional_parentheses` layout when the subscript // Apply the `optional_parentheses` layout when the subscript
// is in a return type position of a function without parameters. // is in a return type position of a function without parameters.
// This ensures the subscript is parenthesized if it has a very // This ensures the subscript is parenthesized if it has a very
// long name that goes over the line length limit. // long name that goes over the line length limit.
return OptionalParentheses::Multiline return OptionalParentheses::Multiline;
} }
// Don't use the best fitting layout for return type annotation because it results in the // Don't use the best fitting layout for return type annotation because it results in the

View file

@ -19,10 +19,7 @@ use crate::expression::parentheses::{
OptionalParentheses, Parentheses, Parenthesize, OptionalParentheses, Parentheses, Parenthesize,
}; };
use crate::prelude::*; use crate::prelude::*;
use crate::preview::{ use crate::preview::is_hug_parens_with_braces_and_square_brackets_enabled;
is_empty_parameters_no_unnecessary_parentheses_around_return_value_enabled,
is_f_string_formatting_enabled, is_hug_parens_with_braces_and_square_brackets_enabled,
};
mod binary_like; mod binary_like;
pub(crate) mod expr_attribute; pub(crate) mod expr_attribute;
@ -388,18 +385,12 @@ impl Format<PyFormatContext<'_>> for MaybeParenthesizeExpression<'_> {
// is parenthesized. Unless, it's the `Parenthesize::IfBreaksParenthesizedNested` layout // is parenthesized. Unless, it's the `Parenthesize::IfBreaksParenthesizedNested` layout
// where parenthesizing nested `maybe_parenthesized_expression` is explicitly desired. // where parenthesizing nested `maybe_parenthesized_expression` is explicitly desired.
_ if f.context().node_level().is_parenthesized() => { _ if f.context().node_level().is_parenthesized() => {
if !is_empty_parameters_no_unnecessary_parentheses_around_return_value_enabled( return if matches!(parenthesize, Parenthesize::IfBreaksParenthesizedNested) {
f.context(), parenthesize_if_expands(&expression.format().with_options(Parentheses::Never))
) { .with_indent(!is_expression_huggable(expression, f.context()))
OptionalParentheses::Never .fmt(f)
} else if matches!(parenthesize, Parenthesize::IfBreaksParenthesizedNested) {
return parenthesize_if_expands(
&expression.format().with_options(Parentheses::Never),
)
.with_indent(!is_expression_huggable(expression, f.context()))
.fmt(f);
} else { } else {
return expression.format().with_options(Parentheses::Never).fmt(f); expression.format().with_options(Parentheses::Never).fmt(f)
} }
} }
needs_parentheses => needs_parentheses, needs_parentheses => needs_parentheses,
@ -409,13 +400,12 @@ impl Format<PyFormatContext<'_>> for MaybeParenthesizeExpression<'_> {
match needs_parentheses { match needs_parentheses {
OptionalParentheses::Multiline => match parenthesize { OptionalParentheses::Multiline => 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::IfRequired => unparenthesized.fmt(f), Parenthesize::IfRequired => unparenthesized.fmt(f),
Parenthesize::Optional | Parenthesize::IfBreaks | Parenthesize::IfBreaksParenthesized | Parenthesize::IfBreaksParenthesizedNested => { Parenthesize::Optional
| Parenthesize::IfBreaks
| Parenthesize::IfBreaksParenthesized
| Parenthesize::IfBreaksParenthesizedNested => {
if can_omit_optional_parentheses(expression, f.context()) { if can_omit_optional_parentheses(expression, f.context()) {
optional_parentheses(&unparenthesized).fmt(f) optional_parentheses(&unparenthesized).fmt(f)
} else { } else {
@ -424,9 +414,6 @@ impl Format<PyFormatContext<'_>> for MaybeParenthesizeExpression<'_> {
} }
}, },
OptionalParentheses::BestFit => match parenthesize { 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::IfBreaksParenthesized | Parenthesize::IfBreaksParenthesizedNested => {
// Can-omit layout is relevant for `"abcd".call`. We don't want to add unnecessary // Can-omit layout is relevant for `"abcd".call`. We don't want to add unnecessary
// parentheses in this case. // parentheses in this case.
@ -454,15 +441,11 @@ impl Format<PyFormatContext<'_>> for MaybeParenthesizeExpression<'_> {
} }
}, },
OptionalParentheses::Never => match parenthesize { OptionalParentheses::Never => match parenthesize {
Parenthesize::IfBreaksParenthesized | Parenthesize::IfBreaksParenthesizedNested if !is_empty_parameters_no_unnecessary_parentheses_around_return_value_enabled(f.context()) => { Parenthesize::Optional
parenthesize_if_expands(&unparenthesized) | Parenthesize::IfBreaks
.with_indent(!is_expression_huggable(expression, f.context())) | Parenthesize::IfRequired
.fmt(f) | Parenthesize::IfBreaksParenthesized
} | Parenthesize::IfBreaksParenthesizedNested => unparenthesized.fmt(f),
Parenthesize::Optional | Parenthesize::IfBreaks | Parenthesize::IfRequired | Parenthesize::IfBreaksParenthesized | Parenthesize::IfBreaksParenthesizedNested => {
unparenthesized.fmt(f)
}
}, },
OptionalParentheses::Always => { OptionalParentheses::Always => {
@ -766,32 +749,6 @@ impl<'input> CanOmitOptionalParenthesesVisitor<'input> {
return; return;
} }
Expr::StringLiteral(ast::ExprStringLiteral { value, .. })
if value.is_implicit_concatenated() =>
{
if !is_f_string_formatting_enabled(self.context) {
self.update_max_precedence(OperatorPrecedence::String);
}
return;
}
Expr::BytesLiteral(ast::ExprBytesLiteral { value, .. })
if value.is_implicit_concatenated() =>
{
if !is_f_string_formatting_enabled(self.context) {
self.update_max_precedence(OperatorPrecedence::String);
}
return;
}
Expr::FString(ast::ExprFString { value, .. }) if value.is_implicit_concatenated() => {
if !is_f_string_formatting_enabled(self.context) {
self.update_max_precedence(OperatorPrecedence::String);
}
return;
}
// Non terminal nodes that don't have a termination token. // Non terminal nodes that don't have a termination token.
Expr::Named(_) | Expr::Generator(_) | Expr::Tuple(_) => {} Expr::Named(_) | Expr::Generator(_) | Expr::Tuple(_) => {}
@ -1193,8 +1150,6 @@ enum OperatorPrecedence {
BitwiseXor, BitwiseXor,
BitwiseOr, BitwiseOr,
Comparator, Comparator,
// Implicit string concatenation
String,
BooleanOperation, BooleanOperation,
Conditional, Conditional,
} }

View file

@ -2935,6 +2935,34 @@ impl<'ast> IntoFormat<PyFormatContext<'ast>> for ast::TypeParamParamSpec {
} }
} }
impl FormatRule<ast::FString, PyFormatContext<'_>> for crate::other::f_string::FormatFString {
#[inline]
fn fmt(&self, node: &ast::FString, f: &mut PyFormatter) -> FormatResult<()> {
FormatNodeRule::<ast::FString>::fmt(self, node, f)
}
}
impl<'ast> AsFormat<PyFormatContext<'ast>> for ast::FString {
type Format<'a> = FormatRefWithRule<
'a,
ast::FString,
crate::other::f_string::FormatFString,
PyFormatContext<'ast>,
>;
fn format(&self) -> Self::Format<'_> {
FormatRefWithRule::new(self, crate::other::f_string::FormatFString::default())
}
}
impl<'ast> IntoFormat<PyFormatContext<'ast>> for ast::FString {
type Format = FormatOwnedWithRule<
ast::FString,
crate::other::f_string::FormatFString,
PyFormatContext<'ast>,
>;
fn into_format(self) -> Self::Format {
FormatOwnedWithRule::new(self, crate::other::f_string::FormatFString::default())
}
}
impl FormatRule<ast::StringLiteral, PyFormatContext<'_>> impl FormatRule<ast::StringLiteral, PyFormatContext<'_>>
for crate::other::string_literal::FormatStringLiteral for crate::other::string_literal::FormatStringLiteral
{ {

View file

@ -7,7 +7,6 @@ use crate::comments::{leading_comments, trailing_comments};
use crate::expression::expr_tuple::TupleParentheses; use crate::expression::expr_tuple::TupleParentheses;
use crate::expression::parentheses::is_expression_parenthesized; use crate::expression::parentheses::is_expression_parenthesized;
use crate::prelude::*; use crate::prelude::*;
use crate::preview::is_comprehension_leading_expression_comments_same_line_enabled;
#[derive(Default)] #[derive(Default)]
pub struct FormatComprehension; pub struct FormatComprehension;
@ -36,14 +35,12 @@ impl FormatNodeRule<Comprehension> for FormatComprehension {
// ) // )
// ] // ]
// ``` // ```
let will_be_parenthesized = let will_be_parenthesized = self.preserve_parentheses
is_comprehension_leading_expression_comments_same_line_enabled(f.context()) && is_expression_parenthesized(
&& self.preserve_parentheses self.expression.into(),
&& is_expression_parenthesized( f.context().comments().ranges(),
self.expression.into(), f.context().source(),
f.context().comments().ranges(), );
f.context().source(),
);
if has_leading_comments && !will_be_parenthesized { if has_leading_comments && !will_be_parenthesized {
soft_line_break_or_space().fmt(f) soft_line_break_or_space().fmt(f)

View file

@ -4,8 +4,7 @@ use ruff_source_file::LineRanges;
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::prelude::*; use crate::prelude::*;
use crate::preview::is_f_string_formatting_enabled; use crate::string::{StringNormalizer, StringQuotes};
use crate::string::{Quoting, StringNormalizer, StringQuotes};
use super::f_string_element::FormatFStringElement; use super::f_string_element::FormatFStringElement;
@ -13,66 +12,25 @@ use super::f_string_element::FormatFStringElement;
/// ///
/// For example, this would be used to format the f-string part in `"foo" f"bar {x}"` /// For example, this would be used to format the f-string part in `"foo" f"bar {x}"`
/// or the standalone f-string in `f"foo {x} bar"`. /// or the standalone f-string in `f"foo {x} bar"`.
pub(crate) struct FormatFString<'a> { #[derive(Default)]
value: &'a FString, pub struct FormatFString;
/// The quoting of an f-string. This is determined by the parent node
/// (f-string expression) and is required to format an f-string correctly.
quoting: Quoting,
}
impl<'a> FormatFString<'a> { impl FormatNodeRule<FString> for FormatFString {
pub(crate) fn new(value: &'a FString, quoting: Quoting) -> Self { fn fmt_fields(&self, item: &FString, f: &mut PyFormatter) -> FormatResult<()> {
Self { value, quoting } let normalizer = StringNormalizer::from_context(f.context());
}
}
impl Format<PyFormatContext<'_>> for FormatFString<'_> { let string_kind = normalizer.choose_quotes(item.into()).flags();
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
// If the preview style is enabled, make the decision on what quotes to use locally for each
// f-string instead of globally for the entire f-string expression.
let quoting = if is_f_string_formatting_enabled(f.context()) {
Quoting::CanChange
} else {
self.quoting
};
let normalizer = StringNormalizer::from_context(f.context()).with_quoting(quoting);
// If f-string formatting is disabled (not in preview), then we will
// fall back to the previous behavior of normalizing the f-string.
if !is_f_string_formatting_enabled(f.context()) {
let result = normalizer.normalize(self.value.into()).fmt(f);
let comments = f.context().comments();
self.value.elements.iter().for_each(|value| {
comments.mark_verbatim_node_comments_formatted(value.into());
// Above method doesn't mark the trailing comments of the f-string elements
// as formatted, so we need to do it manually. For example,
//
// ```python
// f"""foo {
// x:.3f
// # comment
// }"""
// ```
for trailing_comment in comments.trailing(value) {
trailing_comment.mark_formatted();
}
});
return result;
}
let string_kind = normalizer.choose_quotes(self.value.into()).flags();
let context = FStringContext::new( let context = FStringContext::new(
string_kind, string_kind,
FStringLayout::from_f_string(self.value, f.context().source()), FStringLayout::from_f_string(item, f.context().source()),
); );
// Starting prefix and quote // Starting prefix and quote
let quotes = StringQuotes::from(string_kind); let quotes = StringQuotes::from(string_kind);
write!(f, [string_kind.prefix(), quotes])?; write!(f, [string_kind.prefix(), quotes])?;
for element in &self.value.elements { for element in &item.elements {
FormatFStringElement::new(element, context).fmt(f)?; FormatFStringElement::new(element, context).fmt(f)?;
} }

View file

@ -61,8 +61,7 @@ impl<'a> FormatFStringLiteralElement<'a> {
impl Format<PyFormatContext<'_>> for FormatFStringLiteralElement<'_> { impl Format<PyFormatContext<'_>> for FormatFStringLiteralElement<'_> {
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> { fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
let literal_content = f.context().source().slice(self.element); let literal_content = f.context().source().slice(self.element);
let normalized = let normalized = normalize_string(literal_content, 0, self.fstring_flags, false);
normalize_string(literal_content, 0, self.fstring_flags, false, false, true);
match &normalized { match &normalized {
Cow::Borrowed(_) => source_text_slice(self.element.range()).fmt(f), Cow::Borrowed(_) => source_text_slice(self.element.range()).fmt(f),
Cow::Owned(normalized) => text(normalized).fmt(f), Cow::Owned(normalized) => text(normalized).fmt(f),

View file

@ -1,15 +1,10 @@
use ruff_formatter::{write, FormatRuleWithOptions}; use ruff_formatter::{format_args, write, FormatRuleWithOptions};
use ruff_python_ast::AstNode;
use ruff_python_ast::MatchCase; use ruff_python_ast::MatchCase;
use crate::builders::parenthesize_if_expands;
use crate::expression::maybe_parenthesize_expression; use crate::expression::maybe_parenthesize_expression;
use crate::expression::parentheses::{ use crate::expression::parentheses::Parenthesize;
NeedsParentheses, OptionalParentheses, Parentheses, Parenthesize,
};
use crate::pattern::maybe_parenthesize_pattern; use crate::pattern::maybe_parenthesize_pattern;
use crate::prelude::*; use crate::prelude::*;
use crate::preview::is_match_case_parentheses_enabled;
use crate::statement::clause::{clause_body, clause_header, ClauseHeader}; use crate::statement::clause::{clause_body, clause_header, ClauseHeader};
use crate::statement::suite::SuiteKind; use crate::statement::suite::SuiteKind;
@ -39,42 +34,12 @@ impl FormatNodeRule<MatchCase> for FormatMatchCase {
let comments = f.context().comments().clone(); let comments = f.context().comments().clone();
let dangling_item_comments = comments.dangling(item); let dangling_item_comments = comments.dangling(item);
let format_pattern = format_with(|f| {
if is_match_case_parentheses_enabled(f.context()) {
maybe_parenthesize_pattern(pattern, item).fmt(f)
} else {
let has_comments =
comments.has_leading(pattern) || comments.has_trailing_own_line(pattern);
if has_comments {
pattern.format().with_options(Parentheses::Always).fmt(f)
} else {
match pattern.needs_parentheses(item.as_any_node_ref(), f.context()) {
OptionalParentheses::Multiline => parenthesize_if_expands(
&pattern.format().with_options(Parentheses::Never),
)
.fmt(f),
OptionalParentheses::Always => {
pattern.format().with_options(Parentheses::Always).fmt(f)
}
OptionalParentheses::Never | OptionalParentheses::BestFit => {
pattern.format().with_options(Parentheses::Never).fmt(f)
}
}
}
}
});
let format_guard = guard.as_deref().map(|guard| { let format_guard = guard.as_deref().map(|guard| {
format_with(|f| { format_with(|f| {
write!(f, [space(), token("if"), space()])?; write!(f, [space(), token("if"), space()])?;
if is_match_case_parentheses_enabled(f.context()) { maybe_parenthesize_expression(guard, item, Parenthesize::IfBreaksParenthesized)
maybe_parenthesize_expression(guard, item, Parenthesize::IfBreaksParenthesized) .fmt(f)
.fmt(f)
} else {
guard.format().fmt(f)
}
}) })
}); });
@ -84,9 +49,12 @@ impl FormatNodeRule<MatchCase> for FormatMatchCase {
clause_header( clause_header(
ClauseHeader::MatchCase(item), ClauseHeader::MatchCase(item),
dangling_item_comments, dangling_item_comments,
&format_with(|f| { &format_args![
write!(f, [token("case"), space(), format_pattern, format_guard]) token("case"),
}), space(),
maybe_parenthesize_pattern(pattern, item),
format_guard
],
), ),
clause_body( clause_body(
body, body,

View file

@ -2,8 +2,7 @@ use ruff_formatter::FormatRuleWithOptions;
use ruff_python_ast::StringLiteral; use ruff_python_ast::StringLiteral;
use crate::prelude::*; use crate::prelude::*;
use crate::preview::is_f_string_implicit_concatenated_string_literal_quotes_enabled; use crate::string::{docstring, StringNormalizer};
use crate::string::{docstring, Quoting, StringNormalizer};
use crate::QuoteStyle; use crate::QuoteStyle;
#[derive(Default)] #[derive(Default)]
@ -28,12 +27,6 @@ pub enum StringLiteralKind {
String, String,
/// A string literal used as a docstring. /// A string literal used as a docstring.
Docstring, Docstring,
/// A string literal that is implicitly concatenated with an f-string. This
/// makes the overall expression an f-string whose quoting detection comes
/// from the parent node (f-string expression).
#[deprecated]
#[allow(private_interfaces)]
InImplicitlyConcatenatedFString(Quoting),
} }
impl StringLiteralKind { impl StringLiteralKind {
@ -41,26 +34,6 @@ impl StringLiteralKind {
pub(crate) const fn is_docstring(self) -> bool { pub(crate) const fn is_docstring(self) -> bool {
matches!(self, StringLiteralKind::Docstring) matches!(self, StringLiteralKind::Docstring)
} }
/// Returns the quoting to be used for this string literal.
fn quoting(self, context: &PyFormatContext) -> Quoting {
match self {
StringLiteralKind::String | StringLiteralKind::Docstring => Quoting::CanChange,
#[allow(deprecated)]
StringLiteralKind::InImplicitlyConcatenatedFString(quoting) => {
// Allow string literals to pick the "optimal" quote character
// even if any other fstring in the implicit concatenation uses an expression
// containing a quote character.
// TODO: Remove StringLiteralKind::InImplicitlyConcatenatedFString when promoting
// this style to stable and remove the layout from `AnyStringPart::String`.
if is_f_string_implicit_concatenated_string_literal_quotes_enabled(context) {
Quoting::CanChange
} else {
quoting
}
}
}
}
} }
impl FormatNodeRule<StringLiteral> for FormatStringLiteral { impl FormatNodeRule<StringLiteral> for FormatStringLiteral {
@ -75,7 +48,6 @@ impl FormatNodeRule<StringLiteral> for FormatStringLiteral {
}; };
let normalized = StringNormalizer::from_context(f.context()) let normalized = StringNormalizer::from_context(f.context())
.with_quoting(self.layout.quoting(f.context()))
.with_preferred_quote_style(quote_style) .with_preferred_quote_style(quote_style)
.normalize(item.into()); .normalize(item.into());

View file

@ -6,9 +6,7 @@ use crate::expression::parentheses::{
is_expression_parenthesized, parenthesized, Parentheses, Parenthesize, is_expression_parenthesized, parenthesized, Parentheses, Parenthesize,
}; };
use crate::prelude::*; use crate::prelude::*;
use crate::preview::{ use crate::preview::is_with_single_target_parentheses_enabled;
is_with_single_item_pre_39_enabled, is_with_single_target_parentheses_enabled,
};
#[derive(Copy, Clone, Debug, Eq, PartialEq)] #[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum WithItemLayout { pub enum WithItemLayout {
@ -154,9 +152,7 @@ impl FormatNodeRule<WithItem> for FormatWithItem {
} }
WithItemLayout::Python38OrOlder { single } => { WithItemLayout::Python38OrOlder { single } => {
let parenthesize = if (single && is_with_single_item_pre_39_enabled(f.context())) let parenthesize = if single || is_parenthesized {
|| is_parenthesized
{
Parenthesize::IfBreaks Parenthesize::IfBreaks
} else { } else {
Parenthesize::IfRequired Parenthesize::IfRequired

View file

@ -14,7 +14,6 @@ use crate::expression::parentheses::{
optional_parentheses, parenthesized, NeedsParentheses, OptionalParentheses, Parentheses, optional_parentheses, parenthesized, NeedsParentheses, OptionalParentheses, Parentheses,
}; };
use crate::prelude::*; use crate::prelude::*;
use crate::preview::is_join_implicit_concatenated_string_enabled;
pub(crate) mod pattern_arguments; pub(crate) mod pattern_arguments;
pub(crate) mod pattern_keyword; pub(crate) mod pattern_keyword;
@ -227,7 +226,7 @@ pub(crate) fn can_pattern_omit_optional_parentheses(
pattern: &Pattern, pattern: &Pattern,
context: &PyFormatContext, context: &PyFormatContext,
) -> bool { ) -> bool {
let mut visitor = CanOmitOptionalParenthesesVisitor::new(context); let mut visitor = CanOmitOptionalParenthesesVisitor::default();
visitor.visit_pattern(pattern, context); visitor.visit_pattern(pattern, context);
if !visitor.any_parenthesized_expressions { if !visitor.any_parenthesized_expressions {
@ -272,32 +271,16 @@ pub(crate) fn can_pattern_omit_optional_parentheses(
} }
} }
#[derive(Debug)] #[derive(Debug, Default)]
struct CanOmitOptionalParenthesesVisitor<'input> { struct CanOmitOptionalParenthesesVisitor<'input> {
max_precedence: OperatorPrecedence, max_precedence: OperatorPrecedence,
max_precedence_count: usize, max_precedence_count: usize,
any_parenthesized_expressions: bool, any_parenthesized_expressions: bool,
join_implicit_concatenated_strings: bool,
last: Option<&'input Pattern>, last: Option<&'input Pattern>,
first: First<'input>, first: First<'input>,
} }
impl<'a> CanOmitOptionalParenthesesVisitor<'a> { impl<'a> CanOmitOptionalParenthesesVisitor<'a> {
fn new(context: &PyFormatContext) -> Self {
Self {
max_precedence: OperatorPrecedence::default(),
max_precedence_count: 0,
any_parenthesized_expressions: false,
// TODO: Derive default for `CanOmitOptionalParenthesesVisitor` when removing the `join_implicit_concatenated_strings`
// preview style.
join_implicit_concatenated_strings: is_join_implicit_concatenated_string_enabled(
context,
),
last: None,
first: First::default(),
}
}
fn visit_pattern(&mut self, pattern: &'a Pattern, context: &PyFormatContext) { fn visit_pattern(&mut self, pattern: &'a Pattern, context: &PyFormatContext) {
match pattern { match pattern {
Pattern::MatchSequence(_) | Pattern::MatchMapping(_) => { Pattern::MatchSequence(_) | Pattern::MatchMapping(_) => {
@ -305,27 +288,11 @@ impl<'a> CanOmitOptionalParenthesesVisitor<'a> {
} }
Pattern::MatchValue(value) => match &*value.value { Pattern::MatchValue(value) => match &*value.value {
Expr::StringLiteral(string) => { Expr::StringLiteral(_) |
if !self.join_implicit_concatenated_strings { Expr::BytesLiteral(_) |
self.update_max_precedence(OperatorPrecedence::String, string.value.len());
}
}
Expr::BytesLiteral(bytes) => {
if !self.join_implicit_concatenated_strings {
self.update_max_precedence(OperatorPrecedence::String, bytes.value.len());
}
}
// F-strings are allowed according to python's grammar but fail with a syntax error at runtime. // F-strings are allowed according to python's grammar but fail with a syntax error at runtime.
// That's why we need to support them for formatting. // That's why we need to support them for formatting.
Expr::FString(string) => { Expr::FString(_) |
if !self.join_implicit_concatenated_strings {
self.update_max_precedence(
OperatorPrecedence::String,
string.value.as_slice().len(),
);
}
}
Expr::NumberLiteral(_) | Expr::Attribute(_) | Expr::UnaryOp(_) => { Expr::NumberLiteral(_) | Expr::Attribute(_) | Expr::UnaryOp(_) => {
// require no state update other than visit_pattern does. // require no state update other than visit_pattern does.
} }
@ -397,8 +364,6 @@ enum OperatorPrecedence {
None, None,
Additive, Additive,
Or, Or,
// Implicit string concatenation
String,
} }
#[derive(Copy, Clone, Debug, Default)] #[derive(Copy, Clone, Debug, Default)]

View file

@ -5,7 +5,6 @@ use ruff_python_ast::PatternMatchAs;
use crate::comments::dangling_comments; use crate::comments::dangling_comments;
use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses}; use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses};
use crate::prelude::*; use crate::prelude::*;
use crate::preview::is_match_case_parentheses_enabled;
#[derive(Default)] #[derive(Default)]
pub struct FormatPatternMatchAs; pub struct FormatPatternMatchAs;
@ -55,16 +54,12 @@ impl NeedsParentheses for PatternMatchAs {
fn needs_parentheses( fn needs_parentheses(
&self, &self,
_parent: AnyNodeRef, _parent: AnyNodeRef,
context: &PyFormatContext, _context: &PyFormatContext,
) -> OptionalParentheses { ) -> OptionalParentheses {
if is_match_case_parentheses_enabled(context) { if self.name.is_some() {
if self.name.is_some() {
OptionalParentheses::Multiline
} else {
OptionalParentheses::BestFit
}
} else {
OptionalParentheses::Multiline OptionalParentheses::Multiline
} else {
OptionalParentheses::BestFit
} }
} }
} }

View file

@ -8,7 +8,6 @@ use crate::expression::parentheses::{
OptionalParentheses, OptionalParentheses,
}; };
use crate::prelude::*; use crate::prelude::*;
use crate::preview::is_match_case_parentheses_enabled;
#[derive(Default)] #[derive(Default)]
pub struct FormatPatternMatchOr; pub struct FormatPatternMatchOr;
@ -43,11 +42,7 @@ impl FormatNodeRule<PatternMatchOr> for FormatPatternMatchOr {
Ok(()) Ok(())
}); });
if is_match_case_parentheses_enabled(f.context()) { in_parentheses_only_group(&inner).fmt(f)
in_parentheses_only_group(&inner).fmt(f)
} else {
inner.fmt(f)
}
} }
} }

View file

@ -3,7 +3,6 @@ use ruff_python_ast::{PatternMatchSingleton, Singleton};
use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses}; use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses};
use crate::prelude::*; use crate::prelude::*;
use crate::preview::is_match_case_parentheses_enabled;
#[derive(Default)] #[derive(Default)]
pub struct FormatPatternMatchSingleton; pub struct FormatPatternMatchSingleton;
@ -22,12 +21,8 @@ impl NeedsParentheses for PatternMatchSingleton {
fn needs_parentheses( fn needs_parentheses(
&self, &self,
_parent: AnyNodeRef, _parent: AnyNodeRef,
context: &PyFormatContext, _context: &PyFormatContext,
) -> OptionalParentheses { ) -> OptionalParentheses {
if is_match_case_parentheses_enabled(context) { OptionalParentheses::BestFit
OptionalParentheses::BestFit
} else {
OptionalParentheses::Never
}
} }
} }

View file

@ -3,7 +3,6 @@ use ruff_python_ast::PatternMatchValue;
use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses, Parentheses}; use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses, Parentheses};
use crate::prelude::*; use crate::prelude::*;
use crate::preview::is_match_case_parentheses_enabled;
#[derive(Default)] #[derive(Default)]
pub struct FormatPatternMatchValue; pub struct FormatPatternMatchValue;
@ -21,10 +20,6 @@ impl NeedsParentheses for PatternMatchValue {
parent: AnyNodeRef, parent: AnyNodeRef,
context: &PyFormatContext, context: &PyFormatContext,
) -> OptionalParentheses { ) -> OptionalParentheses {
if is_match_case_parentheses_enabled(context) { self.value.needs_parentheses(parent, context)
self.value.needs_parentheses(parent, context)
} else {
OptionalParentheses::Never
}
} }
} }

View file

@ -14,65 +14,6 @@ pub(crate) const fn is_hug_parens_with_braces_and_square_brackets_enabled(
context.is_preview() context.is_preview()
} }
/// Returns `true` if the [`f-string formatting`](https://github.com/astral-sh/ruff/issues/7594) preview style is enabled.
/// WARNING: This preview style depends on `is_f_string_implicit_concatenated_string_literal_quotes_enabled`.
/// TODO: Remove `Quoting` when promoting this preview style and convert `FormatStringPart` etc. regular `FormatWithRule` implementations.
/// TODO: Remove `format_f_string` from `normalize_string` when promoting this preview style.
pub(crate) fn is_f_string_formatting_enabled(context: &PyFormatContext) -> bool {
context.is_preview()
}
/// See [#13539](https://github.com/astral-sh/ruff/pull/13539)
/// Remove `Quoting` when stabilizing this preview style.
pub(crate) fn is_f_string_implicit_concatenated_string_literal_quotes_enabled(
context: &PyFormatContext,
) -> bool {
context.is_preview()
}
pub(crate) fn is_with_single_item_pre_39_enabled(context: &PyFormatContext) -> bool {
context.is_preview()
}
/// See [#12282](https://github.com/astral-sh/ruff/pull/12282).
pub(crate) fn is_comprehension_leading_expression_comments_same_line_enabled(
context: &PyFormatContext,
) -> bool {
context.is_preview()
}
/// See [#9447](https://github.com/astral-sh/ruff/issues/9447)
pub(crate) fn is_empty_parameters_no_unnecessary_parentheses_around_return_value_enabled(
context: &PyFormatContext,
) -> bool {
context.is_preview()
}
/// See [#6933](https://github.com/astral-sh/ruff/issues/6933).
/// This style also covers the black preview styles `remove_redundant_guard_parens` and `parens_for_long_if_clauses_in_case_block `.
/// WARNING: This preview style depends on `is_empty_parameters_no_unnecessary_parentheses_around_return_value_enabled`
/// because it relies on the new semantic of `IfBreaksParenthesized`.
pub(crate) fn is_match_case_parentheses_enabled(context: &PyFormatContext) -> bool {
context.is_preview()
}
/// This preview style fixes a bug with the docstring's `line-length` calculation when using the `dynamic` mode.
/// The new style now respects the indent **inside** the docstring and reduces the `line-length` accordingly
/// so that the docstring's code block fits into the global line-length setting.
pub(crate) fn is_docstring_code_block_in_docstring_indent_enabled(
context: &PyFormatContext,
) -> bool {
context.is_preview()
}
/// Returns `true` if implicitly concatenated strings should be joined if they all fit on a single line.
/// See [#9457](https://github.com/astral-sh/ruff/issues/9457)
/// WARNING: This preview style depends on `is_empty_parameters_no_unnecessary_parentheses_around_return_value_enabled`
/// because it relies on the new semantic of `IfBreaksParenthesized`.
pub(crate) fn is_join_implicit_concatenated_string_enabled(context: &PyFormatContext) -> bool {
context.is_preview()
}
/// Returns `true` if the bugfix for single-with items with a trailing comment targeting Python 3.9 or newer is enabled. /// Returns `true` if the bugfix for single-with items with a trailing comment targeting Python 3.9 or newer is enabled.
/// ///
/// See [#14001](https://github.com/astral-sh/ruff/issues/14001) /// See [#14001](https://github.com/astral-sh/ruff/issues/14001)

View file

@ -3,10 +3,8 @@ use ruff_formatter::write;
use ruff_python_ast::StmtAssert; use ruff_python_ast::StmtAssert;
use crate::comments::SourceComment; use crate::comments::SourceComment;
use crate::expression::maybe_parenthesize_expression; use crate::expression::maybe_parenthesize_expression;
use crate::expression::parentheses::Parenthesize; use crate::expression::parentheses::Parenthesize;
use crate::preview::is_join_implicit_concatenated_string_enabled;
use crate::{has_skip_comment, prelude::*}; use crate::{has_skip_comment, prelude::*};
#[derive(Default)] #[derive(Default)]
@ -30,18 +28,12 @@ impl FormatNodeRule<StmtAssert> for FormatStmtAssert {
)?; )?;
if let Some(msg) = msg { if let Some(msg) = msg {
let parenthesize = if is_join_implicit_concatenated_string_enabled(f.context()) {
Parenthesize::IfBreaksParenthesized
} else {
Parenthesize::IfBreaks
};
write!( write!(
f, f,
[ [
token(","), token(","),
space(), space(),
maybe_parenthesize_expression(msg, item, parenthesize), maybe_parenthesize_expression(msg, item, Parenthesize::IfBreaksParenthesized),
] ]
)?; )?;
} }

View file

@ -1,7 +1,7 @@
use ruff_formatter::{format_args, write, FormatError, RemoveSoftLinesBuffer}; use ruff_formatter::{format_args, write, FormatError, RemoveSoftLinesBuffer};
use ruff_python_ast::{ use ruff_python_ast::{
AnyNodeRef, Expr, ExprAttribute, ExprCall, FStringPart, Operator, StmtAssign, StringLike, AnyNodeRef, Expr, ExprAttribute, ExprCall, FString, FStringPart, Operator, StmtAssign,
TypeParams, StringLike, TypeParams,
}; };
use crate::builders::parenthesize_if_expands; use crate::builders::parenthesize_if_expands;
@ -9,7 +9,6 @@ use crate::comments::{
trailing_comments, Comments, LeadingDanglingTrailingComments, SourceComment, trailing_comments, Comments, LeadingDanglingTrailingComments, SourceComment,
}; };
use crate::context::{NodeLevel, WithNodeLevel}; use crate::context::{NodeLevel, WithNodeLevel};
use crate::expression::expr_f_string::f_string_quoting;
use crate::expression::parentheses::{ use crate::expression::parentheses::{
is_expression_parenthesized, optional_parentheses, NeedsParentheses, OptionalParentheses, is_expression_parenthesized, optional_parentheses, NeedsParentheses, OptionalParentheses,
Parentheses, Parenthesize, Parentheses, Parenthesize,
@ -18,10 +17,7 @@ use crate::expression::{
can_omit_optional_parentheses, has_own_parentheses, has_parentheses, can_omit_optional_parentheses, has_own_parentheses, has_parentheses,
maybe_parenthesize_expression, maybe_parenthesize_expression,
}; };
use crate::other::f_string::{FStringLayout, FormatFString}; use crate::other::f_string::FStringLayout;
use crate::preview::{
is_f_string_formatting_enabled, is_join_implicit_concatenated_string_enabled,
};
use crate::statement::trailing_semicolon; use crate::statement::trailing_semicolon;
use crate::string::implicit::{ use crate::string::implicit::{
FormatImplicitConcatenatedStringExpanded, FormatImplicitConcatenatedStringFlat, FormatImplicitConcatenatedStringExpanded, FormatImplicitConcatenatedStringFlat,
@ -456,7 +452,7 @@ impl Format<PyFormatContext<'_>> for FormatStatementsLastExpression<'_> {
let f_string_flat = format_with(|f| { let f_string_flat = format_with(|f| {
let mut buffer = RemoveSoftLinesBuffer::new(&mut *f); let mut buffer = RemoveSoftLinesBuffer::new(&mut *f);
write!(buffer, [format_f_string]) write!(buffer, [format_f_string.format()])
}) })
.memoized(); .memoized();
@ -518,7 +514,7 @@ impl Format<PyFormatContext<'_>> for FormatStatementsLastExpression<'_> {
// }moreeeeeeeeeeeeeeeee" // }moreeeeeeeeeeeeeeeee"
// ``` // ```
let format_f_string = let format_f_string =
format_with(|f| write!(f, [format_f_string, inline_comments])); format_with(|f| write!(f, [format_f_string.format(), inline_comments]));
best_fitting![single_line, joined_parenthesized, format_f_string] best_fitting![single_line, joined_parenthesized, format_f_string]
.with_mode(BestFittingMode::AllLines) .with_mode(BestFittingMode::AllLines)
@ -668,7 +664,7 @@ impl Format<PyFormatContext<'_>> for FormatStatementsLastExpression<'_> {
// Similar to above, remove any soft line breaks emitted by the f-string // Similar to above, remove any soft line breaks emitted by the f-string
// formatting. // formatting.
let mut buffer = RemoveSoftLinesBuffer::new(&mut *f); let mut buffer = RemoveSoftLinesBuffer::new(&mut *f);
write!(buffer, [format_f_string]) write!(buffer, [format_f_string.format()])
} else { } else {
value.format().with_options(Parentheses::Never).fmt(f) value.format().with_options(Parentheses::Never).fmt(f)
} }
@ -701,20 +697,22 @@ impl Format<PyFormatContext<'_>> for FormatStatementsLastExpression<'_> {
// ) // )
// ``` // ```
let flat_target_parenthesize_value = format_with(|f| { let flat_target_parenthesize_value = format_with(|f| {
write!(f, [last_target, space(), operator, space(), token("("),])?; write!(
f,
if is_join_implicit_concatenated_string_enabled(f.context()) { [
group(&soft_block_indent(&format_args![ last_target,
format_value, space(),
inline_comments operator,
])) space(),
.should_expand(true) token("("),
.fmt(f)?; group(&soft_block_indent(&format_args![
} else { format_value,
block_indent(&format_args![format_value, inline_comments]).fmt(f)?; inline_comments
} ]))
.should_expand(true),
token(")").fmt(f) token(")")
]
)
}); });
// Fall back to parenthesizing (or splitting) the last target part if we can't make the value // Fall back to parenthesizing (or splitting) the last target part if we can't make the value
@ -726,15 +724,16 @@ impl Format<PyFormatContext<'_>> for FormatStatementsLastExpression<'_> {
// ] = c // ] = c
// ``` // ```
let split_target_flat_value = format_with(|f| { let split_target_flat_value = format_with(|f| {
if is_join_implicit_concatenated_string_enabled(f.context()) {
group(&last_target).should_expand(true).fmt(f)?;
} else {
last_target.fmt(f)?;
}
write!( write!(
f, f,
[space(), operator, space(), format_value, inline_comments] [
group(&last_target).should_expand(true),
space(),
operator,
space(),
format_value,
inline_comments
]
) )
}); });
@ -935,7 +934,8 @@ impl Format<PyFormatContext<'_>> for FormatStatementsLastExpression<'_> {
} }
let format_f_string = let format_f_string =
format_with(|f| write!(f, [format_f_string, inline_comments])).memoized(); format_with(|f| write!(f, [format_f_string.format(), inline_comments]))
.memoized();
// Considering the following initial source: // Considering the following initial source:
// //
@ -1102,11 +1102,7 @@ impl Format<PyFormatContext<'_>> for FormatStatementsLastExpression<'_> {
fn format_f_string_assignment<'a>( fn format_f_string_assignment<'a>(
string: StringLike<'a>, string: StringLike<'a>,
context: &PyFormatContext, context: &PyFormatContext,
) -> Option<FormatFString<'a>> { ) -> Option<&'a FString> {
if !is_f_string_formatting_enabled(context) {
return None;
}
let StringLike::FString(expr) = string else { let StringLike::FString(expr) = string else {
return None; return None;
}; };
@ -1128,10 +1124,7 @@ fn format_f_string_assignment<'a>(
return None; return None;
} }
Some(FormatFString::new( Some(f_string)
f_string,
f_string_quoting(expr, context.source()),
))
} }
#[derive(Debug, Default)] #[derive(Debug, Default)]

View file

@ -18,10 +18,6 @@ use {
ruff_text_size::{Ranged, TextLen, TextRange, TextSize}, ruff_text_size::{Ranged, TextLen, TextRange, TextSize},
}; };
use crate::preview::{
is_docstring_code_block_in_docstring_indent_enabled,
is_join_implicit_concatenated_string_enabled,
};
use crate::string::StringQuotes; use crate::string::StringQuotes;
use crate::{prelude::*, DocstringCodeLineWidth, FormatModuleError}; use crate::{prelude::*, DocstringCodeLineWidth, FormatModuleError};
@ -171,7 +167,7 @@ pub(crate) fn format(normalized: &NormalizedString, f: &mut PyFormatter) -> Form
if docstring[first.len()..].trim().is_empty() { if docstring[first.len()..].trim().is_empty() {
// For `"""\n"""` or other whitespace between the quotes, black keeps a single whitespace, // For `"""\n"""` or other whitespace between the quotes, black keeps a single whitespace,
// but `""""""` doesn't get one inserted. // but `""""""` doesn't get one inserted.
if needs_chaperone_space(normalized.flags(), trim_end, f.context()) if needs_chaperone_space(normalized.flags(), trim_end)
|| (trim_end.is_empty() && !docstring.is_empty()) || (trim_end.is_empty() && !docstring.is_empty())
{ {
space().fmt(f)?; space().fmt(f)?;
@ -211,7 +207,7 @@ pub(crate) fn format(normalized: &NormalizedString, f: &mut PyFormatter) -> Form
let trim_end = docstring let trim_end = docstring
.as_ref() .as_ref()
.trim_end_matches(|c: char| c.is_whitespace() && c != '\n'); .trim_end_matches(|c: char| c.is_whitespace() && c != '\n');
if needs_chaperone_space(normalized.flags(), trim_end, f.context()) { if needs_chaperone_space(normalized.flags(), trim_end) {
space().fmt(f)?; space().fmt(f)?;
} }
@ -508,17 +504,15 @@ impl<'src> DocstringLinePrinter<'_, '_, '_, 'src> {
.to_ascii_spaces(indent_width) .to_ascii_spaces(indent_width)
.saturating_add(kind.extra_indent_ascii_spaces()); .saturating_add(kind.extra_indent_ascii_spaces());
if is_docstring_code_block_in_docstring_indent_enabled(self.f.context()) { // Add the in-docstring indentation
// Add the in-docstring indentation current_indent = current_indent.saturating_add(
current_indent = current_indent.saturating_add( u16::try_from(
u16::try_from( kind.indent()
kind.indent() .columns()
.columns() .saturating_sub(self.stripped_indentation.columns()),
.saturating_sub(self.stripped_indentation.columns()), )
) .unwrap_or(u16::MAX),
.unwrap_or(u16::MAX), );
);
}
let width = std::cmp::max(1, global_line_width.saturating_sub(current_indent)); let width = std::cmp::max(1, global_line_width.saturating_sub(current_indent));
LineWidth::try_from(width).expect("width should be capped at a minimum of 1") LineWidth::try_from(width).expect("width should be capped at a minimum of 1")
@ -1607,17 +1601,11 @@ fn docstring_format_source(
/// If the last line of the docstring is `content" """` or `content\ """`, we need a chaperone space /// If the last line of the docstring is `content" """` or `content\ """`, we need a chaperone space
/// that avoids `content""""` and `content\"""`. This does only applies to un-escaped backslashes, /// that avoids `content""""` and `content\"""`. This does only applies to un-escaped backslashes,
/// so `content\\ """` doesn't need a space while `content\\\ """` does. /// so `content\\ """` doesn't need a space while `content\\\ """` does.
pub(super) fn needs_chaperone_space( pub(super) fn needs_chaperone_space(flags: AnyStringFlags, trim_end: &str) -> bool {
flags: AnyStringFlags,
trim_end: &str,
context: &PyFormatContext,
) -> bool {
if trim_end.chars().rev().take_while(|c| *c == '\\').count() % 2 == 1 { if trim_end.chars().rev().take_while(|c| *c == '\\').count() % 2 == 1 {
true true
} else if is_join_implicit_concatenated_string_enabled(context) {
flags.is_triple_quoted() && trim_end.ends_with(flags.quote_style().as_char())
} else { } else {
trim_end.ends_with(flags.quote_style().as_char()) flags.is_triple_quoted() && trim_end.ends_with(flags.quote_style().as_char())
} }
} }

View file

@ -11,13 +11,9 @@ use std::borrow::Cow;
use crate::comments::{leading_comments, trailing_comments}; use crate::comments::{leading_comments, trailing_comments};
use crate::expression::parentheses::in_parentheses_only_soft_line_break_or_space; use crate::expression::parentheses::in_parentheses_only_soft_line_break_or_space;
use crate::other::f_string::{FStringContext, FStringLayout, FormatFString}; use crate::other::f_string::{FStringContext, FStringLayout};
use crate::other::f_string_element::FormatFStringExpressionElement; use crate::other::f_string_element::FormatFStringExpressionElement;
use crate::other::string_literal::StringLiteralKind;
use crate::prelude::*; use crate::prelude::*;
use crate::preview::{
is_f_string_formatting_enabled, is_join_implicit_concatenated_string_enabled,
};
use crate::string::docstring::needs_chaperone_space; use crate::string::docstring::needs_chaperone_space;
use crate::string::normalize::{ use crate::string::normalize::{
is_fstring_with_quoted_debug_expression, is_fstring_with_quoted_format_spec_and_debug, is_fstring_with_quoted_debug_expression, is_fstring_with_quoted_format_spec_and_debug,
@ -82,14 +78,9 @@ impl<'a> FormatImplicitConcatenatedStringExpanded<'a> {
impl Format<PyFormatContext<'_>> for FormatImplicitConcatenatedStringExpanded<'_> { impl Format<PyFormatContext<'_>> for FormatImplicitConcatenatedStringExpanded<'_> {
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> { fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
let comments = f.context().comments().clone(); let comments = f.context().comments().clone();
let quoting = self.string.quoting(f.context().source());
let join_implicit_concatenated_string_enabled =
is_join_implicit_concatenated_string_enabled(f.context());
// Keep implicit concatenated strings expanded unless they're already written on a single line. // Keep implicit concatenated strings expanded unless they're already written on a single line.
if matches!(self.layout, ImplicitConcatenatedLayout::Multipart) if matches!(self.layout, ImplicitConcatenatedLayout::Multipart)
&& join_implicit_concatenated_string_enabled
&& self.string.parts().tuple_windows().any(|(a, b)| { && self.string.parts().tuple_windows().any(|(a, b)| {
f.context() f.context()
.source() .source()
@ -103,23 +94,13 @@ impl Format<PyFormatContext<'_>> for FormatImplicitConcatenatedStringExpanded<'_
for part in self.string.parts() { for part in self.string.parts() {
let format_part = format_with(|f: &mut PyFormatter| match part { let format_part = format_with(|f: &mut PyFormatter| match part {
StringLikePart::String(part) => { StringLikePart::String(part) => part.format().fmt(f),
let kind = if self.string.is_fstring() {
#[allow(deprecated)]
StringLiteralKind::InImplicitlyConcatenatedFString(quoting)
} else {
StringLiteralKind::String
};
part.format().with_options(kind).fmt(f)
}
StringLikePart::Bytes(bytes_literal) => bytes_literal.format().fmt(f), StringLikePart::Bytes(bytes_literal) => bytes_literal.format().fmt(f),
StringLikePart::FString(part) => FormatFString::new(part, quoting).fmt(f), StringLikePart::FString(part) => part.format().fmt(f),
}); });
let part_comments = comments.leading_dangling_trailing(&part); let part_comments = comments.leading_dangling_trailing(&part);
joiner.entry(&format_args![ joiner.entry(&format_args![
(!join_implicit_concatenated_string_enabled).then_some(line_suffix_boundary()),
leading_comments(part_comments.leading), leading_comments(part_comments.leading),
format_part, format_part,
trailing_comments(part_comments.trailing) trailing_comments(part_comments.trailing)
@ -149,10 +130,6 @@ impl<'a> FormatImplicitConcatenatedStringFlat<'a> {
/// Creates a new formatter. Returns `None` if the string can't be merged into a single string. /// Creates a new formatter. Returns `None` if the string can't be merged into a single string.
pub(crate) fn new(string: StringLike<'a>, context: &PyFormatContext) -> Option<Self> { pub(crate) fn new(string: StringLike<'a>, context: &PyFormatContext) -> Option<Self> {
fn merge_flags(string: StringLike, context: &PyFormatContext) -> Option<AnyStringFlags> { fn merge_flags(string: StringLike, context: &PyFormatContext) -> Option<AnyStringFlags> {
if !is_join_implicit_concatenated_string_enabled(context) {
return None;
}
// Multiline strings can never fit on a single line. // Multiline strings can never fit on a single line.
if string.is_multiline(context) { if string.is_multiline(context) {
return None; return None;
@ -323,44 +300,29 @@ impl Format<PyFormatContext<'_>> for FormatImplicitConcatenatedStringFlat<'_> {
} }
StringLikePart::FString(f_string) => { StringLikePart::FString(f_string) => {
if is_f_string_formatting_enabled(f.context()) { for element in &f_string.elements {
for element in &f_string.elements { match element {
match element { FStringElement::Literal(literal) => {
FStringElement::Literal(literal) => { FormatLiteralContent {
FormatLiteralContent { range: literal.range(),
range: literal.range(), flags: self.flags,
flags: self.flags, is_fstring: true,
is_fstring: true, trim_end: false,
trim_end: false, trim_start: false,
trim_start: false,
}
.fmt(f)?;
} }
// Formatting the expression here and in the expanded version is safe **only** .fmt(f)?;
// because we assert that the f-string never contains any comments. }
FStringElement::Expression(expression) => { // Formatting the expression here and in the expanded version is safe **only**
let context = FStringContext::new( // because we assert that the f-string never contains any comments.
self.flags, FStringElement::Expression(expression) => {
FStringLayout::from_f_string( let context = FStringContext::new(
f_string, self.flags,
f.context().source(), FStringLayout::from_f_string(f_string, f.context().source()),
), );
);
FormatFStringExpressionElement::new(expression, context) FormatFStringExpressionElement::new(expression, context).fmt(f)?;
.fmt(f)?;
}
} }
} }
} else {
FormatLiteralContent {
range: part.content_range(),
flags: self.flags,
is_fstring: true,
trim_end: false,
trim_start: false,
}
.fmt(f)?;
} }
} }
} }
@ -386,9 +348,6 @@ impl Format<PyFormatContext<'_>> for FormatLiteralContent {
0, 0,
self.flags, self.flags,
self.flags.is_f_string() && !self.is_fstring, self.flags.is_f_string() && !self.is_fstring,
// TODO: Remove the argument from `normalize_string` when promoting the `is_f_string_formatting_enabled` preview style.
self.flags.is_f_string() && !is_f_string_formatting_enabled(f.context()),
is_f_string_formatting_enabled(f.context()),
); );
// Trim the start and end of the string if it's the first or last part of a docstring. // Trim the start and end of the string if it's the first or last part of a docstring.
@ -413,7 +372,7 @@ impl Format<PyFormatContext<'_>> for FormatLiteralContent {
Cow::Owned(normalized) => text(normalized).fmt(f)?, Cow::Owned(normalized) => text(normalized).fmt(f)?,
} }
if self.trim_end && needs_chaperone_space(self.flags, &normalized, f.context()) { if self.trim_end && needs_chaperone_space(self.flags, &normalized) {
space().fmt(f)?; space().fmt(f)?;
} }
} }

View file

@ -10,22 +10,13 @@ use ruff_python_ast::{
use ruff_source_file::LineRanges; use ruff_source_file::LineRanges;
use ruff_text_size::Ranged; use ruff_text_size::Ranged;
use crate::expression::expr_f_string::f_string_quoting;
use crate::prelude::*; use crate::prelude::*;
use crate::preview::is_f_string_formatting_enabled;
use crate::QuoteStyle; use crate::QuoteStyle;
pub(crate) mod docstring; pub(crate) mod docstring;
pub(crate) mod implicit; pub(crate) mod implicit;
mod normalize; mod normalize;
#[derive(Copy, Clone, Debug, Default)]
pub(crate) enum Quoting {
#[default]
CanChange,
Preserve,
}
impl Format<PyFormatContext<'_>> for AnyStringPrefix { impl Format<PyFormatContext<'_>> for AnyStringPrefix {
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> { fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
// Remove the unicode prefix `u` if any because it is meaningless in Python 3+. // Remove the unicode prefix `u` if any because it is meaningless in Python 3+.
@ -90,19 +81,10 @@ impl From<Quote> for QuoteStyle {
// Extension trait that adds formatter specific helper methods to `StringLike`. // Extension trait that adds formatter specific helper methods to `StringLike`.
pub(crate) trait StringLikeExtensions { pub(crate) trait StringLikeExtensions {
fn quoting(&self, source: &str) -> Quoting;
fn is_multiline(&self, context: &PyFormatContext) -> bool; fn is_multiline(&self, context: &PyFormatContext) -> bool;
} }
impl StringLikeExtensions for ast::StringLike<'_> { impl StringLikeExtensions for ast::StringLike<'_> {
fn quoting(&self, source: &str) -> Quoting {
match self {
Self::String(_) | Self::Bytes(_) => Quoting::CanChange,
Self::FString(f_string) => f_string_quoting(f_string, source),
}
}
fn is_multiline(&self, context: &PyFormatContext) -> bool { fn is_multiline(&self, context: &PyFormatContext) -> bool {
self.parts().any(|part| match part { self.parts().any(|part| match part {
StringLikePart::String(_) | StringLikePart::Bytes(_) => { StringLikePart::String(_) | StringLikePart::Bytes(_) => {
@ -149,15 +131,11 @@ impl StringLikeExtensions for ast::StringLike<'_> {
}) })
} }
if is_f_string_formatting_enabled(context) { contains_line_break_or_comments(
contains_line_break_or_comments( &f_string.elements,
&f_string.elements, context,
context, f_string.flags.is_triple_quoted(),
f_string.flags.is_triple_quoted(), )
)
} else {
context.source().contains_line_break(f_string.range())
}
} }
}) })
} }

View file

@ -12,12 +12,10 @@ use ruff_text_size::{Ranged, TextRange, TextSlice};
use crate::context::FStringState; use crate::context::FStringState;
use crate::prelude::*; use crate::prelude::*;
use crate::preview::is_f_string_formatting_enabled; use crate::string::StringQuotes;
use crate::string::{Quoting, StringQuotes};
use crate::QuoteStyle; use crate::QuoteStyle;
pub(crate) struct StringNormalizer<'a, 'src> { pub(crate) struct StringNormalizer<'a, 'src> {
quoting: Quoting,
preferred_quote_style: Option<QuoteStyle>, preferred_quote_style: Option<QuoteStyle>,
context: &'a PyFormatContext<'src>, context: &'a PyFormatContext<'src>,
} }
@ -25,7 +23,6 @@ pub(crate) struct StringNormalizer<'a, 'src> {
impl<'a, 'src> StringNormalizer<'a, 'src> { impl<'a, 'src> StringNormalizer<'a, 'src> {
pub(crate) fn from_context(context: &'a PyFormatContext<'src>) -> Self { pub(crate) fn from_context(context: &'a PyFormatContext<'src>) -> Self {
Self { Self {
quoting: Quoting::default(),
preferred_quote_style: None, preferred_quote_style: None,
context, context,
} }
@ -36,11 +33,6 @@ impl<'a, 'src> StringNormalizer<'a, 'src> {
self self
} }
pub(crate) fn with_quoting(mut self, quoting: Quoting) -> Self {
self.quoting = quoting;
self
}
/// Determines the preferred quote style for `string`. /// Determines the preferred quote style for `string`.
/// The formatter should use the preferred quote style unless /// The formatter should use the preferred quote style unless
/// it can't because the string contains the preferred quotes OR /// it can't because the string contains the preferred quotes OR
@ -49,112 +41,106 @@ impl<'a, 'src> StringNormalizer<'a, 'src> {
/// Note: If you add more cases here where we return `QuoteStyle::Preserve`, /// Note: If you add more cases here where we return `QuoteStyle::Preserve`,
/// make sure to also add them to [`FormatImplicitConcatenatedStringFlat::new`]. /// make sure to also add them to [`FormatImplicitConcatenatedStringFlat::new`].
pub(super) fn preferred_quote_style(&self, string: StringLikePart) -> QuoteStyle { pub(super) fn preferred_quote_style(&self, string: StringLikePart) -> QuoteStyle {
match self.quoting { let preferred_quote_style = self
Quoting::Preserve => QuoteStyle::Preserve, .preferred_quote_style
Quoting::CanChange => { .unwrap_or(self.context.options().quote_style());
let preferred_quote_style = self
.preferred_quote_style
.unwrap_or(self.context.options().quote_style());
if preferred_quote_style.is_preserve() { if preferred_quote_style.is_preserve() {
return QuoteStyle::Preserve;
}
if let StringLikePart::FString(fstring) = string {
// There are two cases where it's necessary to preserve the quotes if the
// target version is pre 3.12 and the part is an f-string.
if !self.context.options().target_version().supports_pep_701() {
// An f-string expression contains a debug text with a quote character
// because the formatter will emit the debug expression **exactly** the
// same as in the source text.
if is_fstring_with_quoted_debug_expression(fstring, self.context) {
return QuoteStyle::Preserve; return QuoteStyle::Preserve;
} }
if let StringLikePart::FString(fstring) = string { // An f-string expression that contains a triple quoted string literal
// There are two cases where it's necessary to preserve the quotes if the // expression that contains a quote.
// target version is pre 3.12 and the part is an f-string. if is_fstring_with_triple_quoted_literal_expression_containing_quotes(
if !self.context.options().target_version().supports_pep_701() { fstring,
// An f-string expression contains a debug text with a quote character self.context,
// because the formatter will emit the debug expression **exactly** the ) {
// same as in the source text. return QuoteStyle::Preserve;
if is_fstring_with_quoted_debug_expression(fstring, self.context) {
return QuoteStyle::Preserve;
}
// An f-string expression that contains a triple quoted string literal
// expression that contains a quote.
if is_fstring_with_triple_quoted_literal_expression_containing_quotes(
fstring,
self.context,
) {
return QuoteStyle::Preserve;
}
}
// An f-string expression element contains a debug text and the corresponding
// format specifier has a literal element with a quote character.
if is_fstring_with_quoted_format_spec_and_debug(fstring, self.context) {
return QuoteStyle::Preserve;
}
}
// For f-strings prefer alternating the quotes unless The outer string is triple quoted and the inner isn't.
if let FStringState::InsideExpressionElement(parent_context) =
self.context.f_string_state()
{
let parent_flags = parent_context.f_string().flags();
if !parent_flags.is_triple_quoted() || string.flags().is_triple_quoted() {
return QuoteStyle::from(parent_flags.quote_style().opposite());
}
}
// Per PEP 8, always prefer double quotes for triple-quoted strings.
if string.flags().is_triple_quoted() {
// ... unless we're formatting a code snippet inside a docstring,
// then we specifically want to invert our quote style to avoid
// writing out invalid Python.
//
// It's worth pointing out that we can actually wind up being
// somewhat out of sync with PEP8 in this case. Consider this
// example:
//
// def foo():
// '''
// Something.
//
// >>> """tricksy"""
// '''
// pass
//
// Ideally, this would be reformatted as:
//
// def foo():
// """
// Something.
//
// >>> '''tricksy'''
// """
// pass
//
// But the logic here results in the original quoting being
// preserved. This is because the quoting style of the outer
// docstring is determined, in part, by looking at its contents. In
// this case, it notices that it contains a `"""` and thus infers
// that using `'''` would overall read better because it avoids
// the need to escape the interior `"""`. Except... in this case,
// the `"""` is actually part of a code snippet that could get
// reformatted to using a different quoting style itself.
//
// Fixing this would, I believe, require some fairly seismic
// changes to how formatting strings works. Namely, we would need
// to look for code snippets before normalizing the docstring, and
// then figure out the quoting style more holistically by looking
// at the various kinds of quotes used in the code snippets and
// what reformatting them might look like.
//
// Overall this is a bit of a corner case and just inverting the
// style from what the parent ultimately decided upon works, even
// if it doesn't have perfect alignment with PEP8.
if let Some(quote) = self.context.docstring() {
QuoteStyle::from(quote.opposite())
} else {
QuoteStyle::Double
}
} else {
preferred_quote_style
} }
} }
// An f-string expression element contains a debug text and the corresponding
// format specifier has a literal element with a quote character.
if is_fstring_with_quoted_format_spec_and_debug(fstring, self.context) {
return QuoteStyle::Preserve;
}
}
// For f-strings prefer alternating the quotes unless The outer string is triple quoted and the inner isn't.
if let FStringState::InsideExpressionElement(parent_context) = self.context.f_string_state()
{
let parent_flags = parent_context.f_string().flags();
if !parent_flags.is_triple_quoted() || string.flags().is_triple_quoted() {
return QuoteStyle::from(parent_flags.quote_style().opposite());
}
}
// Per PEP 8, always prefer double quotes for triple-quoted strings.
if string.flags().is_triple_quoted() {
// ... unless we're formatting a code snippet inside a docstring,
// then we specifically want to invert our quote style to avoid
// writing out invalid Python.
//
// It's worth pointing out that we can actually wind up being
// somewhat out of sync with PEP8 in this case. Consider this
// example:
//
// def foo():
// '''
// Something.
//
// >>> """tricksy"""
// '''
// pass
//
// Ideally, this would be reformatted as:
//
// def foo():
// """
// Something.
//
// >>> '''tricksy'''
// """
// pass
//
// But the logic here results in the original quoting being
// preserved. This is because the quoting style of the outer
// docstring is determined, in part, by looking at its contents. In
// this case, it notices that it contains a `"""` and thus infers
// that using `'''` would overall read better because it avoids
// the need to escape the interior `"""`. Except... in this case,
// the `"""` is actually part of a code snippet that could get
// reformatted to using a different quoting style itself.
//
// Fixing this would, I believe, require some fairly seismic
// changes to how formatting strings works. Namely, we would need
// to look for code snippets before normalizing the docstring, and
// then figure out the quoting style more holistically by looking
// at the various kinds of quotes used in the code snippets and
// what reformatting them might look like.
//
// Overall this is a bit of a corner case and just inverting the
// style from what the parent ultimately decided upon works, even
// if it doesn't have perfect alignment with PEP8.
if let Some(quote) = self.context.docstring() {
QuoteStyle::from(quote.opposite())
} else {
QuoteStyle::Double
}
} else {
preferred_quote_style
} }
} }
@ -163,7 +149,7 @@ impl<'a, 'src> StringNormalizer<'a, 'src> {
let raw_content = &self.context.source()[string.content_range()]; let raw_content = &self.context.source()[string.content_range()];
let first_quote_or_normalized_char_offset = raw_content let first_quote_or_normalized_char_offset = raw_content
.bytes() .bytes()
.position(|b| matches!(b, b'\\' | b'"' | b'\'' | b'\r' | b'{')); .position(|b| matches!(b, b'\\' | b'"' | b'\'' | b'\r'));
let string_flags = string.flags(); let string_flags = string.flags();
let preferred_style = self.preferred_quote_style(string); let preferred_style = self.preferred_quote_style(string);
@ -214,11 +200,7 @@ impl<'a, 'src> StringNormalizer<'a, 'src> {
raw_content, raw_content,
first_quote_or_escape_offset, first_quote_or_escape_offset,
quote_selection.flags, quote_selection.flags,
// TODO: Remove the `b'{'` in `choose_quotes` when promoting the
// `format_fstring` preview style
false, false,
false,
is_f_string_formatting_enabled(self.context),
) )
} else { } else {
Cow::Borrowed(raw_content) Cow::Borrowed(raw_content)
@ -269,20 +251,14 @@ impl QuoteMetadata {
Self::from_str(text, part.flags(), preferred_quote) Self::from_str(text, part.flags(), preferred_quote)
} }
StringLikePart::FString(fstring) => { StringLikePart::FString(fstring) => {
if is_f_string_formatting_enabled(context) { let metadata = QuoteMetadata::from_str("", part.flags(), preferred_quote);
let metadata = QuoteMetadata::from_str("", part.flags(), preferred_quote);
metadata.merge_fstring_elements( metadata.merge_fstring_elements(
&fstring.elements, &fstring.elements,
fstring.flags, fstring.flags,
context, context,
preferred_quote, preferred_quote,
) )
} else {
let text = &context.source()[part.content_range()];
Self::from_str(text, part.flags(), preferred_quote)
}
} }
} }
} }
@ -635,8 +611,6 @@ pub(crate) fn normalize_string(
start_offset: usize, start_offset: usize,
new_flags: AnyStringFlags, new_flags: AnyStringFlags,
escape_braces: bool, escape_braces: bool,
flip_nested_fstring_quotes: bool,
format_f_string: bool,
) -> Cow<str> { ) -> Cow<str> {
// The normalized string if `input` is not yet normalized. // The normalized string if `input` is not yet normalized.
// `output` must remain empty if `input` is already normalized. // `output` must remain empty if `input` is already normalized.
@ -653,9 +627,6 @@ pub(crate) fn normalize_string(
let is_raw = new_flags.is_raw_string(); let is_raw = new_flags.is_raw_string();
let is_fstring = !format_f_string && new_flags.is_f_string();
let mut formatted_value_nesting = 0u32;
while let Some((index, c)) = chars.next() { while let Some((index, c)) = chars.next() {
if matches!(c, '{' | '}') { if matches!(c, '{' | '}') {
if escape_braces { if escape_braces {
@ -664,17 +635,6 @@ pub(crate) fn normalize_string(
output.push(c); output.push(c);
last_index = index + c.len_utf8(); last_index = index + c.len_utf8();
continue; continue;
} else if is_fstring {
if chars.peek().copied().is_some_and(|(_, next)| next == c) {
// Skip over the second character of the double braces
chars.next();
} else if c == '{' {
formatted_value_nesting += 1;
} else {
// Safe to assume that `c == '}'` here because of the matched pattern above
formatted_value_nesting = formatted_value_nesting.saturating_sub(1);
}
continue;
} }
} }
@ -723,7 +683,7 @@ pub(crate) fn normalize_string(
if !new_flags.is_triple_quoted() { if !new_flags.is_triple_quoted() {
#[allow(clippy::if_same_then_else)] #[allow(clippy::if_same_then_else)]
if next == opposite_quote && formatted_value_nesting == 0 { if next == opposite_quote {
// Remove the escape by ending before the backslash and starting again with the quote // Remove the escape by ending before the backslash and starting again with the quote
chars.next(); chars.next();
output.push_str(&input[last_index..index]); output.push_str(&input[last_index..index]);
@ -734,23 +694,12 @@ pub(crate) fn normalize_string(
} }
} }
} }
} else if !new_flags.is_triple_quoted() } else if !new_flags.is_triple_quoted() && c == preferred_quote {
&& c == preferred_quote
&& formatted_value_nesting == 0
{
// Escape the quote // Escape the quote
output.push_str(&input[last_index..index]); output.push_str(&input[last_index..index]);
output.push('\\'); output.push('\\');
output.push(c); output.push(c);
last_index = index + preferred_quote.len_utf8(); last_index = index + preferred_quote.len_utf8();
} else if c == preferred_quote
&& flip_nested_fstring_quotes
&& formatted_value_nesting > 0
{
// Flip the quotes
output.push_str(&input[last_index..index]);
output.push(opposite_quote);
last_index = index + preferred_quote.len_utf8();
} }
} }
} }
@ -1099,7 +1048,6 @@ fn contains_opposite_quote(content: &str, flags: AnyStringFlags) -> bool {
mod tests { mod tests {
use std::borrow::Cow; use std::borrow::Cow;
use ruff_python_ast::str_prefix::FStringPrefix;
use ruff_python_ast::{ use ruff_python_ast::{
str::Quote, str::Quote,
str_prefix::{AnyStringPrefix, ByteStringPrefix}, str_prefix::{AnyStringPrefix, ByteStringPrefix},
@ -1133,34 +1081,8 @@ mod tests {
false, false,
), ),
false, false,
false,
true,
); );
assert_eq!(r"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a", &normalized); assert_eq!(r"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a", &normalized);
} }
#[test]
fn normalize_nested_fstring() {
let input =
r#"With single quote: ' {my_dict['foo']} With double quote: " {my_dict["bar"]}"#;
let normalized = normalize_string(
input,
0,
AnyStringFlags::new(
AnyStringPrefix::Format(FStringPrefix::Regular),
Quote::Double,
false,
),
false,
true,
false,
);
assert_eq!(
"With single quote: ' {my_dict['foo']} With double quote: \\\" {my_dict['bar']}",
&normalized
);
}
} }

View file

@ -194,41 +194,7 @@ class C:
```diff ```diff
--- Black --- Black
+++ Ruff +++ Ruff
@@ -110,19 +110,20 @@ @@ -161,9 +161,7 @@
value, is_going_to_be="too long to fit in a single line", srsly=True
), "Not what we expected"
- assert {
- key1: value1,
- key2: value2,
- key3: value3,
- key4: value4,
- key5: value5,
- key6: value6,
- key7: value7,
- key8: value8,
- key9: value9,
- } == expected, (
- "Not what we expected and the message is too long to fit in one line"
- )
+ assert (
+ {
+ key1: value1,
+ key2: value2,
+ key3: value3,
+ key4: value4,
+ key5: value5,
+ key6: value6,
+ key7: value7,
+ key8: value8,
+ key9: value9,
+ }
+ == expected
+ ), "Not what we expected and the message is too long to fit in one line"
assert expected(
value, is_going_to_be="too long to fit in a single line", srsly=True
@@ -161,9 +162,7 @@
8 STORE_ATTR 0 (x) 8 STORE_ATTR 0 (x)
10 LOAD_CONST 0 (None) 10 LOAD_CONST 0 (None)
12 RETURN_VALUE 12 RETURN_VALUE
@ -356,20 +322,19 @@ class C:
value, is_going_to_be="too long to fit in a single line", srsly=True value, is_going_to_be="too long to fit in a single line", srsly=True
), "Not what we expected" ), "Not what we expected"
assert ( assert {
{ key1: value1,
key1: value1, key2: value2,
key2: value2, key3: value3,
key3: value3, key4: value4,
key4: value4, key5: value5,
key5: value5, key6: value6,
key6: value6, key7: value7,
key7: value7, key8: value8,
key8: value8, key9: value9,
key9: value9, } == expected, (
} "Not what we expected and the message is too long to fit in one line"
== expected )
), "Not what we expected and the message is too long to fit in one line"
assert expected( assert expected(
value, is_going_to_be="too long to fit in a single line", srsly=True value, is_going_to_be="too long to fit in a single line", srsly=True

View file

@ -194,41 +194,7 @@ class C:
```diff ```diff
--- Black --- Black
+++ Ruff +++ Ruff
@@ -110,19 +110,20 @@ @@ -161,9 +161,7 @@
value, is_going_to_be="too long to fit in a single line", srsly=True
), "Not what we expected"
- assert {
- key1: value1,
- key2: value2,
- key3: value3,
- key4: value4,
- key5: value5,
- key6: value6,
- key7: value7,
- key8: value8,
- key9: value9,
- } == expected, (
- "Not what we expected and the message is too long to fit in one line"
- )
+ assert (
+ {
+ key1: value1,
+ key2: value2,
+ key3: value3,
+ key4: value4,
+ key5: value5,
+ key6: value6,
+ key7: value7,
+ key8: value8,
+ key9: value9,
+ }
+ == expected
+ ), "Not what we expected and the message is too long to fit in one line"
assert expected(
value, is_going_to_be="too long to fit in a single line", srsly=True
@@ -161,9 +162,7 @@
8 STORE_ATTR 0 (x) 8 STORE_ATTR 0 (x)
10 LOAD_CONST 0 (None) 10 LOAD_CONST 0 (None)
12 RETURN_VALUE 12 RETURN_VALUE
@ -356,20 +322,19 @@ class C:
value, is_going_to_be="too long to fit in a single line", srsly=True value, is_going_to_be="too long to fit in a single line", srsly=True
), "Not what we expected" ), "Not what we expected"
assert ( assert {
{ key1: value1,
key1: value1, key2: value2,
key2: value2, key3: value3,
key3: value3, key4: value4,
key4: value4, key5: value5,
key5: value5, key6: value6,
key6: value6, key7: value7,
key7: value7, key8: value8,
key8: value8, key9: value9,
key9: value9, } == expected, (
} "Not what we expected and the message is too long to fit in one line"
== expected )
), "Not what we expected and the message is too long to fit in one line"
assert expected( assert expected(
value, is_going_to_be="too long to fit in a single line", srsly=True value, is_going_to_be="too long to fit in a single line", srsly=True

View file

@ -276,6 +276,19 @@ last_call()
) # note: no trailing comma pre-3.6 ) # note: no trailing comma pre-3.6
call(*gidgets[:2]) call(*gidgets[:2])
call(a, *gidgets[:2]) call(a, *gidgets[:2])
@@ -251,9 +251,9 @@
print(**{1: 3} if False else {x: x for x in range(3)})
print(*lambda x: x)
assert not Test, "Short message"
-assert this is ComplexTest and not requirements.fit_in_a_single_line(
- force=False
-), "Short message"
+assert this is ComplexTest and not requirements.fit_in_a_single_line(force=False), (
+ "Short message"
+)
assert parens is TooMany
for (x,) in (1,), (2,), (3,):
...
``` ```
## Ruff Output ## Ruff Output
@ -534,9 +547,9 @@ print(*[] or [1])
print(**{1: 3} if False else {x: x for x in range(3)}) print(**{1: 3} if False else {x: x for x in range(3)})
print(*lambda x: x) print(*lambda x: x)
assert not Test, "Short message" assert not Test, "Short message"
assert this is ComplexTest and not requirements.fit_in_a_single_line( assert this is ComplexTest and not requirements.fit_in_a_single_line(force=False), (
force=False "Short message"
), "Short message" )
assert parens is TooMany assert parens is TooMany
for (x,) in (1,), (2,), (3,): for (x,) in (1,), (2,), (3,):
... ...

View file

@ -0,0 +1,66 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/f_docstring.py
---
## Input
```python
def foo(e):
f""" {'.'.join(e)}"""
def bar(e):
f"{'.'.join(e)}"
def baz(e):
F""" {'.'.join(e)}"""
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -1,5 +1,5 @@
def foo(e):
- f""" {'.'.join(e)}"""
+ f""" {".".join(e)}"""
def bar(e):
@@ -7,4 +7,4 @@
def baz(e):
- f""" {'.'.join(e)}"""
+ f""" {".".join(e)}"""
```
## Ruff Output
```python
def foo(e):
f""" {".".join(e)}"""
def bar(e):
f"{'.'.join(e)}"
def baz(e):
f""" {".".join(e)}"""
```
## Black Output
```python
def foo(e):
f""" {'.'.join(e)}"""
def bar(e):
f"{'.'.join(e)}"
def baz(e):
f""" {'.'.join(e)}"""
```

View file

@ -105,7 +105,7 @@ elif unformatted:
- "=foo.bar.:main", - "=foo.bar.:main",
- # fmt: on - # fmt: on
- ] # Includes an formatted indentation. - ] # Includes an formatted indentation.
+ "foo-bar" "=foo.bar.:main", + "foo-bar=foo.bar.:main",
+ # fmt: on + # fmt: on
+ ] # Includes an formatted indentation. + ] # Includes an formatted indentation.
}, },
@ -129,7 +129,7 @@ setup(
entry_points={ entry_points={
# fmt: off # fmt: off
"console_scripts": [ "console_scripts": [
"foo-bar" "=foo.bar.:main", "foo-bar=foo.bar.:main",
# fmt: on # fmt: on
] # Includes an formatted indentation. ] # Includes an formatted indentation.
}, },

View file

@ -0,0 +1,74 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/fstring.py
---
## Input
```python
f"f-string without formatted values is just a string"
f"{{NOT a formatted value}}"
f"{{NOT 'a' \"formatted\" \"value\"}}"
f"some f-string with {a} {few():.2f} {formatted.values!r}"
f'some f-string with {a} {few(""):.2f} {formatted.values!r}'
f"{f'''{'nested'} inner'''} outer"
f"\"{f'{nested} inner'}\" outer"
f"space between opening braces: { {a for a in (1, 2, 3)}}"
f'Hello \'{tricky + "example"}\''
f"Tried directories {str(rootdirs)} \
but none started with prefix {parentdir_prefix}"
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -2,10 +2,10 @@
f"{{NOT a formatted value}}"
f'{{NOT \'a\' "formatted" "value"}}'
f"some f-string with {a} {few():.2f} {formatted.values!r}"
-f'some f-string with {a} {few(""):.2f} {formatted.values!r}'
-f"{f'''{'nested'} inner'''} outer"
-f"\"{f'{nested} inner'}\" outer"
-f"space between opening braces: { {a for a in (1, 2, 3)}}"
-f'Hello \'{tricky + "example"}\''
+f"some f-string with {a} {few(''):.2f} {formatted.values!r}"
+f"{f'''{"nested"} inner'''} outer"
+f'"{f"{nested} inner"}" outer'
+f"space between opening braces: { {a for a in (1, 2, 3)} }"
+f"Hello '{tricky + 'example'}'"
f"Tried directories {str(rootdirs)} \
but none started with prefix {parentdir_prefix}"
```
## Ruff Output
```python
f"f-string without formatted values is just a string"
f"{{NOT a formatted value}}"
f'{{NOT \'a\' "formatted" "value"}}'
f"some f-string with {a} {few():.2f} {formatted.values!r}"
f"some f-string with {a} {few(''):.2f} {formatted.values!r}"
f"{f'''{"nested"} inner'''} outer"
f'"{f"{nested} inner"}" outer'
f"space between opening braces: { {a for a in (1, 2, 3)} }"
f"Hello '{tricky + 'example'}'"
f"Tried directories {str(rootdirs)} \
but none started with prefix {parentdir_prefix}"
```
## Black Output
```python
f"f-string without formatted values is just a string"
f"{{NOT a formatted value}}"
f'{{NOT \'a\' "formatted" "value"}}'
f"some f-string with {a} {few():.2f} {formatted.values!r}"
f'some f-string with {a} {few(""):.2f} {formatted.values!r}'
f"{f'''{'nested'} inner'''} outer"
f"\"{f'{nested} inner'}\" outer"
f"space between opening braces: { {a for a in (1, 2, 3)}}"
f'Hello \'{tricky + "example"}\''
f"Tried directories {str(rootdirs)} \
but none started with prefix {parentdir_prefix}"
```

View file

@ -132,6 +132,19 @@ variable: (
```diff ```diff
--- Black --- Black
+++ Ruff +++ Ruff
@@ -52,9 +52,9 @@
pass
-def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> (
- Set["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"]
-):
+def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+]:
json = {
"k": {
"k2": {
@@ -130,9 +130,7 @@ @@ -130,9 +130,7 @@
def foo() -> ( def foo() -> (
@ -225,9 +238,9 @@ def f(
pass pass
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> ( def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[
Set["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"] "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
): ]:
json = { json = {
"k": { "k": {
"k2": { "k2": {

View file

@ -309,33 +309,52 @@ long_unmergable_string_with_pragma = (
```diff ```diff
--- Black --- Black
+++ Ruff +++ Ruff
@@ -167,13 +167,9 @@ @@ -167,14 +167,14 @@
triple_quote_string = """This is a really really really long triple quote string assignment and it should not be touched.""" triple_quote_string = """This is a really really really long triple quote string assignment and it should not be touched."""
-assert ( -assert (
- some_type_of_boolean_expression - some_type_of_boolean_expression
-), "Followed by a really really really long string that is used to provide context to the AssertionError exception." -), "Followed by a really really really long string that is used to provide context to the AssertionError exception."
+assert some_type_of_boolean_expression, "Followed by a really really really long string that is used to provide context to the AssertionError exception." +assert some_type_of_boolean_expression, (
+ "Followed by a really really really long string that is used to provide context to the AssertionError exception."
+)
-assert ( -assert (
- some_type_of_boolean_expression - some_type_of_boolean_expression
-), "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string {}.".format( -), "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string {}.".format(
+assert some_type_of_boolean_expression, "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string {}.".format( - "formatting"
"formatting" +assert some_type_of_boolean_expression, (
+ "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string {}.".format(
+ "formatting"
+ )
) )
@@ -256,9 +252,7 @@ assert some_type_of_boolean_expression, (
@@ -255,10 +255,8 @@
+ " that has been "
+ CONCATENATED + CONCATENATED
+ "using the '+' operator." + "using the '+' operator."
) -)
-annotated_variable: Final = ( -annotated_variable: Final = (
- "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped." - "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped."
-) )
+annotated_variable: Final = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped." +annotated_variable: Final = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped."
annotated_variable: Literal["fakse_literal"] = ( annotated_variable: Literal["fakse_literal"] = (
"This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped." "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped."
) )
@@ -267,9 +265,9 @@
backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\\\"
backslashes = "This is a really 'long' string with \"embedded double quotes\" and 'single' quotes that also handles checking for an odd number of backslashes \\\", like this...\\\\\\"
-short_string = "Hi" " there."
+short_string = "Hi there."
-func_call(short_string=("Hi" " there."))
+func_call(short_string=("Hi there."))
raw_strings = r"Don't" " get" r" merged" " unless they are all raw."
``` ```
## Ruff Output ## Ruff Output
@ -510,10 +529,14 @@ pragma_comment_string2 = "Lines which end with an inline pragma comment of the f
triple_quote_string = """This is a really really really long triple quote string assignment and it should not be touched.""" triple_quote_string = """This is a really really really long triple quote string assignment and it should not be touched."""
assert some_type_of_boolean_expression, "Followed by a really really really long string that is used to provide context to the AssertionError exception." assert some_type_of_boolean_expression, (
"Followed by a really really really long string that is used to provide context to the AssertionError exception."
)
assert some_type_of_boolean_expression, "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string {}.".format( assert some_type_of_boolean_expression, (
"formatting" "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string {}.".format(
"formatting"
)
) )
assert some_type_of_boolean_expression, ( assert some_type_of_boolean_expression, (
@ -604,9 +627,9 @@ backslashes = "This is a really long string with \"embedded\" double quotes and
backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\\\" backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\\\"
backslashes = "This is a really 'long' string with \"embedded double quotes\" and 'single' quotes that also handles checking for an odd number of backslashes \\\", like this...\\\\\\" backslashes = "This is a really 'long' string with \"embedded double quotes\" and 'single' quotes that also handles checking for an odd number of backslashes \\\", like this...\\\\\\"
short_string = "Hi" " there." short_string = "Hi there."
func_call(short_string=("Hi" " there.")) func_call(short_string=("Hi there."))
raw_strings = r"Don't" " get" r" merged" " unless they are all raw." raw_strings = r"Don't" " get" r" merged" " unless they are all raw."

View file

@ -0,0 +1,241 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/cases/pep_572_remove_parens.py
---
## Input
```python
if (foo := 0):
pass
if (foo := 1):
pass
if (y := 5 + 5):
pass
y = (x := 0)
y += (x := 0)
(y := 5 + 5)
test: int = (test2 := 2)
a, b = (test := (1, 2))
# see also https://github.com/psf/black/issues/2139
assert (foo := 42 - 12)
foo(x=(y := f(x)))
def foo(answer=(p := 42)):
...
def foo2(answer: (p := 42) = 5):
...
lambda: (x := 1)
a[(x := 12)]
a[:(x := 13)]
# we don't touch expressions in f-strings but if we do one day, don't break 'em
f'{(x:=10)}'
def a():
return (x := 3)
await (b := 1)
yield (a := 2)
raise (c := 3)
def this_is_so_dumb() -> (please := no):
pass
async def await_the_walrus():
with (x := y):
pass
with (x := y) as z, (a := b) as c:
pass
with (x := await y):
pass
with (x := await a, y := await b):
pass
with ((x := await a, y := await b)):
pass
with (x := await a), (y := await b):
pass
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -35,7 +35,7 @@
a[: (x := 13)]
# we don't touch expressions in f-strings but if we do one day, don't break 'em
-f"{(x:=10)}"
+f"{(x := 10)}"
def a():
```
## Ruff Output
```python
if foo := 0:
pass
if foo := 1:
pass
if y := 5 + 5:
pass
y = (x := 0)
y += (x := 0)
(y := 5 + 5)
test: int = (test2 := 2)
a, b = (test := (1, 2))
# see also https://github.com/psf/black/issues/2139
assert (foo := 42 - 12)
foo(x=(y := f(x)))
def foo(answer=(p := 42)): ...
def foo2(answer: (p := 42) = 5): ...
lambda: (x := 1)
a[(x := 12)]
a[: (x := 13)]
# we don't touch expressions in f-strings but if we do one day, don't break 'em
f"{(x := 10)}"
def a():
return (x := 3)
await (b := 1)
yield (a := 2)
raise (c := 3)
def this_is_so_dumb() -> (please := no):
pass
async def await_the_walrus():
with (x := y):
pass
with (x := y) as z, (a := b) as c:
pass
with (x := await y):
pass
with (x := await a, y := await b):
pass
with (x := await a, y := await b):
pass
with (x := await a), (y := await b):
pass
```
## Black Output
```python
if foo := 0:
pass
if foo := 1:
pass
if y := 5 + 5:
pass
y = (x := 0)
y += (x := 0)
(y := 5 + 5)
test: int = (test2 := 2)
a, b = (test := (1, 2))
# see also https://github.com/psf/black/issues/2139
assert (foo := 42 - 12)
foo(x=(y := f(x)))
def foo(answer=(p := 42)): ...
def foo2(answer: (p := 42) = 5): ...
lambda: (x := 1)
a[(x := 12)]
a[: (x := 13)]
# we don't touch expressions in f-strings but if we do one day, don't break 'em
f"{(x:=10)}"
def a():
return (x := 3)
await (b := 1)
yield (a := 2)
raise (c := 3)
def this_is_so_dumb() -> (please := no):
pass
async def await_the_walrus():
with (x := y):
pass
with (x := y) as z, (a := b) as c:
pass
with (x := await y):
pass
with (x := await a, y := await b):
pass
with (x := await a, y := await b):
pass
with (x := await a), (y := await b):
pass
```

View file

@ -149,15 +149,155 @@ rf"\{"a"}"
```diff ```diff
--- Black --- Black
+++ Ruff +++ Ruff
@@ -119,7 +119,7 @@ @@ -11,16 +11,14 @@
# edge case: FSTRING_MIDDLE containing only whitespace should not be stripped
x = f"{a} {b}"
-x = f"foo {
- 2 + 2
-} bar baz"
+x = f"foo {2 + 2} bar baz"
-x = f"foo {{ {"a {2 + 2} b"}bar {{ baz"
+x = f"foo {{ {'a {2 + 2} b'}bar {{ baz"
+x = f"foo {{ {f'a {2 + 2} b'}bar {{ baz"
x = f"foo {{ {f'a {2 + 2} b'}bar {{ baz"
-x = f"foo {{ {f"a {2 + 2} b"}bar {{ baz"
x = f"foo {{ {f'a {f"a {2 + 2} b"} b'}bar {{ baz"
-x = f"foo {{ {f"a {f"a {2 + 2} b"} b"}bar {{ baz"
+x = f"foo {{ {f'a {f"a {2 + 2} b"} b'}bar {{ baz"
x = """foo {{ {2 + 2}bar
baz"""
@@ -28,74 +26,62 @@
x = f"""foo {{ {2 + 2}bar {{ baz"""
-x = f"""foo {{ {
- 2 + 2
-}bar {{ baz"""
+x = f"""foo {{ {2 + 2}bar {{ baz"""
-x = f"""foo {{ {
- 2 + 2
-}bar
+x = f"""foo {{ {2 + 2}bar
baz"""
x = f"""foo {{ a
foo {2 + 2}bar {{ baz
x = f"foo {{ {
- 2 + 2 # comment
- }bar"
+ 2 + 2 # comment
+}bar"
{{ baz
}} buzz
- {print("abc" + "def"
-)}
+ {print("abc" + "def")}
abc"""
# edge case: end triple quotes at index zero
-f"""foo {2+2} bar
+f"""foo {2 + 2} bar
"""
-f' \' {f"'"} \' '
-f" \" {f'"'} \" "
+f" ' {f"'"} ' "
+f' " {f'"'} " '
-x = f"a{2+2:=^72}b"
-x = f"a{2+2:x}b"
+x = f"a{2 + 2:=^72}b"
+x = f"a{2 + 2:x}b"
rf"foo"
rf"{foo}"
f"{x:{y}d}"
-x = f"a{2+2:=^{x}}b"
-x = f"a{2+2:=^{foo(x+y**2):something else}}b"
-x = f"a{2+2:=^{foo(x+y**2):something else}one more}b"
-f"{(abc:=10)}"
+x = f"a{2 + 2:=^{x}}b"
+x = f"a{2 + 2:=^{foo(x + y**2):something else}}b"
+x = f"a{2 + 2:=^{foo(x + y**2):something else}one more}b"
+f"{(abc := 10)}"
-f"This is a really long string, but just make sure that you reflow fstrings {
- 2+2:d
-}"
-f"This is a really long string, but just make sure that you reflow fstrings correctly {2+2:d}"
+f"This is a really long string, but just make sure that you reflow fstrings {2 + 2:d}"
+f"This is a really long string, but just make sure that you reflow fstrings correctly {2 + 2:d}"
f"{2+2=}"
f"{2+2 = }"
f"{ 2 + 2 = }"
-f"""foo {
- datetime.datetime.now():%Y
+f"""foo {datetime.datetime.now():%Y
%m
%d
}"""
-f"{
-X
-!r
-}"
+f"{X!r}"
raise ValueError(
- "xxxxxxxxxxxIncorrect --line-ranges format, expect START-END, found"
- f" {lines_str!r}"
+ f"xxxxxxxxxxxIncorrect --line-ranges format, expect START-END, found {lines_str!r}"
) )
f"{1:{f'{2}'}}" f"`escape` only permitted in {{'html', 'latex', 'latex-math'}}, \
-f"{1:{f'{2}'}}" @@ -105,8 +91,10 @@
+f'{1:{f'{2}'}}' rf"\{{\}}"
f"{1:{2}d}"
f"""
- WITH {f'''
- {1}_cte AS ()'''}
+ WITH {
+ f'''
+ {1}_cte AS ()'''
+}
"""
value: str = f"""foo
@@ -124,13 +112,15 @@
f'{{\\"kind\\":\\"ConfigMap\\",\\"metadata\\":{{\\"annotations\\":{{}},\\"name\\":\\"cluster-info\\",\\"namespace\\":\\"amazon-cloudwatch\\"}}}}' f'{{\\"kind\\":\\"ConfigMap\\",\\"metadata\\":{{\\"annotations\\":{{}},\\"name\\":\\"cluster-info\\",\\"namespace\\":\\"amazon-cloudwatch\\"}}}}'
-f"""{'''
-'''}"""
+f"""{
+ '''
+'''
+}"""
-f"{'\''}"
-f"{f'\''}"
+f"{"'"}"
+f"{f"'"}"
f"{1}\{{"
f"{2} foo \{{[\}}"
f"\{3}"
-rf"\{"a"}"
+rf"\{'a'}"
``` ```
## Ruff Output ## Ruff Output
@ -176,16 +316,14 @@ x = f"""foo {{ {2 + 2}bar {{ baz"""
# edge case: FSTRING_MIDDLE containing only whitespace should not be stripped # edge case: FSTRING_MIDDLE containing only whitespace should not be stripped
x = f"{a} {b}" x = f"{a} {b}"
x = f"foo { x = f"foo {2 + 2} bar baz"
2 + 2
} bar baz"
x = f"foo {{ {"a {2 + 2} b"}bar {{ baz" x = f"foo {{ {'a {2 + 2} b'}bar {{ baz"
x = f"foo {{ {f'a {2 + 2} b'}bar {{ baz"
x = f"foo {{ {f'a {2 + 2} b'}bar {{ baz" x = f"foo {{ {f'a {2 + 2} b'}bar {{ baz"
x = f"foo {{ {f"a {2 + 2} b"}bar {{ baz"
x = f"foo {{ {f'a {f"a {2 + 2} b"} b'}bar {{ baz" x = f"foo {{ {f'a {f"a {2 + 2} b"} b'}bar {{ baz"
x = f"foo {{ {f"a {f"a {2 + 2} b"} b"}bar {{ baz" x = f"foo {{ {f'a {f"a {2 + 2} b"} b'}bar {{ baz"
x = """foo {{ {2 + 2}bar x = """foo {{ {2 + 2}bar
baz""" baz"""
@ -193,74 +331,62 @@ baz"""
x = f"""foo {{ {2 + 2}bar {{ baz""" x = f"""foo {{ {2 + 2}bar {{ baz"""
x = f"""foo {{ { x = f"""foo {{ {2 + 2}bar {{ baz"""
2 + 2
}bar {{ baz"""
x = f"""foo {{ { x = f"""foo {{ {2 + 2}bar
2 + 2
}bar
baz""" baz"""
x = f"""foo {{ a x = f"""foo {{ a
foo {2 + 2}bar {{ baz foo {2 + 2}bar {{ baz
x = f"foo {{ { x = f"foo {{ {
2 + 2 # comment 2 + 2 # comment
}bar" }bar"
{{ baz {{ baz
}} buzz }} buzz
{print("abc" + "def" {print("abc" + "def")}
)}
abc""" abc"""
# edge case: end triple quotes at index zero # edge case: end triple quotes at index zero
f"""foo {2+2} bar f"""foo {2 + 2} bar
""" """
f' \' {f"'"} \' ' f" ' {f"'"} ' "
f" \" {f'"'} \" " f' " {f'"'} " '
x = f"a{2+2:=^72}b" x = f"a{2 + 2:=^72}b"
x = f"a{2+2:x}b" x = f"a{2 + 2:x}b"
rf"foo" rf"foo"
rf"{foo}" rf"{foo}"
f"{x:{y}d}" f"{x:{y}d}"
x = f"a{2+2:=^{x}}b" x = f"a{2 + 2:=^{x}}b"
x = f"a{2+2:=^{foo(x+y**2):something else}}b" x = f"a{2 + 2:=^{foo(x + y**2):something else}}b"
x = f"a{2+2:=^{foo(x+y**2):something else}one more}b" x = f"a{2 + 2:=^{foo(x + y**2):something else}one more}b"
f"{(abc:=10)}" f"{(abc := 10)}"
f"This is a really long string, but just make sure that you reflow fstrings { f"This is a really long string, but just make sure that you reflow fstrings {2 + 2:d}"
2+2:d f"This is a really long string, but just make sure that you reflow fstrings correctly {2 + 2:d}"
}"
f"This is a really long string, but just make sure that you reflow fstrings correctly {2+2:d}"
f"{2+2=}" f"{2+2=}"
f"{2+2 = }" f"{2+2 = }"
f"{ 2 + 2 = }" f"{ 2 + 2 = }"
f"""foo { f"""foo {datetime.datetime.now():%Y
datetime.datetime.now():%Y
%m %m
%d %d
}""" }"""
f"{ f"{X!r}"
X
!r
}"
raise ValueError( raise ValueError(
"xxxxxxxxxxxIncorrect --line-ranges format, expect START-END, found" f"xxxxxxxxxxxIncorrect --line-ranges format, expect START-END, found {lines_str!r}"
f" {lines_str!r}"
) )
f"`escape` only permitted in {{'html', 'latex', 'latex-math'}}, \ f"`escape` only permitted in {{'html', 'latex', 'latex-math'}}, \
@ -270,8 +396,10 @@ x = f"\N{GREEK CAPITAL LETTER DELTA} \N{SNOWMAN} {x}"
rf"\{{\}}" rf"\{{\}}"
f""" f"""
WITH {f''' WITH {
{1}_cte AS ()'''} f'''
{1}_cte AS ()'''
}
""" """
value: str = f"""foo value: str = f"""foo
@ -284,21 +412,23 @@ log(
) )
f"{1:{f'{2}'}}" f"{1:{f'{2}'}}"
f'{1:{f'{2}'}}' f"{1:{f'{2}'}}"
f"{1:{2}d}" f"{1:{2}d}"
f'{{\\"kind\\":\\"ConfigMap\\",\\"metadata\\":{{\\"annotations\\":{{}},\\"name\\":\\"cluster-info\\",\\"namespace\\":\\"amazon-cloudwatch\\"}}}}' f'{{\\"kind\\":\\"ConfigMap\\",\\"metadata\\":{{\\"annotations\\":{{}},\\"name\\":\\"cluster-info\\",\\"namespace\\":\\"amazon-cloudwatch\\"}}}}'
f"""{''' f"""{
'''}""" '''
'''
}"""
f"{'\''}" f"{"'"}"
f"{f'\''}" f"{f"'"}"
f"{1}\{{" f"{1}\{{"
f"{2} foo \{{[\}}" f"{2} foo \{{[\}}"
f"\{3}" f"\{3}"
rf"\{"a"}" rf"\{'a'}"
``` ```
## Black Output ## Black Output

View file

@ -127,39 +127,7 @@ def foo(a,b) -> tuple[int, int, int,]:
return 2 * a return 2 * a
@@ -99,25 +107,31 @@ @@ -117,7 +125,9 @@
return 2
-def foo() -> tuple[
- loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,
- loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,
- loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,
-]:
+def foo() -> (
+ tuple[
+ loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,
+ loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,
+ loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,
+ ]
+):
return 2
# Magic trailing comma example
-def foo() -> tuple[
- int,
- int,
- int,
-]:
+def foo() -> (
+ tuple[
+ int,
+ int,
+ int,
+ ]
+):
return 2
# Magic trailing comma example, with params # Magic trailing comma example, with params
@ -284,24 +252,20 @@ def foo() -> tuple[int, int, int]:
return 2 return 2
def foo() -> ( def foo() -> tuple[
tuple[ loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,
loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,
loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong,
loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong, ]:
]
):
return 2 return 2
# Magic trailing comma example # Magic trailing comma example
def foo() -> ( def foo() -> tuple[
tuple[ int,
int, int,
int, int,
int, ]:
]
):
return 2 return 2

View file

@ -64,11 +64,9 @@ assert xxxxxxxxx.xxxxxxxxx.xxxxxxxxx(
-assert xxxxxxxxx.xxxxxxxxx.xxxxxxxxx( -assert xxxxxxxxx.xxxxxxxxx.xxxxxxxxx(
- xxxxxxxxx - xxxxxxxxx
-).xxxxxxxxxxxxxxxxxx(), ( -).xxxxxxxxxxxxxxxxxx(), (
- "xxx {xxxxxxxxx} xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +assert xxxxxxxxx.xxxxxxxxx.xxxxxxxxx(xxxxxxxxx).xxxxxxxxxxxxxxxxxx(), (
-) "xxx {xxxxxxxxx} xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+assert ( )
+ xxxxxxxxx.xxxxxxxxx.xxxxxxxxx(xxxxxxxxx).xxxxxxxxxxxxxxxxxx()
+), "xxx {xxxxxxxxx} xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
``` ```
## Ruff Output ## Ruff Output
@ -117,9 +115,9 @@ assert (
) )
# Regression test for https://github.com/psf/black/issues/3414. # Regression test for https://github.com/psf/black/issues/3414.
assert ( assert xxxxxxxxx.xxxxxxxxx.xxxxxxxxx(xxxxxxxxx).xxxxxxxxxxxxxxxxxx(), (
xxxxxxxxx.xxxxxxxxx.xxxxxxxxx(xxxxxxxxx).xxxxxxxxxxxxxxxxxx() "xxx {xxxxxxxxx} xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
), "xxx {xxxxxxxxx} xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" )
``` ```
## Black Output ## Black Output

View file

@ -71,7 +71,15 @@ f"\"{a}\"{'hello' * b}\"{c}\""
```diff ```diff
--- Black --- Black
+++ Ruff +++ Ruff
@@ -25,7 +25,12 @@ @@ -18,14 +18,19 @@
quote"""
f"just a normal {f} string"
f"""This is a triple-quoted {f}-string"""
-f'MOAR {" ".join([])}'
f"MOAR {' '.join([])}"
+f"MOAR {' '.join([])}"
r"raw string ftw"
r"Date d\'expiration:(.*)"
r'Tricky "quote' r'Tricky "quote'
r"Not-so-tricky \"quote" r"Not-so-tricky \"quote"
rf"{yay}" rf"{yay}"
@ -85,6 +93,25 @@ f"\"{a}\"{'hello' * b}\"{c}\""
re.compile(r'[\\"]') re.compile(r'[\\"]')
"x = ''; y = \"\"" "x = ''; y = \"\""
"x = '''; y = \"\"" "x = '''; y = \"\""
@@ -40,14 +45,14 @@
'\\""'
"\\''"
"Lots of \\\\\\\\'quotes'"
-f'{y * " "} \'{z}\''
+f"{y * ' '} '{z}'"
f"{{y * \" \"}} '{z}'"
-f'\'{z}\' {y * " "}'
+f"'{z}' {y * ' '}"
f"{y * x} '{z}'"
"'{z}' {y * \" \"}"
"{y * x} '{z}'"
# We must bail out if changing the quotes would introduce backslashes in f-string
# expressions. xref: https://github.com/psf/black/issues/2348
-f"\"{b}\"{' ' * (long-len(b)+1)}: \"{sts}\",\n"
-f"\"{a}\"{'hello' * b}\"{c}\""
+f'"{b}"{" " * (long - len(b) + 1)}: "{sts}",\n'
+f'"{a}"{"hello" * b}"{c}"'
``` ```
## Ruff Output ## Ruff Output
@ -110,7 +137,7 @@ f"\"{a}\"{'hello' * b}\"{c}\""
quote""" quote"""
f"just a normal {f} string" f"just a normal {f} string"
f"""This is a triple-quoted {f}-string""" f"""This is a triple-quoted {f}-string"""
f'MOAR {" ".join([])}' f"MOAR {' '.join([])}"
f"MOAR {' '.join([])}" f"MOAR {' '.join([])}"
r"raw string ftw" r"raw string ftw"
r"Date d\'expiration:(.*)" r"Date d\'expiration:(.*)"
@ -137,17 +164,17 @@ re.compile(r'[\\"]')
'\\""' '\\""'
"\\''" "\\''"
"Lots of \\\\\\\\'quotes'" "Lots of \\\\\\\\'quotes'"
f'{y * " "} \'{z}\'' f"{y * ' '} '{z}'"
f"{{y * \" \"}} '{z}'" f"{{y * \" \"}} '{z}'"
f'\'{z}\' {y * " "}' f"'{z}' {y * ' '}"
f"{y * x} '{z}'" f"{y * x} '{z}'"
"'{z}' {y * \" \"}" "'{z}' {y * \" \"}"
"{y * x} '{z}'" "{y * x} '{z}'"
# We must bail out if changing the quotes would introduce backslashes in f-string # We must bail out if changing the quotes would introduce backslashes in f-string
# expressions. xref: https://github.com/psf/black/issues/2348 # expressions. xref: https://github.com/psf/black/issues/2348
f"\"{b}\"{' ' * (long-len(b)+1)}: \"{sts}\",\n" f'"{b}"{" " * (long - len(b) + 1)}: "{sts}",\n'
f"\"{a}\"{'hello' * b}\"{c}\"" f'"{a}"{"hello" * b}"{c}"'
``` ```
## Black Output ## Black Output

View file

@ -616,7 +616,9 @@ def length_doctest():
Integer length of the list of numbers. Integer length of the list of numbers.
Example: Example:
>>> length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) >>> length(
... [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
... )
20 20
""" """
@ -631,7 +633,9 @@ def length_doctest_underindent():
Integer length of the list of numbers. Integer length of the list of numbers.
Example: Example:
>>> length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) >>> length(
... [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
... )
20 20
""" """
@ -649,7 +653,9 @@ def length_markdown():
Example: Example:
``` ```
length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]) length(
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]
)
``` ```
""" """
@ -659,7 +665,9 @@ def length_rst():
""" """
Do cool stuff:: Do cool stuff::
length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]) length(
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]
)
""" """
pass pass
@ -670,7 +678,9 @@ def length_rst_in_section():
Examples: Examples:
Do cool stuff:: Do cool stuff::
length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) length(
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
)
""" """
pass pass
``` ```
@ -695,11 +705,13 @@ def length_rst_in_section():
""" """
@@ -300,7 +302,28 @@ @@ -300,9 +302,28 @@
Integer length of the list of numbers. Integer length of the list of numbers.
Example: Example:
- >>> length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) - >>> length(
- ... [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
- ... )
+ >>> length([ + >>> length([
+ ... 1, + ... 1,
+ ... 2, + ... 2,
@ -725,11 +737,13 @@ def length_rst_in_section():
20 20
""" """
@@ -315,7 +338,28 @@ @@ -317,9 +338,28 @@
Integer length of the list of numbers. Integer length of the list of numbers.
Example: Example:
- >>> length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) - >>> length(
- ... [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
- ... )
+ >>> length([ + >>> length([
+ ... 1, + ... 1,
+ ... 2, + ... 2,
@ -755,11 +769,13 @@ def length_rst_in_section():
20 20
""" """
@@ -333,7 +377,29 @@ @@ -337,9 +377,29 @@
Example: Example:
``` ```
- length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]) - length(
- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]
- )
+ length([ + length([
+ 1, + 1,
+ 2, + 2,
@ -786,11 +802,13 @@ def length_rst_in_section():
``` ```
""" """
@@ -343,7 +409,29 @@ @@ -349,9 +409,29 @@
""" """
Do cool stuff:: Do cool stuff::
- length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]) - length(
- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]
- )
+ length([ + length([
+ 1, + 1,
+ 2, + 2,
@ -817,11 +835,13 @@ def length_rst_in_section():
""" """
pass pass
@@ -354,6 +442,27 @@ @@ -362,8 +442,27 @@
Examples: Examples:
Do cool stuff:: Do cool stuff::
- length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) - length(
- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
- )
+ length([ + length([
+ 1, + 1,
+ 2, + 2,
@ -1157,7 +1177,9 @@ def length_doctest():
Integer length of the list of numbers. Integer length of the list of numbers.
Example: Example:
>>> length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) >>> length(
... [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
... )
20 20
""" """
@ -1172,7 +1194,9 @@ def length_doctest_underindent():
Integer length of the list of numbers. Integer length of the list of numbers.
Example: Example:
>>> length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) >>> length(
... [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
... )
20 20
""" """
@ -1190,7 +1214,9 @@ def length_markdown():
Example: Example:
``` ```
length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]) length(
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]
)
``` ```
""" """
@ -1200,7 +1226,9 @@ def length_rst():
""" """
Do cool stuff:: Do cool stuff::
length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]) length(
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]
)
""" """
pass pass
@ -1211,7 +1239,9 @@ def length_rst_in_section():
Examples: Examples:
Do cool stuff:: Do cool stuff::
length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) length(
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
)
""" """
pass pass
``` ```
@ -1221,11 +1251,13 @@ def length_rst_in_section():
```diff ```diff
--- Stable --- Stable
+++ Preview +++ Preview
@@ -290,7 +290,28 @@ @@ -290,9 +290,28 @@
Integer length of the list of numbers. Integer length of the list of numbers.
Example: Example:
- >>> length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) - >>> length(
- ... [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
- ... )
+ >>> length([ + >>> length([
+ ... 1, + ... 1,
+ ... 2, + ... 2,
@ -1251,11 +1283,13 @@ def length_rst_in_section():
20 20
""" """
@@ -305,7 +326,28 @@ @@ -307,9 +326,28 @@
Integer length of the list of numbers. Integer length of the list of numbers.
Example: Example:
- >>> length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) - >>> length(
- ... [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
- ... )
+ >>> length([ + >>> length([
+ ... 1, + ... 1,
+ ... 2, + ... 2,
@ -1281,11 +1315,13 @@ def length_rst_in_section():
20 20
""" """
@@ -323,7 +365,29 @@ @@ -327,9 +365,29 @@
Example: Example:
``` ```
- length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]) - length(
- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]
- )
+ length([ + length([
+ 1, + 1,
+ 2, + 2,
@ -1312,11 +1348,13 @@ def length_rst_in_section():
``` ```
""" """
@@ -333,7 +397,29 @@ @@ -339,9 +397,29 @@
""" """
Do cool stuff:: Do cool stuff::
- length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]) - length(
- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]
- )
+ length([ + length([
+ 1, + 1,
+ 2, + 2,
@ -1343,11 +1381,13 @@ def length_rst_in_section():
""" """
pass pass
@@ -344,6 +430,27 @@ @@ -352,8 +430,27 @@
Examples: Examples:
Do cool stuff:: Do cool stuff::
- length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) - length(
- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
- )
+ length([ + length([
+ 1, + 1,
+ 2, + 2,
@ -1693,7 +1733,9 @@ def length_doctest():
Integer length of the list of numbers. Integer length of the list of numbers.
Example: Example:
>>> length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) >>> length(
... [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
... )
20 20
""" """
@ -1708,7 +1750,9 @@ def length_doctest_underindent():
Integer length of the list of numbers. Integer length of the list of numbers.
Example: Example:
>>> length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) >>> length(
... [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
... )
20 20
""" """
@ -1726,7 +1770,9 @@ def length_markdown():
Example: Example:
``` ```
length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]) length(
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]
)
``` ```
""" """
@ -1736,7 +1782,9 @@ def length_rst():
""" """
Do cool stuff:: Do cool stuff::
length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]) length(
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]
)
""" """
pass pass
@ -1747,7 +1795,9 @@ def length_rst_in_section():
Examples: Examples:
Do cool stuff:: Do cool stuff::
length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) length(
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
)
""" """
pass pass
``` ```
@ -1772,11 +1822,13 @@ def length_rst_in_section():
""" """
@@ -300,7 +302,28 @@ @@ -300,9 +302,28 @@
Integer length of the list of numbers. Integer length of the list of numbers.
Example: Example:
- >>> length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) - >>> length(
- ... [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
- ... )
+ >>> length([ + >>> length([
+ ... 1, + ... 1,
+ ... 2, + ... 2,
@ -1802,11 +1854,13 @@ def length_rst_in_section():
20 20
""" """
@@ -315,7 +338,28 @@ @@ -317,9 +338,28 @@
Integer length of the list of numbers. Integer length of the list of numbers.
Example: Example:
- >>> length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) - >>> length(
- ... [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
- ... )
+ >>> length([ + >>> length([
+ ... 1, + ... 1,
+ ... 2, + ... 2,
@ -1832,11 +1886,13 @@ def length_rst_in_section():
20 20
""" """
@@ -333,7 +377,29 @@ @@ -337,9 +377,29 @@
Example: Example:
``` ```
- length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]) - length(
- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]
- )
+ length([ + length([
+ 1, + 1,
+ 2, + 2,
@ -1863,11 +1919,13 @@ def length_rst_in_section():
``` ```
""" """
@@ -343,7 +409,29 @@ @@ -349,9 +409,29 @@
""" """
Do cool stuff:: Do cool stuff::
- length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]) - length(
- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]
- )
+ length([ + length([
+ 1, + 1,
+ 2, + 2,
@ -1894,11 +1952,13 @@ def length_rst_in_section():
""" """
pass pass
@@ -354,6 +442,27 @@ @@ -362,8 +442,27 @@
Examples: Examples:
Do cool stuff:: Do cool stuff::
- length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) - length(
- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
- )
+ length([ + length([
+ 1, + 1,
+ 2, + 2,
@ -2822,7 +2882,30 @@ def length_rst_in_section():
Examples: Examples:
Do cool stuff:: Do cool stuff::
length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) length(
[
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
]
)
""" """
pass pass
``` ```
@ -3063,11 +3146,34 @@ def length_rst_in_section():
""" """
pass pass
@@ -878,6 +872,27 @@ @@ -878,29 +872,27 @@
Examples: Examples:
Do cool stuff:: Do cool stuff::
- length([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]) - length(
- [
- 1,
- 2,
- 3,
- 4,
- 5,
- 6,
- 7,
- 8,
- 9,
- 10,
- 11,
- 12,
- 13,
- 14,
- 15,
- 16,
- 17,
- 18,
- 19,
- 20,
- ]
- )
+ length([ + length([
+ 1, + 1,
+ 2, + 2,

View file

@ -911,52 +911,20 @@ if True:
) )
# This f-string should be flattened # This f-string should be flattened
xxxxxxxxxxxxxxxx = f"aaaaaaaaaaaaaaaaaaaaa { xxxxxxxxxxxxxxxx = f"aaaaaaaaaaaaaaaaaaaaa {expression} bbbbbbbbbbbbbbbbbbbbbbbb" + (
expression } bbbbbbbbbbbbbbbbbbbbbbbb" + (yyyyyyyyyyyyyy + zzzzzzzzzzz) yyyyyyyyyyyyyy + zzzzzzzzzzz
)
# This is not a multiline f-string, but the expression is too long so it should be # This is not a multiline f-string, but the expression is too long so it should be
# wrapped in parentheses. # wrapped in parentheses.
f"hellooooooooooooooooooooooo \ (
worlddddddddddddddddddddddddddddddddd" + ( f"hellooooooooooooooooooooooo \
aaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbb worlddddddddddddddddddddddddddddddddd"
+ (aaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbb)
) )
aaaaaaaaaaa = f"hellooooooooooooooooooooooo \ aaaaaaaaaaa = (
worlddddddddddddddddddddddddddddddddd" + ( f"hellooooooooooooooooooooooo \
aaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbb worlddddddddddddddddddddddddddddddddd"
+ (aaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbb)
) )
``` ```
## Preview changes
```diff
--- Stable
+++ Preview
@@ -468,16 +468,19 @@
)
# This f-string should be flattened
-xxxxxxxxxxxxxxxx = f"aaaaaaaaaaaaaaaaaaaaa {
- expression } bbbbbbbbbbbbbbbbbbbbbbbb" + (yyyyyyyyyyyyyy + zzzzzzzzzzz)
+xxxxxxxxxxxxxxxx = f"aaaaaaaaaaaaaaaaaaaaa {expression} bbbbbbbbbbbbbbbbbbbbbbbb" + (
+ yyyyyyyyyyyyyy + zzzzzzzzzzz
+)
# This is not a multiline f-string, but the expression is too long so it should be
# wrapped in parentheses.
-f"hellooooooooooooooooooooooo \
- worlddddddddddddddddddddddddddddddddd" + (
- aaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbb
+(
+ f"hellooooooooooooooooooooooo \
+ worlddddddddddddddddddddddddddddddddd"
+ + (aaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbb)
)
-aaaaaaaaaaa = f"hellooooooooooooooooooooooo \
- worlddddddddddddddddddddddddddddddddd" + (
- aaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbb
+aaaaaaaaaaa = (
+ f"hellooooooooooooooooooooooo \
+ worlddddddddddddddddddddddddddddddddd"
+ + (aaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbb)
)
```

View file

@ -399,27 +399,9 @@ c = (
"dddddddddddddddddddddddddd" % aaaaaaaaaaaa + x "dddddddddddddddddddddddddd" % aaaaaaaaaaaa + x
) )
"a" "b" "c" + "d" "e" + "f" "g" + "h" "i" "j" "abc" + "de" + "fg" + "hij"
class EC2REPATH: class EC2REPATH:
f.write("Pathway name" + "\t" "Database Identifier" + "\t" "Source database" + "\n") f.write("Pathway name" + "\tDatabase Identifier" + "\tSource database" + "\n")
```
## Preview changes
```diff
--- Stable
+++ Preview
@@ -197,8 +197,8 @@
"dddddddddddddddddddddddddd" % aaaaaaaaaaaa + x
)
-"a" "b" "c" + "d" "e" + "f" "g" + "h" "i" "j"
+"abc" + "de" + "fg" + "hij"
class EC2REPATH:
- f.write("Pathway name" + "\t" "Database Identifier" + "\t" "Source database" + "\n")
+ f.write("Pathway name" + "\tDatabase Identifier" + "\tSource database" + "\n")
``` ```

View file

@ -211,9 +211,9 @@ String \"\"\"
# String continuation # String continuation
b"Let's" b"start" b"with" b"a" b"simple" b"example" b"Let'sstartwithasimpleexample"
b"Let's" b"start" b"with" b"a" b"simple" b"example" b"now repeat after me:" b"I am confident" b"I am confident" b"I am confident" b"I am confident" b"I am confident" b"Let'sstartwithasimpleexamplenow repeat after me:I am confidentI am confidentI am confidentI am confidentI am confident"
( (
b"Let's" b"Let's"
@ -280,39 +280,12 @@ test_particular = [
] ]
# Parenthesized string continuation with messed up indentation # Parenthesized string continuation with messed up indentation
{"key": ([], b"a" b"b" b"c")} {"key": ([], b"abc")}
b"Unicode Escape sequence don't apply to bytes: \N{0x} \u{ABCD} \U{ABCDEFGH}" b"Unicode Escape sequence don't apply to bytes: \N{0x} \u{ABCD} \U{ABCDEFGH}"
``` ```
#### Preview changes
```diff
--- Stable
+++ Preview
@@ -63,9 +63,9 @@
# String continuation
-b"Let's" b"start" b"with" b"a" b"simple" b"example"
+b"Let'sstartwithasimpleexample"
-b"Let's" b"start" b"with" b"a" b"simple" b"example" b"now repeat after me:" b"I am confident" b"I am confident" b"I am confident" b"I am confident" b"I am confident"
+b"Let'sstartwithasimpleexamplenow repeat after me:I am confidentI am confidentI am confidentI am confidentI am confident"
(
b"Let's"
@@ -132,6 +132,6 @@
]
# Parenthesized string continuation with messed up indentation
-{"key": ([], b"a" b"b" b"c")}
+{"key": ([], b"abc")}
b"Unicode Escape sequence don't apply to bytes: \N{0x} \u{ABCD} \U{ABCDEFGH}"
```
### Output 2 ### Output 2
``` ```
indent-style = space indent-style = space
@ -394,9 +367,9 @@ String \"\"\"
# String continuation # String continuation
b"Let's" b'start' b'with' b'a' b'simple' b'example' b"Let'sstartwithasimpleexample"
b"Let's" b'start' b'with' b'a' b'simple' b'example' b'now repeat after me:' b'I am confident' b'I am confident' b'I am confident' b'I am confident' b'I am confident' b"Let'sstartwithasimpleexamplenow repeat after me:I am confidentI am confidentI am confidentI am confidentI am confident"
( (
b"Let's" b"Let's"
@ -463,34 +436,7 @@ test_particular = [
] ]
# Parenthesized string continuation with messed up indentation # Parenthesized string continuation with messed up indentation
{'key': ([], b'a' b'b' b'c')} {'key': ([], b'abc')}
b"Unicode Escape sequence don't apply to bytes: \N{0x} \u{ABCD} \U{ABCDEFGH}" b"Unicode Escape sequence don't apply to bytes: \N{0x} \u{ABCD} \U{ABCDEFGH}"
``` ```
#### Preview changes
```diff
--- Stable
+++ Preview
@@ -63,9 +63,9 @@
# String continuation
-b"Let's" b'start' b'with' b'a' b'simple' b'example'
+b"Let'sstartwithasimpleexample"
-b"Let's" b'start' b'with' b'a' b'simple' b'example' b'now repeat after me:' b'I am confident' b'I am confident' b'I am confident' b'I am confident' b'I am confident'
+b"Let'sstartwithasimpleexamplenow repeat after me:I am confidentI am confidentI am confidentI am confidentI am confident"
(
b"Let's"
@@ -132,6 +132,6 @@
]
# Parenthesized string continuation with messed up indentation
-{'key': ([], b'a' b'b' b'c')}
+{'key': ([], b'abc')}
b"Unicode Escape sequence don't apply to bytes: \N{0x} \u{ABCD} \U{ABCDEFGH}"
```

View file

@ -355,22 +355,7 @@ f"aaaaaaaaaaaaaaaa \
) )
``` ```
## Outputs ## Output
### Output 1
```
indent-style = space
line-width = 88
indent-width = 4
quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Enabled
target_version = Py39
source_type = Python
```
```python ```python
"aaaaaaaaabbbbbbbbbbbbbbbbbbbb" # Join "aaaaaaaaabbbbbbbbbbbbbbbbbbbb" # Join
@ -616,12 +601,9 @@ assert (
############################################################################## ##############################################################################
# Use can_omit_optional_parentheses layout to avoid an instability where the formatter # Use can_omit_optional_parentheses layout to avoid an instability where the formatter
# picks the can_omit_optional_parentheses layout when the strings are joined. # picks the can_omit_optional_parentheses layout when the strings are joined.
if f"implicitconcatenatedstring" + f"implicitconcadddddddddddedring" * len([ if f"implicitconcatenatedstring" + f"implicitconcadddddddddddedring" * len(
aaaaaa, [aaaaaa, bbbbbbbbbbbbbbbb, cccccccccccccccccc, ddddddddddddddddddddddddddd]
bbbbbbbbbbbbbbbb, ):
cccccccccccccccccc,
ddddddddddddddddddddddddddd,
]):
pass pass
# Keep parenthesizing multiline - implicit concatenated strings # Keep parenthesizing multiline - implicit concatenated strings
@ -792,3 +774,26 @@ f"aaaaaaaaaaaaaaaa \
ddddddddddddddddddd" # comment 4 ddddddddddddddddddd" # comment 4
) )
``` ```
## Preview changes
```diff
--- Stable
+++ Preview
@@ -242,9 +242,12 @@
##############################################################################
# Use can_omit_optional_parentheses layout to avoid an instability where the formatter
# picks the can_omit_optional_parentheses layout when the strings are joined.
-if f"implicitconcatenatedstring" + f"implicitconcadddddddddddedring" * len(
- [aaaaaa, bbbbbbbbbbbbbbbb, cccccccccccccccccc, ddddddddddddddddddddddddddd]
-):
+if f"implicitconcatenatedstring" + f"implicitconcadddddddddddedring" * len([
+ aaaaaa,
+ bbbbbbbbbbbbbbbb,
+ cccccccccccccccccc,
+ ddddddddddddddddddddddddddd,
+]):
pass
# Keep parenthesizing multiline - implicit concatenated strings
```

View file

@ -1,7 +1,6 @@
--- ---
source: crates/ruff_python_formatter/tests/fixtures.rs source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/join_implicit_concatenated_string_assignment.py input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/join_implicit_concatenated_string_assignment.py
snapshot_kind: text
--- ---
## Input ## Input
```python ```python
@ -300,22 +299,7 @@ aaaaa[aaaaaaaaaaa] = (
) )
``` ```
## Outputs ## Output
### Output 1
```
indent-style = space
line-width = 88
indent-width = 4
quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Enabled
target_version = Py39
source_type = Python
```
```python ```python
## Implicit concatenated strings with a trailing comment but a non splittable target. ## Implicit concatenated strings with a trailing comment but a non splittable target.

View file

@ -1,7 +1,6 @@
--- ---
source: crates/ruff_python_formatter/tests/fixtures.rs source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/join_implicit_concatenated_string_preserve.py input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/join_implicit_concatenated_string_preserve.py
snapshot_kind: text
--- ---
## Input ## Input
```python ```python
@ -31,7 +30,7 @@ line-ending = LineFeed
magic-trailing-comma = Respect magic-trailing-comma = Respect
docstring-code = Disabled docstring-code = Disabled
docstring-code-line-width = "dynamic" docstring-code-line-width = "dynamic"
preview = Enabled preview = Disabled
target_version = Py39 target_version = Py39
source_type = Python source_type = Python
``` ```
@ -63,7 +62,7 @@ line-ending = LineFeed
magic-trailing-comma = Respect magic-trailing-comma = Respect
docstring-code = Disabled docstring-code = Disabled
docstring-code-line-width = "dynamic" docstring-code-line-width = "dynamic"
preview = Enabled preview = Disabled
target_version = Py312 target_version = Py312
source_type = Python source_type = Python
``` ```

View file

@ -323,24 +323,20 @@ aaaaaaaaaaaaaaaaaaaaa = [
# Leading expression comments: # Leading expression comments:
y = [ y = [
a a
for for (
(
# comment # comment
a a
) in ) in (
(
# comment # comment
x x
) )
if if (
(
# asdasd # asdasd
"askldaklsdnmklasmdlkasmdlkasmdlkasmdasd" "askldaklsdnmklasmdlkasmdlkasmdlkasmdasd"
!= "as,mdnaskldmlkasdmlaksdmlkasdlkasdm" != "as,mdnaskldmlkasdmlaksdmlkasdlkasdm"
and "zxcm,.nzxclm,zxnckmnzxckmnzxczxc" != "zxcasdasdlmnasdlknaslkdnmlaskdm" and "zxcm,.nzxclm,zxnckmnzxckmnzxczxc" != "zxcasdasdlmnasdlknaslkdnmlaskdm"
) )
if if (
(
# comment # comment
x x
) )
@ -383,39 +379,3 @@ y = [
x x
] ]
``` ```
## Preview changes
```diff
--- Stable
+++ Preview
@@ -142,24 +142,20 @@
# Leading expression comments:
y = [
a
- for
- (
+ for (
# comment
a
- ) in
- (
+ ) in (
# comment
x
)
- if
- (
+ if (
# asdasd
"askldaklsdnmklasmdlkasmdlkasmdlkasmdasd"
!= "as,mdnaskldmlkasdmlaksdmlkasdlkasdm"
and "zxcm,.nzxclm,zxnckmnzxckmnzxczxc" != "zxcasdasdlmnasdlknaslkdnmlaskdm"
)
- if
- (
+ if (
# comment
x
)
```

View file

@ -310,9 +310,9 @@ String \"\"\"
# String continuation # String continuation
"Let's" "start" "with" "a" "simple" "example" "Let'sstartwithasimpleexample"
"Let's" "start" "with" "a" "simple" "example" "now repeat after me:" "I am confident" "I am confident" "I am confident" "I am confident" "I am confident" "Let'sstartwithasimpleexamplenow repeat after me:I am confidentI am confidentI am confidentI am confidentI am confident"
( (
"Let's" "Let's"
@ -379,7 +379,7 @@ test_particular = [
] ]
# Parenthesized string continuation with messed up indentation # Parenthesized string continuation with messed up indentation
{"key": ([], "a" "b" "c")} {"key": ([], "abc")}
# Regression test for https://github.com/astral-sh/ruff/issues/5893 # Regression test for https://github.com/astral-sh/ruff/issues/5893
@ -412,19 +412,31 @@ a = """\\\x1f"""
# In preview, don't collapse implicit concatenated strings that can't be joined into a single string # In preview, don't collapse implicit concatenated strings that can't be joined into a single string
# and that are multiline in the source. # and that are multiline in the source.
(r"aaaaaaaaa" r"bbbbbbbbbbbbbbbbbbbb") (
r"aaaaaaaaa"
(r"aaaaaaaaa" r"bbbbbbbbbbbbbbbbbbbb" "cccccccccccccccccccccc") r"bbbbbbbbbbbbbbbbbbbb"
)
("""aaaaaaaaa""" """bbbbbbbbbbbbbbbbbbbb""")
( (
f"""aaaa{ r"aaaaaaaaa"
10}aaaaa""" r"bbbbbbbbbbbbbbbbbbbb"
"cccccccccccccccccccccc"
)
(
"""aaaaaaaaa"""
"""bbbbbbbbbbbbbbbbbbbb"""
)
(
f"""aaaa{10}aaaaa"""
rf"""bbbbbbbbbbbbbbbbbbbb""" rf"""bbbbbbbbbbbbbbbbbbbb"""
) )
if (r"aaaaaaaaa" r"bbbbbbbbbbbbbbbbbbbb") + ["aaaaaaaaaa", "bbbbbbbbbbbbbbbb"]: if (
r"aaaaaaaaa"
r"bbbbbbbbbbbbbbbbbbbb"
) + ["aaaaaaaaaa", "bbbbbbbbbbbbbbbb"]:
... ...
@ -433,24 +445,19 @@ if (r"aaaaaaaaa" r"bbbbbbbbbbbbbbbbbbbb") + ["aaaaaaaaaa", "bbbbbbbbbbbbbbbb"]:
r"aaaaaaaaa" r"bbbbbbbbbbbbbbbbbbbb" r"aaaaaaaaa" r"bbbbbbbbbbbbbbbbbbbb"
( (f"aaaa{10}aaaaa" rf"bbbbbbbbbbbbbbbbbbbb")
f"aaaa{
10}aaaaa"
rf"bbbbbbbbbbbbbbbbbbbb"
)
(r"""aaaaaaaaa""" r"""bbbbbbbbbbbbbbbbbbbb""") (r"""aaaaaaaaa""" r"""bbbbbbbbbbbbbbbbbbbb""")
( (f"""aaaa{10}aaaaa""" rf"""bbbbbbbbbbbbbbbbbbbb""")
f"""aaaa{
10}aaaaa"""
rf"""bbbbbbbbbbbbbbbbbbbb"""
)
# In docstring positions # In docstring positions
def docstring(): def docstring():
r"aaaaaaaaa" "bbbbbbbbbbbbbbbbbbbb" (
r"aaaaaaaaa"
"bbbbbbbbbbbbbbbbbbbb"
)
def docstring_flat(): def docstring_flat():
@ -465,103 +472,6 @@ def docstring_flat_overlong():
``` ```
#### Preview changes
```diff
--- Stable
+++ Preview
@@ -70,9 +70,9 @@
# String continuation
-"Let's" "start" "with" "a" "simple" "example"
+"Let'sstartwithasimpleexample"
-"Let's" "start" "with" "a" "simple" "example" "now repeat after me:" "I am confident" "I am confident" "I am confident" "I am confident" "I am confident"
+"Let'sstartwithasimpleexamplenow repeat after me:I am confidentI am confidentI am confidentI am confidentI am confident"
(
"Let's"
@@ -139,7 +139,7 @@
]
# Parenthesized string continuation with messed up indentation
-{"key": ([], "a" "b" "c")}
+{"key": ([], "abc")}
# Regression test for https://github.com/astral-sh/ruff/issues/5893
@@ -172,19 +172,31 @@
# In preview, don't collapse implicit concatenated strings that can't be joined into a single string
# and that are multiline in the source.
-(r"aaaaaaaaa" r"bbbbbbbbbbbbbbbbbbbb")
+(
+ r"aaaaaaaaa"
+ r"bbbbbbbbbbbbbbbbbbbb"
+)
-(r"aaaaaaaaa" r"bbbbbbbbbbbbbbbbbbbb" "cccccccccccccccccccccc")
+(
+ r"aaaaaaaaa"
+ r"bbbbbbbbbbbbbbbbbbbb"
+ "cccccccccccccccccccccc"
+)
-("""aaaaaaaaa""" """bbbbbbbbbbbbbbbbbbbb""")
+(
+ """aaaaaaaaa"""
+ """bbbbbbbbbbbbbbbbbbbb"""
+)
(
- f"""aaaa{
- 10}aaaaa"""
+ f"""aaaa{10}aaaaa"""
rf"""bbbbbbbbbbbbbbbbbbbb"""
)
-if (r"aaaaaaaaa" r"bbbbbbbbbbbbbbbbbbbb") + ["aaaaaaaaaa", "bbbbbbbbbbbbbbbb"]:
+if (
+ r"aaaaaaaaa"
+ r"bbbbbbbbbbbbbbbbbbbb"
+) + ["aaaaaaaaaa", "bbbbbbbbbbbbbbbb"]:
...
@@ -193,24 +205,19 @@
r"aaaaaaaaa" r"bbbbbbbbbbbbbbbbbbbb"
-(
- f"aaaa{
- 10}aaaaa"
- rf"bbbbbbbbbbbbbbbbbbbb"
-)
+(f"aaaa{10}aaaaa" rf"bbbbbbbbbbbbbbbbbbbb")
(r"""aaaaaaaaa""" r"""bbbbbbbbbbbbbbbbbbbb""")
-(
- f"""aaaa{
- 10}aaaaa"""
- rf"""bbbbbbbbbbbbbbbbbbbb"""
-)
+(f"""aaaa{10}aaaaa""" rf"""bbbbbbbbbbbbbbbbbbbb""")
# In docstring positions
def docstring():
- r"aaaaaaaaa" "bbbbbbbbbbbbbbbbbbbb"
+ (
+ r"aaaaaaaaa"
+ "bbbbbbbbbbbbbbbbbbbb"
+ )
def docstring_flat():
```
### Output 2 ### Output 2
``` ```
indent-style = space indent-style = space
@ -650,9 +560,9 @@ String \"\"\"
# String continuation # String continuation
"Let's" 'start' 'with' 'a' 'simple' 'example' "Let'sstartwithasimpleexample"
"Let's" 'start' 'with' 'a' 'simple' 'example' 'now repeat after me:' 'I am confident' 'I am confident' 'I am confident' 'I am confident' 'I am confident' "Let'sstartwithasimpleexamplenow repeat after me:I am confidentI am confidentI am confidentI am confidentI am confident"
( (
"Let's" "Let's"
@ -719,7 +629,7 @@ test_particular = [
] ]
# Parenthesized string continuation with messed up indentation # Parenthesized string continuation with messed up indentation
{'key': ([], 'a' 'b' 'c')} {'key': ([], 'abc')}
# Regression test for https://github.com/astral-sh/ruff/issues/5893 # Regression test for https://github.com/astral-sh/ruff/issues/5893
@ -752,19 +662,31 @@ a = """\\\x1f"""
# In preview, don't collapse implicit concatenated strings that can't be joined into a single string # In preview, don't collapse implicit concatenated strings that can't be joined into a single string
# and that are multiline in the source. # and that are multiline in the source.
(r'aaaaaaaaa' r'bbbbbbbbbbbbbbbbbbbb') (
r'aaaaaaaaa'
(r'aaaaaaaaa' r'bbbbbbbbbbbbbbbbbbbb' 'cccccccccccccccccccccc') r'bbbbbbbbbbbbbbbbbbbb'
)
("""aaaaaaaaa""" """bbbbbbbbbbbbbbbbbbbb""")
( (
f"""aaaa{ r'aaaaaaaaa'
10}aaaaa""" r'bbbbbbbbbbbbbbbbbbbb'
'cccccccccccccccccccccc'
)
(
"""aaaaaaaaa"""
"""bbbbbbbbbbbbbbbbbbbb"""
)
(
f"""aaaa{10}aaaaa"""
rf"""bbbbbbbbbbbbbbbbbbbb""" rf"""bbbbbbbbbbbbbbbbbbbb"""
) )
if (r'aaaaaaaaa' r'bbbbbbbbbbbbbbbbbbbb') + ['aaaaaaaaaa', 'bbbbbbbbbbbbbbbb']: if (
r'aaaaaaaaa'
r'bbbbbbbbbbbbbbbbbbbb'
) + ['aaaaaaaaaa', 'bbbbbbbbbbbbbbbb']:
... ...
@ -773,24 +695,19 @@ if (r'aaaaaaaaa' r'bbbbbbbbbbbbbbbbbbbb') + ['aaaaaaaaaa', 'bbbbbbbbbbbbbbbb']:
r'aaaaaaaaa' r'bbbbbbbbbbbbbbbbbbbb' r'aaaaaaaaa' r'bbbbbbbbbbbbbbbbbbbb'
( (f'aaaa{10}aaaaa' rf'bbbbbbbbbbbbbbbbbbbb')
f'aaaa{
10}aaaaa'
rf'bbbbbbbbbbbbbbbbbbbb'
)
(r"""aaaaaaaaa""" r"""bbbbbbbbbbbbbbbbbbbb""") (r"""aaaaaaaaa""" r"""bbbbbbbbbbbbbbbbbbbb""")
( (f"""aaaa{10}aaaaa""" rf"""bbbbbbbbbbbbbbbbbbbb""")
f"""aaaa{
10}aaaaa"""
rf"""bbbbbbbbbbbbbbbbbbbb"""
)
# In docstring positions # In docstring positions
def docstring(): def docstring():
r'aaaaaaaaa' 'bbbbbbbbbbbbbbbbbbbb' (
r'aaaaaaaaa'
'bbbbbbbbbbbbbbbbbbbb'
)
def docstring_flat(): def docstring_flat():
@ -803,100 +720,3 @@ def docstring_flat_overlong():
r'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' r'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
) )
``` ```
#### Preview changes
```diff
--- Stable
+++ Preview
@@ -70,9 +70,9 @@
# String continuation
-"Let's" 'start' 'with' 'a' 'simple' 'example'
+"Let'sstartwithasimpleexample"
-"Let's" 'start' 'with' 'a' 'simple' 'example' 'now repeat after me:' 'I am confident' 'I am confident' 'I am confident' 'I am confident' 'I am confident'
+"Let'sstartwithasimpleexamplenow repeat after me:I am confidentI am confidentI am confidentI am confidentI am confident"
(
"Let's"
@@ -139,7 +139,7 @@
]
# Parenthesized string continuation with messed up indentation
-{'key': ([], 'a' 'b' 'c')}
+{'key': ([], 'abc')}
# Regression test for https://github.com/astral-sh/ruff/issues/5893
@@ -172,19 +172,31 @@
# In preview, don't collapse implicit concatenated strings that can't be joined into a single string
# and that are multiline in the source.
-(r'aaaaaaaaa' r'bbbbbbbbbbbbbbbbbbbb')
+(
+ r'aaaaaaaaa'
+ r'bbbbbbbbbbbbbbbbbbbb'
+)
-(r'aaaaaaaaa' r'bbbbbbbbbbbbbbbbbbbb' 'cccccccccccccccccccccc')
+(
+ r'aaaaaaaaa'
+ r'bbbbbbbbbbbbbbbbbbbb'
+ 'cccccccccccccccccccccc'
+)
-("""aaaaaaaaa""" """bbbbbbbbbbbbbbbbbbbb""")
+(
+ """aaaaaaaaa"""
+ """bbbbbbbbbbbbbbbbbbbb"""
+)
(
- f"""aaaa{
- 10}aaaaa"""
+ f"""aaaa{10}aaaaa"""
rf"""bbbbbbbbbbbbbbbbbbbb"""
)
-if (r'aaaaaaaaa' r'bbbbbbbbbbbbbbbbbbbb') + ['aaaaaaaaaa', 'bbbbbbbbbbbbbbbb']:
+if (
+ r'aaaaaaaaa'
+ r'bbbbbbbbbbbbbbbbbbbb'
+) + ['aaaaaaaaaa', 'bbbbbbbbbbbbbbbb']:
...
@@ -193,24 +205,19 @@
r'aaaaaaaaa' r'bbbbbbbbbbbbbbbbbbbb'
-(
- f'aaaa{
- 10}aaaaa'
- rf'bbbbbbbbbbbbbbbbbbbb'
-)
+(f'aaaa{10}aaaaa' rf'bbbbbbbbbbbbbbbbbbbb')
(r"""aaaaaaaaa""" r"""bbbbbbbbbbbbbbbbbbbb""")
-(
- f"""aaaa{
- 10}aaaaa"""
- rf"""bbbbbbbbbbbbbbbbbbbb"""
-)
+(f"""aaaa{10}aaaaa""" rf"""bbbbbbbbbbbbbbbbbbbb""")
# In docstring positions
def docstring():
- r'aaaaaaaaa' 'bbbbbbbbbbbbbbbbbbbb'
+ (
+ r'aaaaaaaaa'
+ 'bbbbbbbbbbbbbbbbbbbb'
+ )
def docstring_flat():
```

View file

@ -210,7 +210,7 @@ yield (
) )
) )
yield "Cache key will cause errors if used with memcached: %r " "(longer than %s)" % ( yield "Cache key will cause errors if used with memcached: %r (longer than %s)" % (
key, key,
MEMCACHE_MAX_KEY_LENGTH, MEMCACHE_MAX_KEY_LENGTH,
) )
@ -228,8 +228,7 @@ yield (
"Django to create, modify, and delete the table" "Django to create, modify, and delete the table"
) )
yield ( yield (
"# Feel free to rename the models, but don't rename db_table values or " "# Feel free to rename the models, but don't rename db_table values or field names."
"field names."
) )
yield ( yield (
@ -241,8 +240,7 @@ yield (
"Django to create, modify, and delete the table" "Django to create, modify, and delete the table"
) )
yield ( yield (
"# Feel free to rename the models, but don't rename db_table values or " "# Feel free to rename the models, but don't rename db_table values or field names."
"field names."
) )
# Regression test for: https://github.com/astral-sh/ruff/issues/7420 # Regression test for: https://github.com/astral-sh/ruff/issues/7420
@ -278,39 +276,3 @@ result = yield (
print((yield x)) print((yield x))
``` ```
## Preview changes
```diff
--- Stable
+++ Preview
@@ -78,7 +78,7 @@
)
)
-yield "Cache key will cause errors if used with memcached: %r " "(longer than %s)" % (
+yield "Cache key will cause errors if used with memcached: %r (longer than %s)" % (
key,
MEMCACHE_MAX_KEY_LENGTH,
)
@@ -96,8 +96,7 @@
"Django to create, modify, and delete the table"
)
yield (
- "# Feel free to rename the models, but don't rename db_table values or "
- "field names."
+ "# Feel free to rename the models, but don't rename db_table values or field names."
)
yield (
@@ -109,8 +108,7 @@
"Django to create, modify, and delete the table"
)
yield (
- "# Feel free to rename the models, but don't rename db_table values or "
- "field names."
+ "# Feel free to rename the models, but don't rename db_table values or field names."
)
# Regression test for: https://github.com/astral-sh/ruff/issues/7420
```

View file

@ -304,31 +304,43 @@ match x:
# Patterns that use BestFit should be parenthesized if they exceed the configured line width # Patterns that use BestFit should be parenthesized if they exceed the configured line width
# but fit within parentheses. # but fit within parentheses.
match x: match x:
case "averyLongStringThatGetsParenthesizedOnceItExceedsTheConfiguredLineWidthFitsPar": case (
"averyLongStringThatGetsParenthesizedOnceItExceedsTheConfiguredLineWidthFitsPar"
):
pass pass
match x: match x:
case b"averyLongStringThatGetsParenthesizedOnceItExceedsTheConfiguredLineWidthFitsPa": case (
b"averyLongStringThatGetsParenthesizedOnceItExceedsTheConfiguredLineWidthFitsPa"
):
pass pass
match x: match x:
case f"averyLongStringThatGetsParenthesizedOnceItExceedsTheConfiguredLineWidthFitsPa": case (
f"averyLongStringThatGetsParenthesizedOnceItExceedsTheConfiguredLineWidthFitsPa"
):
pass pass
match x: match x:
case 5444444444444444444444444444444444444444444444444444444444444444444444444444444j: case (
5444444444444444444444444444444444444444444444444444444444444444444444444444444j
):
pass pass
match x: match x:
case 5444444444444444444444444444444444444444444444444444444444444444444444444444444: case (
5444444444444444444444444444444444444444444444444444444444444444444444444444444
):
pass pass
match x: match x:
case 5.44444444444444444444444444444444444444444444444444444444444444444444444444444: case (
5.44444444444444444444444444444444444444444444444444444444444444444444444444444
):
pass pass
@ -385,108 +397,89 @@ match x:
match x: match x:
case "an implicit concatenated" "string literal" "in a match case" "that goes over multiple lines": case (
"an implicit concatenated"
"string literal"
"in a match case"
"that goes over multiple lines"
):
pass pass
## Patterns ending with a sequence, mapping, class, or parenthesized pattern should break the parenthesized-like pattern first ## Patterns ending with a sequence, mapping, class, or parenthesized pattern should break the parenthesized-like pattern first
match x: match x:
case ( case A | [
A aaaaaa,
| [ bbbbbbbbbbbbbbbb,
aaaaaa, cccccccccccccccccc,
bbbbbbbbbbbbbbbb, ddddddddddddddddddddddddddd,
cccccccccccccccccc, ]:
ddddddddddddddddddddddddddd,
]
):
pass pass
match x: match x:
case ( case A | (
A aaaaaa,
| ( bbbbbbbbbbbbbbbb,
aaaaaa, cccccccccccccccccc,
bbbbbbbbbbbbbbbb, ddddddddddddddddddddddddddd,
cccccccccccccccccc,
ddddddddddddddddddddddddddd,
)
): ):
pass pass
match x: match x:
case ( case A | {
A "a": aaaaaa,
| { "b": bbbbbbbbbbbbbbbb,
"a": aaaaaa, "c": cccccccccccccccccc,
"b": bbbbbbbbbbbbbbbb, "d": ddddddddddddddddddddddddddd,
"c": cccccccccccccccccc, }:
"d": ddddddddddddddddddddddddddd, pass
}
match x:
case A | Class(
aaaaaa,
bbbbbbbbbbbbbbbb,
cccccccccccccccccc,
ddddddddddddddddddddddddddd,
): ):
pass pass
match x: match x:
case ( case A | (
A aaaaaaaaaaaaaaaaaaa.bbbbbbbbbbbbbbbbbbbbbbb.cccccccccccccccccccccccccccc.ddddddddddddddddddddddd
| Class(
aaaaaa,
bbbbbbbbbbbbbbbb,
cccccccccccccccccc,
ddddddddddddddddddddddddddd,
)
):
pass
match x:
case (
A
| (
aaaaaaaaaaaaaaaaaaa.bbbbbbbbbbbbbbbbbbbbbbb.cccccccccccccccccccccccccccc.ddddddddddddddddddddddd
)
): ):
pass pass
## Patterns starting with a sequence, mapping, class, or parenthesized pattern should break the parenthesized-like pattern first ## Patterns starting with a sequence, mapping, class, or parenthesized pattern should break the parenthesized-like pattern first
match x: match x:
case ( case [
[ aaaaaa,
aaaaaa, bbbbbbbbbbbbbbbb,
bbbbbbbbbbbbbbbb, cccccccccccccccccc,
cccccccccccccccccc, ddddddddddddddddddddddddddd,
ddddddddddddddddddddddddddd, ] | A:
]
| A
):
pass pass
match x: match x:
case ( case (
( aaaaaa,
aaaaaa, bbbbbbbbbbbbbbbb,
bbbbbbbbbbbbbbbb, cccccccccccccccccc,
cccccccccccccccccc, ddddddddddddddddddddddddddd,
ddddddddddddddddddddddddddd, ) | A:
)
| A
):
pass pass
match x: match x:
case ( case {
{ "a": aaaaaa,
"a": aaaaaa, "b": bbbbbbbbbbbbbbbb,
"b": bbbbbbbbbbbbbbbb, "c": cccccccccccccccccc,
"c": cccccccccccccccccc, "d": ddddddddddddddddddddddddddd,
"d": ddddddddddddddddddddddddddd, } | A:
}
| A
):
pass pass
@ -503,8 +496,7 @@ match x:
## Not for non-parenthesized sequence patterns ## Not for non-parenthesized sequence patterns
match x: match x:
case ( case (
(1) (1) | aaaaaaaaaaaaaaaaaaaaaaaaaaaa,
| aaaaaaaaaaaaaaaaaaaaaaaaaaaa,
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb,
ccccccccccccccccccccccccccccccccc, ccccccccccccccccccccccccccccccccc,
): ):
@ -523,448 +515,81 @@ match x:
## Always use parentheses for implicitly concatenated strings ## Always use parentheses for implicitly concatenated strings
match x: match x:
case ( case "implicitconcatenatedstring" | [
"implicit" "concatenated" "string" aaaaaa,
| [aaaaaa, bbbbbbbbbbbbbbbb, cccccccccccccccccc, ddddddddddddddddddddddddddd] bbbbbbbbbbbbbbbb,
): cccccccccccccccccc,
ddddddddddddddddddddddddddd,
]:
pass pass
match x: match x:
case ( case b"implicitconcatenatedstring" | [
b"implicit" b"concatenated" b"string" aaaaaa,
| [aaaaaa, bbbbbbbbbbbbbbbb, cccccccccccccccccc, ddddddddddddddddddddddddddd] bbbbbbbbbbbbbbbb,
): cccccccccccccccccc,
ddddddddddddddddddddddddddd,
]:
pass pass
match x: match x:
case ( case f"implicitconcatenatedstring" | [
f"implicit" "concatenated" "string" aaaaaa,
| [aaaaaa, bbbbbbbbbbbbbbbb, cccccccccccccccccc, ddddddddddddddddddddddddddd] bbbbbbbbbbbbbbbb,
): cccccccccccccccccc,
ddddddddddddddddddddddddddd,
]:
pass pass
## Complex number expressions and unary expressions ## Complex number expressions and unary expressions
match x: match x:
case ( case 4 - 3j | [
4 - 3j aaaaaaaaaaaaaaaaaaaaaaaa,
| [ bbbbbbbbbbbbbbbbbbbbbbbbbbbb,
aaaaaaaaaaaaaaaaaaaaaaaa, cccccccccccccccccccccccccccccccccccccccc,
bbbbbbbbbbbbbbbbbbbbbbbbbbbb, ]:
cccccccccccccccccccccccccccccccccccccccc,
]
):
pass pass
match x: match x:
case ( case 4 + 3j | [
4 + 3j aaaaaaaaaaaaaaaaaaaaaaaa,
| [ bbbbbbbbbbbbbbbbbbbbbbbbbbbb,
aaaaaaaaaaaaaaaaaaaaaaaa, cccccccccccccccccccccccccccccccccccccccc,
bbbbbbbbbbbbbbbbbbbbbbbbbbbb, ]:
cccccccccccccccccccccccccccccccccccccccc,
]
):
pass pass
match x: match x:
case ( case -1 | [
-1 aaaaaaaaaaaaaaaaaaaaaaaaaaaa,
| [ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb,
aaaaaaaaaaaaaaaaaaaaaaaaaaaa, ccccccccccccccccccccccccccccccccc,
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, ]:
ccccccccccccccccccccccccccccccccc,
]
):
pass pass
### Parenthesized patterns ### Parenthesized patterns
match x: match x:
case ( case (1) | [
(1) aaaaaaaaaaaaaaaaaaaaaaaaaaaa,
| [ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb,
aaaaaaaaaaaaaaaaaaaaaaaaaaaa, ccccccccccccccccccccccccccccccccc,
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, ]:
ccccccccccccccccccccccccccccccccc,
]
):
pass pass
match x: match x:
case ( case ( # comment
( # comment 1
1 ) | [
) aaaaaaaaaaaaaaaaaaaaaaaaaaaa,
| [ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb,
aaaaaaaaaaaaaaaaaaaaaaaaaaaa, ccccccccccccccccccccccccccccccccc,
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, ]:
ccccccccccccccccccccccccccccccccc,
]
):
pass pass
``` ```
## Preview changes
```diff
--- Stable
+++ Preview
@@ -1,31 +1,43 @@
# Patterns that use BestFit should be parenthesized if they exceed the configured line width
# but fit within parentheses.
match x:
- case "averyLongStringThatGetsParenthesizedOnceItExceedsTheConfiguredLineWidthFitsPar":
+ case (
+ "averyLongStringThatGetsParenthesizedOnceItExceedsTheConfiguredLineWidthFitsPar"
+ ):
pass
match x:
- case b"averyLongStringThatGetsParenthesizedOnceItExceedsTheConfiguredLineWidthFitsPa":
+ case (
+ b"averyLongStringThatGetsParenthesizedOnceItExceedsTheConfiguredLineWidthFitsPa"
+ ):
pass
match x:
- case f"averyLongStringThatGetsParenthesizedOnceItExceedsTheConfiguredLineWidthFitsPa":
+ case (
+ f"averyLongStringThatGetsParenthesizedOnceItExceedsTheConfiguredLineWidthFitsPa"
+ ):
pass
match x:
- case 5444444444444444444444444444444444444444444444444444444444444444444444444444444j:
+ case (
+ 5444444444444444444444444444444444444444444444444444444444444444444444444444444j
+ ):
pass
match x:
- case 5444444444444444444444444444444444444444444444444444444444444444444444444444444:
+ case (
+ 5444444444444444444444444444444444444444444444444444444444444444444444444444444
+ ):
pass
match x:
- case 5.44444444444444444444444444444444444444444444444444444444444444444444444444444:
+ case (
+ 5.44444444444444444444444444444444444444444444444444444444444444444444444444444
+ ):
pass
@@ -82,108 +94,89 @@
match x:
- case "an implicit concatenated" "string literal" "in a match case" "that goes over multiple lines":
+ case (
+ "an implicit concatenated"
+ "string literal"
+ "in a match case"
+ "that goes over multiple lines"
+ ):
pass
## Patterns ending with a sequence, mapping, class, or parenthesized pattern should break the parenthesized-like pattern first
match x:
- case (
- A
- | [
- aaaaaa,
- bbbbbbbbbbbbbbbb,
- cccccccccccccccccc,
- ddddddddddddddddddddddddddd,
- ]
- ):
+ case A | [
+ aaaaaa,
+ bbbbbbbbbbbbbbbb,
+ cccccccccccccccccc,
+ ddddddddddddddddddddddddddd,
+ ]:
pass
match x:
- case (
- A
- | (
- aaaaaa,
- bbbbbbbbbbbbbbbb,
- cccccccccccccccccc,
- ddddddddddddddddddddddddddd,
- )
+ case A | (
+ aaaaaa,
+ bbbbbbbbbbbbbbbb,
+ cccccccccccccccccc,
+ ddddddddddddddddddddddddddd,
):
pass
match x:
- case (
- A
- | {
- "a": aaaaaa,
- "b": bbbbbbbbbbbbbbbb,
- "c": cccccccccccccccccc,
- "d": ddddddddddddddddddddddddddd,
- }
- ):
+ case A | {
+ "a": aaaaaa,
+ "b": bbbbbbbbbbbbbbbb,
+ "c": cccccccccccccccccc,
+ "d": ddddddddddddddddddddddddddd,
+ }:
pass
match x:
- case (
- A
- | Class(
- aaaaaa,
- bbbbbbbbbbbbbbbb,
- cccccccccccccccccc,
- ddddddddddddddddddddddddddd,
- )
+ case A | Class(
+ aaaaaa,
+ bbbbbbbbbbbbbbbb,
+ cccccccccccccccccc,
+ ddddddddddddddddddddddddddd,
):
pass
match x:
- case (
- A
- | (
- aaaaaaaaaaaaaaaaaaa.bbbbbbbbbbbbbbbbbbbbbbb.cccccccccccccccccccccccccccc.ddddddddddddddddddddddd
- )
+ case A | (
+ aaaaaaaaaaaaaaaaaaa.bbbbbbbbbbbbbbbbbbbbbbb.cccccccccccccccccccccccccccc.ddddddddddddddddddddddd
):
pass
## Patterns starting with a sequence, mapping, class, or parenthesized pattern should break the parenthesized-like pattern first
match x:
- case (
- [
- aaaaaa,
- bbbbbbbbbbbbbbbb,
- cccccccccccccccccc,
- ddddddddddddddddddddddddddd,
- ]
- | A
- ):
+ case [
+ aaaaaa,
+ bbbbbbbbbbbbbbbb,
+ cccccccccccccccccc,
+ ddddddddddddddddddddddddddd,
+ ] | A:
pass
match x:
case (
- (
- aaaaaa,
- bbbbbbbbbbbbbbbb,
- cccccccccccccccccc,
- ddddddddddddddddddddddddddd,
- )
- | A
- ):
+ aaaaaa,
+ bbbbbbbbbbbbbbbb,
+ cccccccccccccccccc,
+ ddddddddddddddddddddddddddd,
+ ) | A:
pass
match x:
- case (
- {
- "a": aaaaaa,
- "b": bbbbbbbbbbbbbbbb,
- "c": cccccccccccccccccc,
- "d": ddddddddddddddddddddddddddd,
- }
- | A
- ):
+ case {
+ "a": aaaaaa,
+ "b": bbbbbbbbbbbbbbbb,
+ "c": cccccccccccccccccc,
+ "d": ddddddddddddddddddddddddddd,
+ } | A:
pass
@@ -200,8 +193,7 @@
## Not for non-parenthesized sequence patterns
match x:
case (
- (1)
- | aaaaaaaaaaaaaaaaaaaaaaaaaaaa,
+ (1) | aaaaaaaaaaaaaaaaaaaaaaaaaaaa,
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb,
ccccccccccccccccccccccccccccccccc,
):
@@ -220,89 +212,80 @@
## Always use parentheses for implicitly concatenated strings
match x:
- case (
- "implicit" "concatenated" "string"
- | [aaaaaa, bbbbbbbbbbbbbbbb, cccccccccccccccccc, ddddddddddddddddddddddddddd]
- ):
+ case "implicitconcatenatedstring" | [
+ aaaaaa,
+ bbbbbbbbbbbbbbbb,
+ cccccccccccccccccc,
+ ddddddddddddddddddddddddddd,
+ ]:
pass
match x:
- case (
- b"implicit" b"concatenated" b"string"
- | [aaaaaa, bbbbbbbbbbbbbbbb, cccccccccccccccccc, ddddddddddddddddddddddddddd]
- ):
+ case b"implicitconcatenatedstring" | [
+ aaaaaa,
+ bbbbbbbbbbbbbbbb,
+ cccccccccccccccccc,
+ ddddddddddddddddddddddddddd,
+ ]:
pass
match x:
- case (
- f"implicit" "concatenated" "string"
- | [aaaaaa, bbbbbbbbbbbbbbbb, cccccccccccccccccc, ddddddddddddddddddddddddddd]
- ):
+ case f"implicitconcatenatedstring" | [
+ aaaaaa,
+ bbbbbbbbbbbbbbbb,
+ cccccccccccccccccc,
+ ddddddddddddddddddddddddddd,
+ ]:
pass
## Complex number expressions and unary expressions
match x:
- case (
- 4 - 3j
- | [
- aaaaaaaaaaaaaaaaaaaaaaaa,
- bbbbbbbbbbbbbbbbbbbbbbbbbbbb,
- cccccccccccccccccccccccccccccccccccccccc,
- ]
- ):
+ case 4 - 3j | [
+ aaaaaaaaaaaaaaaaaaaaaaaa,
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbb,
+ cccccccccccccccccccccccccccccccccccccccc,
+ ]:
pass
match x:
- case (
- 4 + 3j
- | [
- aaaaaaaaaaaaaaaaaaaaaaaa,
- bbbbbbbbbbbbbbbbbbbbbbbbbbbb,
- cccccccccccccccccccccccccccccccccccccccc,
- ]
- ):
+ case 4 + 3j | [
+ aaaaaaaaaaaaaaaaaaaaaaaa,
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbb,
+ cccccccccccccccccccccccccccccccccccccccc,
+ ]:
pass
match x:
- case (
- -1
- | [
- aaaaaaaaaaaaaaaaaaaaaaaaaaaa,
- bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb,
- ccccccccccccccccccccccccccccccccc,
- ]
- ):
+ case -1 | [
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaa,
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb,
+ ccccccccccccccccccccccccccccccccc,
+ ]:
pass
### Parenthesized patterns
match x:
- case (
- (1)
- | [
- aaaaaaaaaaaaaaaaaaaaaaaaaaaa,
- bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb,
- ccccccccccccccccccccccccccccccccc,
- ]
- ):
+ case (1) | [
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaa,
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb,
+ ccccccccccccccccccccccccccccccccc,
+ ]:
pass
match x:
- case (
- ( # comment
- 1
- )
- | [
- aaaaaaaaaaaaaaaaaaaaaaaaaaaa,
- bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb,
- ccccccccccccccccccccccccccccccccc,
- ]
- ):
+ case ( # comment
+ 1
+ ) | [
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaa,
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb,
+ ccccccccccccccccccccccccccccccccc,
+ ]:
pass
```

View file

@ -109,10 +109,10 @@ rb"""rb double triple"""
rb"""br single triple""" rb"""br single triple"""
rb"""br double triple""" rb"""br double triple"""
'single1' 'single2' 'single1single2'
'single1' 'double2' 'single1double2'
'double1' 'single2' 'double1single2'
'double1' 'double2' 'double1double2'
def docstring_single_triple(): def docstring_single_triple():
@ -132,28 +132,6 @@ def docstring_single():
``` ```
#### Preview changes
```diff
--- Stable
+++ Preview
@@ -33,10 +33,10 @@
rb"""br single triple"""
rb"""br double triple"""
-'single1' 'single2'
-'single1' 'double2'
-'double1' 'single2'
-'double1' 'double2'
+'single1single2'
+'single1double2'
+'double1single2'
+'double1double2'
def docstring_single_triple():
```
### Output 2 ### Output 2
``` ```
indent-style = space indent-style = space
@ -205,10 +183,10 @@ rb"""rb double triple"""
rb"""br single triple""" rb"""br single triple"""
rb"""br double triple""" rb"""br double triple"""
"single1" "single2" "single1single2"
"single1" "double2" "single1double2"
"double1" "single2" "double1single2"
"double1" "double2" "double1double2"
def docstring_single_triple(): def docstring_single_triple():
@ -228,28 +206,6 @@ def docstring_single():
``` ```
#### Preview changes
```diff
--- Stable
+++ Preview
@@ -33,10 +33,10 @@
rb"""br single triple"""
rb"""br double triple"""
-"single1" "single2"
-"single1" "double2"
-"double1" "single2"
-"double1" "double2"
+"single1single2"
+"single1double2"
+"double1single2"
+"double1double2"
def docstring_single_triple():
```
### Output 3 ### Output 3
``` ```
indent-style = space indent-style = space
@ -301,10 +257,10 @@ rb"""rb double triple"""
rb'''br single triple''' rb'''br single triple'''
rb"""br double triple""" rb"""br double triple"""
'single1' 'single2' 'single1single2'
'single1' "double2" 'single1double2'
"double1" 'single2' "double1single2"
"double1" "double2" "double1double2"
def docstring_single_triple(): def docstring_single_triple():
@ -322,25 +278,3 @@ def docstring_double():
def docstring_single(): def docstring_single():
'single' 'single'
``` ```
#### Preview changes
```diff
--- Stable
+++ Preview
@@ -33,10 +33,10 @@
rb'''br single triple'''
rb"""br double triple"""
-'single1' 'single2'
-'single1' "double2"
-"double1" 'single2'
-"double1" "double2"
+'single1single2'
+'single1double2'
+"double1single2"
+"double1double2"
def docstring_single_triple():
```

View file

@ -1,7 +1,6 @@
--- ---
source: crates/ruff_python_formatter/tests/fixtures.rs source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/stub.pyi input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/range_formatting/stub.pyi
snapshot_kind: text
--- ---
## Input ## Input
```python ```python
@ -34,7 +33,7 @@ line-ending = LineFeed
magic-trailing-comma = Respect magic-trailing-comma = Respect
docstring-code = Disabled docstring-code = Disabled
docstring-code-line-width = "dynamic" docstring-code-line-width = "dynamic"
preview = Enabled preview = Disabled
target_version = Py39 target_version = Py39
source_type = Stub source_type = Stub
``` ```

View file

@ -212,50 +212,47 @@ assert (
def test(): def test():
assert ( assert {
{ key1: value1,
key1: value1, key2: value2,
key2: value2, key3: value3,
key3: value3, key4: value4,
key4: value4, key5: value5,
key5: value5, key6: value6,
key6: value6, key7: value7,
key7: value7, key8: value8,
key8: value8, key9: value9,
key9: value9, } == expected, (
} "Not what we expected and the message is too long to fit ineeeeee one line"
== expected )
), "Not what we expected and the message is too long to fit ineeeeee one line"
assert ( assert {
{ key1: value1,
key1: value1, key2: value2,
key2: value2, key3: value3,
key3: value3, key4: value4,
key4: value4, key5: value5,
key5: value5, key6: value6,
key6: value6, key7: value7,
key7: value7, key8: value8,
key8: value8, key9: value9,
key9: value9, } == expected, (
} "Not what we expected and the message is too long to fit in one lineeeee"
== expected )
), "Not what we expected and the message is too long to fit in one lineeeee"
assert ( assert {
{ key1: value1,
key1: value1, key2: value2,
key2: value2, key3: value3,
key3: value3, key4: value4,
key4: value4, key5: value5,
key5: value5, key6: value6,
key6: value6, key7: value7,
key7: value7, key8: value8,
key8: value8, key9: value9,
key9: value9, } == expected, (
} "Not what we expected and the message is too long to fit in one lineeeeeeeeeeeee"
== expected )
), "Not what we expected and the message is too long to fit in one lineeeeeeeeeeeee"
assert ( assert (
{ {
@ -285,7 +282,9 @@ def test():
key9: value9, key9: value9,
} }
== expectedeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee == expectedeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
), "Not what we expected and the message is too long to fit in one lineeeeeeeeeeeeeeeee" ), (
"Not what we expected and the message is too long to fit in one lineeeeeeeeeeeeeeeee"
)
assert expected == { assert expected == {
key1: value1, key1: value1,
@ -299,20 +298,19 @@ def test():
key9: value9, key9: value9,
}, "Not what we expected and the message is too long to fit ineeeeee one line" }, "Not what we expected and the message is too long to fit ineeeeee one line"
assert ( assert expected == {
expected key1: value1,
== { key2: value2,
key1: value1, key3: value3,
key2: value2, key4: value4,
key3: value3, key5: value5,
key4: value4, key6: value6,
key5: value5, key7: value7,
key6: value6, key8: value8,
key7: value7, key9: value9,
key8: value8, }, (
key9: value9, "Not what we expected and the message is too long to fit in one lineeeeeeeeeeeeeeeeeeee"
} )
), "Not what we expected and the message is too long to fit in one lineeeeeeeeeeeeeeeeeeee"
assert ( assert (
expectedeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee expectedeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
@ -342,7 +340,9 @@ def test():
key8: value8, key8: value8,
key9: value9, key9: value9,
} }
), "Not what we expected and the message is too long to fit in one lineeeeeeeeeeeeeee" ), (
"Not what we expected and the message is too long to fit in one lineeeeeeeeeeeeeee"
)
# Test for https://github.com/astral-sh/ruff/issues/7246 # Test for https://github.com/astral-sh/ruff/issues/7246
@ -361,156 +361,3 @@ assert package.files == [
}, },
] ]
``` ```
## Preview changes
```diff
--- Stable
+++ Preview
@@ -30,50 +30,47 @@
def test():
- assert (
- {
- key1: value1,
- key2: value2,
- key3: value3,
- key4: value4,
- key5: value5,
- key6: value6,
- key7: value7,
- key8: value8,
- key9: value9,
- }
- == expected
- ), "Not what we expected and the message is too long to fit ineeeeee one line"
+ assert {
+ key1: value1,
+ key2: value2,
+ key3: value3,
+ key4: value4,
+ key5: value5,
+ key6: value6,
+ key7: value7,
+ key8: value8,
+ key9: value9,
+ } == expected, (
+ "Not what we expected and the message is too long to fit ineeeeee one line"
+ )
- assert (
- {
- key1: value1,
- key2: value2,
- key3: value3,
- key4: value4,
- key5: value5,
- key6: value6,
- key7: value7,
- key8: value8,
- key9: value9,
- }
- == expected
- ), "Not what we expected and the message is too long to fit in one lineeeee"
+ assert {
+ key1: value1,
+ key2: value2,
+ key3: value3,
+ key4: value4,
+ key5: value5,
+ key6: value6,
+ key7: value7,
+ key8: value8,
+ key9: value9,
+ } == expected, (
+ "Not what we expected and the message is too long to fit in one lineeeee"
+ )
- assert (
- {
- key1: value1,
- key2: value2,
- key3: value3,
- key4: value4,
- key5: value5,
- key6: value6,
- key7: value7,
- key8: value8,
- key9: value9,
- }
- == expected
- ), "Not what we expected and the message is too long to fit in one lineeeeeeeeeeeee"
+ assert {
+ key1: value1,
+ key2: value2,
+ key3: value3,
+ key4: value4,
+ key5: value5,
+ key6: value6,
+ key7: value7,
+ key8: value8,
+ key9: value9,
+ } == expected, (
+ "Not what we expected and the message is too long to fit in one lineeeeeeeeeeeee"
+ )
assert (
{
@@ -103,7 +100,9 @@
key9: value9,
}
== expectedeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
- ), "Not what we expected and the message is too long to fit in one lineeeeeeeeeeeeeeeee"
+ ), (
+ "Not what we expected and the message is too long to fit in one lineeeeeeeeeeeeeeeee"
+ )
assert expected == {
key1: value1,
@@ -117,20 +116,19 @@
key9: value9,
}, "Not what we expected and the message is too long to fit ineeeeee one line"
- assert (
- expected
- == {
- key1: value1,
- key2: value2,
- key3: value3,
- key4: value4,
- key5: value5,
- key6: value6,
- key7: value7,
- key8: value8,
- key9: value9,
- }
- ), "Not what we expected and the message is too long to fit in one lineeeeeeeeeeeeeeeeeeee"
+ assert expected == {
+ key1: value1,
+ key2: value2,
+ key3: value3,
+ key4: value4,
+ key5: value5,
+ key6: value6,
+ key7: value7,
+ key8: value8,
+ key9: value9,
+ }, (
+ "Not what we expected and the message is too long to fit in one lineeeeeeeeeeeeeeeeeeee"
+ )
assert (
expectedeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
@@ -160,7 +158,9 @@
key8: value8,
key9: value9,
}
- ), "Not what we expected and the message is too long to fit in one lineeeeeeeeeeeeeee"
+ ), (
+ "Not what we expected and the message is too long to fit in one lineeeeeeeeeeeeeee"
+ )
# Test for https://github.com/astral-sh/ruff/issues/7246
```

View file

@ -1,7 +1,6 @@
--- ---
source: crates/ruff_python_formatter/tests/fixtures.rs source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/assignment_split_value_first.py input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/assignment_split_value_first.py
snapshot_kind: text
--- ---
## Input ## Input
```python ```python
@ -242,22 +241,7 @@ type A[VeryLongTypeNameThatShouldBreakFirstToTheRightBeforeSplitngtinthatExceeds
``` ```
## Outputs ## Output
### Output 1
```
indent-style = space
line-width = 88
indent-width = 4
quote-style = Double
line-ending = LineFeed
magic-trailing-comma = Respect
docstring-code = Disabled
docstring-code-line-width = "dynamic"
preview = Enabled
target_version = Py39
source_type = Python
```
```python ```python
####### #######
# Unsplittable target and value # Unsplittable target and value

View file

@ -695,7 +695,7 @@ match newlines:
case "case comment with newlines" if foo == 2: # second case "case comment with newlines" if foo == 2: # second
pass pass
case "one", "newline" if (foo := 1): # third case "one", "newline" if foo := 1: # third
pass pass
case "two newlines": case "two newlines":
@ -708,7 +708,9 @@ match newlines:
match long_lines: match long_lines:
case "this is a long line for if condition" if aaaaaaaaahhhhhhhh == 1 and bbbbbbaaaaaaaaaaa == 2: # comment case "this is a long line for if condition" if (
aaaaaaaaahhhhhhhh == 1 and bbbbbbaaaaaaaaaaa == 2
): # comment
pass pass
case "this is a long line for if condition with parentheses" if ( case "this is a long line for if condition with parentheses" if (
@ -719,7 +721,7 @@ match long_lines:
case "named expressions aren't special" if foo := 1: case "named expressions aren't special" if foo := 1:
pass pass
case "named expressions aren't that special" if (foo := 1): case "named expressions aren't that special" if foo := 1:
pass pass
case "but with already broken long lines" if ( case "but with already broken long lines" if (
@ -727,9 +729,9 @@ match long_lines:
): # another comment ): # another comment
pass pass
case { case {"long_long_long_key": str(long_long_long_key)} if (
"long_long_long_key": str(long_long_long_key) value := "long long long long long long long long long long long value"
} if value := "long long long long long long long long long long long value": ):
pass pass
@ -824,7 +826,9 @@ match pattern_singleton:
# trailing own 2 # trailing own 2
): ):
pass pass
case True: # trailing case (
True # trailing
):
pass pass
case False: case False:
pass pass
@ -875,7 +879,9 @@ match foo:
1 1
): ):
y = 1 y = 1
case 1: # comment case (
1 # comment
):
y = 1 y = 1
case ( case (
1 1
@ -1133,11 +1139,8 @@ match pattern_match_or:
pass pass
case ( case (
( a # trailing
a # trailing ) | (b):
)
| (b)
):
pass pass
case a | b | c: case a | b | c:
@ -1151,8 +1154,7 @@ match pattern_match_or:
pass pass
case ( # end of line case ( # end of line
a a | b
| b
# own line # own line
): ):
pass pass
@ -1284,98 +1286,3 @@ match guard_comments:
): ):
pass pass
``` ```
## Preview changes
```diff
--- Stable
+++ Preview
@@ -69,7 +69,7 @@
case "case comment with newlines" if foo == 2: # second
pass
- case "one", "newline" if (foo := 1): # third
+ case "one", "newline" if foo := 1: # third
pass
case "two newlines":
@@ -82,7 +82,9 @@
match long_lines:
- case "this is a long line for if condition" if aaaaaaaaahhhhhhhh == 1 and bbbbbbaaaaaaaaaaa == 2: # comment
+ case "this is a long line for if condition" if (
+ aaaaaaaaahhhhhhhh == 1 and bbbbbbaaaaaaaaaaa == 2
+ ): # comment
pass
case "this is a long line for if condition with parentheses" if (
@@ -93,7 +95,7 @@
case "named expressions aren't special" if foo := 1:
pass
- case "named expressions aren't that special" if (foo := 1):
+ case "named expressions aren't that special" if foo := 1:
pass
case "but with already broken long lines" if (
@@ -101,9 +103,9 @@
): # another comment
pass
- case {
- "long_long_long_key": str(long_long_long_key)
- } if value := "long long long long long long long long long long long value":
+ case {"long_long_long_key": str(long_long_long_key)} if (
+ value := "long long long long long long long long long long long value"
+ ):
pass
@@ -198,7 +200,9 @@
# trailing own 2
):
pass
- case True: # trailing
+ case (
+ True # trailing
+ ):
pass
case False:
pass
@@ -249,7 +253,9 @@
1
):
y = 1
- case 1: # comment
+ case (
+ 1 # comment
+ ):
y = 1
case (
1
@@ -507,11 +513,8 @@
pass
case (
- (
- a # trailing
- )
- | (b)
- ):
+ a # trailing
+ ) | (b):
pass
case a | b | c:
@@ -525,8 +528,7 @@
pass
case ( # end of line
- a
- | b
+ a | b
# own line
):
pass
```

View file

@ -336,32 +336,24 @@ def f[aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa](
# Breaking return type annotations. Black adds parentheses if the parameters are # Breaking return type annotations. Black adds parentheses if the parameters are
# empty; otherwise, it leverages the expressions own parentheses if possible. # empty; otherwise, it leverages the expressions own parentheses if possible.
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> ( def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[
Set[ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ]: ...
]
): ...
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> ( def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[
Set[ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ]: ...
]
): ...
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> ( def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[
Set[ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ]: ...
]
): ...
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> ( def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[
Set[ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ]: ...
]
): ...
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx( def xxxxxxxxxxxxxxxxxxxxxxxxxxxx(
@ -462,11 +454,8 @@ def xxxxxxxxxxxxxxxxxxxxxxxxxxxx(
): ... ): ...
def double() -> ( def double() -> first_item and foo.bar.baz().bop(
first_item 1,
and foo.bar.baz().bop(
1,
)
): ):
return 2 * a return 2 * a
@ -520,69 +509,3 @@ def process_board_action(
) -> Optional[Tuple[str, str]]: ) -> Optional[Tuple[str, str]]:
pass pass
``` ```
## Preview changes
```diff
--- Stable
+++ Preview
@@ -131,32 +131,24 @@
# Breaking return type annotations. Black adds parentheses if the parameters are
# empty; otherwise, it leverages the expressions own parentheses if possible.
-def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> (
- Set[
- "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
- ]
-): ...
+def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+]: ...
-def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> (
- Set[
- "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
- ]
-): ...
+def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+]: ...
-def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> (
- Set[
- "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
- ]
-): ...
+def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+]: ...
-def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> (
- Set[
- "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
- ]
-): ...
+def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> Set[
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+]: ...
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx(
@@ -257,11 +249,8 @@
): ...
-def double() -> (
- first_item
- and foo.bar.baz().bop(
- 1,
- )
+def double() -> first_item and foo.bar.baz().bop(
+ 1,
):
return 2 * a
```

View file

@ -245,11 +245,9 @@ def test_return_union_with_elements_exceeding_length() -> (
######################################################################################### #########################################################################################
def test_return_multiline_string_type_annotation() -> ( def test_return_multiline_string_type_annotation() -> """str
"""str
| list[str] | list[str]
""" """:
):
pass pass
@ -267,12 +265,12 @@ def test_return_multiline_string_binary_expression_return_type_annotation() -> (
######################################################################################### #########################################################################################
def test_implicit_concatenated_string_return_type() -> "str" "bbbbbbbbbbbbbbbb": def test_implicit_concatenated_string_return_type() -> "strbbbbbbbbbbbbbbbb":
pass pass
def test_overlong_implicit_concatenated_string_return_type() -> ( def test_overlong_implicit_concatenated_string_return_type() -> (
"liiiiiiiiiiiisssssst[str]" "bbbbbbbbbbbbbbbb" "liiiiiiiiiiiisssssst[str]bbbbbbbbbbbbbbbb"
): ):
pass pass
@ -295,9 +293,9 @@ def no_parameters_subscript_return_type() -> list[str]:
# 1. Black tries to keep the list flat by parenthesizing the list as shown below even when the `list` identifier # 1. Black tries to keep the list flat by parenthesizing the list as shown below even when the `list` identifier
# fits on the header line. IMO, this adds unnecessary parentheses that can be avoided # fits on the header line. IMO, this adds unnecessary parentheses that can be avoided
# and supporting it requires extra complexity (best_fitting! layout) # and supporting it requires extra complexity (best_fitting! layout)
def no_parameters_overlong_subscript_return_type_with_single_element() -> ( def no_parameters_overlong_subscript_return_type_with_single_element() -> list[
list[xxxxxxxxxxxxxxxxxxxxx] xxxxxxxxxxxxxxxxxxxxx
): ]:
pass pass
@ -306,23 +304,18 @@ def no_parameters_overlong_subscript_return_type_with_single_element() -> (
# `list[`. It is also inconsistent with how subscripts are normally formatted where it first tries to fit the entire subscript, # `list[`. It is also inconsistent with how subscripts are normally formatted where it first tries to fit the entire subscript,
# then splits after `list[` but keeps all elements on a single line, and finally, splits after each element. # then splits after `list[` but keeps all elements on a single line, and finally, splits after each element.
# IMO: Splitting after the `list[` and trying to keep the elements together when possible seems more consistent. # IMO: Splitting after the `list[` and trying to keep the elements together when possible seems more consistent.
def no_parameters_subscript_return_type_multiple_elements() -> ( def no_parameters_subscript_return_type_multiple_elements() -> list[
list[ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, ]:
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
]
):
pass pass
# Black removes the parentheses even the elements exceed the configured line width. # Black removes the parentheses even the elements exceed the configured line width.
# So does Ruff. # So does Ruff.
def no_parameters_subscript_return_type_multiple_overlong_elements() -> ( def no_parameters_subscript_return_type_multiple_overlong_elements() -> list[
list[ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, ]:
]
):
pass pass
@ -341,11 +334,9 @@ def no_parameters_subscriptreturn_type_with_overlong_value_() -> (
# `no_parameters_subscript_return_type_multiple_overlong_elements` shows. However, it doesn't # `no_parameters_subscript_return_type_multiple_overlong_elements` shows. However, it doesn't
# when the subscript contains a single element. Black then keeps the parentheses. # when the subscript contains a single element. Black then keeps the parentheses.
# Ruff removes the parentheses in this case for consistency. # Ruff removes the parentheses in this case for consistency.
def no_parameters_overlong_subscript_return_type_with_overlong_single_element() -> ( def no_parameters_overlong_subscript_return_type_with_overlong_single_element() -> list[
list[ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ]:
]
):
pass pass
@ -354,13 +345,10 @@ def no_parameters_overlong_subscript_return_type_with_overlong_single_element()
######################################################################################### #########################################################################################
def test_binary_expression_return_type_annotation() -> ( def test_binary_expression_return_type_annotation() -> aaaaaaaaaaaaaaaaaaaaaaaaaa > [
aaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
> [ bbbbbbbbbbbbbbbbbbbbbbbbb,
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, ]:
bbbbbbbbbbbbbbbbbbbbbbbbb,
]
):
pass pass
@ -370,139 +358,9 @@ def test_binary_expression_return_type_annotation() -> (
# Don't paranthesize lists # Don't paranthesize lists
def f() -> ( def f() -> [
[ a,
a, b,
b, ]:
]
):
pass pass
``` ```
## Preview changes
```diff
--- Stable
+++ Preview
@@ -58,11 +58,9 @@
#########################################################################################
-def test_return_multiline_string_type_annotation() -> (
- """str
+def test_return_multiline_string_type_annotation() -> """str
| list[str]
-"""
-):
+""":
pass
@@ -80,12 +78,12 @@
#########################################################################################
-def test_implicit_concatenated_string_return_type() -> "str" "bbbbbbbbbbbbbbbb":
+def test_implicit_concatenated_string_return_type() -> "strbbbbbbbbbbbbbbbb":
pass
def test_overlong_implicit_concatenated_string_return_type() -> (
- "liiiiiiiiiiiisssssst[str]" "bbbbbbbbbbbbbbbb"
+ "liiiiiiiiiiiisssssst[str]bbbbbbbbbbbbbbbb"
):
pass
@@ -108,9 +106,9 @@
# 1. Black tries to keep the list flat by parenthesizing the list as shown below even when the `list` identifier
# fits on the header line. IMO, this adds unnecessary parentheses that can be avoided
# and supporting it requires extra complexity (best_fitting! layout)
-def no_parameters_overlong_subscript_return_type_with_single_element() -> (
- list[xxxxxxxxxxxxxxxxxxxxx]
-):
+def no_parameters_overlong_subscript_return_type_with_single_element() -> list[
+ xxxxxxxxxxxxxxxxxxxxx
+]:
pass
@@ -119,23 +117,18 @@
# `list[`. It is also inconsistent with how subscripts are normally formatted where it first tries to fit the entire subscript,
# then splits after `list[` but keeps all elements on a single line, and finally, splits after each element.
# IMO: Splitting after the `list[` and trying to keep the elements together when possible seems more consistent.
-def no_parameters_subscript_return_type_multiple_elements() -> (
- list[
- xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
- xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
- ]
-):
+def no_parameters_subscript_return_type_multiple_elements() -> list[
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+]:
pass
# Black removes the parentheses even the elements exceed the configured line width.
# So does Ruff.
-def no_parameters_subscript_return_type_multiple_overlong_elements() -> (
- list[
- xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
- xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
- ]
-):
+def no_parameters_subscript_return_type_multiple_overlong_elements() -> list[
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx,
+]:
pass
@@ -154,11 +147,9 @@
# `no_parameters_subscript_return_type_multiple_overlong_elements` shows. However, it doesn't
# when the subscript contains a single element. Black then keeps the parentheses.
# Ruff removes the parentheses in this case for consistency.
-def no_parameters_overlong_subscript_return_type_with_overlong_single_element() -> (
- list[
- xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- ]
-):
+def no_parameters_overlong_subscript_return_type_with_overlong_single_element() -> list[
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+]:
pass
@@ -167,13 +158,10 @@
#########################################################################################
-def test_binary_expression_return_type_annotation() -> (
- aaaaaaaaaaaaaaaaaaaaaaaaaa
- > [
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
- bbbbbbbbbbbbbbbbbbbbbbbbb,
- ]
-):
+def test_binary_expression_return_type_annotation() -> aaaaaaaaaaaaaaaaaaaaaaaaaa > [
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
+ bbbbbbbbbbbbbbbbbbbbbbbbb,
+]:
pass
@@ -183,10 +171,8 @@
# Don't paranthesize lists
-def f() -> (
- [
- a,
- b,
- ]
-):
+def f() -> [
+ a,
+ b,
+]:
pass
```

View file

@ -288,13 +288,13 @@ def test_return_multiline_string_binary_expression_return_type_annotation(
######################################################################################### #########################################################################################
def test_implicit_concatenated_string_return_type(a) -> "str" "bbbbbbbbbbbbbbbb": def test_implicit_concatenated_string_return_type(a) -> "strbbbbbbbbbbbbbbbb":
pass pass
def test_overlong_implicit_concatenated_string_return_type( def test_overlong_implicit_concatenated_string_return_type(
a, a,
) -> "liiiiiiiiiiiisssssst[str]" "bbbbbbbbbbbbbbbb": ) -> "liiiiiiiiiiiisssssst[str]bbbbbbbbbbbbbbbb":
pass pass
@ -413,26 +413,3 @@ def test_return_multiline_string_binary_expression_return_type_annotation(
]: ]:
pass pass
``` ```
## Preview changes
```diff
--- Stable
+++ Preview
@@ -82,13 +82,13 @@
#########################################################################################
-def test_implicit_concatenated_string_return_type(a) -> "str" "bbbbbbbbbbbbbbbb":
+def test_implicit_concatenated_string_return_type(a) -> "strbbbbbbbbbbbbbbbb":
pass
def test_overlong_implicit_concatenated_string_return_type(
a,
-) -> "liiiiiiiiiiiisssssst[str]" "bbbbbbbbbbbbbbbb":
+) -> "liiiiiiiiiiiisssssst[str]bbbbbbbbbbbbbbbb":
pass
```

View file

@ -1,7 +1,6 @@
--- ---
source: crates/ruff_python_formatter/tests/fixtures.rs source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/with.py input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/with.py
snapshot_kind: text
--- ---
## Input ## Input
```python ```python
@ -444,7 +443,9 @@ with (
with a: # should remove brackets with a: # should remove brackets
pass pass
with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c: with (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
) as c:
pass pass
@ -609,7 +610,9 @@ with ( # outer comment
pass pass
# Breaking of with items. # Breaking of with items.
with test as ( # bar # foo with (
test # bar
) as ( # foo
# test # test
foo foo
): ):
@ -621,7 +624,9 @@ with test as (
): ):
pass pass
with test as ( # bar # foo # baz with (
test # bar
) as ( # foo # baz
# test # test
foo foo
): ):
@ -674,7 +679,9 @@ with (
) as b: ) as b:
pass pass
with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as b: with (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
) as b:
pass pass
with ( with (
@ -717,15 +724,19 @@ if True:
pass pass
if True: if True:
with anyio.CancelScope( with (
shield=True anyio.CancelScope(shield=True)
) if get_running_loop() else contextlib.nullcontext(): if get_running_loop()
else contextlib.nullcontext()
):
pass pass
if True: if True:
with anyio.CancelScope( with (
shield=True anyio.CancelScope(shield=True)
) if get_running_loop() else contextlib.nullcontext() as c: if get_running_loop()
else contextlib.nullcontext()
) as c:
pass pass
with Child(aaaaaaaaa, bbbbbbbbbbbbbbb, cccccc), Document( with Child(aaaaaaaaa, bbbbbbbbbbbbbbb, cccccc), Document(
@ -739,16 +750,22 @@ with open(
): ):
pass pass
with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb: with (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
pass pass
with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as b: with (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
) as b:
pass pass
if True: if True:
with anyio.CancelScope( with (
shield=True anyio.CancelScope(shield=True)
) if get_running_loop() else contextlib.nullcontext() as b: if get_running_loop()
else contextlib.nullcontext()
) as b:
pass pass
@ -800,103 +817,7 @@ with (
```diff ```diff
--- Stable --- Stable
+++ Preview +++ Preview
@@ -49,7 +49,9 @@ @@ -377,42 +377,36 @@
with a: # should remove brackets
pass
-with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c:
+with (
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+) as c:
pass
@@ -214,7 +216,9 @@
pass
# Breaking of with items.
-with test as ( # bar # foo
+with (
+ test # bar
+) as ( # foo
# test
foo
):
@@ -226,7 +230,9 @@
):
pass
-with test as ( # bar # foo # baz
+with (
+ test # bar
+) as ( # foo # baz
# test
foo
):
@@ -279,7 +285,9 @@
) as b:
pass
-with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as b:
+with (
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+) as b:
pass
with (
@@ -322,15 +330,19 @@
pass
if True:
- with anyio.CancelScope(
- shield=True
- ) if get_running_loop() else contextlib.nullcontext():
+ with (
+ anyio.CancelScope(shield=True)
+ if get_running_loop()
+ else contextlib.nullcontext()
+ ):
pass
if True:
- with anyio.CancelScope(
- shield=True
- ) if get_running_loop() else contextlib.nullcontext() as c:
+ with (
+ anyio.CancelScope(shield=True)
+ if get_running_loop()
+ else contextlib.nullcontext()
+ ) as c:
pass
with Child(aaaaaaaaa, bbbbbbbbbbbbbbb, cccccc), Document(
@@ -344,57 +356,57 @@
):
pass
-with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb:
+with (
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+):
pass
-with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as b:
+with (
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+) as b:
pass
if True:
- with anyio.CancelScope(
- shield=True
- ) if get_running_loop() else contextlib.nullcontext() as b:
+ with (
+ anyio.CancelScope(shield=True)
+ if get_running_loop()
+ else contextlib.nullcontext()
+ ) as b:
pass
# Regression test for https://github.com/astral-sh/ruff/issues/14001 # Regression test for https://github.com/astral-sh/ruff/issues/14001
with ( with (