Skip to content

Commit

Permalink
Add "preserve" quote-style to mimic Black's skip-string-normalization (
Browse files Browse the repository at this point in the history
…#8822)

Co-authored-by: Micha Reiser <micha@reiser.io>
  • Loading branch information
sciyoshi and MichaReiser authored Dec 7, 2023
1 parent 6bbabce commit 2414298
Show file tree
Hide file tree
Showing 10 changed files with 482 additions and 96 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[
{
"quote_style": "single"
},
{
"quote_style": "double"
},
{
"quote_style": "preserve"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'single'
"double"
r'r single'
r"r double"
f'f single'
f"f double"
fr'fr single'
fr"fr double"
rf'rf single'
rf"rf double"
b'b single'
b"b double"
rb'rb single'
rb"rb double"
br'br single'
br"br double"

'''single triple'''
"""double triple"""
r'''r single triple'''
r"""r double triple"""
f'''f single triple'''
f"""f double triple"""
fr'''fr single triple'''
fr"""fr double triple"""
rf'''rf single triple'''
rf"""rf double triple"""
b'''b single triple'''
b"""b double triple"""
rb'''rb single triple'''
rb"""rb double triple"""
br'''br single triple'''
br"""br double triple"""

'single1' 'single2'
'single1' "double2"
"double1" 'single2'
"double1" "double2"

def docstring_single_triple():
'''single triple'''

def docstring_double_triple():
"""double triple"""

def docstring_double():
"double triple"

def docstring_single():
'single'
17 changes: 9 additions & 8 deletions crates/ruff_python_formatter/src/context.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::comments::Comments;
use crate::{PyFormatOptions, QuoteStyle};
use crate::expression::string::QuoteChar;
use crate::PyFormatOptions;
use ruff_formatter::{Buffer, FormatContext, GroupId, SourceCode};
use ruff_source_file::Locator;
use std::fmt::{Debug, Formatter};
Expand All @@ -12,14 +13,14 @@ pub struct PyFormatContext<'a> {
comments: Comments<'a>,
node_level: NodeLevel,
/// Set to a non-None value when the formatter is running on a code
/// snippet within a docstring. The value should be the quote style of the
/// snippet within a docstring. The value should be the quote character of the
/// docstring containing the code snippet.
///
/// Various parts of the formatter may inspect this state to change how it
/// works. For example, multi-line strings will always be written with a
/// quote style that is inverted from the one here in order to ensure that
/// the formatted Python code will be valid.
docstring: Option<QuoteStyle>,
docstring: Option<QuoteChar>,
}

impl<'a> PyFormatContext<'a> {
Expand Down Expand Up @@ -57,20 +58,20 @@ impl<'a> PyFormatContext<'a> {
/// Returns a non-None value only if the formatter is running on a code
/// snippet within a docstring.
///
/// The quote style returned corresponds to the quoting used for the
/// The quote character returned corresponds to the quoting used for the
/// docstring containing the code snippet currently being formatted.
pub(crate) fn docstring(&self) -> Option<QuoteStyle> {
pub(crate) fn docstring(&self) -> Option<QuoteChar> {
self.docstring
}

/// Return a new context suitable for formatting code snippets within a
/// docstring.
///
/// The quote style given should correspond to the style of quoting used
/// The quote character given should correspond to the quote character used
/// for the docstring containing the code snippets.
pub(crate) fn in_docstring(self, style: QuoteStyle) -> PyFormatContext<'a> {
pub(crate) fn in_docstring(self, quote: QuoteChar) -> PyFormatContext<'a> {
PyFormatContext {
docstring: Some(style),
docstring: Some(quote),
..self
}
}
Expand Down
26 changes: 14 additions & 12 deletions crates/ruff_python_formatter/src/expression/string/docstring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ use {
ruff_text_size::{Ranged, TextLen, TextRange, TextSize},
};

use crate::{prelude::*, FormatModuleError, QuoteStyle};
use crate::{prelude::*, FormatModuleError};

use super::NormalizedString;
use super::{NormalizedString, QuoteChar};

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

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

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

/// The quote style used by the docstring being printed.
quote_style: QuoteStyle,
/// The quote character used by the docstring being printed.
quote_char: QuoteChar,

/// The current code example detected in the docstring.
code_example: CodeExample<'src>,
Expand Down Expand Up @@ -476,7 +476,7 @@ impl<'ast, 'buf, 'fmt, 'src> DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> {
// instead of later, and as a result, get more consistent
// results.
.with_indent_style(IndentStyle::Space);
let printed = match docstring_format_source(options, self.quote_style, &codeblob) {
let printed = match docstring_format_source(options, self.quote_char, &codeblob) {
Ok(printed) => printed,
Err(FormatModuleError::FormatError(err)) => return Err(err),
Err(
Expand All @@ -498,9 +498,11 @@ impl<'ast, 'buf, 'fmt, 'src> DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> {
// a docstring. As we fix corner cases over time, we can perhaps
// remove this check. See the `doctest_invalid_skipped` tests in
// `docstring_code_examples.py` for when this check is relevant.
let wrapped = match self.quote_style {
QuoteStyle::Single => std::format!("'''{}'''", printed.as_code()),
QuoteStyle::Double => std::format!(r#""""{}""""#, printed.as_code()),
let wrapped = match self.quote_char {
QuoteChar::Single => std::format!("'''{}'''", printed.as_code()),
QuoteChar::Double => {
std::format!(r#""""{}""""#, printed.as_code())
}
};
let result = ruff_python_parser::parse(
&wrapped,
Expand Down Expand Up @@ -1483,7 +1485,7 @@ enum CodeExampleAddAction<'src> {
/// inside of a docstring.
fn docstring_format_source(
options: crate::PyFormatOptions,
docstring_quote_style: QuoteStyle,
docstring_quote_style: QuoteChar,
source: &str,
) -> Result<Printed, FormatModuleError> {
use ruff_python_parser::AsMode;
Expand All @@ -1510,7 +1512,7 @@ fn docstring_format_source(
/// that avoids `content""""` and `content\"""`. This does only applies to un-escaped backslashes,
/// so `content\\ """` doesn't need a space while `content\\\ """` does.
fn needs_chaperone_space(normalized: &NormalizedString, trim_end: &str) -> bool {
trim_end.ends_with(normalized.quotes.style.as_char())
trim_end.ends_with(normalized.quotes.quote_char.as_char())
|| trim_end.chars().rev().take_while(|c| *c == '\\').count() % 2 == 1
}

Expand Down
Loading

0 comments on commit 2414298

Please sign in to comment.