Alternate quotes for strings inside f-strings in preview (#13860)

This commit is contained in:
Micha Reiser 2024-10-23 07:57:53 +02:00 committed by GitHub
parent f335fe4d4a
commit 2f88f84972
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 556 additions and 118 deletions

View file

@ -1,12 +1,12 @@
use ruff_formatter::write;
use ruff_python_ast::{AnyStringFlags, FString, StringFlags};
use ruff_source_file::Locator;
use crate::prelude::*;
use crate::preview::{
is_f_string_formatting_enabled, is_f_string_implicit_concatenated_string_literal_quotes_enabled,
};
use crate::string::{Quoting, StringNormalizer, StringQuotes};
use ruff_formatter::write;
use ruff_python_ast::{AnyStringFlags, FString, StringFlags};
use ruff_source_file::Locator;
use ruff_text_size::Ranged;
use super::f_string_element::FormatFStringElement;
@ -35,7 +35,7 @@ impl Format<PyFormatContext<'_>> for FormatFString<'_> {
// f-string instead of globally for the entire f-string expression.
let quoting =
if is_f_string_implicit_concatenated_string_literal_quotes_enabled(f.context()) {
f_string_quoting(self.value, &locator)
Quoting::CanChange
} else {
self.quoting
};
@ -92,17 +92,21 @@ impl Format<PyFormatContext<'_>> for FormatFString<'_> {
#[derive(Clone, Copy, Debug)]
pub(crate) struct FStringContext {
flags: AnyStringFlags,
/// The string flags of the enclosing f-string part.
enclosing_flags: AnyStringFlags,
layout: FStringLayout,
}
impl FStringContext {
const fn new(flags: AnyStringFlags, layout: FStringLayout) -> Self {
Self { flags, layout }
Self {
enclosing_flags: flags,
layout,
}
}
pub(crate) fn flags(self) -> AnyStringFlags {
self.flags
self.enclosing_flags
}
pub(crate) const fn layout(self) -> FStringLayout {
@ -149,20 +153,3 @@ impl FStringLayout {
matches!(self, FStringLayout::Multiline)
}
}
fn f_string_quoting(f_string: &FString, locator: &Locator) -> Quoting {
let triple_quoted = f_string.flags.is_triple_quoted();
if f_string.elements.expressions().any(|expression| {
let string_content = locator.slice(expression.range());
if triple_quoted {
string_content.contains(r#"""""#) || string_content.contains("'''")
} else {
string_content.contains(['"', '\''])
}
}) {
Quoting::Preserve
} else {
Quoting::CanChange
}
}

View file

@ -2,8 +2,8 @@ use std::borrow::Cow;
use ruff_formatter::{format_args, write, Buffer, RemoveSoftLinesBuffer};
use ruff_python_ast::{
ConversionFlag, Expr, FStringElement, FStringExpressionElement, FStringLiteralElement,
StringFlags,
AnyStringFlags, ConversionFlag, Expr, FStringElement, FStringExpressionElement,
FStringLiteralElement, StringFlags,
};
use ruff_text_size::Ranged;
@ -33,7 +33,7 @@ impl Format<PyFormatContext<'_>> for FormatFStringElement<'_> {
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
match self.element {
FStringElement::Literal(string_literal) => {
FormatFStringLiteralElement::new(string_literal, self.context).fmt(f)
FormatFStringLiteralElement::new(string_literal, self.context.flags()).fmt(f)
}
FStringElement::Expression(expression) => {
FormatFStringExpressionElement::new(expression, self.context).fmt(f)
@ -45,19 +45,23 @@ impl Format<PyFormatContext<'_>> for FormatFStringElement<'_> {
/// Formats an f-string literal element.
pub(crate) struct FormatFStringLiteralElement<'a> {
element: &'a FStringLiteralElement,
context: FStringContext,
/// Flags of the enclosing F-string part
fstring_flags: AnyStringFlags,
}
impl<'a> FormatFStringLiteralElement<'a> {
pub(crate) fn new(element: &'a FStringLiteralElement, context: FStringContext) -> Self {
Self { element, context }
pub(crate) fn new(element: &'a FStringLiteralElement, fstring_flags: AnyStringFlags) -> Self {
Self {
element,
fstring_flags,
}
}
}
impl Format<PyFormatContext<'_>> for FormatFStringLiteralElement<'_> {
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
let literal_content = f.context().locator().slice(self.element.range());
let normalized = normalize_string(literal_content, 0, self.context.flags(), true);
let normalized = normalize_string(literal_content, 0, self.fstring_flags, true);
match &normalized {
Cow::Borrowed(_) => source_text_slice(self.element.range()).fmt(f),
Cow::Owned(normalized) => text(normalized).fmt(f),