Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 3 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
5 changes: 2 additions & 3 deletions crates/ruff_dev/src/format_dev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,7 @@ pub(crate) fn main(args: &Args) -> anyhow::Result<ExitCode> {
}
info!(
parent: None,
"Done: {} stability errors, {} files, similarity index {:.5}), files with differences: {} took {:.2}s, {} input files contained syntax errors ",
error_count,
"Done: {error_count} stability/syntax errors, {} files, similarity index {:.5}), files with differences: {} took {:.2}s, {} input files contained syntax errors ",
result.file_count,
result.statistics.similarity_index(),
result.statistics.files_with_differences,
Expand Down Expand Up @@ -796,7 +795,7 @@ impl CheckFileError {
| CheckFileError::PrintError(_)
| CheckFileError::Panic { .. } => false,
#[cfg(not(debug_assertions))]
CheckFileError::Slow(_) => false,
CheckFileError::Slow(_) => true,
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions crates/ruff_python_ast/src/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,10 @@ impl StringLikePart<'_> {
self.end() - kind.closer_len(),
)
}

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

impl<'a> From<&'a ast::StringLiteral> for StringLikePart<'a> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbb" } ccccccccccccccc"
x = f"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { # comment 9
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbb" = } ccccccccccccccc"
x = f"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa { # comment 9
'bbbbbbbbbbbbbbbbbbbbbbbbbbbbb' = } ccccccccccccccc"

# Multiple larger expressions which exceeds the line length limit. Here, we need to decide
# whether to split at the first or second expression. This should work similarly to the
Expand Down Expand Up @@ -144,18 +146,37 @@
{'aaaaaaaaaaaaaaaaaaaaaaaaa', 'bbbbbbbbbbbbbbbbbbbbbbbbbbb', 'cccccccccccccccccccccccccc'}
}"

#############################################################################################
# Quotes
#############################################################################################
f"foo 'bar' {x}"
f"foo \"bar\" {x}"
f'foo "bar" {x}'
f'foo \'bar\' {x}'
f"foo {"bar"}"
f"foo {'\'bar\''}"

f"single quoted '{x}' double quoted \"{x}\"" # Same number of quotes => use preferred quote style
f"single quote ' {x} double quoted \"{x}\"" # More double quotes => use single quotes
f"single quoted '{x}' double quote \" {x}" # More single quotes => use double quotes

fr"single quotes ' {x}" # Keep double because `'` can't be escaped
fr'double quotes " {x}' # Keep single because `"` can't be escaped
fr'flip quotes {x}' # Use preferred quotes, because raw string contains now quotes.

# Here, the formatter will remove the escapes which is correct because they aren't allowed
# pre 3.12. This means we can assume that the f-string is used in the context of 3.12.
f"foo {'\'bar\''}"
f"foo {'\"bar\"'}"

# Quotes inside the expressions have no impact on the quote selection of the outer string.
# Required so that the following two examples result in the same formatting.
f'foo {10 + len("bar")}'
f"foo {10 + len('bar')}"

# Pre 312, preserve the outer quotes if the f-string contains quotes in the debug expression
f'foo {10 + len("bar")=}'
f'''foo {10 + len('''bar''')=}'''
f'''foo {10 + len('bar')=}''' # Fine to change the quotes because it uses triple quotes

# Triple-quoted strings
# It's ok to use the same quote char for the inner string if it's single-quoted.
Expand All @@ -164,6 +185,10 @@
# But if the inner string is also triple-quoted then we should preserve the existing quotes.
f"""test {'''inner'''}"""

# Not valid Pre 3.12
f"""test {f'inner {'''inner inner'''}'}"""
f"""test {f'''inner {"""inner inner"""}'''}"""

# Magic trailing comma
#
# The expression formatting will result in breaking it across multiple lines with a
Expand Down Expand Up @@ -312,6 +337,6 @@
# Implicit concatenated f-string containing quotes
_ = (
'This string should change its quotes to double quotes'
f'This string uses double quotes in an expression {"woah"}'
f'This string uses double quotes in an expression {"it's a quote"}'
f'This f-string does not use any quotes.'
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@

# Quotes reuse
f"{'a'}"

# 312+, it's okay to change the outer quotes even when there's a debug expression using the same quotes
f'foo {10 + len("bar")=}'
37 changes: 12 additions & 25 deletions crates/ruff_python_formatter/src/other/f_string.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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
};
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
}
18 changes: 11 additions & 7 deletions crates/ruff_python_formatter/src/other/f_string_element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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)
Expand All @@ -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),
Expand Down
2 changes: 2 additions & 0 deletions crates/ruff_python_formatter/src/preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ pub(crate) const fn is_hug_parens_with_braces_and_square_brackets_enabled(
}

/// 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`.
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 stabalizing this preview style.
pub(crate) fn is_f_string_implicit_concatenated_string_literal_quotes_enabled(
context: &PyFormatContext,
) -> bool {
Expand Down
Loading
Loading