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

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

View file

@ -4,8 +4,7 @@ use ruff_source_file::LineRanges;
use ruff_text_size::Ranged;
use crate::prelude::*;
use crate::preview::is_f_string_formatting_enabled;
use crate::string::{Quoting, StringNormalizer, StringQuotes};
use crate::string::{StringNormalizer, StringQuotes};
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}"`
/// or the standalone f-string in `f"foo {x} bar"`.
pub(crate) struct FormatFString<'a> {
value: &'a FString,
/// 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,
}
#[derive(Default)]
pub struct FormatFString;
impl<'a> FormatFString<'a> {
pub(crate) fn new(value: &'a FString, quoting: Quoting) -> Self {
Self { value, quoting }
}
}
impl FormatNodeRule<FString> for FormatFString {
fn fmt_fields(&self, item: &FString, f: &mut PyFormatter) -> FormatResult<()> {
let normalizer = StringNormalizer::from_context(f.context());
impl Format<PyFormatContext<'_>> for FormatFString<'_> {
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 string_kind = normalizer.choose_quotes(item.into()).flags();
let context = FStringContext::new(
string_kind,
FStringLayout::from_f_string(self.value, f.context().source()),
FStringLayout::from_f_string(item, f.context().source()),
);
// Starting prefix and quote
let quotes = StringQuotes::from(string_kind);
write!(f, [string_kind.prefix(), quotes])?;
for element in &self.value.elements {
for element in &item.elements {
FormatFStringElement::new(element, context).fmt(f)?;
}

View file

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

View file

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

View file

@ -2,8 +2,7 @@ use ruff_formatter::FormatRuleWithOptions;
use ruff_python_ast::StringLiteral;
use crate::prelude::*;
use crate::preview::is_f_string_implicit_concatenated_string_literal_quotes_enabled;
use crate::string::{docstring, Quoting, StringNormalizer};
use crate::string::{docstring, StringNormalizer};
use crate::QuoteStyle;
#[derive(Default)]
@ -28,12 +27,6 @@ pub enum StringLiteralKind {
String,
/// A string literal used as a 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 {
@ -41,26 +34,6 @@ impl StringLiteralKind {
pub(crate) const fn is_docstring(self) -> bool {
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 {
@ -75,7 +48,6 @@ impl FormatNodeRule<StringLiteral> for FormatStringLiteral {
};
let normalized = StringNormalizer::from_context(f.context())
.with_quoting(self.layout.quoting(f.context()))
.with_preferred_quote_style(quote_style)
.normalize(item.into());

View file

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