Skip to content

Commit 2414298

Browse files
Add "preserve" quote-style to mimic Black's skip-string-normalization (#8822)
Co-authored-by: Micha Reiser <[email protected]>
1 parent 6bbabce commit 2414298

File tree

10 files changed

+482
-96
lines changed

10 files changed

+482
-96
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[
2+
{
3+
"quote_style": "single"
4+
},
5+
{
6+
"quote_style": "double"
7+
},
8+
{
9+
"quote_style": "preserve"
10+
}
11+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
'single'
2+
"double"
3+
r'r single'
4+
r"r double"
5+
f'f single'
6+
f"f double"
7+
fr'fr single'
8+
fr"fr double"
9+
rf'rf single'
10+
rf"rf double"
11+
b'b single'
12+
b"b double"
13+
rb'rb single'
14+
rb"rb double"
15+
br'br single'
16+
br"br double"
17+
18+
'''single triple'''
19+
"""double triple"""
20+
r'''r single triple'''
21+
r"""r double triple"""
22+
f'''f single triple'''
23+
f"""f double triple"""
24+
fr'''fr single triple'''
25+
fr"""fr double triple"""
26+
rf'''rf single triple'''
27+
rf"""rf double triple"""
28+
b'''b single triple'''
29+
b"""b double triple"""
30+
rb'''rb single triple'''
31+
rb"""rb double triple"""
32+
br'''br single triple'''
33+
br"""br double triple"""
34+
35+
'single1' 'single2'
36+
'single1' "double2"
37+
"double1" 'single2'
38+
"double1" "double2"
39+
40+
def docstring_single_triple():
41+
'''single triple'''
42+
43+
def docstring_double_triple():
44+
"""double triple"""
45+
46+
def docstring_double():
47+
"double triple"
48+
49+
def docstring_single():
50+
'single'

crates/ruff_python_formatter/src/context.rs

+9-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::comments::Comments;
2-
use crate::{PyFormatOptions, QuoteStyle};
2+
use crate::expression::string::QuoteChar;
3+
use crate::PyFormatOptions;
34
use ruff_formatter::{Buffer, FormatContext, GroupId, SourceCode};
45
use ruff_source_file::Locator;
56
use std::fmt::{Debug, Formatter};
@@ -12,14 +13,14 @@ pub struct PyFormatContext<'a> {
1213
comments: Comments<'a>,
1314
node_level: NodeLevel,
1415
/// Set to a non-None value when the formatter is running on a code
15-
/// snippet within a docstring. The value should be the quote style of the
16+
/// snippet within a docstring. The value should be the quote character of the
1617
/// docstring containing the code snippet.
1718
///
1819
/// Various parts of the formatter may inspect this state to change how it
1920
/// works. For example, multi-line strings will always be written with a
2021
/// quote style that is inverted from the one here in order to ensure that
2122
/// the formatted Python code will be valid.
22-
docstring: Option<QuoteStyle>,
23+
docstring: Option<QuoteChar>,
2324
}
2425

2526
impl<'a> PyFormatContext<'a> {
@@ -57,20 +58,20 @@ impl<'a> PyFormatContext<'a> {
5758
/// Returns a non-None value only if the formatter is running on a code
5859
/// snippet within a docstring.
5960
///
60-
/// The quote style returned corresponds to the quoting used for the
61+
/// The quote character returned corresponds to the quoting used for the
6162
/// docstring containing the code snippet currently being formatted.
62-
pub(crate) fn docstring(&self) -> Option<QuoteStyle> {
63+
pub(crate) fn docstring(&self) -> Option<QuoteChar> {
6364
self.docstring
6465
}
6566

6667
/// Return a new context suitable for formatting code snippets within a
6768
/// docstring.
6869
///
69-
/// The quote style given should correspond to the style of quoting used
70+
/// The quote character given should correspond to the quote character used
7071
/// for the docstring containing the code snippets.
71-
pub(crate) fn in_docstring(self, style: QuoteStyle) -> PyFormatContext<'a> {
72+
pub(crate) fn in_docstring(self, quote: QuoteChar) -> PyFormatContext<'a> {
7273
PyFormatContext {
73-
docstring: Some(style),
74+
docstring: Some(quote),
7475
..self
7576
}
7677
}

crates/ruff_python_formatter/src/expression/string/docstring.rs

+14-12
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ use {
1313
ruff_text_size::{Ranged, TextLen, TextRange, TextSize},
1414
};
1515

16-
use crate::{prelude::*, FormatModuleError, QuoteStyle};
16+
use crate::{prelude::*, FormatModuleError};
1717

18-
use super::NormalizedString;
18+
use super::{NormalizedString, QuoteChar};
1919

2020
/// Format a docstring by trimming whitespace and adjusting the indentation.
2121
///
@@ -139,7 +139,7 @@ pub(super) fn format(normalized: &NormalizedString, f: &mut PyFormatter) -> Form
139139

140140
// Edge case: The first line is `""" "content`, so we need to insert chaperone space that keep
141141
// inner quotes and closing quotes from getting to close to avoid `""""content`
142-
if trim_both.starts_with(normalized.quotes.style.as_char()) {
142+
if trim_both.starts_with(normalized.quotes.quote_char.as_char()) {
143143
space().fmt(f)?;
144144
}
145145

@@ -192,7 +192,7 @@ pub(super) fn format(normalized: &NormalizedString, f: &mut PyFormatter) -> Form
192192
offset,
193193
stripped_indentation_length,
194194
already_normalized,
195-
quote_style: normalized.quotes.style,
195+
quote_char: normalized.quotes.quote_char,
196196
code_example: CodeExample::default(),
197197
}
198198
.add_iter(lines)?;
@@ -250,8 +250,8 @@ struct DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> {
250250
/// is, the formatter can take a fast path.
251251
already_normalized: bool,
252252

253-
/// The quote style used by the docstring being printed.
254-
quote_style: QuoteStyle,
253+
/// The quote character used by the docstring being printed.
254+
quote_char: QuoteChar,
255255

256256
/// The current code example detected in the docstring.
257257
code_example: CodeExample<'src>,
@@ -476,7 +476,7 @@ impl<'ast, 'buf, 'fmt, 'src> DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> {
476476
// instead of later, and as a result, get more consistent
477477
// results.
478478
.with_indent_style(IndentStyle::Space);
479-
let printed = match docstring_format_source(options, self.quote_style, &codeblob) {
479+
let printed = match docstring_format_source(options, self.quote_char, &codeblob) {
480480
Ok(printed) => printed,
481481
Err(FormatModuleError::FormatError(err)) => return Err(err),
482482
Err(
@@ -498,9 +498,11 @@ impl<'ast, 'buf, 'fmt, 'src> DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> {
498498
// a docstring. As we fix corner cases over time, we can perhaps
499499
// remove this check. See the `doctest_invalid_skipped` tests in
500500
// `docstring_code_examples.py` for when this check is relevant.
501-
let wrapped = match self.quote_style {
502-
QuoteStyle::Single => std::format!("'''{}'''", printed.as_code()),
503-
QuoteStyle::Double => std::format!(r#""""{}""""#, printed.as_code()),
501+
let wrapped = match self.quote_char {
502+
QuoteChar::Single => std::format!("'''{}'''", printed.as_code()),
503+
QuoteChar::Double => {
504+
std::format!(r#""""{}""""#, printed.as_code())
505+
}
504506
};
505507
let result = ruff_python_parser::parse(
506508
&wrapped,
@@ -1483,7 +1485,7 @@ enum CodeExampleAddAction<'src> {
14831485
/// inside of a docstring.
14841486
fn docstring_format_source(
14851487
options: crate::PyFormatOptions,
1486-
docstring_quote_style: QuoteStyle,
1488+
docstring_quote_style: QuoteChar,
14871489
source: &str,
14881490
) -> Result<Printed, FormatModuleError> {
14891491
use ruff_python_parser::AsMode;
@@ -1510,7 +1512,7 @@ fn docstring_format_source(
15101512
/// that avoids `content""""` and `content\"""`. This does only applies to un-escaped backslashes,
15111513
/// so `content\\ """` doesn't need a space while `content\\\ """` does.
15121514
fn needs_chaperone_space(normalized: &NormalizedString, trim_end: &str) -> bool {
1513-
trim_end.ends_with(normalized.quotes.style.as_char())
1515+
trim_end.ends_with(normalized.quotes.quote_char.as_char())
15141516
|| trim_end.chars().rev().take_while(|c| *c == '\\').count() % 2 == 1
15151517
}
15161518

0 commit comments

Comments
 (0)