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

Avoid invalid syntax for format-spec with quotes for all Python versions #14625

Merged
merged 1 commit into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -664,3 +664,45 @@

# Regression test for https://github.com/astral-sh/ruff/issues/14487
f"aaaaaaaaaaaaaaaaaaaaaaaaaa {10**27} bbbbbbbbbbbbbbbbbbbbbbbbbb ccccccccccccccccccccccccc"

# 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")=}'
f'''foo {10 + len("""bar""")=}'''

# 312+, it's okay to change the quotes here without creating an invalid f-string
f'{"""other " """}'
f'{"""other " """ + "more"}'
f'{b"""other " """}'
f'{f"""other " """}'


# Regression tests for https://github.com/astral-sh/ruff/issues/13935
f'{1: hy "user"}'
f'{1:hy "user"}'
f'{1: abcd "{1}" }'
f'{1: abcd "{'aa'}" }'
f'{1=: "abcd {'aa'}}'
f'{x:a{z:hy "user"}} \'\'\''

# Changing the outer quotes is fine because the format-spec is in a nested expression.
f'{f'{z=:hy "user"}'} \'\'\''


# We have to be careful about changing the quotes if the f-string has a debug expression because it is inserted verbatim.
f'{1=: "abcd \'\'}' # Don't change the outer quotes, or it results in a syntax error
f'{1=: abcd \'\'}' # Changing the quotes here is fine because the inner quotes aren't the opposite quotes
f'{1=: abcd \"\"}' # Changing the quotes here is fine because the inner quotes are escaped
# Don't change the quotes in the following cases:
f'{x=:hy "user"} \'\'\''
f'{x=:a{y:hy "user"}} \'\'\''
f'{x=:a{y:{z:hy "user"}}} \'\'\''
f'{x:a{y=:{z:hy "user"}}} \'\'\''

# This is fine because the debug expression and format spec are in a nested expression

f"""{1=: "this" is fine}"""
f'''{1=: "this" is fine}''' # Change quotes to double quotes because they're preferred
f'{1=: {'ab"cd"'}}' # It's okay if the quotes are in an expression part.

This file was deleted.

This file was deleted.

19 changes: 11 additions & 8 deletions crates/ruff_python_formatter/src/string/normalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,26 +60,29 @@ impl<'a, 'src> StringNormalizer<'a, 'src> {
return QuoteStyle::Preserve;
}

// There are two cases where it's necessary to preserve the quotes
// if the target version is pre 3.12 and the part is an f-string.
if !self.context.options().target_version().supports_pep_701() {
if let StringLikePart::FString(fstring) = string {
if let StringLikePart::FString(fstring) = string {
// There are two cases where it's necessary to preserve the quotes if the
// target version is pre 3.12 and the part is an f-string.
if !self.context.options().target_version().supports_pep_701() {
// An f-string expression contains a debug text with a quote character
// because the formatter will emit the debug expression **exactly** the same as in the source text.
// because the formatter will emit the debug expression **exactly** the
// same as in the source text.
if is_fstring_with_quoted_debug_expression(fstring, self.context) {
return QuoteStyle::Preserve;
}

// An f-string expression that contains a triple quoted string literal expression
// that contains a quote.
// An f-string expression that contains a triple quoted string literal
// expression that contains a quote.
if is_fstring_with_triple_quoted_literal_expression_containing_quotes(
fstring,
self.context,
) {
return QuoteStyle::Preserve;
}
}
} else if let StringLikePart::FString(fstring) = string {

// An f-string expression element contains a debug text and the corresponding
// format specifier has a literal element with a quote character.
if is_fstring_with_quoted_format_spec_and_debug(fstring, self.context) {
return QuoteStyle::Preserve;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,48 @@ _ = (

# Regression test for https://github.com/astral-sh/ruff/issues/14487
f"aaaaaaaaaaaaaaaaaaaaaaaaaa {10**27} bbbbbbbbbbbbbbbbbbbbbbbbbb ccccccccccccccccccccccccc"

# 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")=}'
f'''foo {10 + len("""bar""")=}'''

# 312+, it's okay to change the quotes here without creating an invalid f-string
f'{"""other " """}'
f'{"""other " """ + "more"}'
f'{b"""other " """}'
f'{f"""other " """}'


# Regression tests for https://github.com/astral-sh/ruff/issues/13935
f'{1: hy "user"}'
f'{1:hy "user"}'
f'{1: abcd "{1}" }'
f'{1: abcd "{'aa'}" }'
f'{1=: "abcd {'aa'}}'
f'{x:a{z:hy "user"}} \'\'\''

# Changing the outer quotes is fine because the format-spec is in a nested expression.
f'{f'{z=:hy "user"}'} \'\'\''


# We have to be careful about changing the quotes if the f-string has a debug expression because it is inserted verbatim.
f'{1=: "abcd \'\'}' # Don't change the outer quotes, or it results in a syntax error
f'{1=: abcd \'\'}' # Changing the quotes here is fine because the inner quotes aren't the opposite quotes
f'{1=: abcd \"\"}' # Changing the quotes here is fine because the inner quotes are escaped
# Don't change the quotes in the following cases:
f'{x=:hy "user"} \'\'\''
f'{x=:a{y:hy "user"}} \'\'\''
f'{x=:a{y:{z:hy "user"}}} \'\'\''
f'{x:a{y=:{z:hy "user"}}} \'\'\''

# This is fine because the debug expression and format spec are in a nested expression

f"""{1=: "this" is fine}"""
f'''{1=: "this" is fine}''' # Change quotes to double quotes because they're preferred
f'{1=: {'ab"cd"'}}' # It's okay if the quotes are in an expression part.
```

## Outputs
Expand Down Expand Up @@ -1398,6 +1440,48 @@ _ = (

# Regression test for https://github.com/astral-sh/ruff/issues/14487
f"aaaaaaaaaaaaaaaaaaaaaaaaaa {10**27} bbbbbbbbbbbbbbbbbbbbbbbbbb ccccccccccccccccccccccccc"

# 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")=}'
f'''foo {10 + len("""bar""")=}'''

# 312+, it's okay to change the quotes here without creating an invalid f-string
f'{"""other " """}'
f'{"""other " """ + "more"}'
f'{b"""other " """}'
f'{f"""other " """}'


# Regression tests for https://github.com/astral-sh/ruff/issues/13935
f'{1: hy "user"}'
f'{1:hy "user"}'
f'{1: abcd "{1}" }'
f'{1: abcd "{"aa"}" }'
f'{1=: "abcd {'aa'}}'
f"{x:a{z:hy \"user\"}} '''"

# Changing the outer quotes is fine because the format-spec is in a nested expression.
f"{f'{z=:hy "user"}'} '''"


# We have to be careful about changing the quotes if the f-string has a debug expression because it is inserted verbatim.
f'{1=: "abcd \'\'}' # Don't change the outer quotes, or it results in a syntax error
f"{1=: abcd \'\'}" # Changing the quotes here is fine because the inner quotes aren't the opposite quotes
f"{1=: abcd \"\"}" # Changing the quotes here is fine because the inner quotes are escaped
# Don't change the quotes in the following cases:
f'{x=:hy "user"} \'\'\''
f'{x=:a{y:hy "user"}} \'\'\''
f'{x=:a{y:{z:hy "user"}}} \'\'\''
f'{x:a{y=:{z:hy "user"}}} \'\'\''

# This is fine because the debug expression and format spec are in a nested expression

f"""{1=: "this" is fine}"""
f"""{1=: "this" is fine}""" # Change quotes to double quotes because they're preferred
f'{1=: {'ab"cd"'}}' # It's okay if the quotes are in an expression part.
```


Expand Down Expand Up @@ -2078,6 +2162,48 @@ _ = (

# Regression test for https://github.com/astral-sh/ruff/issues/14487
f"aaaaaaaaaaaaaaaaaaaaaaaaaa {10**27} bbbbbbbbbbbbbbbbbbbbbbbbbb ccccccccccccccccccccccccc"

# 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")=}'
f'''foo {10 + len("""bar""")=}'''

# 312+, it's okay to change the quotes here without creating an invalid f-string
f'{"""other " """}'
f'{"""other " """ + "more"}'
f'{b"""other " """}'
f'{f"""other " """}'


# Regression tests for https://github.com/astral-sh/ruff/issues/13935
f'{1: hy "user"}'
f'{1:hy "user"}'
f'{1: abcd "{1}" }'
f'{1: abcd "{'aa'}" }'
f'{1=: "abcd {'aa'}}'
f'{x:a{z:hy "user"}} \'\'\''

# Changing the outer quotes is fine because the format-spec is in a nested expression.
f'{f'{z=:hy "user"}'} \'\'\''


# We have to be careful about changing the quotes if the f-string has a debug expression because it is inserted verbatim.
f'{1=: "abcd \'\'}' # Don't change the outer quotes, or it results in a syntax error
f'{1=: abcd \'\'}' # Changing the quotes here is fine because the inner quotes aren't the opposite quotes
f'{1=: abcd \"\"}' # Changing the quotes here is fine because the inner quotes are escaped
# Don't change the quotes in the following cases:
f'{x=:hy "user"} \'\'\''
f'{x=:a{y:hy "user"}} \'\'\''
f'{x=:a{y:{z:hy "user"}}} \'\'\''
f'{x:a{y=:{z:hy "user"}}} \'\'\''

# This is fine because the debug expression and format spec are in a nested expression

f"""{1=: "this" is fine}"""
f"""{1=: "this" is fine}""" # Change quotes to double quotes because they're preferred
f'{1=: {'ab"cd"'}}' # It's okay if the quotes are in an expression part.
```


Expand Down Expand Up @@ -2915,4 +3041,28 @@ f"aaaaaaaaaaaaaaaaaaaaaaaaaa {10**27} bbbbbbbbbbbbbbbbbbbbbbbbbb ccccccccccccccc
)

# Regression test for https://github.com/astral-sh/ruff/issues/14487
@@ -678,18 +726,18 @@
f'{1: hy "user"}'
f'{1:hy "user"}'
f'{1: abcd "{1}" }'
-f'{1: abcd "{'aa'}" }'
+f'{1: abcd "{"aa"}" }'
f'{1=: "abcd {'aa'}}'
-f'{x:a{z:hy "user"}} \'\'\''
+f"{x:a{z:hy \"user\"}} '''"

# Changing the outer quotes is fine because the format-spec is in a nested expression.
-f'{f'{z=:hy "user"}'} \'\'\''
+f"{f'{z=:hy "user"}'} '''"


# We have to be careful about changing the quotes if the f-string has a debug expression because it is inserted verbatim.
f'{1=: "abcd \'\'}' # Don't change the outer quotes, or it results in a syntax error
-f'{1=: abcd \'\'}' # Changing the quotes here is fine because the inner quotes aren't the opposite quotes
-f'{1=: abcd \"\"}' # Changing the quotes here is fine because the inner quotes are escaped
+f"{1=: abcd \'\'}" # Changing the quotes here is fine because the inner quotes aren't the opposite quotes
+f"{1=: abcd \"\"}" # Changing the quotes here is fine because the inner quotes are escaped
# Don't change the quotes in the following cases:
f'{x=:hy "user"} \'\'\''
f'{x=:a{y:hy "user"}} \'\'\''
```
Loading