mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-23 13:05:06 +00:00
Format docstrings (#6452)
**Summary** Implement docstring formatting **Test Plan** Matches black's `docstring.py` fixture exactly, added some new cases for what is hard to debug with black and with what black doesn't cover. similarity index: main: zulip: 0.99702 django: 0.99784 warehouse: 0.99585 build: 0.75623 transformers: 0.99469 cpython: 0.75989 typeshed: 0.74853 this branch: zulip: 0.99702 django: 0.99784 warehouse: 0.99585 build: 0.75623 transformers: 0.99464 cpython: 0.75517 typeshed: 0.74853 The regression in transformers is actually an improvement in a file they don't format with black (they run `black examples tests src utils setup.py conftest.py`, the difference is in hubconf.py). cpython doesn't use black. Closes #6196
This commit is contained in:
parent
910dbbd9b6
commit
01eceaf0dc
9 changed files with 1064 additions and 1245 deletions
15
crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring.options.json
vendored
Normal file
15
crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring.options.json
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"indent_style": {
|
||||||
|
"Space": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indent_style": {
|
||||||
|
"Space": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"indent_style": "Tab"
|
||||||
|
}
|
||||||
|
]
|
102
crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring.py
vendored
Normal file
102
crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring.py
vendored
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
def single_line_backslashes1():
|
||||||
|
""" content\ """
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def single_line_backslashes2():
|
||||||
|
""" content\\ """
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def single_line_backslashes3():
|
||||||
|
""" content\\\ """
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def multiline_backslashes1():
|
||||||
|
"""This is a docstring with
|
||||||
|
some lines of text\ """
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def multiline_backslashes2():
|
||||||
|
"""This is a docstring with
|
||||||
|
some lines of text\\ """
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def multiline_backslashes3():
|
||||||
|
"""This is a docstring with
|
||||||
|
some lines of text\\\ """
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def multiple_negatively_indented_docstring_lines():
|
||||||
|
"""a
|
||||||
|
b
|
||||||
|
c
|
||||||
|
d
|
||||||
|
e
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def overindentend_docstring():
|
||||||
|
"""a
|
||||||
|
over-indented
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def comment_before_docstring():
|
||||||
|
# don't lose this function comment ...
|
||||||
|
"""Does nothing.
|
||||||
|
|
||||||
|
But it has comments
|
||||||
|
""" # ... neither lose this function comment
|
||||||
|
|
||||||
|
|
||||||
|
class CommentBeforeDocstring():
|
||||||
|
# don't lose this class comment ...
|
||||||
|
"""Empty class.
|
||||||
|
|
||||||
|
But it has comments
|
||||||
|
""" # ... neither lose this class comment
|
||||||
|
|
||||||
|
|
||||||
|
class IndentMeSome:
|
||||||
|
def doc_string_without_linebreak_after_colon(self): """ This is somewhat strange
|
||||||
|
a
|
||||||
|
b
|
||||||
|
We format this a is the docstring had started properly indented on the next
|
||||||
|
line if the target indentation. This may we incorrect since source and target
|
||||||
|
indentation can be incorrect, but this is also an edge case.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class IgnoreImplicitlyConcatenatedStrings:
|
||||||
|
"""""" ""
|
||||||
|
|
||||||
|
|
||||||
|
def docstring_that_ends_with_quote_and_a_line_break1():
|
||||||
|
"""
|
||||||
|
he said "the news of my death have been greatly exaggerated"
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def docstring_that_ends_with_quote_and_a_line_break2():
|
||||||
|
"""he said "the news of my death have been greatly exaggerated"
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def docstring_that_ends_with_quote_and_a_line_break3():
|
||||||
|
"""he said "the news of my death have been greatly exaggerated"
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class TabbedIndent:
|
||||||
|
def tabbed_indent(self):
|
||||||
|
"""check for correct tabbed formatting
|
||||||
|
^^^^^^^^^^
|
||||||
|
Normal indented line
|
||||||
|
- autor
|
||||||
|
"""
|
|
@ -1,16 +1,16 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
|
|
||||||
|
use ruff_formatter::{format_args, write, FormatError};
|
||||||
use ruff_python_ast::node::AnyNodeRef;
|
use ruff_python_ast::node::AnyNodeRef;
|
||||||
|
use ruff_python_ast::str::is_implicit_concatenation;
|
||||||
use ruff_python_ast::{self as ast, ExprConstant, ExprFString, Ranged};
|
use ruff_python_ast::{self as ast, ExprConstant, ExprFString, Ranged};
|
||||||
use ruff_python_parser::lexer::{lex_starts_at, LexicalError, LexicalErrorType};
|
use ruff_python_parser::lexer::{lex_starts_at, LexicalError, LexicalErrorType};
|
||||||
use ruff_python_parser::{Mode, Tok};
|
use ruff_python_parser::{Mode, Tok};
|
||||||
use ruff_source_file::Locator;
|
use ruff_source_file::Locator;
|
||||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||||
|
|
||||||
use ruff_formatter::{format_args, write, FormatError};
|
|
||||||
use ruff_python_ast::str::is_implicit_concatenation;
|
|
||||||
|
|
||||||
use crate::comments::{leading_comments, trailing_comments};
|
use crate::comments::{leading_comments, trailing_comments};
|
||||||
use crate::expression::parentheses::{
|
use crate::expression::parentheses::{
|
||||||
in_parentheses_only_group, in_parentheses_only_soft_line_break_or_space,
|
in_parentheses_only_group, in_parentheses_only_soft_line_break_or_space,
|
||||||
|
@ -78,7 +78,7 @@ pub(super) struct FormatString<'a> {
|
||||||
pub enum StringLayout {
|
pub enum StringLayout {
|
||||||
#[default]
|
#[default]
|
||||||
Default,
|
Default,
|
||||||
|
DocString,
|
||||||
ImplicitConcatenatedBinaryLeftSide,
|
ImplicitConcatenatedBinaryLeftSide,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,10 +109,25 @@ impl<'a> Format<PyFormatContext<'_>> for FormatString<'a> {
|
||||||
if is_implicit_concatenation(string_content) {
|
if is_implicit_concatenation(string_content) {
|
||||||
in_parentheses_only_group(&FormatStringContinuation::new(self.string)).fmt(f)
|
in_parentheses_only_group(&FormatStringContinuation::new(self.string)).fmt(f)
|
||||||
} else {
|
} else {
|
||||||
FormatStringPart::new(string_range, self.string.quoting(&f.context().locator()))
|
FormatStringPart::new(
|
||||||
.fmt(f)
|
string_range,
|
||||||
|
self.string.quoting(&f.context().locator()),
|
||||||
|
&f.context().locator(),
|
||||||
|
f.options().quote_style(),
|
||||||
|
)
|
||||||
|
.fmt(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
StringLayout::DocString => {
|
||||||
|
let string_part = FormatStringPart::new(
|
||||||
|
self.string.range(),
|
||||||
|
// f-strings can't be docstrings
|
||||||
|
Quoting::CanChange,
|
||||||
|
&f.context().locator(),
|
||||||
|
f.options().quote_style(),
|
||||||
|
);
|
||||||
|
format_docstring(&string_part, f)
|
||||||
|
}
|
||||||
StringLayout::ImplicitConcatenatedBinaryLeftSide => {
|
StringLayout::ImplicitConcatenatedBinaryLeftSide => {
|
||||||
FormatStringContinuation::new(self.string).fmt(f)
|
FormatStringContinuation::new(self.string).fmt(f)
|
||||||
}
|
}
|
||||||
|
@ -137,6 +152,7 @@ impl Format<PyFormatContext<'_>> for FormatStringContinuation<'_> {
|
||||||
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
||||||
let comments = f.context().comments().clone();
|
let comments = f.context().comments().clone();
|
||||||
let locator = f.context().locator();
|
let locator = f.context().locator();
|
||||||
|
let quote_style = f.options().quote_style();
|
||||||
let mut dangling_comments = comments.dangling_comments(self.string);
|
let mut dangling_comments = comments.dangling_comments(self.string);
|
||||||
|
|
||||||
let string_range = self.string.range();
|
let string_range = self.string.range();
|
||||||
|
@ -213,7 +229,12 @@ impl Format<PyFormatContext<'_>> for FormatStringContinuation<'_> {
|
||||||
joiner.entry(&format_args![
|
joiner.entry(&format_args![
|
||||||
line_suffix_boundary(),
|
line_suffix_boundary(),
|
||||||
leading_comments(leading_part_comments),
|
leading_comments(leading_part_comments),
|
||||||
FormatStringPart::new(token_range, self.string.quoting(&locator)),
|
FormatStringPart::new(
|
||||||
|
token_range,
|
||||||
|
self.string.quoting(&locator),
|
||||||
|
&locator,
|
||||||
|
quote_style,
|
||||||
|
),
|
||||||
trailing_comments(trailing_part_comments)
|
trailing_comments(trailing_part_comments)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -235,63 +256,67 @@ impl Format<PyFormatContext<'_>> for FormatStringContinuation<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FormatStringPart {
|
struct FormatStringPart {
|
||||||
part_range: TextRange,
|
prefix: StringPrefix,
|
||||||
quoting: Quoting,
|
preferred_quotes: StringQuotes,
|
||||||
|
range: TextRange,
|
||||||
|
is_raw_string: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FormatStringPart {
|
impl FormatStringPart {
|
||||||
const fn new(range: TextRange, quoting: Quoting) -> Self {
|
fn new(range: TextRange, quoting: Quoting, locator: &Locator, quote_style: QuoteStyle) -> Self {
|
||||||
|
let string_content = locator.slice(range);
|
||||||
|
|
||||||
|
let prefix = StringPrefix::parse(string_content);
|
||||||
|
let after_prefix = &string_content[usize::from(prefix.text_len())..];
|
||||||
|
|
||||||
|
let quotes =
|
||||||
|
StringQuotes::parse(after_prefix).expect("Didn't find string quotes after prefix");
|
||||||
|
let relative_raw_content_range = TextRange::new(
|
||||||
|
prefix.text_len() + quotes.text_len(),
|
||||||
|
string_content.text_len() - quotes.text_len(),
|
||||||
|
);
|
||||||
|
let raw_content_range = relative_raw_content_range + range.start();
|
||||||
|
|
||||||
|
let raw_content = &string_content[relative_raw_content_range];
|
||||||
|
let is_raw_string = prefix.is_raw_string();
|
||||||
|
let preferred_quotes = match quoting {
|
||||||
|
Quoting::Preserve => quotes,
|
||||||
|
Quoting::CanChange => {
|
||||||
|
if is_raw_string {
|
||||||
|
preferred_quotes_raw(raw_content, quotes, quote_style)
|
||||||
|
} else {
|
||||||
|
preferred_quotes(raw_content, quotes, quote_style)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
part_range: range,
|
prefix,
|
||||||
quoting,
|
range: raw_content_range,
|
||||||
|
preferred_quotes,
|
||||||
|
is_raw_string,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Format<PyFormatContext<'_>> for FormatStringPart {
|
impl Format<PyFormatContext<'_>> for FormatStringPart {
|
||||||
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
||||||
let string_content = f.context().locator().slice(self.part_range);
|
let (normalized, contains_newlines) = normalize_string(
|
||||||
|
f.context().locator().slice(self.range),
|
||||||
let prefix = StringPrefix::parse(string_content);
|
self.preferred_quotes,
|
||||||
let after_prefix = &string_content[usize::from(prefix.text_len())..];
|
self.is_raw_string,
|
||||||
|
|
||||||
let quotes = StringQuotes::parse(after_prefix).ok_or(FormatError::syntax_error(
|
|
||||||
"Didn't find string quotes after prefix",
|
|
||||||
))?;
|
|
||||||
let relative_raw_content_range = TextRange::new(
|
|
||||||
prefix.text_len() + quotes.text_len(),
|
|
||||||
string_content.text_len() - quotes.text_len(),
|
|
||||||
);
|
);
|
||||||
let raw_content_range = relative_raw_content_range + self.part_range.start();
|
|
||||||
|
|
||||||
let raw_content = &string_content[relative_raw_content_range];
|
|
||||||
let is_raw_string = prefix.is_raw_string();
|
|
||||||
let preferred_quotes = match self.quoting {
|
|
||||||
Quoting::Preserve => quotes,
|
|
||||||
Quoting::CanChange => {
|
|
||||||
if is_raw_string {
|
|
||||||
preferred_quotes_raw(raw_content, quotes, f.options().quote_style())
|
|
||||||
} else {
|
|
||||||
preferred_quotes(raw_content, quotes, f.options().quote_style())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
write!(f, [prefix, preferred_quotes])?;
|
|
||||||
|
|
||||||
let (normalized, contains_newlines) =
|
|
||||||
normalize_string(raw_content, preferred_quotes, is_raw_string);
|
|
||||||
|
|
||||||
|
write!(f, [self.prefix, self.preferred_quotes])?;
|
||||||
match normalized {
|
match normalized {
|
||||||
Cow::Borrowed(_) => {
|
Cow::Borrowed(_) => {
|
||||||
source_text_slice(raw_content_range, contains_newlines).fmt(f)?;
|
source_text_slice(self.range, contains_newlines).fmt(f)?;
|
||||||
}
|
}
|
||||||
Cow::Owned(normalized) => {
|
Cow::Owned(normalized) => {
|
||||||
dynamic_text(&normalized, Some(raw_content_range.start())).fmt(f)?;
|
dynamic_text(&normalized, Some(self.range.start())).fmt(f)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.preferred_quotes.fmt(f)
|
||||||
preferred_quotes.fmt(f)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -639,3 +664,311 @@ fn normalize_string(
|
||||||
|
|
||||||
(normalized, newlines)
|
(normalized, newlines)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// For docstring indentation, black counts spaces as 1 and tabs by increasing the indentation up
|
||||||
|
/// to the next multiple of 8. This is effectively a port of
|
||||||
|
/// [`str.expandtabs`](https://docs.python.org/3/library/stdtypes.html#str.expandtabs),
|
||||||
|
/// which black [calls with the default tab width of 8](https://github.com/psf/black/blob/c36e468794f9256d5e922c399240d49782ba04f1/src/black/strings.py#L61)
|
||||||
|
fn count_indentation_like_black(line: &str) -> TextSize {
|
||||||
|
let tab_width: u32 = 8;
|
||||||
|
let mut indentation = TextSize::default();
|
||||||
|
for char in line.chars() {
|
||||||
|
if char == '\t' {
|
||||||
|
// Pad to the next multiple of tab_width
|
||||||
|
indentation += TextSize::from(tab_width - (indentation.to_u32().rem_euclid(tab_width)));
|
||||||
|
} else if char.is_whitespace() {
|
||||||
|
indentation += char.text_len();
|
||||||
|
} else {
|
||||||
|
return indentation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
indentation
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format a docstring by trimming whitespace and adjusting the indentation.
|
||||||
|
///
|
||||||
|
/// Summary of changes we make:
|
||||||
|
/// * Normalize the string like all other strings
|
||||||
|
/// * Ignore docstring that have an escaped newline
|
||||||
|
/// * Trim all trailing whitespace, except for a chaperone space that avoids quotes or backslashes
|
||||||
|
/// in the last line.
|
||||||
|
/// * Trim leading whitespace on the first line, again except for a chaperone space
|
||||||
|
/// * If there is only content in the first line and after that only whitespace, collapse the
|
||||||
|
/// docstring into one line
|
||||||
|
/// * Adjust the indentation (see below)
|
||||||
|
///
|
||||||
|
/// # Docstring indentation
|
||||||
|
///
|
||||||
|
/// Unlike any other string, like black we change the indentation of docstring lines.
|
||||||
|
///
|
||||||
|
/// We want to preserve the indentation inside the docstring relative to the suite statement/block
|
||||||
|
/// indent that the docstring statement is in, but also want to apply the change of the outer
|
||||||
|
/// indentation in the docstring, e.g.
|
||||||
|
/// ```python
|
||||||
|
/// def sparkle_sky():
|
||||||
|
/// """Make a pretty sparkly sky.
|
||||||
|
/// * * ✨ *. .
|
||||||
|
/// * * ✨ .
|
||||||
|
/// . * . ✨ * . .
|
||||||
|
/// """
|
||||||
|
/// ```
|
||||||
|
/// should become
|
||||||
|
/// ```python
|
||||||
|
/// def sparkle_sky():
|
||||||
|
/// """Make a pretty sparkly sky.
|
||||||
|
/// * * ✨ *. .
|
||||||
|
/// * * ✨ .
|
||||||
|
/// . * . ✨ * . .
|
||||||
|
/// """
|
||||||
|
/// ```
|
||||||
|
/// We can't compute the full indentation here since we don't know what the block indent of
|
||||||
|
/// the doc comment will be yet and which we can only have added by formatting each line
|
||||||
|
/// separately with a hard line break. This means we need to strip shared indentation from
|
||||||
|
/// docstring while preserving the in-docstring bigger-than-suite-statement indentation. Example:
|
||||||
|
/// ```python
|
||||||
|
/// def f():
|
||||||
|
/// """first line
|
||||||
|
/// line a
|
||||||
|
/// line b
|
||||||
|
/// """
|
||||||
|
/// ```
|
||||||
|
/// The docstring indentation is 2, the block indents will change this to 4 (but we can't
|
||||||
|
/// determine this at this point). The indentation of line a is 2, so we trim ` line a`
|
||||||
|
/// to `line a`. For line b it's 5, so we trim it to `line b` and pad with 5-2=3 spaces to
|
||||||
|
/// ` line b`. The closing quotes, being on their own line, are stripped get only the
|
||||||
|
/// default indentation. Fully formatted:
|
||||||
|
/// ```python
|
||||||
|
/// def f():
|
||||||
|
/// """first line
|
||||||
|
/// line a
|
||||||
|
/// line b
|
||||||
|
/// """
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Tabs are counted by padding them to the next multiple of 8 according to
|
||||||
|
/// [`str.expandtabs`](https://docs.python.org/3/library/stdtypes.html#str.expandtabs). When
|
||||||
|
/// we see indentation that contains a tab or any other none ascii-space whitespace we rewrite the
|
||||||
|
/// string.
|
||||||
|
///
|
||||||
|
/// Additionally, if any line in the docstring has less indentation than the docstring
|
||||||
|
/// (effectively a negative indentation wrt. to the current level), we pad all lines to the
|
||||||
|
/// level of the docstring with spaces.
|
||||||
|
/// ```python
|
||||||
|
/// def f():
|
||||||
|
/// """first line
|
||||||
|
/// line a
|
||||||
|
/// line b
|
||||||
|
/// line c
|
||||||
|
/// """
|
||||||
|
/// ```
|
||||||
|
/// Here line a is 3 columns negatively indented, so we pad all lines by an extra 3 spaces:
|
||||||
|
/// ```python
|
||||||
|
/// def f():
|
||||||
|
/// """first line
|
||||||
|
/// line a
|
||||||
|
/// line b
|
||||||
|
/// line c
|
||||||
|
/// """
|
||||||
|
/// ```
|
||||||
|
fn format_docstring(string_part: &FormatStringPart, f: &mut PyFormatter) -> FormatResult<()> {
|
||||||
|
let locator = f.context().locator();
|
||||||
|
|
||||||
|
// Black doesn't change the indentation of docstrings that contain an escaped newline
|
||||||
|
if locator.slice(string_part.range).contains("\\\n") {
|
||||||
|
return string_part.fmt(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (normalized, _) = normalize_string(
|
||||||
|
locator.slice(string_part.range),
|
||||||
|
string_part.preferred_quotes,
|
||||||
|
string_part.is_raw_string,
|
||||||
|
);
|
||||||
|
// is_borrowed is unstable :/
|
||||||
|
let already_normalized = matches!(normalized, Cow::Borrowed(_));
|
||||||
|
|
||||||
|
let mut lines = normalized.lines().peekable();
|
||||||
|
|
||||||
|
// Start the string
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
[
|
||||||
|
source_position(string_part.range.start()),
|
||||||
|
string_part.prefix,
|
||||||
|
string_part.preferred_quotes
|
||||||
|
]
|
||||||
|
)?;
|
||||||
|
// We track where in the source docstring we are (in source code byte offsets)
|
||||||
|
let mut offset = string_part.range.start();
|
||||||
|
|
||||||
|
// The first line directly after the opening quotes has different rules than the rest, mainly
|
||||||
|
// that we remove all leading whitespace as there's no indentation
|
||||||
|
let first = lines.next().unwrap_or_default();
|
||||||
|
// Black trims whitespace using [`str.strip()`](https://docs.python.org/3/library/stdtypes.html#str.strip)
|
||||||
|
// https://github.com/psf/black/blob/b4dca26c7d93f930bbd5a7b552807370b60d4298/src/black/strings.py#L77-L85
|
||||||
|
// So we use the unicode whitespace definition through `trim_{start,end}` instead of the python
|
||||||
|
// tokenizer whitespace definition in `trim_whitespace_{start,end}`.
|
||||||
|
let trim_end = first.trim_end();
|
||||||
|
let trim_both = trim_end.trim_start();
|
||||||
|
|
||||||
|
// 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(string_part.preferred_quotes.style.as_char()) {
|
||||||
|
space().fmt(f)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !trim_end.is_empty() {
|
||||||
|
// For the first line of the docstring we strip the leading and trailing whitespace, e.g.
|
||||||
|
// `""" content ` to `"""content`
|
||||||
|
let leading_whitespace = trim_end.text_len() - trim_both.text_len();
|
||||||
|
let trimmed_line_range =
|
||||||
|
TextRange::at(offset, trim_end.text_len()).add_start(leading_whitespace);
|
||||||
|
if already_normalized {
|
||||||
|
source_text_slice(trimmed_line_range, ContainsNewlines::No).fmt(f)?;
|
||||||
|
} else {
|
||||||
|
dynamic_text(trim_both, Some(trimmed_line_range.start())).fmt(f)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offset += first.text_len();
|
||||||
|
|
||||||
|
// Check if we have a single line (or empty) docstring
|
||||||
|
if normalized[first.len()..].trim().is_empty() {
|
||||||
|
// For `"""\n"""` or other whitespace between the quotes, black keeps a single whitespace,
|
||||||
|
// but `""""""` doesn't get one inserted.
|
||||||
|
if needs_chaperone_space(string_part, trim_end)
|
||||||
|
|| (trim_end.is_empty() && !normalized.is_empty())
|
||||||
|
{
|
||||||
|
space().fmt(f)?;
|
||||||
|
}
|
||||||
|
string_part.preferred_quotes.fmt(f)?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
hard_line_break().fmt(f)?;
|
||||||
|
// We know that the normalized string has \n line endings
|
||||||
|
offset += "\n".text_len();
|
||||||
|
|
||||||
|
// If some line of the docstring is less indented than the function body, we pad all lines to
|
||||||
|
// align it with the docstring statement. Conversely, if all lines are over-indented, we strip
|
||||||
|
// the extra indentation. We call this stripped indentation since it's relative to the block
|
||||||
|
// indent printer-made indentation.
|
||||||
|
let stripped_indentation = lines
|
||||||
|
.clone()
|
||||||
|
// We don't want to count whitespace-only lines as miss-indented
|
||||||
|
.filter(|line| !line.trim().is_empty())
|
||||||
|
.map(count_indentation_like_black)
|
||||||
|
.min()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
while let Some(line) = lines.next() {
|
||||||
|
let is_last = lines.peek().is_none();
|
||||||
|
format_docstring_line(
|
||||||
|
line,
|
||||||
|
is_last,
|
||||||
|
offset,
|
||||||
|
stripped_indentation,
|
||||||
|
already_normalized,
|
||||||
|
f,
|
||||||
|
)?;
|
||||||
|
// We know that the normalized string has \n line endings
|
||||||
|
offset += line.text_len() + "\n".text_len();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same special case in the last line as for the first line
|
||||||
|
let trim_end = normalized
|
||||||
|
.as_ref()
|
||||||
|
.trim_end_matches(|c: char| c.is_whitespace() && c != '\n');
|
||||||
|
if needs_chaperone_space(string_part, trim_end) {
|
||||||
|
space().fmt(f)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
[
|
||||||
|
string_part.preferred_quotes,
|
||||||
|
source_position(string_part.range.end())
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the last line of the docstring is `content" """` or `content\ """`, we need a chaperone space
|
||||||
|
/// 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(string_part: &FormatStringPart, trim_end: &str) -> bool {
|
||||||
|
trim_end.ends_with(string_part.preferred_quotes.style.as_char())
|
||||||
|
|| trim_end.chars().rev().take_while(|c| *c == '\\').count() % 2 == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format a docstring line that is not the first line
|
||||||
|
fn format_docstring_line(
|
||||||
|
line: &str,
|
||||||
|
is_last: bool,
|
||||||
|
offset: TextSize,
|
||||||
|
stripped_indentation: TextSize,
|
||||||
|
already_normalized: bool,
|
||||||
|
f: &mut PyFormatter,
|
||||||
|
) -> FormatResult<()> {
|
||||||
|
let trim_end = line.trim_end();
|
||||||
|
if trim_end.is_empty() {
|
||||||
|
return if is_last {
|
||||||
|
// If the doc string ends with ` """`, the last line is ` `, but we don't want to
|
||||||
|
// insert an empty line (but close the docstring)
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
empty_line().fmt(f)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let tab_or_non_ascii_space = trim_end
|
||||||
|
.chars()
|
||||||
|
.take_while(|c| c.is_whitespace())
|
||||||
|
.any(|c| c != ' ');
|
||||||
|
|
||||||
|
if tab_or_non_ascii_space {
|
||||||
|
// We strip the indentation that is shared with the docstring statement, unless a line
|
||||||
|
// was indented less than the docstring statement, in which we strip only this much
|
||||||
|
// indentation to implicitly pad all lines by the difference, or all lines were
|
||||||
|
// overindented, in which case we strip the additional whitespace (see example in
|
||||||
|
// [`format_docstring`] doc comment). We then prepend the in-docstring indentation to the
|
||||||
|
// string.
|
||||||
|
let indent_len = count_indentation_like_black(trim_end) - stripped_indentation;
|
||||||
|
let in_docstring_indent = " ".repeat(indent_len.to_usize()) + trim_end.trim_start();
|
||||||
|
dynamic_text(&in_docstring_indent, Some(offset)).fmt(f)?;
|
||||||
|
} else {
|
||||||
|
// Take the string with the trailing whitespace removed, then also skip the leading
|
||||||
|
// whitespace
|
||||||
|
let trimmed_line_range =
|
||||||
|
TextRange::at(offset, trim_end.text_len()).add_start(stripped_indentation);
|
||||||
|
if already_normalized {
|
||||||
|
source_text_slice(trimmed_line_range, ContainsNewlines::No).fmt(f)?;
|
||||||
|
} else {
|
||||||
|
// All indents are ascii spaces, so the slicing is correct
|
||||||
|
dynamic_text(
|
||||||
|
&trim_end[stripped_indentation.to_usize()..],
|
||||||
|
Some(trimmed_line_range.start()),
|
||||||
|
)
|
||||||
|
.fmt(f)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We handled the case that the closing quotes are on their own line above (the last line is
|
||||||
|
// empty except for whitespace). If they are on the same line as content, we don't insert a line
|
||||||
|
// break.
|
||||||
|
if !is_last {
|
||||||
|
hard_line_break().fmt(f)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::expression::string::count_indentation_like_black;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_indentation_like_black() {
|
||||||
|
assert_eq!(count_indentation_like_black("\t \t \t").to_u32(), 24);
|
||||||
|
assert_eq!(count_indentation_like_black("\t \t").to_u32(), 24);
|
||||||
|
assert_eq!(count_indentation_like_black("\t\t\t").to_u32(), 24);
|
||||||
|
assert_eq!(count_indentation_like_black(" ").to_u32(), 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
use ruff_formatter::{write, FormatOwnedWithRule, FormatRefWithRule, FormatRuleWithOptions};
|
use ruff_formatter::{write, FormatOwnedWithRule, FormatRefWithRule, FormatRuleWithOptions};
|
||||||
use ruff_python_ast::helpers::is_compound_statement;
|
use ruff_python_ast::helpers::is_compound_statement;
|
||||||
use ruff_python_ast::{self as ast, Constant, Expr, Ranged, Stmt, Suite};
|
use ruff_python_ast::str::is_implicit_concatenation;
|
||||||
|
use ruff_python_ast::{self as ast, Expr, Ranged, Stmt, Suite};
|
||||||
|
use ruff_python_ast::{Constant, ExprConstant};
|
||||||
use ruff_python_trivia::{lines_after_ignoring_trivia, lines_before};
|
use ruff_python_trivia::{lines_after_ignoring_trivia, lines_before};
|
||||||
|
use ruff_source_file::Locator;
|
||||||
|
|
||||||
|
use crate::comments::{leading_comments, trailing_comments};
|
||||||
use crate::context::{NodeLevel, WithNodeLevel};
|
use crate::context::{NodeLevel, WithNodeLevel};
|
||||||
|
use crate::expression::expr_constant::ExprConstantLayout;
|
||||||
|
use crate::expression::string::StringLayout;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// Level at which the [`Suite`] appears in the source code.
|
/// Level at which the [`Suite`] appears in the source code.
|
||||||
|
@ -71,44 +77,76 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
||||||
}
|
}
|
||||||
write!(f, [first.format()])?;
|
write!(f, [first.format()])?;
|
||||||
}
|
}
|
||||||
SuiteKind::Class if is_docstring(first) => {
|
SuiteKind::Function => {
|
||||||
if !comments.has_leading_comments(first) && lines_before(first.start(), source) > 1
|
if let Some(constant) = get_docstring(first, &f.context().locator()) {
|
||||||
{
|
write!(
|
||||||
// Allow up to one empty line before a class docstring, e.g., this is
|
f,
|
||||||
// stable formatting:
|
[
|
||||||
|
// We format the expression, but the statement carries the comments
|
||||||
|
leading_comments(comments.leading_comments(first)),
|
||||||
|
constant
|
||||||
|
.format()
|
||||||
|
.with_options(ExprConstantLayout::String(StringLayout::DocString)),
|
||||||
|
trailing_comments(comments.trailing_comments(first)),
|
||||||
|
]
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
write!(f, [first.format()])?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SuiteKind::Class => {
|
||||||
|
if let Some(constant) = get_docstring(first, &f.context().locator()) {
|
||||||
|
if !comments.has_leading_comments(first)
|
||||||
|
&& lines_before(first.start(), source) > 1
|
||||||
|
{
|
||||||
|
// Allow up to one empty line before a class docstring
|
||||||
|
// ```python
|
||||||
|
// class Test:
|
||||||
|
//
|
||||||
|
// """Docstring"""
|
||||||
|
// ```
|
||||||
|
write!(f, [empty_line()])?;
|
||||||
|
}
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
[
|
||||||
|
// We format the expression, but the statement carries the comments
|
||||||
|
leading_comments(comments.leading_comments(first)),
|
||||||
|
constant
|
||||||
|
.format()
|
||||||
|
.with_options(ExprConstantLayout::String(StringLayout::DocString)),
|
||||||
|
trailing_comments(comments.trailing_comments(first)),
|
||||||
|
]
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Enforce an empty line after a class docstring
|
||||||
// ```python
|
// ```python
|
||||||
// class Test:
|
// class Test:
|
||||||
|
// """Docstring"""
|
||||||
|
//
|
||||||
|
// ...
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// class Test:
|
||||||
//
|
//
|
||||||
// """Docstring"""
|
// """Docstring"""
|
||||||
|
//
|
||||||
|
// ...
|
||||||
// ```
|
// ```
|
||||||
write!(f, [empty_line()])?;
|
// Unlike black, we add the newline also after single quoted docstrings
|
||||||
}
|
if let Some(second) = iter.next() {
|
||||||
write!(f, [first.format()])?;
|
// Format the subsequent statement immediately. This rule takes precedence
|
||||||
|
// over the rules in the loop below (and most of them won't apply anyway,
|
||||||
// Enforce an empty line after a class docstring, e.g., these are both stable
|
// e.g., we know the first statement isn't an import).
|
||||||
// formatting:
|
write!(f, [empty_line(), second.format()])?;
|
||||||
// ```python
|
last = second;
|
||||||
// class Test:
|
}
|
||||||
// """Docstring"""
|
} else {
|
||||||
//
|
// No docstring, use normal formatting
|
||||||
// ...
|
write!(f, [first.format()])?;
|
||||||
//
|
|
||||||
//
|
|
||||||
// class Test:
|
|
||||||
//
|
|
||||||
// """Docstring"""
|
|
||||||
//
|
|
||||||
// ...
|
|
||||||
// ```
|
|
||||||
if let Some(second) = iter.next() {
|
|
||||||
// Format the subsequent statement immediately. This rule takes precedence
|
|
||||||
// over the rules in the loop below (and most of them won't apply anyway,
|
|
||||||
// e.g., we know the first statement isn't an import).
|
|
||||||
write!(f, [empty_line(), second.format()])?;
|
|
||||||
last = second;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
SuiteKind::TopLevel => {
|
||||||
write!(f, [first.format()])?;
|
write!(f, [first.format()])?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -218,18 +256,27 @@ const fn is_import_definition(stmt: &Stmt) -> bool {
|
||||||
matches!(stmt, Stmt::Import(_) | Stmt::ImportFrom(_))
|
matches!(stmt, Stmt::Import(_) | Stmt::ImportFrom(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_docstring(stmt: &Stmt) -> bool {
|
/// Checks if the statement is a simple string that can be formatted as a docstring
|
||||||
|
fn get_docstring<'a>(stmt: &'a Stmt, locator: &Locator) -> Option<&'a ExprConstant> {
|
||||||
let Stmt::Expr(ast::StmtExpr { value, .. }) = stmt else {
|
let Stmt::Expr(ast::StmtExpr { value, .. }) = stmt else {
|
||||||
return false;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
matches!(
|
let Expr::Constant(constant) = value.as_ref() else {
|
||||||
value.as_ref(),
|
return None;
|
||||||
Expr::Constant(ast::ExprConstant {
|
};
|
||||||
value: Constant::Str(..),
|
if let ExprConstant {
|
||||||
..
|
value: Constant::Str(..),
|
||||||
})
|
range,
|
||||||
)
|
..
|
||||||
|
} = constant
|
||||||
|
{
|
||||||
|
if is_implicit_concatenation(locator.slice(*range)) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
return Some(constant);
|
||||||
|
}
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FormatRuleWithOptions<Suite, PyFormatContext<'_>> for FormatSuite {
|
impl FormatRuleWithOptions<Suite, PyFormatContext<'_>> for FormatSuite {
|
||||||
|
@ -260,7 +307,6 @@ impl<'ast> IntoFormat<PyFormatContext<'ast>> for Suite {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use ruff_formatter::format;
|
use ruff_formatter::format;
|
||||||
|
|
||||||
use ruff_python_parser::parse_suite;
|
use ruff_python_parser::parse_suite;
|
||||||
|
|
||||||
use crate::comments::Comments;
|
use crate::comments::Comments;
|
||||||
|
|
|
@ -135,7 +135,7 @@ def multiline_backslash_3():
|
||||||
```diff
|
```diff
|
||||||
--- Black
|
--- Black
|
||||||
+++ Ruff
|
+++ Ruff
|
||||||
@@ -1,73 +1,75 @@
|
@@ -1,24 +1,24 @@
|
||||||
class ALonelyClass:
|
class ALonelyClass:
|
||||||
- '''
|
- '''
|
||||||
+ """
|
+ """
|
||||||
|
@ -167,96 +167,7 @@ def multiline_backslash_3():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def foo():
|
@@ -97,27 +97,27 @@
|
||||||
- """This is a docstring with
|
|
||||||
- some lines of text here
|
|
||||||
- """
|
|
||||||
+ """This is a docstring with
|
|
||||||
+ some lines of text here
|
|
||||||
+ """
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def baz():
|
|
||||||
'''"This" is a string with some
|
|
||||||
- embedded "quotes"'''
|
|
||||||
+ embedded "quotes"'''
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def poit():
|
|
||||||
"""
|
|
||||||
- Lorem ipsum dolor sit amet.
|
|
||||||
+ Lorem ipsum dolor sit amet.
|
|
||||||
|
|
||||||
- Consectetur adipiscing elit:
|
|
||||||
- - sed do eiusmod tempor incididunt ut labore
|
|
||||||
- - dolore magna aliqua
|
|
||||||
- - enim ad minim veniam
|
|
||||||
- - quis nostrud exercitation ullamco laboris nisi
|
|
||||||
- - aliquip ex ea commodo consequat
|
|
||||||
- """
|
|
||||||
+ Consectetur adipiscing elit:
|
|
||||||
+ - sed do eiusmod tempor incididunt ut labore
|
|
||||||
+ - dolore magna aliqua
|
|
||||||
+ - enim ad minim veniam
|
|
||||||
+ - quis nostrud exercitation ullamco laboris nisi
|
|
||||||
+ - aliquip ex ea commodo consequat
|
|
||||||
+ """
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def under_indent():
|
|
||||||
"""
|
|
||||||
- These lines are indented in a way that does not
|
|
||||||
- make sense.
|
|
||||||
- """
|
|
||||||
+ These lines are indented in a way that does not
|
|
||||||
+make sense.
|
|
||||||
+ """
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def over_indent():
|
|
||||||
"""
|
|
||||||
- This has a shallow indent
|
|
||||||
- - But some lines are deeper
|
|
||||||
- - And the closing quote is too deep
|
|
||||||
+ This has a shallow indent
|
|
||||||
+ - But some lines are deeper
|
|
||||||
+ - And the closing quote is too deep
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def single_line():
|
|
||||||
- """But with a newline after it!"""
|
|
||||||
+ """But with a newline after it!
|
|
||||||
+
|
|
||||||
+ """
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@@ -83,41 +85,41 @@
|
|
||||||
|
|
||||||
def and_that():
|
|
||||||
"""
|
|
||||||
- "hey yah" """
|
|
||||||
+ "hey yah" """
|
|
||||||
|
|
||||||
|
|
||||||
def and_this():
|
|
||||||
- '''
|
|
||||||
- "hey yah"'''
|
|
||||||
+ '''
|
|
||||||
+ "hey yah"'''
|
|
||||||
|
|
||||||
|
|
||||||
def believe_it_or_not_this_is_in_the_py_stdlib():
|
|
||||||
- '''
|
|
||||||
- "hey yah"'''
|
|
||||||
+ '''
|
|
||||||
+"hey yah"'''
|
|
||||||
|
|
||||||
|
|
||||||
def shockingly_the_quotes_are_normalized_v2():
|
def shockingly_the_quotes_are_normalized_v2():
|
||||||
|
@ -285,14 +196,14 @@ def multiline_backslash_3():
|
||||||
- '''
|
- '''
|
||||||
- hey there \ '''
|
- hey there \ '''
|
||||||
+ """
|
+ """
|
||||||
+ hey there \ """
|
+ hey there \ """
|
||||||
|
|
||||||
|
|
||||||
def multiline_backslash_3():
|
def multiline_backslash_3():
|
||||||
- '''
|
- '''
|
||||||
- already escaped \\'''
|
- already escaped \\'''
|
||||||
+ """
|
+ """
|
||||||
+ already escaped \\ """
|
+ already escaped \\"""
|
||||||
```
|
```
|
||||||
|
|
||||||
## Ruff Output
|
## Ruff Output
|
||||||
|
@ -323,53 +234,51 @@ def shockingly_the_quotes_are_normalized():
|
||||||
|
|
||||||
|
|
||||||
def foo():
|
def foo():
|
||||||
"""This is a docstring with
|
"""This is a docstring with
|
||||||
some lines of text here
|
some lines of text here
|
||||||
"""
|
"""
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def baz():
|
def baz():
|
||||||
'''"This" is a string with some
|
'''"This" is a string with some
|
||||||
embedded "quotes"'''
|
embedded "quotes"'''
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def poit():
|
def poit():
|
||||||
"""
|
"""
|
||||||
Lorem ipsum dolor sit amet.
|
Lorem ipsum dolor sit amet.
|
||||||
|
|
||||||
Consectetur adipiscing elit:
|
Consectetur adipiscing elit:
|
||||||
- sed do eiusmod tempor incididunt ut labore
|
- sed do eiusmod tempor incididunt ut labore
|
||||||
- dolore magna aliqua
|
- dolore magna aliqua
|
||||||
- enim ad minim veniam
|
- enim ad minim veniam
|
||||||
- quis nostrud exercitation ullamco laboris nisi
|
- quis nostrud exercitation ullamco laboris nisi
|
||||||
- aliquip ex ea commodo consequat
|
- aliquip ex ea commodo consequat
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def under_indent():
|
def under_indent():
|
||||||
"""
|
"""
|
||||||
These lines are indented in a way that does not
|
These lines are indented in a way that does not
|
||||||
make sense.
|
make sense.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def over_indent():
|
def over_indent():
|
||||||
"""
|
"""
|
||||||
This has a shallow indent
|
This has a shallow indent
|
||||||
- But some lines are deeper
|
- But some lines are deeper
|
||||||
- And the closing quote is too deep
|
- And the closing quote is too deep
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def single_line():
|
def single_line():
|
||||||
"""But with a newline after it!
|
"""But with a newline after it!"""
|
||||||
|
|
||||||
"""
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@ -385,17 +294,17 @@ def that():
|
||||||
|
|
||||||
def and_that():
|
def and_that():
|
||||||
"""
|
"""
|
||||||
"hey yah" """
|
"hey yah" """
|
||||||
|
|
||||||
|
|
||||||
def and_this():
|
def and_this():
|
||||||
'''
|
'''
|
||||||
"hey yah"'''
|
"hey yah"'''
|
||||||
|
|
||||||
|
|
||||||
def believe_it_or_not_this_is_in_the_py_stdlib():
|
def believe_it_or_not_this_is_in_the_py_stdlib():
|
||||||
'''
|
'''
|
||||||
"hey yah"'''
|
"hey yah"'''
|
||||||
|
|
||||||
|
|
||||||
def shockingly_the_quotes_are_normalized_v2():
|
def shockingly_the_quotes_are_normalized_v2():
|
||||||
|
@ -417,12 +326,12 @@ def multiline_backslash_1():
|
||||||
|
|
||||||
def multiline_backslash_2():
|
def multiline_backslash_2():
|
||||||
"""
|
"""
|
||||||
hey there \ """
|
hey there \ """
|
||||||
|
|
||||||
|
|
||||||
def multiline_backslash_3():
|
def multiline_backslash_3():
|
||||||
"""
|
"""
|
||||||
already escaped \\ """
|
already escaped \\"""
|
||||||
```
|
```
|
||||||
|
|
||||||
## Black Output
|
## Black Output
|
||||||
|
|
|
@ -1,105 +0,0 @@
|
||||||
---
|
|
||||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
|
||||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments_non_breaking_space.py
|
|
||||||
---
|
|
||||||
## Input
|
|
||||||
|
|
||||||
```py
|
|
||||||
from .config import ( ConfigTypeAttributes, Int, Path, # String,
|
|
||||||
# DEFAULT_TYPE_ATTRIBUTES,
|
|
||||||
)
|
|
||||||
|
|
||||||
result = 1 # A simple comment
|
|
||||||
result = ( 1, ) # Another one
|
|
||||||
|
|
||||||
result = 1 # type: ignore
|
|
||||||
result = 1# This comment is talking about type: ignore
|
|
||||||
square = Square(4) # type: Optional[Square]
|
|
||||||
|
|
||||||
def function(a:int=42):
|
|
||||||
""" This docstring is already formatted
|
|
||||||
a
|
|
||||||
b
|
|
||||||
"""
|
|
||||||
# There's a NBSP + 3 spaces before
|
|
||||||
# And 4 spaces on the next line
|
|
||||||
pass
|
|
||||||
```
|
|
||||||
|
|
||||||
## Black Differences
|
|
||||||
|
|
||||||
```diff
|
|
||||||
--- Black
|
|
||||||
+++ Ruff
|
|
||||||
@@ -14,9 +14,9 @@
|
|
||||||
|
|
||||||
|
|
||||||
def function(a: int = 42):
|
|
||||||
- """This docstring is already formatted
|
|
||||||
- a
|
|
||||||
- b
|
|
||||||
+ """ This docstring is already formatted
|
|
||||||
+ a
|
|
||||||
+ b
|
|
||||||
"""
|
|
||||||
# There's a NBSP + 3 spaces before
|
|
||||||
# And 4 spaces on the next line
|
|
||||||
```
|
|
||||||
|
|
||||||
## Ruff Output
|
|
||||||
|
|
||||||
```py
|
|
||||||
from .config import (
|
|
||||||
ConfigTypeAttributes,
|
|
||||||
Int,
|
|
||||||
Path, # String,
|
|
||||||
# DEFAULT_TYPE_ATTRIBUTES,
|
|
||||||
)
|
|
||||||
|
|
||||||
result = 1 # A simple comment
|
|
||||||
result = (1,) # Another one
|
|
||||||
|
|
||||||
result = 1 # type: ignore
|
|
||||||
result = 1 # This comment is talking about type: ignore
|
|
||||||
square = Square(4) # type: Optional[Square]
|
|
||||||
|
|
||||||
|
|
||||||
def function(a: int = 42):
|
|
||||||
""" This docstring is already formatted
|
|
||||||
a
|
|
||||||
b
|
|
||||||
"""
|
|
||||||
# There's a NBSP + 3 spaces before
|
|
||||||
# And 4 spaces on the next line
|
|
||||||
pass
|
|
||||||
```
|
|
||||||
|
|
||||||
## Black Output
|
|
||||||
|
|
||||||
```py
|
|
||||||
from .config import (
|
|
||||||
ConfigTypeAttributes,
|
|
||||||
Int,
|
|
||||||
Path, # String,
|
|
||||||
# DEFAULT_TYPE_ATTRIBUTES,
|
|
||||||
)
|
|
||||||
|
|
||||||
result = 1 # A simple comment
|
|
||||||
result = (1,) # Another one
|
|
||||||
|
|
||||||
result = 1 # type: ignore
|
|
||||||
result = 1 # This comment is talking about type: ignore
|
|
||||||
square = Square(4) # type: Optional[Square]
|
|
||||||
|
|
||||||
|
|
||||||
def function(a: int = 42):
|
|
||||||
"""This docstring is already formatted
|
|
||||||
a
|
|
||||||
b
|
|
||||||
"""
|
|
||||||
# There's a NBSP + 3 spaces before
|
|
||||||
# And 4 spaces on the next line
|
|
||||||
pass
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
|
@ -1,924 +0,0 @@
|
||||||
---
|
|
||||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
|
||||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/docstring.py
|
|
||||||
---
|
|
||||||
## Input
|
|
||||||
|
|
||||||
```py
|
|
||||||
class MyClass:
|
|
||||||
""" Multiline
|
|
||||||
class docstring
|
|
||||||
"""
|
|
||||||
|
|
||||||
def method(self):
|
|
||||||
"""Multiline
|
|
||||||
method docstring
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def foo():
|
|
||||||
"""This is a docstring with
|
|
||||||
some lines of text here
|
|
||||||
"""
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def bar():
|
|
||||||
'''This is another docstring
|
|
||||||
with more lines of text
|
|
||||||
'''
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def baz():
|
|
||||||
'''"This" is a string with some
|
|
||||||
embedded "quotes"'''
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def troz():
|
|
||||||
'''Indentation with tabs
|
|
||||||
is just as OK
|
|
||||||
'''
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def zort():
|
|
||||||
"""Another
|
|
||||||
multiline
|
|
||||||
docstring
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def poit():
|
|
||||||
"""
|
|
||||||
Lorem ipsum dolor sit amet.
|
|
||||||
|
|
||||||
Consectetur adipiscing elit:
|
|
||||||
- sed do eiusmod tempor incididunt ut labore
|
|
||||||
- dolore magna aliqua
|
|
||||||
- enim ad minim veniam
|
|
||||||
- quis nostrud exercitation ullamco laboris nisi
|
|
||||||
- aliquip ex ea commodo consequat
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def under_indent():
|
|
||||||
"""
|
|
||||||
These lines are indented in a way that does not
|
|
||||||
make sense.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def over_indent():
|
|
||||||
"""
|
|
||||||
This has a shallow indent
|
|
||||||
- But some lines are deeper
|
|
||||||
- And the closing quote is too deep
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def single_line():
|
|
||||||
"""But with a newline after it!
|
|
||||||
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def this():
|
|
||||||
r"""
|
|
||||||
'hey ho'
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def that():
|
|
||||||
""" "hey yah" """
|
|
||||||
|
|
||||||
|
|
||||||
def and_that():
|
|
||||||
"""
|
|
||||||
"hey yah" """
|
|
||||||
|
|
||||||
|
|
||||||
def and_this():
|
|
||||||
'''
|
|
||||||
"hey yah"'''
|
|
||||||
|
|
||||||
|
|
||||||
def multiline_whitespace():
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
def oneline_whitespace():
|
|
||||||
''' '''
|
|
||||||
|
|
||||||
|
|
||||||
def empty():
|
|
||||||
""""""
|
|
||||||
|
|
||||||
|
|
||||||
def single_quotes():
|
|
||||||
'testing'
|
|
||||||
|
|
||||||
|
|
||||||
def believe_it_or_not_this_is_in_the_py_stdlib(): '''
|
|
||||||
"hey yah"'''
|
|
||||||
|
|
||||||
|
|
||||||
def ignored_docstring():
|
|
||||||
"""a => \
|
|
||||||
b"""
|
|
||||||
|
|
||||||
def single_line_docstring_with_whitespace():
|
|
||||||
""" This should be stripped """
|
|
||||||
|
|
||||||
def docstring_with_inline_tabs_and_space_indentation():
|
|
||||||
"""hey
|
|
||||||
|
|
||||||
tab separated value
|
|
||||||
tab at start of line and then a tab separated value
|
|
||||||
multiple tabs at the beginning and inline
|
|
||||||
mixed tabs and spaces at beginning. next line has mixed tabs and spaces only.
|
|
||||||
|
|
||||||
line ends with some tabs
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def docstring_with_inline_tabs_and_tab_indentation():
|
|
||||||
"""hey
|
|
||||||
|
|
||||||
tab separated value
|
|
||||||
tab at start of line and then a tab separated value
|
|
||||||
multiple tabs at the beginning and inline
|
|
||||||
mixed tabs and spaces at beginning. next line has mixed tabs and spaces only.
|
|
||||||
|
|
||||||
line ends with some tabs
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def backslash_space():
|
|
||||||
"""\ """
|
|
||||||
|
|
||||||
|
|
||||||
def multiline_backslash_1():
|
|
||||||
'''
|
|
||||||
hey\there\
|
|
||||||
\ '''
|
|
||||||
|
|
||||||
|
|
||||||
def multiline_backslash_2():
|
|
||||||
'''
|
|
||||||
hey there \ '''
|
|
||||||
|
|
||||||
# Regression test for #3425
|
|
||||||
def multiline_backslash_really_long_dont_crash():
|
|
||||||
"""
|
|
||||||
hey there hello guten tag hi hoow are you ola zdravstvuyte ciao como estas ca va \ """
|
|
||||||
|
|
||||||
|
|
||||||
def multiline_backslash_3():
|
|
||||||
'''
|
|
||||||
already escaped \\ '''
|
|
||||||
|
|
||||||
|
|
||||||
def my_god_its_full_of_stars_1():
|
|
||||||
"I'm sorry Dave\u2001"
|
|
||||||
|
|
||||||
|
|
||||||
# the space below is actually a \u2001, removed in output
|
|
||||||
def my_god_its_full_of_stars_2():
|
|
||||||
"I'm sorry Dave "
|
|
||||||
|
|
||||||
|
|
||||||
def docstring_almost_at_line_limit():
|
|
||||||
"""long docstring................................................................."""
|
|
||||||
|
|
||||||
|
|
||||||
def docstring_almost_at_line_limit2():
|
|
||||||
"""long docstring.................................................................
|
|
||||||
|
|
||||||
..................................................................................
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def docstring_at_line_limit():
|
|
||||||
"""long docstring................................................................"""
|
|
||||||
|
|
||||||
|
|
||||||
def multiline_docstring_at_line_limit():
|
|
||||||
"""first line-----------------------------------------------------------------------
|
|
||||||
|
|
||||||
second line----------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
|
|
||||||
def stable_quote_normalization_with_immediate_inner_single_quote(self):
|
|
||||||
''''<text here>
|
|
||||||
|
|
||||||
<text here, since without another non-empty line black is stable>
|
|
||||||
'''
|
|
||||||
```
|
|
||||||
|
|
||||||
## Black Differences
|
|
||||||
|
|
||||||
```diff
|
|
||||||
--- Black
|
|
||||||
+++ Ruff
|
|
||||||
@@ -1,83 +1,85 @@
|
|
||||||
class MyClass:
|
|
||||||
- """Multiline
|
|
||||||
- class docstring
|
|
||||||
- """
|
|
||||||
+ """ Multiline
|
|
||||||
+ class docstring
|
|
||||||
+ """
|
|
||||||
|
|
||||||
def method(self):
|
|
||||||
"""Multiline
|
|
||||||
- method docstring
|
|
||||||
- """
|
|
||||||
+ method docstring
|
|
||||||
+ """
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def foo():
|
|
||||||
- """This is a docstring with
|
|
||||||
- some lines of text here
|
|
||||||
- """
|
|
||||||
+ """This is a docstring with
|
|
||||||
+ some lines of text here
|
|
||||||
+ """
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def bar():
|
|
||||||
"""This is another docstring
|
|
||||||
- with more lines of text
|
|
||||||
- """
|
|
||||||
+ with more lines of text
|
|
||||||
+ """
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def baz():
|
|
||||||
'''"This" is a string with some
|
|
||||||
- embedded "quotes"'''
|
|
||||||
+ embedded "quotes"'''
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def troz():
|
|
||||||
"""Indentation with tabs
|
|
||||||
- is just as OK
|
|
||||||
- """
|
|
||||||
+ is just as OK
|
|
||||||
+ """
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def zort():
|
|
||||||
"""Another
|
|
||||||
- multiline
|
|
||||||
- docstring
|
|
||||||
- """
|
|
||||||
+ multiline
|
|
||||||
+ docstring
|
|
||||||
+ """
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def poit():
|
|
||||||
"""
|
|
||||||
- Lorem ipsum dolor sit amet.
|
|
||||||
+ Lorem ipsum dolor sit amet.
|
|
||||||
|
|
||||||
- Consectetur adipiscing elit:
|
|
||||||
- - sed do eiusmod tempor incididunt ut labore
|
|
||||||
- - dolore magna aliqua
|
|
||||||
- - enim ad minim veniam
|
|
||||||
- - quis nostrud exercitation ullamco laboris nisi
|
|
||||||
- - aliquip ex ea commodo consequat
|
|
||||||
- """
|
|
||||||
+ Consectetur adipiscing elit:
|
|
||||||
+ - sed do eiusmod tempor incididunt ut labore
|
|
||||||
+ - dolore magna aliqua
|
|
||||||
+ - enim ad minim veniam
|
|
||||||
+ - quis nostrud exercitation ullamco laboris nisi
|
|
||||||
+ - aliquip ex ea commodo consequat
|
|
||||||
+ """
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def under_indent():
|
|
||||||
"""
|
|
||||||
- These lines are indented in a way that does not
|
|
||||||
- make sense.
|
|
||||||
- """
|
|
||||||
+ These lines are indented in a way that does not
|
|
||||||
+make sense.
|
|
||||||
+ """
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def over_indent():
|
|
||||||
"""
|
|
||||||
- This has a shallow indent
|
|
||||||
- - But some lines are deeper
|
|
||||||
- - And the closing quote is too deep
|
|
||||||
+ This has a shallow indent
|
|
||||||
+ - But some lines are deeper
|
|
||||||
+ - And the closing quote is too deep
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def single_line():
|
|
||||||
- """But with a newline after it!"""
|
|
||||||
+ """But with a newline after it!
|
|
||||||
+
|
|
||||||
+ """
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@@ -93,20 +95,25 @@
|
|
||||||
|
|
||||||
def and_that():
|
|
||||||
"""
|
|
||||||
- "hey yah" """
|
|
||||||
+ "hey yah" """
|
|
||||||
|
|
||||||
|
|
||||||
def and_this():
|
|
||||||
- '''
|
|
||||||
- "hey yah"'''
|
|
||||||
+ '''
|
|
||||||
+ "hey yah"'''
|
|
||||||
|
|
||||||
|
|
||||||
def multiline_whitespace():
|
|
||||||
- """ """
|
|
||||||
+ """
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+ """
|
|
||||||
|
|
||||||
|
|
||||||
def oneline_whitespace():
|
|
||||||
- """ """
|
|
||||||
+ """ """
|
|
||||||
|
|
||||||
|
|
||||||
def empty():
|
|
||||||
@@ -118,8 +125,8 @@
|
|
||||||
|
|
||||||
|
|
||||||
def believe_it_or_not_this_is_in_the_py_stdlib():
|
|
||||||
- '''
|
|
||||||
- "hey yah"'''
|
|
||||||
+ '''
|
|
||||||
+"hey yah"'''
|
|
||||||
|
|
||||||
|
|
||||||
def ignored_docstring():
|
|
||||||
@@ -128,31 +135,31 @@
|
|
||||||
|
|
||||||
|
|
||||||
def single_line_docstring_with_whitespace():
|
|
||||||
- """This should be stripped"""
|
|
||||||
+ """ This should be stripped """
|
|
||||||
|
|
||||||
|
|
||||||
def docstring_with_inline_tabs_and_space_indentation():
|
|
||||||
"""hey
|
|
||||||
|
|
||||||
tab separated value
|
|
||||||
- tab at start of line and then a tab separated value
|
|
||||||
- multiple tabs at the beginning and inline
|
|
||||||
- mixed tabs and spaces at beginning. next line has mixed tabs and spaces only.
|
|
||||||
-
|
|
||||||
- line ends with some tabs
|
|
||||||
+ tab at start of line and then a tab separated value
|
|
||||||
+ multiple tabs at the beginning and inline
|
|
||||||
+ mixed tabs and spaces at beginning. next line has mixed tabs and spaces only.
|
|
||||||
+
|
|
||||||
+ line ends with some tabs
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def docstring_with_inline_tabs_and_tab_indentation():
|
|
||||||
"""hey
|
|
||||||
|
|
||||||
- tab separated value
|
|
||||||
- tab at start of line and then a tab separated value
|
|
||||||
- multiple tabs at the beginning and inline
|
|
||||||
- mixed tabs and spaces at beginning. next line has mixed tabs and spaces only.
|
|
||||||
-
|
|
||||||
- line ends with some tabs
|
|
||||||
- """
|
|
||||||
+ tab separated value
|
|
||||||
+ tab at start of line and then a tab separated value
|
|
||||||
+ multiple tabs at the beginning and inline
|
|
||||||
+ mixed tabs and spaces at beginning. next line has mixed tabs and spaces only.
|
|
||||||
+
|
|
||||||
+ line ends with some tabs
|
|
||||||
+ """
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@@ -168,7 +175,7 @@
|
|
||||||
|
|
||||||
def multiline_backslash_2():
|
|
||||||
"""
|
|
||||||
- hey there \ """
|
|
||||||
+ hey there \ """
|
|
||||||
|
|
||||||
|
|
||||||
# Regression test for #3425
|
|
||||||
@@ -179,7 +186,7 @@
|
|
||||||
|
|
||||||
def multiline_backslash_3():
|
|
||||||
"""
|
|
||||||
- already escaped \\"""
|
|
||||||
+ already escaped \\ """
|
|
||||||
|
|
||||||
|
|
||||||
def my_god_its_full_of_stars_1():
|
|
||||||
@@ -188,7 +195,7 @@
|
|
||||||
|
|
||||||
# the space below is actually a \u2001, removed in output
|
|
||||||
def my_god_its_full_of_stars_2():
|
|
||||||
- "I'm sorry Dave"
|
|
||||||
+ "I'm sorry Dave "
|
|
||||||
|
|
||||||
|
|
||||||
def docstring_almost_at_line_limit():
|
|
||||||
```
|
|
||||||
|
|
||||||
## Ruff Output
|
|
||||||
|
|
||||||
```py
|
|
||||||
class MyClass:
|
|
||||||
""" Multiline
|
|
||||||
class docstring
|
|
||||||
"""
|
|
||||||
|
|
||||||
def method(self):
|
|
||||||
"""Multiline
|
|
||||||
method docstring
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def foo():
|
|
||||||
"""This is a docstring with
|
|
||||||
some lines of text here
|
|
||||||
"""
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def bar():
|
|
||||||
"""This is another docstring
|
|
||||||
with more lines of text
|
|
||||||
"""
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def baz():
|
|
||||||
'''"This" is a string with some
|
|
||||||
embedded "quotes"'''
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def troz():
|
|
||||||
"""Indentation with tabs
|
|
||||||
is just as OK
|
|
||||||
"""
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def zort():
|
|
||||||
"""Another
|
|
||||||
multiline
|
|
||||||
docstring
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def poit():
|
|
||||||
"""
|
|
||||||
Lorem ipsum dolor sit amet.
|
|
||||||
|
|
||||||
Consectetur adipiscing elit:
|
|
||||||
- sed do eiusmod tempor incididunt ut labore
|
|
||||||
- dolore magna aliqua
|
|
||||||
- enim ad minim veniam
|
|
||||||
- quis nostrud exercitation ullamco laboris nisi
|
|
||||||
- aliquip ex ea commodo consequat
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def under_indent():
|
|
||||||
"""
|
|
||||||
These lines are indented in a way that does not
|
|
||||||
make sense.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def over_indent():
|
|
||||||
"""
|
|
||||||
This has a shallow indent
|
|
||||||
- But some lines are deeper
|
|
||||||
- And the closing quote is too deep
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def single_line():
|
|
||||||
"""But with a newline after it!
|
|
||||||
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def this():
|
|
||||||
r"""
|
|
||||||
'hey ho'
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def that():
|
|
||||||
""" "hey yah" """
|
|
||||||
|
|
||||||
|
|
||||||
def and_that():
|
|
||||||
"""
|
|
||||||
"hey yah" """
|
|
||||||
|
|
||||||
|
|
||||||
def and_this():
|
|
||||||
'''
|
|
||||||
"hey yah"'''
|
|
||||||
|
|
||||||
|
|
||||||
def multiline_whitespace():
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def oneline_whitespace():
|
|
||||||
""" """
|
|
||||||
|
|
||||||
|
|
||||||
def empty():
|
|
||||||
""""""
|
|
||||||
|
|
||||||
|
|
||||||
def single_quotes():
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
|
|
||||||
def believe_it_or_not_this_is_in_the_py_stdlib():
|
|
||||||
'''
|
|
||||||
"hey yah"'''
|
|
||||||
|
|
||||||
|
|
||||||
def ignored_docstring():
|
|
||||||
"""a => \
|
|
||||||
b"""
|
|
||||||
|
|
||||||
|
|
||||||
def single_line_docstring_with_whitespace():
|
|
||||||
""" This should be stripped """
|
|
||||||
|
|
||||||
|
|
||||||
def docstring_with_inline_tabs_and_space_indentation():
|
|
||||||
"""hey
|
|
||||||
|
|
||||||
tab separated value
|
|
||||||
tab at start of line and then a tab separated value
|
|
||||||
multiple tabs at the beginning and inline
|
|
||||||
mixed tabs and spaces at beginning. next line has mixed tabs and spaces only.
|
|
||||||
|
|
||||||
line ends with some tabs
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def docstring_with_inline_tabs_and_tab_indentation():
|
|
||||||
"""hey
|
|
||||||
|
|
||||||
tab separated value
|
|
||||||
tab at start of line and then a tab separated value
|
|
||||||
multiple tabs at the beginning and inline
|
|
||||||
mixed tabs and spaces at beginning. next line has mixed tabs and spaces only.
|
|
||||||
|
|
||||||
line ends with some tabs
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def backslash_space():
|
|
||||||
"""\ """
|
|
||||||
|
|
||||||
|
|
||||||
def multiline_backslash_1():
|
|
||||||
"""
|
|
||||||
hey\there\
|
|
||||||
\ """
|
|
||||||
|
|
||||||
|
|
||||||
def multiline_backslash_2():
|
|
||||||
"""
|
|
||||||
hey there \ """
|
|
||||||
|
|
||||||
|
|
||||||
# Regression test for #3425
|
|
||||||
def multiline_backslash_really_long_dont_crash():
|
|
||||||
"""
|
|
||||||
hey there hello guten tag hi hoow are you ola zdravstvuyte ciao como estas ca va \ """
|
|
||||||
|
|
||||||
|
|
||||||
def multiline_backslash_3():
|
|
||||||
"""
|
|
||||||
already escaped \\ """
|
|
||||||
|
|
||||||
|
|
||||||
def my_god_its_full_of_stars_1():
|
|
||||||
"I'm sorry Dave\u2001"
|
|
||||||
|
|
||||||
|
|
||||||
# the space below is actually a \u2001, removed in output
|
|
||||||
def my_god_its_full_of_stars_2():
|
|
||||||
"I'm sorry Dave "
|
|
||||||
|
|
||||||
|
|
||||||
def docstring_almost_at_line_limit():
|
|
||||||
"""long docstring................................................................."""
|
|
||||||
|
|
||||||
|
|
||||||
def docstring_almost_at_line_limit2():
|
|
||||||
"""long docstring.................................................................
|
|
||||||
|
|
||||||
..................................................................................
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def docstring_at_line_limit():
|
|
||||||
"""long docstring................................................................"""
|
|
||||||
|
|
||||||
|
|
||||||
def multiline_docstring_at_line_limit():
|
|
||||||
"""first line-----------------------------------------------------------------------
|
|
||||||
|
|
||||||
second line----------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
|
|
||||||
def stable_quote_normalization_with_immediate_inner_single_quote(self):
|
|
||||||
"""'<text here>
|
|
||||||
|
|
||||||
<text here, since without another non-empty line black is stable>
|
|
||||||
"""
|
|
||||||
```
|
|
||||||
|
|
||||||
## Black Output
|
|
||||||
|
|
||||||
```py
|
|
||||||
class MyClass:
|
|
||||||
"""Multiline
|
|
||||||
class docstring
|
|
||||||
"""
|
|
||||||
|
|
||||||
def method(self):
|
|
||||||
"""Multiline
|
|
||||||
method docstring
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def foo():
|
|
||||||
"""This is a docstring with
|
|
||||||
some lines of text here
|
|
||||||
"""
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def bar():
|
|
||||||
"""This is another docstring
|
|
||||||
with more lines of text
|
|
||||||
"""
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def baz():
|
|
||||||
'''"This" is a string with some
|
|
||||||
embedded "quotes"'''
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def troz():
|
|
||||||
"""Indentation with tabs
|
|
||||||
is just as OK
|
|
||||||
"""
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def zort():
|
|
||||||
"""Another
|
|
||||||
multiline
|
|
||||||
docstring
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def poit():
|
|
||||||
"""
|
|
||||||
Lorem ipsum dolor sit amet.
|
|
||||||
|
|
||||||
Consectetur adipiscing elit:
|
|
||||||
- sed do eiusmod tempor incididunt ut labore
|
|
||||||
- dolore magna aliqua
|
|
||||||
- enim ad minim veniam
|
|
||||||
- quis nostrud exercitation ullamco laboris nisi
|
|
||||||
- aliquip ex ea commodo consequat
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def under_indent():
|
|
||||||
"""
|
|
||||||
These lines are indented in a way that does not
|
|
||||||
make sense.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def over_indent():
|
|
||||||
"""
|
|
||||||
This has a shallow indent
|
|
||||||
- But some lines are deeper
|
|
||||||
- And the closing quote is too deep
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def single_line():
|
|
||||||
"""But with a newline after it!"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def this():
|
|
||||||
r"""
|
|
||||||
'hey ho'
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def that():
|
|
||||||
""" "hey yah" """
|
|
||||||
|
|
||||||
|
|
||||||
def and_that():
|
|
||||||
"""
|
|
||||||
"hey yah" """
|
|
||||||
|
|
||||||
|
|
||||||
def and_this():
|
|
||||||
'''
|
|
||||||
"hey yah"'''
|
|
||||||
|
|
||||||
|
|
||||||
def multiline_whitespace():
|
|
||||||
""" """
|
|
||||||
|
|
||||||
|
|
||||||
def oneline_whitespace():
|
|
||||||
""" """
|
|
||||||
|
|
||||||
|
|
||||||
def empty():
|
|
||||||
""""""
|
|
||||||
|
|
||||||
|
|
||||||
def single_quotes():
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
|
|
||||||
def believe_it_or_not_this_is_in_the_py_stdlib():
|
|
||||||
'''
|
|
||||||
"hey yah"'''
|
|
||||||
|
|
||||||
|
|
||||||
def ignored_docstring():
|
|
||||||
"""a => \
|
|
||||||
b"""
|
|
||||||
|
|
||||||
|
|
||||||
def single_line_docstring_with_whitespace():
|
|
||||||
"""This should be stripped"""
|
|
||||||
|
|
||||||
|
|
||||||
def docstring_with_inline_tabs_and_space_indentation():
|
|
||||||
"""hey
|
|
||||||
|
|
||||||
tab separated value
|
|
||||||
tab at start of line and then a tab separated value
|
|
||||||
multiple tabs at the beginning and inline
|
|
||||||
mixed tabs and spaces at beginning. next line has mixed tabs and spaces only.
|
|
||||||
|
|
||||||
line ends with some tabs
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def docstring_with_inline_tabs_and_tab_indentation():
|
|
||||||
"""hey
|
|
||||||
|
|
||||||
tab separated value
|
|
||||||
tab at start of line and then a tab separated value
|
|
||||||
multiple tabs at the beginning and inline
|
|
||||||
mixed tabs and spaces at beginning. next line has mixed tabs and spaces only.
|
|
||||||
|
|
||||||
line ends with some tabs
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def backslash_space():
|
|
||||||
"""\ """
|
|
||||||
|
|
||||||
|
|
||||||
def multiline_backslash_1():
|
|
||||||
"""
|
|
||||||
hey\there\
|
|
||||||
\ """
|
|
||||||
|
|
||||||
|
|
||||||
def multiline_backslash_2():
|
|
||||||
"""
|
|
||||||
hey there \ """
|
|
||||||
|
|
||||||
|
|
||||||
# Regression test for #3425
|
|
||||||
def multiline_backslash_really_long_dont_crash():
|
|
||||||
"""
|
|
||||||
hey there hello guten tag hi hoow are you ola zdravstvuyte ciao como estas ca va \ """
|
|
||||||
|
|
||||||
|
|
||||||
def multiline_backslash_3():
|
|
||||||
"""
|
|
||||||
already escaped \\"""
|
|
||||||
|
|
||||||
|
|
||||||
def my_god_its_full_of_stars_1():
|
|
||||||
"I'm sorry Dave\u2001"
|
|
||||||
|
|
||||||
|
|
||||||
# the space below is actually a \u2001, removed in output
|
|
||||||
def my_god_its_full_of_stars_2():
|
|
||||||
"I'm sorry Dave"
|
|
||||||
|
|
||||||
|
|
||||||
def docstring_almost_at_line_limit():
|
|
||||||
"""long docstring................................................................."""
|
|
||||||
|
|
||||||
|
|
||||||
def docstring_almost_at_line_limit2():
|
|
||||||
"""long docstring.................................................................
|
|
||||||
|
|
||||||
..................................................................................
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def docstring_at_line_limit():
|
|
||||||
"""long docstring................................................................"""
|
|
||||||
|
|
||||||
|
|
||||||
def multiline_docstring_at_line_limit():
|
|
||||||
"""first line-----------------------------------------------------------------------
|
|
||||||
|
|
||||||
second line----------------------------------------------------------------------"""
|
|
||||||
|
|
||||||
|
|
||||||
def stable_quote_normalization_with_immediate_inner_single_quote(self):
|
|
||||||
"""'<text here>
|
|
||||||
|
|
||||||
<text here, since without another non-empty line black is stable>
|
|
||||||
"""
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
|
@ -62,11 +62,7 @@ def single_quote_docstring_over_line_limit2():
|
||||||
```diff
|
```diff
|
||||||
--- Black
|
--- Black
|
||||||
+++ Ruff
|
+++ Ruff
|
||||||
@@ -1,9 +1,11 @@
|
@@ -3,7 +3,8 @@
|
||||||
def docstring_almost_at_line_limit():
|
|
||||||
- """long docstring................................................................."""
|
|
||||||
+ """long docstring.................................................................
|
|
||||||
+ """
|
|
||||||
|
|
||||||
|
|
||||||
def docstring_almost_at_line_limit_with_prefix():
|
def docstring_almost_at_line_limit_with_prefix():
|
||||||
|
@ -82,8 +78,7 @@ def single_quote_docstring_over_line_limit2():
|
||||||
|
|
||||||
```py
|
```py
|
||||||
def docstring_almost_at_line_limit():
|
def docstring_almost_at_line_limit():
|
||||||
"""long docstring.................................................................
|
"""long docstring................................................................."""
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def docstring_almost_at_line_limit_with_prefix():
|
def docstring_almost_at_line_limit_with_prefix():
|
||||||
|
|
|
@ -0,0 +1,448 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||||
|
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring.py
|
||||||
|
---
|
||||||
|
## Input
|
||||||
|
```py
|
||||||
|
def single_line_backslashes1():
|
||||||
|
""" content\ """
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def single_line_backslashes2():
|
||||||
|
""" content\\ """
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def single_line_backslashes3():
|
||||||
|
""" content\\\ """
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def multiline_backslashes1():
|
||||||
|
"""This is a docstring with
|
||||||
|
some lines of text\ """
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def multiline_backslashes2():
|
||||||
|
"""This is a docstring with
|
||||||
|
some lines of text\\ """
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def multiline_backslashes3():
|
||||||
|
"""This is a docstring with
|
||||||
|
some lines of text\\\ """
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def multiple_negatively_indented_docstring_lines():
|
||||||
|
"""a
|
||||||
|
b
|
||||||
|
c
|
||||||
|
d
|
||||||
|
e
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def overindentend_docstring():
|
||||||
|
"""a
|
||||||
|
over-indented
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def comment_before_docstring():
|
||||||
|
# don't lose this function comment ...
|
||||||
|
"""Does nothing.
|
||||||
|
|
||||||
|
But it has comments
|
||||||
|
""" # ... neither lose this function comment
|
||||||
|
|
||||||
|
|
||||||
|
class CommentBeforeDocstring():
|
||||||
|
# don't lose this class comment ...
|
||||||
|
"""Empty class.
|
||||||
|
|
||||||
|
But it has comments
|
||||||
|
""" # ... neither lose this class comment
|
||||||
|
|
||||||
|
|
||||||
|
class IndentMeSome:
|
||||||
|
def doc_string_without_linebreak_after_colon(self): """ This is somewhat strange
|
||||||
|
a
|
||||||
|
b
|
||||||
|
We format this a is the docstring had started properly indented on the next
|
||||||
|
line if the target indentation. This may we incorrect since source and target
|
||||||
|
indentation can be incorrect, but this is also an edge case.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class IgnoreImplicitlyConcatenatedStrings:
|
||||||
|
"""""" ""
|
||||||
|
|
||||||
|
|
||||||
|
def docstring_that_ends_with_quote_and_a_line_break1():
|
||||||
|
"""
|
||||||
|
he said "the news of my death have been greatly exaggerated"
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def docstring_that_ends_with_quote_and_a_line_break2():
|
||||||
|
"""he said "the news of my death have been greatly exaggerated"
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def docstring_that_ends_with_quote_and_a_line_break3():
|
||||||
|
"""he said "the news of my death have been greatly exaggerated"
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class TabbedIndent:
|
||||||
|
def tabbed_indent(self):
|
||||||
|
"""check for correct tabbed formatting
|
||||||
|
^^^^^^^^^^
|
||||||
|
Normal indented line
|
||||||
|
- autor
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
## Outputs
|
||||||
|
### Output 1
|
||||||
|
```
|
||||||
|
indent-style = Spaces, size: 4
|
||||||
|
line-width = 88
|
||||||
|
quote-style = Double
|
||||||
|
magic-trailing-comma = Respect
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
def single_line_backslashes1():
|
||||||
|
"""content\ """
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def single_line_backslashes2():
|
||||||
|
"""content\\"""
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def single_line_backslashes3():
|
||||||
|
"""content\\\ """
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def multiline_backslashes1():
|
||||||
|
"""This is a docstring with
|
||||||
|
some lines of text\ """
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def multiline_backslashes2():
|
||||||
|
"""This is a docstring with
|
||||||
|
some lines of text\\"""
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def multiline_backslashes3():
|
||||||
|
"""This is a docstring with
|
||||||
|
some lines of text\\\ """
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def multiple_negatively_indented_docstring_lines():
|
||||||
|
"""a
|
||||||
|
b
|
||||||
|
c
|
||||||
|
d
|
||||||
|
e
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def overindentend_docstring():
|
||||||
|
"""a
|
||||||
|
over-indented
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def comment_before_docstring():
|
||||||
|
# don't lose this function comment ...
|
||||||
|
"""Does nothing.
|
||||||
|
|
||||||
|
But it has comments
|
||||||
|
""" # ... neither lose this function comment
|
||||||
|
|
||||||
|
|
||||||
|
class CommentBeforeDocstring:
|
||||||
|
# don't lose this class comment ...
|
||||||
|
"""Empty class.
|
||||||
|
|
||||||
|
But it has comments
|
||||||
|
""" # ... neither lose this class comment
|
||||||
|
|
||||||
|
|
||||||
|
class IndentMeSome:
|
||||||
|
def doc_string_without_linebreak_after_colon(self):
|
||||||
|
"""This is somewhat strange
|
||||||
|
a
|
||||||
|
b
|
||||||
|
We format this a is the docstring had started properly indented on the next
|
||||||
|
line if the target indentation. This may we incorrect since source and target
|
||||||
|
indentation can be incorrect, but this is also an edge case.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class IgnoreImplicitlyConcatenatedStrings:
|
||||||
|
"""""" ""
|
||||||
|
|
||||||
|
|
||||||
|
def docstring_that_ends_with_quote_and_a_line_break1():
|
||||||
|
"""
|
||||||
|
he said "the news of my death have been greatly exaggerated"
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def docstring_that_ends_with_quote_and_a_line_break2():
|
||||||
|
"""he said "the news of my death have been greatly exaggerated" """
|
||||||
|
|
||||||
|
|
||||||
|
def docstring_that_ends_with_quote_and_a_line_break3():
|
||||||
|
"""he said "the news of my death have been greatly exaggerated" """
|
||||||
|
|
||||||
|
|
||||||
|
class TabbedIndent:
|
||||||
|
def tabbed_indent(self):
|
||||||
|
"""check for correct tabbed formatting
|
||||||
|
^^^^^^^^^^
|
||||||
|
Normal indented line
|
||||||
|
- autor
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Output 2
|
||||||
|
```
|
||||||
|
indent-style = Spaces, size: 2
|
||||||
|
line-width = 88
|
||||||
|
quote-style = Double
|
||||||
|
magic-trailing-comma = Respect
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
def single_line_backslashes1():
|
||||||
|
"""content\ """
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def single_line_backslashes2():
|
||||||
|
"""content\\"""
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def single_line_backslashes3():
|
||||||
|
"""content\\\ """
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def multiline_backslashes1():
|
||||||
|
"""This is a docstring with
|
||||||
|
some lines of text\ """
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def multiline_backslashes2():
|
||||||
|
"""This is a docstring with
|
||||||
|
some lines of text\\"""
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def multiline_backslashes3():
|
||||||
|
"""This is a docstring with
|
||||||
|
some lines of text\\\ """
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def multiple_negatively_indented_docstring_lines():
|
||||||
|
"""a
|
||||||
|
b
|
||||||
|
c
|
||||||
|
d
|
||||||
|
e
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def overindentend_docstring():
|
||||||
|
"""a
|
||||||
|
over-indented
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def comment_before_docstring():
|
||||||
|
# don't lose this function comment ...
|
||||||
|
"""Does nothing.
|
||||||
|
|
||||||
|
But it has comments
|
||||||
|
""" # ... neither lose this function comment
|
||||||
|
|
||||||
|
|
||||||
|
class CommentBeforeDocstring:
|
||||||
|
# don't lose this class comment ...
|
||||||
|
"""Empty class.
|
||||||
|
|
||||||
|
But it has comments
|
||||||
|
""" # ... neither lose this class comment
|
||||||
|
|
||||||
|
|
||||||
|
class IndentMeSome:
|
||||||
|
def doc_string_without_linebreak_after_colon(self):
|
||||||
|
"""This is somewhat strange
|
||||||
|
a
|
||||||
|
b
|
||||||
|
We format this a is the docstring had started properly indented on the next
|
||||||
|
line if the target indentation. This may we incorrect since source and target
|
||||||
|
indentation can be incorrect, but this is also an edge case.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class IgnoreImplicitlyConcatenatedStrings:
|
||||||
|
"""""" ""
|
||||||
|
|
||||||
|
|
||||||
|
def docstring_that_ends_with_quote_and_a_line_break1():
|
||||||
|
"""
|
||||||
|
he said "the news of my death have been greatly exaggerated"
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def docstring_that_ends_with_quote_and_a_line_break2():
|
||||||
|
"""he said "the news of my death have been greatly exaggerated" """
|
||||||
|
|
||||||
|
|
||||||
|
def docstring_that_ends_with_quote_and_a_line_break3():
|
||||||
|
"""he said "the news of my death have been greatly exaggerated" """
|
||||||
|
|
||||||
|
|
||||||
|
class TabbedIndent:
|
||||||
|
def tabbed_indent(self):
|
||||||
|
"""check for correct tabbed formatting
|
||||||
|
^^^^^^^^^^
|
||||||
|
Normal indented line
|
||||||
|
- autor
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Output 3
|
||||||
|
```
|
||||||
|
indent-style = Tab
|
||||||
|
line-width = 88
|
||||||
|
quote-style = Double
|
||||||
|
magic-trailing-comma = Respect
|
||||||
|
```
|
||||||
|
|
||||||
|
```py
|
||||||
|
def single_line_backslashes1():
|
||||||
|
"""content\ """
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def single_line_backslashes2():
|
||||||
|
"""content\\"""
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def single_line_backslashes3():
|
||||||
|
"""content\\\ """
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def multiline_backslashes1():
|
||||||
|
"""This is a docstring with
|
||||||
|
some lines of text\ """
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def multiline_backslashes2():
|
||||||
|
"""This is a docstring with
|
||||||
|
some lines of text\\"""
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def multiline_backslashes3():
|
||||||
|
"""This is a docstring with
|
||||||
|
some lines of text\\\ """
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def multiple_negatively_indented_docstring_lines():
|
||||||
|
"""a
|
||||||
|
b
|
||||||
|
c
|
||||||
|
d
|
||||||
|
e
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def overindentend_docstring():
|
||||||
|
"""a
|
||||||
|
over-indented
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def comment_before_docstring():
|
||||||
|
# don't lose this function comment ...
|
||||||
|
"""Does nothing.
|
||||||
|
|
||||||
|
But it has comments
|
||||||
|
""" # ... neither lose this function comment
|
||||||
|
|
||||||
|
|
||||||
|
class CommentBeforeDocstring:
|
||||||
|
# don't lose this class comment ...
|
||||||
|
"""Empty class.
|
||||||
|
|
||||||
|
But it has comments
|
||||||
|
""" # ... neither lose this class comment
|
||||||
|
|
||||||
|
|
||||||
|
class IndentMeSome:
|
||||||
|
def doc_string_without_linebreak_after_colon(self):
|
||||||
|
"""This is somewhat strange
|
||||||
|
a
|
||||||
|
b
|
||||||
|
We format this a is the docstring had started properly indented on the next
|
||||||
|
line if the target indentation. This may we incorrect since source and target
|
||||||
|
indentation can be incorrect, but this is also an edge case.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class IgnoreImplicitlyConcatenatedStrings:
|
||||||
|
"""""" ""
|
||||||
|
|
||||||
|
|
||||||
|
def docstring_that_ends_with_quote_and_a_line_break1():
|
||||||
|
"""
|
||||||
|
he said "the news of my death have been greatly exaggerated"
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def docstring_that_ends_with_quote_and_a_line_break2():
|
||||||
|
"""he said "the news of my death have been greatly exaggerated" """
|
||||||
|
|
||||||
|
|
||||||
|
def docstring_that_ends_with_quote_and_a_line_break3():
|
||||||
|
"""he said "the news of my death have been greatly exaggerated" """
|
||||||
|
|
||||||
|
|
||||||
|
class TabbedIndent:
|
||||||
|
def tabbed_indent(self):
|
||||||
|
"""check for correct tabbed formatting
|
||||||
|
^^^^^^^^^^
|
||||||
|
Normal indented line
|
||||||
|
- autor
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue