Normalize implicit concatenated f-string quotes per part (#13539)

This commit is contained in:
Micha Reiser 2024-10-08 11:59:17 +02:00 committed by GitHub
parent 42fcbef876
commit fc661e193a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 171 additions and 62 deletions

View file

@ -1,23 +1,28 @@
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::QuoteStyle;
pub(crate) struct FormatStringLiteral<'a> {
value: &'a StringLiteral,
#[derive(Default)]
pub struct FormatStringLiteral {
layout: StringLiteralKind,
}
impl<'a> FormatStringLiteral<'a> {
pub(crate) fn new(value: &'a StringLiteral, layout: StringLiteralKind) -> Self {
Self { value, layout }
impl FormatRuleWithOptions<StringLiteral, PyFormatContext<'_>> for FormatStringLiteral {
type Options = StringLiteralKind;
fn with_options(mut self, layout: StringLiteralKind) -> Self {
self.layout = layout;
self
}
}
/// The kind of a string literal.
#[derive(Copy, Clone, Debug, Default)]
pub(crate) enum StringLiteralKind {
pub enum StringLiteralKind {
/// A normal string literal e.g., `"foo"`.
#[default]
String,
@ -26,6 +31,8 @@ pub(crate) enum StringLiteralKind {
/// 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),
}
@ -36,16 +43,28 @@ impl StringLiteralKind {
}
/// Returns the quoting to be used for this string literal.
fn quoting(self) -> Quoting {
fn quoting(self, context: &PyFormatContext) -> Quoting {
match self {
StringLiteralKind::String | StringLiteralKind::Docstring => Quoting::CanChange,
StringLiteralKind::InImplicitlyConcatenatedFString(quoting) => quoting,
#[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 Format<PyFormatContext<'_>> for FormatStringLiteral<'_> {
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
impl FormatNodeRule<StringLiteral> for FormatStringLiteral {
fn fmt_fields(&self, item: &StringLiteral, f: &mut PyFormatter) -> FormatResult<()> {
let quote_style = f.options().quote_style();
let quote_style = if self.layout.is_docstring() && !quote_style.is_preserve() {
// Per PEP 8 and PEP 257, always prefer double quotes for docstrings,
@ -56,9 +75,9 @@ impl Format<PyFormatContext<'_>> for FormatStringLiteral<'_> {
};
let normalized = StringNormalizer::from_context(f.context())
.with_quoting(self.layout.quoting())
.with_quoting(self.layout.quoting(f.context()))
.with_preferred_quote_style(quote_style)
.normalize(self.value.into());
.normalize(item.into());
if self.layout.is_docstring() {
docstring::format(&normalized, f)