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))
) {
OptionalParentheses::Never
} else if matches!(parenthesize, Parenthesize::IfBreaksParenthesizedNested) {
return parenthesize_if_expands(
&expression.format().with_options(Parentheses::Never),
)
.with_indent(!is_expression_huggable(expression, f.context())) .with_indent(!is_expression_huggable(expression, f.context()))
.fmt(f); .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,9 +35,7 @@ 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())
&& self.preserve_parentheses
&& is_expression_parenthesized( && is_expression_parenthesized(
self.expression.into(), self.expression.into(),
f.context().comments().ranges(), f.context().comments().ranges(),

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 OptionalParentheses::Multiline
} else { } else {
OptionalParentheses::BestFit OptionalParentheses::BestFit
} }
} else {
OptionalParentheses::Multiline
}
} }
} }

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()) { [
last_target,
space(),
operator,
space(),
token("("),
group(&soft_block_indent(&format_args![ group(&soft_block_indent(&format_args![
format_value, format_value,
inline_comments inline_comments
])) ]))
.should_expand(true) .should_expand(true),
.fmt(f)?; token(")")
} else { ]
block_indent(&format_args![format_value, inline_comments]).fmt(f)?; )
}
token(")").fmt(f)
}); });
// 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,7 +504,6 @@ 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(
@ -518,7 +513,6 @@ impl<'src> DocstringLinePrinter<'_, '_, '_, 'src> {
) )
.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,7 +300,6 @@ 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) => {
@ -341,27 +317,13 @@ impl Format<PyFormatContext<'_>> for FormatImplicitConcatenatedStringFlat<'_> {
FStringElement::Expression(expression) => { FStringElement::Expression(expression) => {
let context = FStringContext::new( let context = FStringContext::new(
self.flags, self.flags,
FStringLayout::from_f_string( FStringLayout::from_f_string(f_string, f.context().source()),
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,9 +41,6 @@ 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 {
Quoting::Preserve => QuoteStyle::Preserve,
Quoting::CanChange => {
let preferred_quote_style = self let preferred_quote_style = self
.preferred_quote_style .preferred_quote_style
.unwrap_or(self.context.options().quote_style()); .unwrap_or(self.context.options().quote_style());
@ -89,8 +78,7 @@ impl<'a, 'src> StringNormalizer<'a, 'src> {
} }
// For f-strings prefer alternating the quotes unless The outer string is triple quoted and the inner isn't. // 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) = if let FStringState::InsideExpressionElement(parent_context) = self.context.f_string_state()
self.context.f_string_state()
{ {
let parent_flags = parent_context.f_string().flags(); let parent_flags = parent_context.f_string().flags();
@ -155,15 +143,13 @@ impl<'a, 'src> StringNormalizer<'a, 'src> {
preferred_quote_style preferred_quote_style
} }
} }
}
}
/// Computes the strings preferred quotes. /// Computes the strings preferred quotes.
pub(crate) fn choose_quotes(&self, string: StringLikePart) -> QuoteSelection { pub(crate) fn choose_quotes(&self, string: StringLikePart) -> QuoteSelection {
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,7 +251,6 @@ 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(
@ -278,11 +259,6 @@ impl QuoteMetadata {
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,8 +322,7 @@ 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,
@ -367,9 +332,9 @@ class C:
key7: value7, key7: value7,
key8: value8, key8: value8,
key9: value9, key9: value9,
} } == expected, (
== expected "Not what we expected and the message is too long to fit in one line"
), "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,8 +322,7 @@ 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,
@ -367,9 +332,9 @@ class C:
key7: value7, key7: value7,
key8: value8, key8: value8,
key9: value9, key9: value9,
} } == expected, (
== expected "Not what we expected and the message is too long to fit in one line"
), "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,11 +529,15 @@ 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, (
"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, ( 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 %s." "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string %s."
@ -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,14 +331,10 @@ 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
@ -214,16 +348,15 @@ x = f"""foo {{ a
}} 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"
@ -238,29 +371,22 @@ 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 \ f"hellooooooooooooooooooooooo \
worlddddddddddddddddddddddddddddddddd" + ( worlddddddddddddddddddddddddddddddddd"
aaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbb + (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,34 +280,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}"
```
#### 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}" b"Unicode Escape sequence don't apply to bytes: \N{0x} \u{ABCD} \U{ABCDEFGH}"
``` ```
@ -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}"
```
#### 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}" 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, aaaaaa,
bbbbbbbbbbbbbbbb, bbbbbbbbbbbbbbbb,
cccccccccccccccccc, cccccccccccccccccc,
ddddddddddddddddddddddddddd, ddddddddddddddddddddddddddd,
] ]:
):
pass pass
match x: match x:
case ( case A | (
A
| (
aaaaaa, aaaaaa,
bbbbbbbbbbbbbbbb, bbbbbbbbbbbbbbbb,
cccccccccccccccccc, cccccccccccccccccc,
ddddddddddddddddddddddddddd, ddddddddddddddddddddddddddd,
)
): ):
pass pass
match x: match x:
case ( case A | {
A
| {
"a": aaaaaa, "a": aaaaaa,
"b": bbbbbbbbbbbbbbbb, "b": bbbbbbbbbbbbbbbb,
"c": cccccccccccccccccc, "c": cccccccccccccccccc,
"d": ddddddddddddddddddddddddddd, "d": ddddddddddddddddddddddddddd,
} }:
):
pass pass
match x: match x:
case ( case A | Class(
A
| Class(
aaaaaa, aaaaaa,
bbbbbbbbbbbbbbbb, bbbbbbbbbbbbbbbb,
cccccccccccccccccc, cccccccccccccccccc,
ddddddddddddddddddddddddddd, ddddddddddddddddddddddddddd,
)
): ):
pass pass
match x: match x:
case ( case A | (
A
| (
aaaaaaaaaaaaaaaaaaa.bbbbbbbbbbbbbbbbbbbbbbb.cccccccccccccccccccccccccccc.ddddddddddddddddddddddd 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, aaaaaaaaaaaaaaaaaaaaaaaa,
bbbbbbbbbbbbbbbbbbbbbbbbbbbb, bbbbbbbbbbbbbbbbbbbbbbbbbbbb,
cccccccccccccccccccccccccccccccccccccccc, cccccccccccccccccccccccccccccccccccccccc,
] ]:
):
pass pass
match x: match x:
case ( case 4 + 3j | [
4 + 3j
| [
aaaaaaaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaaaaaaa,
bbbbbbbbbbbbbbbbbbbbbbbbbbbb, bbbbbbbbbbbbbbbbbbbbbbbbbbbb,
cccccccccccccccccccccccccccccccccccccccc, cccccccccccccccccccccccccccccccccccccccc,
] ]:
):
pass pass
match x: match x:
case ( case -1 | [
-1
| [
aaaaaaaaaaaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaaaaaaaaaaa,
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb,
ccccccccccccccccccccccccccccccccc, ccccccccccccccccccccccccccccccccc,
] ]:
):
pass pass
### Parenthesized patterns ### Parenthesized patterns
match x: match x:
case ( case (1) | [
(1)
| [
aaaaaaaaaaaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaaaaaaaaaaa,
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb,
ccccccccccccccccccccccccccccccccc, ccccccccccccccccccccccccccccccccc,
] ]:
):
pass pass
match x: match x:
case ( case ( # comment
( # comment
1 1
) ) | [
| [
aaaaaaaaaaaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaaaaaaaaaaa,
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb, bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb,
ccccccccccccccccccccccccccccccccc, ccccccccccccccccccccccccccccccccc,
] ]:
):
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 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,8 +212,7 @@ assert (
def test(): def test():
assert ( assert {
{
key1: value1, key1: value1,
key2: value2, key2: value2,
key3: value3, key3: value3,
@ -223,12 +222,11 @@ def test():
key7: value7, key7: value7,
key8: value8, key8: value8,
key9: value9, key9: value9,
} } == expected, (
== expected "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 {
{
key1: value1, key1: value1,
key2: value2, key2: value2,
key3: value3, key3: value3,
@ -238,12 +236,11 @@ def test():
key7: value7, key7: value7,
key8: value8, key8: value8,
key9: value9, key9: value9,
} } == expected, (
== expected "Not what we expected and the message is too long to fit in one lineeeee"
), "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,
@ -253,9 +250,9 @@ def test():
key7: value7, key7: value7,
key8: value8, key8: value8,
key9: value9, key9: value9,
} } == expected, (
== expected "Not what we expected and the message is too long to fit in one lineeeeeeeeeeeee"
), "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,9 +298,7 @@ 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, key1: value1,
key2: value2, key2: value2,
key3: value3, key3: value3,
@ -311,8 +308,9 @@ def test():
key7: value7, key7: value7,
key8: value8, key8: value8,
key9: value9, 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
and foo.bar.baz().bop(
1, 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, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
bbbbbbbbbbbbbbbbbbbbbbbbb, 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
```
## 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 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 (