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

Add "preserve" quote-style to mimic Black's skip-string-normalization #8822

Merged
merged 6 commits into from
Dec 7, 2023
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
@@ -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 @@ -466,7 +466,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 @@ -488,9 +488,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 @@ -1231,7 +1233,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 @@ -1258,7 +1260,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
Loading