Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion crates/ruff_python_formatter/src/other/bytes_literal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ pub struct FormatBytesLiteral;
impl FormatNodeRule<BytesLiteral> for FormatBytesLiteral {
fn fmt_fields(&self, item: &BytesLiteral, f: &mut PyFormatter) -> FormatResult<()> {
StringNormalizer::from_context(f.context())
.with_preferred_quote_style(f.options().quote_style())
.normalize(item.into())
.fmt(f)
}
Expand Down
4 changes: 1 addition & 3 deletions crates/ruff_python_formatter/src/other/f_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ impl Format<PyFormatContext<'_>> for FormatFString<'_> {
self.quoting
};

let normalizer = StringNormalizer::from_context(f.context())
.with_quoting(quoting)
.with_preferred_quote_style(f.options().quote_style());
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.
Expand Down
77 changes: 22 additions & 55 deletions crates/ruff_python_formatter/src/string/any.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ use ruff_source_file::Locator;
use ruff_text_size::{Ranged, TextRange};

use crate::expression::expr_f_string::f_string_quoting;
use crate::other::f_string::FormatFString;
use crate::other::string_literal::StringLiteralKind;
use crate::prelude::*;
use crate::string::Quoting;

/// Represents any kind of string expression. This could be either a string,
Expand Down Expand Up @@ -46,6 +43,10 @@ impl<'a> AnyString<'a> {
}
}

pub(crate) const fn is_fstring(self) -> bool {
matches!(self, Self::FString(_))
}

/// Returns the quoting to be used for this string.
pub(super) fn quoting(self, locator: &Locator<'_>) -> Quoting {
match self {
Expand All @@ -54,23 +55,21 @@ impl<'a> AnyString<'a> {
}
}

/// Returns a vector of all the [`AnyStringPart`] of this string.
pub(super) fn parts(self, quoting: Quoting) -> AnyStringPartsIter<'a> {
/// Returns an iterator over the [`AnyStringPart`]s of this string.
pub(super) fn parts(self) -> AnyStringPartsIter<'a> {
match self {
Self::String(ExprStringLiteral { value, .. }) => {
AnyStringPartsIter::String(value.iter())
}
Self::Bytes(ExprBytesLiteral { value, .. }) => AnyStringPartsIter::Bytes(value.iter()),
Self::FString(ExprFString { value, .. }) => {
AnyStringPartsIter::FString(value.iter(), quoting)
}
Self::FString(ExprFString { value, .. }) => AnyStringPartsIter::FString(value.iter()),
}
}

pub(crate) fn is_multiline(self, source: &str) -> bool {
match self {
AnyString::String(_) | AnyString::Bytes(_) => {
self.parts(Quoting::default())
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is kind of what triggered the refactor. It felt off that it is necessary to pass an arbitrary Quoating to iterate over the parts.

self.parts()
.next()
.is_some_and(|part| part.flags().is_triple_quoted())
&& memchr2(b'\n', b'\r', source[self.range()].as_bytes()).is_some()
Expand Down Expand Up @@ -139,36 +138,20 @@ impl<'a> From<&'a ExprFString> for AnyString<'a> {
pub(super) enum AnyStringPartsIter<'a> {
String(std::slice::Iter<'a, StringLiteral>),
Bytes(std::slice::Iter<'a, ast::BytesLiteral>),
FString(std::slice::Iter<'a, ast::FStringPart>, Quoting),
FString(std::slice::Iter<'a, ast::FStringPart>),
}

impl<'a> Iterator for AnyStringPartsIter<'a> {
type Item = AnyStringPart<'a>;

fn next(&mut self) -> Option<Self::Item> {
let part = match self {
Self::String(inner) => {
let part = inner.next()?;
AnyStringPart::String {
part,
layout: StringLiteralKind::String,
}
}
Self::String(inner) => AnyStringPart::String(inner.next()?),
Self::Bytes(inner) => AnyStringPart::Bytes(inner.next()?),
Self::FString(inner, quoting) => {
let part = inner.next()?;
match part {
ast::FStringPart::Literal(string_literal) => AnyStringPart::String {
part: string_literal,
#[allow(deprecated)]
layout: StringLiteralKind::InImplicitlyConcatenatedFString(*quoting),
},
ast::FStringPart::FString(f_string) => AnyStringPart::FString {
part: f_string,
quoting: *quoting,
},
}
}
Self::FString(inner) => match inner.next()? {
ast::FStringPart::Literal(string_literal) => AnyStringPart::String(string_literal),
ast::FStringPart::FString(f_string) => AnyStringPart::FString(f_string),
},
};

Some(part)
Expand All @@ -183,53 +166,37 @@ impl FusedIterator for AnyStringPartsIter<'_> {}
/// This is constructed from the [`AnyString::parts`] method on [`AnyString`].
#[derive(Clone, Debug)]
pub(super) enum AnyStringPart<'a> {
String {
part: &'a ast::StringLiteral,
layout: StringLiteralKind,
},
String(&'a ast::StringLiteral),
Bytes(&'a ast::BytesLiteral),
FString {
part: &'a ast::FString,
quoting: Quoting,
},
FString(&'a ast::FString),
}

impl AnyStringPart<'_> {
fn flags(&self) -> AnyStringFlags {
match self {
Self::String { part, .. } => part.flags.into(),
Self::String(part) => part.flags.into(),
Self::Bytes(bytes_literal) => bytes_literal.flags.into(),
Self::FString { part, .. } => part.flags.into(),
Self::FString(part) => part.flags.into(),
}
}
}

impl<'a> From<&AnyStringPart<'a>> for AnyNodeRef<'a> {
fn from(value: &AnyStringPart<'a>) -> Self {
match value {
AnyStringPart::String { part, .. } => AnyNodeRef::StringLiteral(part),
AnyStringPart::String(part) => AnyNodeRef::StringLiteral(part),
AnyStringPart::Bytes(part) => AnyNodeRef::BytesLiteral(part),
AnyStringPart::FString { part, .. } => AnyNodeRef::FString(part),
AnyStringPart::FString(part) => AnyNodeRef::FString(part),
}
}
}

impl Ranged for AnyStringPart<'_> {
fn range(&self) -> TextRange {
match self {
Self::String { part, .. } => part.range(),
Self::String(part) => part.range(),
Self::Bytes(part) => part.range(),
Self::FString { part, .. } => part.range(),
}
}
}

impl Format<PyFormatContext<'_>> for AnyStringPart<'_> {
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
match self {
AnyStringPart::String { part, layout } => part.format().with_options(*layout).fmt(f),
AnyStringPart::Bytes(bytes_literal) => bytes_literal.format().fmt(f),
AnyStringPart::FString { part, quoting } => FormatFString::new(part, *quoting).fmt(f),
Self::FString(part) => part.range(),
}
}
}
23 changes: 21 additions & 2 deletions crates/ruff_python_formatter/src/string/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ use ruff_text_size::{Ranged, TextRange};

use crate::comments::{leading_comments, trailing_comments};
use crate::expression::parentheses::in_parentheses_only_soft_line_break_or_space;
use crate::other::f_string::FormatFString;
use crate::other::string_literal::StringLiteralKind;
use crate::prelude::*;
use crate::string::any::AnyStringPart;
use crate::QuoteStyle;

mod any;
Expand Down Expand Up @@ -46,12 +49,28 @@ impl Format<PyFormatContext<'_>> for FormatImplicitConcatenatedString<'_> {

let mut joiner = f.join_with(in_parentheses_only_soft_line_break_or_space());

for part in self.string.parts(quoting) {
for part in self.string.parts() {
let part_comments = comments.leading_dangling_trailing(&part);

let format_part = format_with(|f: &mut PyFormatter| match part {
AnyStringPart::String(part) => {
let kind = if self.string.is_fstring() {
#[allow(deprecated)]
StringLiteralKind::InImplicitlyConcatenatedFString(quoting)
} else {
StringLiteralKind::String
};

part.format().with_options(kind).fmt(f)
}
AnyStringPart::Bytes(bytes_literal) => bytes_literal.format().fmt(f),
AnyStringPart::FString(part) => FormatFString::new(part, quoting).fmt(f),
});

joiner.entry(&format_args![
line_suffix_boundary(),
leading_comments(part_comments.leading),
part,
format_part,
trailing_comments(part_comments.trailing)
]);
}
Expand Down
2 changes: 1 addition & 1 deletion crates/ruff_python_formatter/src/string/normalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ impl<'a, 'src> StringNormalizer<'a, 'src> {
pub(crate) fn from_context(context: &'a PyFormatContext<'src>) -> Self {
Self {
quoting: Quoting::default(),
preferred_quote_style: QuoteStyle::default(),
preferred_quote_style: context.options().quote_style(),
context,
}
}
Expand Down