Disallow newlines in format specifiers of single quoted f- or t-strings (#18708)

This commit is contained in:
Micha Reiser 2025-06-18 14:56:15 +02:00 committed by GitHub
parent 23261a38a0
commit 1188ffccc4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 521 additions and 513 deletions

View file

@ -323,27 +323,18 @@ fn handle_enclosed_comment<'a>(
AnyNodeRef::TString(tstring) => CommentPlacement::dangling(tstring, comment),
AnyNodeRef::InterpolatedElement(element) => {
if let Some(preceding) = comment.preceding_node() {
if comment.line_position().is_own_line() && element.format_spec.is_some() {
return if comment.following_node().is_some() {
// Own line comment before format specifier
// ```py
// aaaaaaaaaaa = f"""asaaaaaaaaaaaaaaaa {
// aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd
// # comment
// :.3f} cccccccccc"""
// ```
CommentPlacement::trailing(preceding, comment)
} else {
// TODO: This can be removed once format specifiers with a newline are a syntax error.
// This is to handle cases like:
// ```py
// x = f"{x !s
// :>0
// # comment 21
// }"
// ```
CommentPlacement::trailing(element, comment)
};
// Own line comment before format specifier
// ```py
// aaaaaaaaaaa = f"""asaaaaaaaaaaaaaaaa {
// aaaaaaaaaaaa + bbbbbbbbbbbb + ccccccccccccccc + dddddddd
// # comment
// :.3f} cccccccccc"""
// ```
if comment.line_position().is_own_line()
&& element.format_spec.is_some()
&& comment.following_node().is_some()
{
return CommentPlacement::trailing(preceding, comment);
}
}

View file

@ -7,7 +7,7 @@ use ruff_python_ast::{
};
use ruff_text_size::{Ranged, TextSlice};
use crate::comments::{dangling_open_parenthesis_comments, trailing_comments};
use crate::comments::dangling_open_parenthesis_comments;
use crate::context::{
InterpolatedStringState, NodeLevel, WithInterpolatedStringState, WithNodeLevel,
};
@ -203,7 +203,7 @@ impl Format<PyFormatContext<'_>> for FormatInterpolatedElement<'_> {
// # comment 27
// :test}"
// ```
if comments.has_trailing_own_line(expression) {
if comments.has_trailing(expression) {
soft_line_break().fmt(f)?;
}
@ -214,31 +214,6 @@ impl Format<PyFormatContext<'_>> for FormatInterpolatedElement<'_> {
}
}
// These trailing comments can only occur if the format specifier is
// present. For example,
//
// ```python
// f"{
// x:.3f
// # comment
// }"
// ```
// This can also be triggered outside of a format spec, at
// least until https://github.com/astral-sh/ruff/issues/18632 is a syntax error
// TODO(https://github.com/astral-sh/ruff/issues/18632) Remove this
// and double check if it is still necessary for the triple quoted case
// once this is a syntax error.
// ```py
// f"{
// foo
// :{x}
// # comment 28
// } woah {x}"
// ```
// Any other trailing comments are attached to the expression itself.
trailing_comments(comments.trailing(self.element)).fmt(f)?;
if conversion.is_none() && format_spec.is_none() {
bracket_spacing.fmt(f)?;
}
@ -258,15 +233,7 @@ impl Format<PyFormatContext<'_>> for FormatInterpolatedElement<'_> {
let mut f = WithNodeLevel::new(NodeLevel::ParenthesizedExpression, f);
if self.context.is_multiline() {
// TODO: The `or comments.has_trailing...` can be removed once newlines in format specs are a syntax error.
// This is to support the following case:
// ```py
// x = f"{x !s
// :>0
// # comment 21
// }"
// ```
if format_spec.is_none() || comments.has_trailing_own_line(self.element) {
if format_spec.is_none() {
group(&format_args![
open_parenthesis_comments,
soft_block_indent(&item)
@ -276,6 +243,7 @@ impl Format<PyFormatContext<'_>> for FormatInterpolatedElement<'_> {
// For strings ending with a format spec, don't add a newline between the end of the format spec
// and closing curly brace because that is invalid syntax for single quoted strings and
// the newline is preserved as part of the format spec for triple quoted strings.
group(&format_args![
open_parenthesis_comments,
indent(&format_args![soft_line_break(), item])