Prefer the configured quote style

<!--
Thank you for contributing to Ruff! To help us out with reviewing, please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

This PR extends the string formatting to respect the configured quote style.

<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

Extended the string test with new cases and set it up to run twice: Once with the `quote_style: Doube`, and once with `quote_style: Single` single and double quotes. 

<!-- How was it tested? -->
This commit is contained in:
Micha Reiser 2023-06-26 14:24:25 +02:00 committed by GitHub
parent f18a1f70de
commit 313711aaf9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 165 additions and 24 deletions

View file

@ -0,0 +1,8 @@
[
{
"quote_style": "double"
},
{
"quote_style": "single"
}
]

View file

@ -12,7 +12,7 @@
# Prefer double quotes for string with equal amount of single and double quotes
'" \' " " \'\''
"' \" '' \" \" '"
"' \" '' \" \""
"\\' \"\""
'\\\' ""'
@ -47,6 +47,16 @@ String ""
String """
'''
'''Multiline
String "'''
"""Multiline
String '''
"""
"""Multiline
String '"""
'''Multiline
String \"\"\"
'''

View file

@ -58,7 +58,8 @@ impl Format<PyFormatContext<'_>> for FormatStringPart {
let raw_content_range = relative_raw_content_range + self.part_range.start();
let raw_content = &string_content[relative_raw_content_range];
let (preferred_quotes, contains_newlines) = preferred_quotes(raw_content, quotes);
let (preferred_quotes, contains_newlines) =
preferred_quotes(raw_content, quotes, f.options().quote_style());
write!(f, [prefix, preferred_quotes])?;
@ -148,14 +149,20 @@ impl Format<PyFormatContext<'_>> for StringPrefix {
/// Detects the preferred quotes for `input`.
/// * single quoted strings: The preferred quote style is the one that requires less escape sequences.
/// * triple quoted strings: Use double quotes except the string contains a sequence of `"""`.
fn preferred_quotes(input: &str, quotes: StringQuotes) -> (StringQuotes, ContainsNewlines) {
fn preferred_quotes(
input: &str,
quotes: StringQuotes,
configured_style: QuoteStyle,
) -> (StringQuotes, ContainsNewlines) {
let mut contains_newlines = ContainsNewlines::No;
let preferred_style = if quotes.triple {
let mut use_single_quotes = false;
// True if the string contains a triple quote sequence of the configured quote style.
let mut uses_triple_quotes = false;
let mut chars = input.chars().peekable();
while let Some(c) = chars.next() {
let configured_quote_char = configured_style.as_char();
match c {
'\n' | '\r' => contains_newlines = ContainsNewlines::Yes,
'\\' => {
@ -163,24 +170,25 @@ fn preferred_quotes(input: &str, quotes: StringQuotes) -> (StringQuotes, Contain
chars.next();
}
}
'"' => {
// `"` or `'`
c if c == configured_quote_char => {
match chars.peek().copied() {
Some('"') => {
// `""`
Some(c) if c == configured_quote_char => {
// `""` or `''`
chars.next();
if chars.peek().copied() == Some('"') {
// `"""`
if chars.peek().copied() == Some(configured_quote_char) {
// `"""` or `'''`
chars.next();
use_single_quotes = true;
uses_triple_quotes = true;
}
}
Some(_) => {
// Single quote, this is ok
// A single quote char, this is ok
}
None => {
// Trailing quote at the end of the comment
use_single_quotes = true;
uses_triple_quotes = true;
}
}
}
@ -188,10 +196,12 @@ fn preferred_quotes(input: &str, quotes: StringQuotes) -> (StringQuotes, Contain
}
}
if use_single_quotes {
QuoteStyle::Single
if uses_triple_quotes {
// String contains a triple quote sequence of the configured quote style.
// Keep the existing quote style.
quotes.style
} else {
QuoteStyle::Double
configured_style
}
} else {
let mut single_quotes = 0u32;
@ -215,10 +225,21 @@ fn preferred_quotes(input: &str, quotes: StringQuotes) -> (StringQuotes, Contain
}
}
if double_quotes > single_quotes {
QuoteStyle::Single
} else {
QuoteStyle::Double
match configured_style {
QuoteStyle::Single => {
if single_quotes > double_quotes {
QuoteStyle::Double
} else {
QuoteStyle::Single
}
}
QuoteStyle::Double => {
if double_quotes > single_quotes {
QuoteStyle::Single
} else {
QuoteStyle::Double
}
}
}
};
@ -286,7 +307,7 @@ fn normalize_quotes(input: &str, quotes: StringQuotes) -> Cow<str> {
let style = quotes.style;
let preferred_quote = style.as_char();
let opposite_quote = style.opposite().as_char();
let opposite_quote = style.invert().as_char();
let mut chars = input.char_indices();

View file

@ -109,7 +109,7 @@ impl QuoteStyle {
}
#[must_use]
pub const fn opposite(self) -> QuoteStyle {
pub const fn invert(self) -> QuoteStyle {
match self {
QuoteStyle::Single => QuoteStyle::Double,
QuoteStyle::Double => QuoteStyle::Single,

View file

@ -18,7 +18,7 @@ input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression
# Prefer double quotes for string with equal amount of single and double quotes
'" \' " " \'\''
"' \" '' \" \" '"
"' \" '' \" \""
"\\' \"\""
'\\\' ""'
@ -53,12 +53,30 @@ String ""
String """
'''
'''Multiline
String "'''
"""Multiline
String '''
"""
"""Multiline
String '"""
'''Multiline
String \"\"\"
'''
```
## Output
## Outputs
### Output 1
```
indent-style = Spaces, size: 4
line-width = 88
quote-style = Double
magic-trailing-comma = Respect
```
```py
"' test"
'" test'
@ -74,7 +92,7 @@ String \"\"\"
# Prefer double quotes for string with equal amount of single and double quotes
"\" ' \" \" ''"
"' \" '' \" \" '"
"' \" '' \" \""
'\\\' ""'
'\\\' ""'
@ -109,10 +127,94 @@ String ""
String """
'''
'''Multiline
String "'''
"""Multiline
String '''
"""
"""Multiline
String '"""
"""Multiline
String \"\"\"
"""
```
### Output 2
```
indent-style = Spaces, size: 4
line-width = 88
quote-style = Single
magic-trailing-comma = Respect
```
```py
"' test"
'" test'
'" test'
"' test"
# Prefer single quotes for string with more double quotes
'\' " " \'\' " " \''
# Prefer double quotes for string with more single quotes
'\' " " \'\' " " \''
# Prefer double quotes for string with equal amount of single and double quotes
'" \' " " \'\''
'\' " \'\' " "'
'\\\' ""'
'\\\' ""'
'Test'
'Test'
r'Test'
R'Test'
'This string will not include \
backslashes or newline characters.'
if True:
'This string will not include \
backslashes or newline characters.'
'''Multiline
String \"
'''
'''Multiline
String \'
'''
'''Multiline
String ""
'''
'''Multiline
String """
'''
'''Multiline
String "'''
"""Multiline
String '''
"""
"""Multiline
String '"""
'''Multiline
String \"\"\"
'''
```