mirror of https://github.com/astral-sh/ruff
Remove empty line before raw dostrings
**Summary** This fixes a deviation with black where black would remove empty lines before raw docstrings for some reason.
This commit is contained in:
parent
60ca6885b1
commit
1743ef8398
|
|
@ -102,6 +102,11 @@ class Test:
|
||||||
x = 1
|
x = 1
|
||||||
|
|
||||||
|
|
||||||
|
class EmptyLineBeforeRawDocstring:
|
||||||
|
|
||||||
|
r"""Character and line based layer over a BufferedIOBase object, buffer."""
|
||||||
|
|
||||||
|
|
||||||
class C(): # comment
|
class C(): # comment
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -496,7 +496,7 @@ impl Format<PyFormatContext<'_>> for NormalizedString<'_> {
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
pub(super) struct StringPrefix: u8 {
|
pub(crate) struct StringPrefix: u8 {
|
||||||
const UNICODE = 0b0000_0001;
|
const UNICODE = 0b0000_0001;
|
||||||
/// `r"test"`
|
/// `r"test"`
|
||||||
const RAW = 0b0000_0010;
|
const RAW = 0b0000_0010;
|
||||||
|
|
@ -508,7 +508,7 @@ bitflags! {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StringPrefix {
|
impl StringPrefix {
|
||||||
pub(super) fn parse(input: &str) -> StringPrefix {
|
pub(crate) fn parse(input: &str) -> StringPrefix {
|
||||||
let chars = input.chars();
|
let chars = input.chars();
|
||||||
let mut prefix = StringPrefix::empty();
|
let mut prefix = StringPrefix::empty();
|
||||||
|
|
||||||
|
|
@ -533,15 +533,15 @@ impl StringPrefix {
|
||||||
prefix
|
prefix
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) const fn text_len(self) -> TextSize {
|
pub(crate) const fn text_len(self) -> TextSize {
|
||||||
TextSize::new(self.bits().count_ones())
|
TextSize::new(self.bits().count_ones())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) const fn is_raw_string(self) -> bool {
|
pub(crate) const fn is_raw_string(self) -> bool {
|
||||||
self.contains(StringPrefix::RAW) || self.contains(StringPrefix::RAW_UPPER)
|
self.contains(StringPrefix::RAW) || self.contains(StringPrefix::RAW_UPPER)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) const fn is_fstring(self) -> bool {
|
pub(crate) const fn is_fstring(self) -> bool {
|
||||||
self.contains(StringPrefix::F_STRING)
|
self.contains(StringPrefix::F_STRING)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use crate::comments::{
|
||||||
};
|
};
|
||||||
use crate::context::{NodeLevel, WithNodeLevel};
|
use crate::context::{NodeLevel, WithNodeLevel};
|
||||||
use crate::expression::expr_constant::ExprConstantLayout;
|
use crate::expression::expr_constant::ExprConstantLayout;
|
||||||
use crate::expression::string::StringLayout;
|
use crate::expression::string::{StringLayout, StringPrefix};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::statement::stmt_expr::FormatStmtExpr;
|
use crate::statement::stmt_expr::FormatStmtExpr;
|
||||||
use crate::verbatim::{
|
use crate::verbatim::{
|
||||||
|
|
@ -99,9 +99,13 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
||||||
|
|
||||||
SuiteKind::Class => {
|
SuiteKind::Class => {
|
||||||
if let Some(docstring) = DocstringStmt::try_from_statement(first) {
|
if let Some(docstring) = DocstringStmt::try_from_statement(first) {
|
||||||
|
let prefix =
|
||||||
|
StringPrefix::parse(f.context().locator().slice(docstring.0.range()));
|
||||||
if !comments.has_leading(first)
|
if !comments.has_leading(first)
|
||||||
&& lines_before(first.start(), source) > 1
|
&& lines_before(first.start(), source) > 1
|
||||||
&& !source_type.is_stub()
|
&& !source_type.is_stub()
|
||||||
|
// For some reason black removes the empty line before raw docstrings
|
||||||
|
&& !prefix.is_raw_string()
|
||||||
{
|
{
|
||||||
// Allow up to one empty line before a class docstring, e.g., this is
|
// Allow up to one empty line before a class docstring, e.g., this is
|
||||||
// stable formatting:
|
// stable formatting:
|
||||||
|
|
@ -484,8 +488,10 @@ impl<'ast> IntoFormat<PyFormatContext<'ast>> for Suite {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A statement representing a docstring.
|
/// A statement representing a docstring.
|
||||||
|
///
|
||||||
|
/// We keep both the outer statement and the inner constant here for convenience.
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub(crate) struct DocstringStmt<'a>(&'a Stmt);
|
pub(crate) struct DocstringStmt<'a>(&'a Stmt, &'a ExprConstant);
|
||||||
|
|
||||||
impl<'a> DocstringStmt<'a> {
|
impl<'a> DocstringStmt<'a> {
|
||||||
/// Checks if the statement is a simple string that can be formatted as a docstring
|
/// Checks if the statement is a simple string that can be formatted as a docstring
|
||||||
|
|
@ -494,9 +500,9 @@ impl<'a> DocstringStmt<'a> {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Expr::Constant(ExprConstant { value, .. }) = value.as_ref() {
|
if let Expr::Constant(expr_constant @ ExprConstant { value, .. }) = value.as_ref() {
|
||||||
if !value.is_implicit_concatenated() {
|
if (value.is_str() || value.is_unicode_string()) && !value.is_implicit_concatenated() {
|
||||||
return Some(DocstringStmt(stmt));
|
return Some(DocstringStmt(stmt, expr_constant));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -512,21 +518,12 @@ impl Format<PyFormatContext<'_>> for DocstringStmt<'_> {
|
||||||
if FormatStmtExpr.is_suppressed(node_comments.trailing, f.context()) {
|
if FormatStmtExpr.is_suppressed(node_comments.trailing, f.context()) {
|
||||||
suppressed_node(self.0).fmt(f)
|
suppressed_node(self.0).fmt(f)
|
||||||
} else {
|
} else {
|
||||||
// SAFETY: Safe because `DocStringStmt` guarantees that it only ever wraps a `ExprStmt` containing a `ConstantExpr`.
|
|
||||||
let constant = self
|
|
||||||
.0
|
|
||||||
.as_expr_stmt()
|
|
||||||
.unwrap()
|
|
||||||
.value
|
|
||||||
.as_constant_expr()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// We format the expression, but the statement carries the comments
|
// We format the expression, but the statement carries the comments
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
[
|
[
|
||||||
leading_comments(node_comments.leading),
|
leading_comments(node_comments.leading),
|
||||||
constant
|
self.1
|
||||||
.format()
|
.format()
|
||||||
.with_options(ExprConstantLayout::String(StringLayout::DocString)),
|
.with_options(ExprConstantLayout::String(StringLayout::DocString)),
|
||||||
trailing_comments(node_comments.trailing),
|
trailing_comments(node_comments.trailing),
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,10 @@ class Test:
|
||||||
x = 1
|
x = 1
|
||||||
|
|
||||||
|
|
||||||
|
class EmptyLineBeforeRawDocstring:
|
||||||
|
|
||||||
|
r"""Character and line based layer over a BufferedIOBase object, buffer."""
|
||||||
|
|
||||||
class C(): # comment
|
class C(): # comment
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -356,6 +360,10 @@ class Test:
|
||||||
x = 1
|
x = 1
|
||||||
|
|
||||||
|
|
||||||
|
class EmptyLineBeforeRawDocstring:
|
||||||
|
r"""Character and line based layer over a BufferedIOBase object, buffer."""
|
||||||
|
|
||||||
|
|
||||||
class C: # comment
|
class C: # comment
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue