mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-26 11:59:35 +00:00
Normalize implicit concatenated f-string quotes per part (#13539)
This commit is contained in:
parent
42fcbef876
commit
fc661e193a
11 changed files with 171 additions and 62 deletions
|
@ -33,14 +33,15 @@ node_lines = (
|
||||||
nodes = []
|
nodes = []
|
||||||
for node_line in node_lines:
|
for node_line in node_lines:
|
||||||
node = node_line.split("(")[1].split(")")[0].split("::")[-1].split("<")[0]
|
node = node_line.split("(")[1].split(")")[0].split("::")[-1].split("<")[0]
|
||||||
# `FString` and `StringLiteral` has a custom implementation while the formatting for
|
# `FString` has a custom implementation while the formatting for
|
||||||
# `FStringLiteralElement` and `FStringExpressionElement` are handled by the `FString`
|
# `FStringLiteralElement`, `FStringFormatSpec` and `FStringExpressionElement` are handled by the `FString`
|
||||||
# implementation.
|
# implementation.
|
||||||
if node in (
|
if node in (
|
||||||
"FString",
|
"FString",
|
||||||
"StringLiteral",
|
|
||||||
"FStringLiteralElement",
|
"FStringLiteralElement",
|
||||||
"FStringExpressionElement",
|
"FStringExpressionElement",
|
||||||
|
"FStringFormatSpec",
|
||||||
|
"Identifier",
|
||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
nodes.append(node)
|
nodes.append(node)
|
||||||
|
|
|
@ -307,3 +307,11 @@ hello {
|
||||||
]
|
]
|
||||||
} --------
|
} --------
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# 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 f-string does not use any quotes.'
|
||||||
|
)
|
||||||
|
|
|
@ -4,37 +4,17 @@ use ruff_python_ast::{AnyNodeRef, ExprStringLiteral};
|
||||||
use crate::expression::parentheses::{
|
use crate::expression::parentheses::{
|
||||||
in_parentheses_only_group, NeedsParentheses, OptionalParentheses,
|
in_parentheses_only_group, NeedsParentheses, OptionalParentheses,
|
||||||
};
|
};
|
||||||
use crate::other::string_literal::{FormatStringLiteral, StringLiteralKind};
|
use crate::other::string_literal::StringLiteralKind;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::string::{AnyString, FormatImplicitConcatenatedString};
|
use crate::string::{AnyString, FormatImplicitConcatenatedString};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct FormatExprStringLiteral {
|
pub struct FormatExprStringLiteral {
|
||||||
kind: ExprStringLiteralKind,
|
kind: StringLiteralKind,
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Copy, Clone, Debug)]
|
|
||||||
pub enum ExprStringLiteralKind {
|
|
||||||
#[default]
|
|
||||||
String,
|
|
||||||
Docstring,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExprStringLiteralKind {
|
|
||||||
const fn string_literal_kind(self) -> StringLiteralKind {
|
|
||||||
match self {
|
|
||||||
ExprStringLiteralKind::String => StringLiteralKind::String,
|
|
||||||
ExprStringLiteralKind::Docstring => StringLiteralKind::Docstring,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fn is_docstring(self) -> bool {
|
|
||||||
matches!(self, ExprStringLiteralKind::Docstring)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FormatRuleWithOptions<ExprStringLiteral, PyFormatContext<'_>> for FormatExprStringLiteral {
|
impl FormatRuleWithOptions<ExprStringLiteral, PyFormatContext<'_>> for FormatExprStringLiteral {
|
||||||
type Options = ExprStringLiteralKind;
|
type Options = StringLiteralKind;
|
||||||
|
|
||||||
fn with_options(mut self, options: Self::Options) -> Self {
|
fn with_options(mut self, options: Self::Options) -> Self {
|
||||||
self.kind = options;
|
self.kind = options;
|
||||||
|
@ -47,9 +27,7 @@ impl FormatNodeRule<ExprStringLiteral> for FormatExprStringLiteral {
|
||||||
let ExprStringLiteral { value, .. } = item;
|
let ExprStringLiteral { value, .. } = item;
|
||||||
|
|
||||||
match value.as_slice() {
|
match value.as_slice() {
|
||||||
[string_literal] => {
|
[string_literal] => string_literal.format().with_options(self.kind).fmt(f),
|
||||||
FormatStringLiteral::new(string_literal, self.kind.string_literal_kind()).fmt(f)
|
|
||||||
}
|
|
||||||
_ => {
|
_ => {
|
||||||
// This is just a sanity check because [`DocstringStmt::try_from_statement`]
|
// This is just a sanity check because [`DocstringStmt::try_from_statement`]
|
||||||
// ensures that the docstring is a *single* string literal.
|
// ensures that the docstring is a *single* string literal.
|
||||||
|
|
|
@ -2935,6 +2935,42 @@ impl<'ast> IntoFormat<PyFormatContext<'ast>> for ast::TypeParamParamSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FormatRule<ast::StringLiteral, PyFormatContext<'_>>
|
||||||
|
for crate::other::string_literal::FormatStringLiteral
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn fmt(&self, node: &ast::StringLiteral, f: &mut PyFormatter) -> FormatResult<()> {
|
||||||
|
FormatNodeRule::<ast::StringLiteral>::fmt(self, node, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'ast> AsFormat<PyFormatContext<'ast>> for ast::StringLiteral {
|
||||||
|
type Format<'a> = FormatRefWithRule<
|
||||||
|
'a,
|
||||||
|
ast::StringLiteral,
|
||||||
|
crate::other::string_literal::FormatStringLiteral,
|
||||||
|
PyFormatContext<'ast>,
|
||||||
|
>;
|
||||||
|
fn format(&self) -> Self::Format<'_> {
|
||||||
|
FormatRefWithRule::new(
|
||||||
|
self,
|
||||||
|
crate::other::string_literal::FormatStringLiteral::default(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'ast> IntoFormat<PyFormatContext<'ast>> for ast::StringLiteral {
|
||||||
|
type Format = FormatOwnedWithRule<
|
||||||
|
ast::StringLiteral,
|
||||||
|
crate::other::string_literal::FormatStringLiteral,
|
||||||
|
PyFormatContext<'ast>,
|
||||||
|
>;
|
||||||
|
fn into_format(self) -> Self::Format {
|
||||||
|
FormatOwnedWithRule::new(
|
||||||
|
self,
|
||||||
|
crate::other::string_literal::FormatStringLiteral::default(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FormatRule<ast::BytesLiteral, PyFormatContext<'_>>
|
impl FormatRule<ast::BytesLiteral, PyFormatContext<'_>>
|
||||||
for crate::other::bytes_literal::FormatBytesLiteral
|
for crate::other::bytes_literal::FormatBytesLiteral
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
|
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_formatter::write;
|
||||||
use ruff_python_ast::{AnyStringFlags, FString, StringFlags};
|
use ruff_python_ast::{AnyStringFlags, FString, StringFlags};
|
||||||
use ruff_source_file::Locator;
|
use ruff_source_file::Locator;
|
||||||
|
use ruff_text_size::Ranged;
|
||||||
use crate::prelude::*;
|
|
||||||
use crate::preview::is_f_string_formatting_enabled;
|
|
||||||
use crate::string::{Quoting, StringNormalizer, StringQuotes};
|
|
||||||
|
|
||||||
use super::f_string_element::FormatFStringElement;
|
use super::f_string_element::FormatFStringElement;
|
||||||
|
|
||||||
|
@ -29,8 +31,17 @@ impl Format<PyFormatContext<'_>> for FormatFString<'_> {
|
||||||
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
||||||
let locator = f.context().locator();
|
let locator = f.context().locator();
|
||||||
|
|
||||||
|
// If the preview style is enabled, make the decision on what quotes to use locally for each
|
||||||
|
// 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)
|
||||||
|
} else {
|
||||||
|
self.quoting
|
||||||
|
};
|
||||||
|
|
||||||
let normalizer = StringNormalizer::from_context(f.context())
|
let normalizer = StringNormalizer::from_context(f.context())
|
||||||
.with_quoting(self.quoting)
|
.with_quoting(quoting)
|
||||||
.with_preferred_quote_style(f.options().quote_style());
|
.with_preferred_quote_style(f.options().quote_style());
|
||||||
|
|
||||||
// If f-string formatting is disabled (not in preview), then we will
|
// If f-string formatting is disabled (not in preview), then we will
|
||||||
|
@ -140,3 +151,20 @@ impl FStringLayout {
|
||||||
matches!(self, FStringLayout::Multiline)
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use ruff_python_ast::FStringPart;
|
use ruff_python_ast::FStringPart;
|
||||||
|
|
||||||
use crate::other::f_string::FormatFString;
|
use crate::other::f_string::FormatFString;
|
||||||
use crate::other::string_literal::{FormatStringLiteral, StringLiteralKind};
|
use crate::other::string_literal::StringLiteralKind;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::string::Quoting;
|
use crate::string::Quoting;
|
||||||
|
|
||||||
|
@ -25,13 +25,12 @@ impl<'a> FormatFStringPart<'a> {
|
||||||
impl Format<PyFormatContext<'_>> for FormatFStringPart<'_> {
|
impl Format<PyFormatContext<'_>> for FormatFStringPart<'_> {
|
||||||
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
||||||
match self.part {
|
match self.part {
|
||||||
FStringPart::Literal(string_literal) => FormatStringLiteral::new(
|
#[allow(deprecated)]
|
||||||
string_literal,
|
FStringPart::Literal(string_literal) => string_literal
|
||||||
// If an f-string part is a string literal, the f-string is always
|
.format()
|
||||||
// implicitly concatenated e.g., `"foo" f"bar {x}"`. A standalone
|
.with_options(StringLiteralKind::InImplicitlyConcatenatedFString(
|
||||||
// string literal would be a string expression, not an f-string.
|
self.quoting,
|
||||||
StringLiteralKind::InImplicitlyConcatenatedFString(self.quoting),
|
))
|
||||||
)
|
|
||||||
.fmt(f),
|
.fmt(f),
|
||||||
FStringPart::FString(f_string) => FormatFString::new(f_string, self.quoting).fmt(f),
|
FStringPart::FString(f_string) => FormatFString::new(f_string, self.quoting).fmt(f),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,28 @@
|
||||||
|
use ruff_formatter::FormatRuleWithOptions;
|
||||||
use ruff_python_ast::StringLiteral;
|
use ruff_python_ast::StringLiteral;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use crate::preview::is_f_string_implicit_concatenated_string_literal_quotes_enabled;
|
||||||
use crate::string::{docstring, Quoting, StringNormalizer};
|
use crate::string::{docstring, Quoting, StringNormalizer};
|
||||||
use crate::QuoteStyle;
|
use crate::QuoteStyle;
|
||||||
|
|
||||||
pub(crate) struct FormatStringLiteral<'a> {
|
#[derive(Default)]
|
||||||
value: &'a StringLiteral,
|
pub struct FormatStringLiteral {
|
||||||
layout: StringLiteralKind,
|
layout: StringLiteralKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> FormatStringLiteral<'a> {
|
impl FormatRuleWithOptions<StringLiteral, PyFormatContext<'_>> for FormatStringLiteral {
|
||||||
pub(crate) fn new(value: &'a StringLiteral, layout: StringLiteralKind) -> Self {
|
type Options = StringLiteralKind;
|
||||||
Self { value, layout }
|
|
||||||
|
fn with_options(mut self, layout: StringLiteralKind) -> Self {
|
||||||
|
self.layout = layout;
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The kind of a string literal.
|
/// The kind of a string literal.
|
||||||
#[derive(Copy, Clone, Debug, Default)]
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
pub(crate) enum StringLiteralKind {
|
pub enum StringLiteralKind {
|
||||||
/// A normal string literal e.g., `"foo"`.
|
/// A normal string literal e.g., `"foo"`.
|
||||||
#[default]
|
#[default]
|
||||||
String,
|
String,
|
||||||
|
@ -26,6 +31,8 @@ pub(crate) enum StringLiteralKind {
|
||||||
/// A string literal that is implicitly concatenated with an f-string. This
|
/// A string literal that is implicitly concatenated with an f-string. This
|
||||||
/// makes the overall expression an f-string whose quoting detection comes
|
/// makes the overall expression an f-string whose quoting detection comes
|
||||||
/// from the parent node (f-string expression).
|
/// from the parent node (f-string expression).
|
||||||
|
#[deprecated]
|
||||||
|
#[allow(private_interfaces)]
|
||||||
InImplicitlyConcatenatedFString(Quoting),
|
InImplicitlyConcatenatedFString(Quoting),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,16 +43,28 @@ impl StringLiteralKind {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the quoting to be used for this string literal.
|
/// Returns the quoting to be used for this string literal.
|
||||||
fn quoting(self) -> Quoting {
|
fn quoting(self, context: &PyFormatContext) -> Quoting {
|
||||||
match self {
|
match self {
|
||||||
StringLiteralKind::String | StringLiteralKind::Docstring => Quoting::CanChange,
|
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<'_> {
|
impl FormatNodeRule<StringLiteral> for FormatStringLiteral {
|
||||||
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
fn fmt_fields(&self, item: &StringLiteral, f: &mut PyFormatter) -> FormatResult<()> {
|
||||||
let quote_style = f.options().quote_style();
|
let quote_style = f.options().quote_style();
|
||||||
let quote_style = if self.layout.is_docstring() && !quote_style.is_preserve() {
|
let quote_style = if self.layout.is_docstring() && !quote_style.is_preserve() {
|
||||||
// Per PEP 8 and PEP 257, always prefer double quotes for docstrings,
|
// 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())
|
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)
|
.with_preferred_quote_style(quote_style)
|
||||||
.normalize(self.value.into());
|
.normalize(item.into());
|
||||||
|
|
||||||
if self.layout.is_docstring() {
|
if self.layout.is_docstring() {
|
||||||
docstring::format(&normalized, f)
|
docstring::format(&normalized, f)
|
||||||
|
|
|
@ -19,6 +19,13 @@ pub(crate) fn is_f_string_formatting_enabled(context: &PyFormatContext) -> bool
|
||||||
context.is_preview()
|
context.is_preview()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// See [#13539](https://github.com/astral-sh/ruff/pull/13539)
|
||||||
|
pub(crate) fn is_f_string_implicit_concatenated_string_literal_quotes_enabled(
|
||||||
|
context: &PyFormatContext,
|
||||||
|
) -> bool {
|
||||||
|
context.is_preview()
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn is_with_single_item_pre_39_enabled(context: &PyFormatContext) -> bool {
|
pub(crate) fn is_with_single_item_pre_39_enabled(context: &PyFormatContext) -> bool {
|
||||||
context.is_preview()
|
context.is_preview()
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ use crate::comments::{
|
||||||
leading_comments, trailing_comments, Comments, LeadingDanglingTrailingComments,
|
leading_comments, trailing_comments, Comments, LeadingDanglingTrailingComments,
|
||||||
};
|
};
|
||||||
use crate::context::{NodeLevel, TopLevelStatementPosition, WithIndentLevel, WithNodeLevel};
|
use crate::context::{NodeLevel, TopLevelStatementPosition, WithIndentLevel, WithNodeLevel};
|
||||||
use crate::expression::expr_string_literal::ExprStringLiteralKind;
|
use crate::other::string_literal::StringLiteralKind;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::statement::stmt_expr::FormatStmtExpr;
|
use crate::statement::stmt_expr::FormatStmtExpr;
|
||||||
use crate::verbatim::{
|
use crate::verbatim::{
|
||||||
|
@ -850,7 +850,7 @@ impl Format<PyFormatContext<'_>> for DocstringStmt<'_> {
|
||||||
.then_some(source_position(self.docstring.start())),
|
.then_some(source_position(self.docstring.start())),
|
||||||
string_literal
|
string_literal
|
||||||
.format()
|
.format()
|
||||||
.with_options(ExprStringLiteralKind::Docstring),
|
.with_options(StringLiteralKind::Docstring),
|
||||||
f.options()
|
f.options()
|
||||||
.source_map_generation()
|
.source_map_generation()
|
||||||
.is_enabled()
|
.is_enabled()
|
||||||
|
|
|
@ -11,7 +11,7 @@ use ruff_text_size::{Ranged, TextRange};
|
||||||
|
|
||||||
use crate::expression::expr_f_string::f_string_quoting;
|
use crate::expression::expr_f_string::f_string_quoting;
|
||||||
use crate::other::f_string::FormatFString;
|
use crate::other::f_string::FormatFString;
|
||||||
use crate::other::string_literal::{FormatStringLiteral, StringLiteralKind};
|
use crate::other::string_literal::StringLiteralKind;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::string::Quoting;
|
use crate::string::Quoting;
|
||||||
|
|
||||||
|
@ -160,6 +160,7 @@ impl<'a> Iterator for AnyStringPartsIter<'a> {
|
||||||
match part {
|
match part {
|
||||||
ast::FStringPart::Literal(string_literal) => AnyStringPart::String {
|
ast::FStringPart::Literal(string_literal) => AnyStringPart::String {
|
||||||
part: string_literal,
|
part: string_literal,
|
||||||
|
#[allow(deprecated)]
|
||||||
layout: StringLiteralKind::InImplicitlyConcatenatedFString(*quoting),
|
layout: StringLiteralKind::InImplicitlyConcatenatedFString(*quoting),
|
||||||
},
|
},
|
||||||
ast::FStringPart::FString(f_string) => AnyStringPart::FString {
|
ast::FStringPart::FString(f_string) => AnyStringPart::FString {
|
||||||
|
@ -226,9 +227,7 @@ impl Ranged for AnyStringPart<'_> {
|
||||||
impl Format<PyFormatContext<'_>> for AnyStringPart<'_> {
|
impl Format<PyFormatContext<'_>> for AnyStringPart<'_> {
|
||||||
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
||||||
match self {
|
match self {
|
||||||
AnyStringPart::String { part, layout } => {
|
AnyStringPart::String { part, layout } => part.format().with_options(*layout).fmt(f),
|
||||||
FormatStringLiteral::new(part, *layout).fmt(f)
|
|
||||||
}
|
|
||||||
AnyStringPart::Bytes(bytes_literal) => bytes_literal.format().fmt(f),
|
AnyStringPart::Bytes(bytes_literal) => bytes_literal.format().fmt(f),
|
||||||
AnyStringPart::FString { part, quoting } => FormatFString::new(part, *quoting).fmt(f),
|
AnyStringPart::FString { part, quoting } => FormatFString::new(part, *quoting).fmt(f),
|
||||||
}
|
}
|
||||||
|
|
|
@ -313,6 +313,14 @@ hello {
|
||||||
]
|
]
|
||||||
} --------
|
} --------
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# 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 f-string does not use any quotes.'
|
||||||
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Outputs
|
## Outputs
|
||||||
|
@ -649,6 +657,14 @@ hello {
|
||||||
]
|
]
|
||||||
} --------
|
} --------
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# 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 f-string does not use any quotes."
|
||||||
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -973,6 +989,14 @@ hello {
|
||||||
]
|
]
|
||||||
} --------
|
} --------
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# 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 f-string does not use any quotes.'
|
||||||
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -1279,7 +1303,7 @@ hello {
|
||||||
# comment 27
|
# comment 27
|
||||||
# comment 28
|
# comment 28
|
||||||
} woah {x}"
|
} woah {x}"
|
||||||
@@ -287,19 +299,19 @@
|
@@ -287,27 +299,27 @@
|
||||||
if indent2:
|
if indent2:
|
||||||
foo = f"""hello world
|
foo = f"""hello world
|
||||||
hello {
|
hello {
|
||||||
|
@ -1314,4 +1338,14 @@ hello {
|
||||||
+ ]
|
+ ]
|
||||||
+ } --------
|
+ } --------
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# Implicit concatenated f-string containing quotes
|
||||||
|
_ = (
|
||||||
|
- 'This string should change its quotes to double quotes'
|
||||||
|
+ "This string should change its quotes to double quotes"
|
||||||
|
f'This string uses double quotes in an expression {"woah"}'
|
||||||
|
- f'This f-string does not use any quotes.'
|
||||||
|
+ f"This f-string does not use any quotes."
|
||||||
|
)
|
||||||
```
|
```
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue