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 bitflags::bitflags;
|
||||
|
||||
use ruff_formatter::{format_args, write, FormatError};
|
||||
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_parser::lexer::{lex_starts_at, LexicalError, LexicalErrorType};
|
||||
use ruff_python_parser::{Mode, Tok};
|
||||
use ruff_source_file::Locator;
|
||||
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::expression::parentheses::{
|
||||
in_parentheses_only_group, in_parentheses_only_soft_line_break_or_space,
|
||||
|
@ -78,7 +78,7 @@ pub(super) struct FormatString<'a> {
|
|||
pub enum StringLayout {
|
||||
#[default]
|
||||
Default,
|
||||
|
||||
DocString,
|
||||
ImplicitConcatenatedBinaryLeftSide,
|
||||
}
|
||||
|
||||
|
@ -109,10 +109,25 @@ impl<'a> Format<PyFormatContext<'_>> for FormatString<'a> {
|
|||
if is_implicit_concatenation(string_content) {
|
||||
in_parentheses_only_group(&FormatStringContinuation::new(self.string)).fmt(f)
|
||||
} else {
|
||||
FormatStringPart::new(string_range, self.string.quoting(&f.context().locator()))
|
||||
FormatStringPart::new(
|
||||
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 => {
|
||||
FormatStringContinuation::new(self.string).fmt(f)
|
||||
}
|
||||
|
@ -137,6 +152,7 @@ impl Format<PyFormatContext<'_>> for FormatStringContinuation<'_> {
|
|||
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
let comments = f.context().comments().clone();
|
||||
let locator = f.context().locator();
|
||||
let quote_style = f.options().quote_style();
|
||||
let mut dangling_comments = comments.dangling_comments(self.string);
|
||||
|
||||
let string_range = self.string.range();
|
||||
|
@ -213,7 +229,12 @@ impl Format<PyFormatContext<'_>> for FormatStringContinuation<'_> {
|
|||
joiner.entry(&format_args![
|
||||
line_suffix_boundary(),
|
||||
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)
|
||||
]);
|
||||
|
||||
|
@ -235,63 +256,67 @@ impl Format<PyFormatContext<'_>> for FormatStringContinuation<'_> {
|
|||
}
|
||||
|
||||
struct FormatStringPart {
|
||||
part_range: TextRange,
|
||||
quoting: Quoting,
|
||||
prefix: StringPrefix,
|
||||
preferred_quotes: StringQuotes,
|
||||
range: TextRange,
|
||||
is_raw_string: bool,
|
||||
}
|
||||
|
||||
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 {
|
||||
part_range: range,
|
||||
quoting,
|
||||
prefix,
|
||||
range: raw_content_range,
|
||||
preferred_quotes,
|
||||
is_raw_string,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Format<PyFormatContext<'_>> for FormatStringPart {
|
||||
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
let string_content = f.context().locator().slice(self.part_range);
|
||||
|
||||
let prefix = StringPrefix::parse(string_content);
|
||||
let after_prefix = &string_content[usize::from(prefix.text_len())..];
|
||||
|
||||
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 (normalized, contains_newlines) = normalize_string(
|
||||
f.context().locator().slice(self.range),
|
||||
self.preferred_quotes,
|
||||
self.is_raw_string,
|
||||
);
|
||||
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 {
|
||||
Cow::Borrowed(_) => {
|
||||
source_text_slice(raw_content_range, contains_newlines).fmt(f)?;
|
||||
source_text_slice(self.range, contains_newlines).fmt(f)?;
|
||||
}
|
||||
Cow::Owned(normalized) => {
|
||||
dynamic_text(&normalized, Some(raw_content_range.start())).fmt(f)?;
|
||||
dynamic_text(&normalized, Some(self.range.start())).fmt(f)?;
|
||||
}
|
||||
}
|
||||
|
||||
preferred_quotes.fmt(f)
|
||||
self.preferred_quotes.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -639,3 +664,311 @@ fn normalize_string(
|
|||
|
||||
(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_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_source_file::Locator;
|
||||
|
||||
use crate::comments::{leading_comments, trailing_comments};
|
||||
use crate::context::{NodeLevel, WithNodeLevel};
|
||||
use crate::expression::expr_constant::ExprConstantLayout;
|
||||
use crate::expression::string::StringLayout;
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Level at which the [`Suite`] appears in the source code.
|
||||
|
@ -71,11 +77,29 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
|||
}
|
||||
write!(f, [first.format()])?;
|
||||
}
|
||||
SuiteKind::Class if is_docstring(first) => {
|
||||
if !comments.has_leading_comments(first) && lines_before(first.start(), source) > 1
|
||||
SuiteKind::Function => {
|
||||
if let Some(constant) = get_docstring(first, &f.context().locator()) {
|
||||
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)),
|
||||
]
|
||||
)?;
|
||||
} 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, e.g., this is
|
||||
// stable formatting:
|
||||
// Allow up to one empty line before a class docstring
|
||||
// ```python
|
||||
// class Test:
|
||||
//
|
||||
|
@ -83,10 +107,19 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
|||
// ```
|
||||
write!(f, [empty_line()])?;
|
||||
}
|
||||
write!(f, [first.format()])?;
|
||||
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, e.g., these are both stable
|
||||
// formatting:
|
||||
// Enforce an empty line after a class docstring
|
||||
// ```python
|
||||
// class Test:
|
||||
// """Docstring"""
|
||||
|
@ -100,6 +133,7 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
|||
//
|
||||
// ...
|
||||
// ```
|
||||
// Unlike black, we add the newline also after single quoted docstrings
|
||||
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,
|
||||
|
@ -107,8 +141,12 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
|||
write!(f, [empty_line(), second.format()])?;
|
||||
last = second;
|
||||
}
|
||||
} else {
|
||||
// No docstring, use normal formatting
|
||||
write!(f, [first.format()])?;
|
||||
}
|
||||
_ => {
|
||||
}
|
||||
SuiteKind::TopLevel => {
|
||||
write!(f, [first.format()])?;
|
||||
}
|
||||
}
|
||||
|
@ -218,18 +256,27 @@ const fn is_import_definition(stmt: &Stmt) -> bool {
|
|||
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 {
|
||||
return false;
|
||||
return None;
|
||||
};
|
||||
|
||||
matches!(
|
||||
value.as_ref(),
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
let Expr::Constant(constant) = value.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
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 {
|
||||
|
@ -260,7 +307,6 @@ impl<'ast> IntoFormat<PyFormatContext<'ast>> for Suite {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ruff_formatter::format;
|
||||
|
||||
use ruff_python_parser::parse_suite;
|
||||
|
||||
use crate::comments::Comments;
|
||||
|
|
|
@ -135,7 +135,7 @@ def multiline_backslash_3():
|
|||
```diff
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -1,73 +1,75 @@
|
||||
@@ -1,24 +1,24 @@
|
||||
class ALonelyClass:
|
||||
- '''
|
||||
+ """
|
||||
|
@ -167,96 +167,7 @@ def multiline_backslash_3():
|
|||
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 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"'''
|
||||
@@ -97,27 +97,27 @@
|
||||
|
||||
|
||||
def shockingly_the_quotes_are_normalized_v2():
|
||||
|
@ -292,7 +203,7 @@ def multiline_backslash_3():
|
|||
- '''
|
||||
- already escaped \\'''
|
||||
+ """
|
||||
+ already escaped \\ """
|
||||
+ already escaped \\"""
|
||||
```
|
||||
|
||||
## Ruff Output
|
||||
|
@ -352,7 +263,7 @@ def poit():
|
|||
def under_indent():
|
||||
"""
|
||||
These lines are indented in a way that does not
|
||||
make sense.
|
||||
make sense.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
@ -367,9 +278,7 @@ def over_indent():
|
|||
|
||||
|
||||
def single_line():
|
||||
"""But with a newline after it!
|
||||
|
||||
"""
|
||||
"""But with a newline after it!"""
|
||||
pass
|
||||
|
||||
|
||||
|
@ -395,7 +304,7 @@ def and_this():
|
|||
|
||||
def believe_it_or_not_this_is_in_the_py_stdlib():
|
||||
'''
|
||||
"hey yah"'''
|
||||
"hey yah"'''
|
||||
|
||||
|
||||
def shockingly_the_quotes_are_normalized_v2():
|
||||
|
@ -422,7 +331,7 @@ def multiline_backslash_2():
|
|||
|
||||
def multiline_backslash_3():
|
||||
"""
|
||||
already escaped \\ """
|
||||
already escaped \\"""
|
||||
```
|
||||
|
||||
## 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
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -1,9 +1,11 @@
|
||||
def docstring_almost_at_line_limit():
|
||||
- """long docstring................................................................."""
|
||||
+ """long docstring.................................................................
|
||||
+ """
|
||||
@@ -3,7 +3,8 @@
|
||||
|
||||
|
||||
def docstring_almost_at_line_limit_with_prefix():
|
||||
|
@ -82,8 +78,7 @@ def single_quote_docstring_over_line_limit2():
|
|||
|
||||
```py
|
||||
def docstring_almost_at_line_limit():
|
||||
"""long docstring.................................................................
|
||||
"""
|
||||
"""long docstring................................................................."""
|
||||
|
||||
|
||||
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