Skip to content

Commit

Permalink
Support fmt: skip on statement and decorator level
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaReiser committed Aug 14, 2023
1 parent df98068 commit 8731120
Show file tree
Hide file tree
Showing 17 changed files with 366 additions and 277 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

@FormattedDecorator(a =b)
# leading comment
@MyDecorator( # dangling comment
list = [1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12], x = some_other_function_call({ "test": "value", "more": "other"})) # fmt: skip
# leading class comment
class Test:
pass



@FormattedDecorator(a =b)
# leading comment
@MyDecorator( # dangling comment
list = [1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12], x = some_other_function_call({ "test": "value", "more": "other"})) # fmt: skip
# leading class comment
def test():
pass

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
def test():
# leading comment
""" This docstring does not
get formatted
""" # fmt: skip
# trailing comment

def test():
# leading comment
""" This docstring gets formatted
""" # trailing comment

and_this + gets + formatted + too
21 changes: 21 additions & 0 deletions crates/ruff_python_formatter/src/comments/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,27 @@ impl<'a> Comments<'a> {
.parts(&NodeRefEqualityKey::from_ref(node.into()))
}

pub(crate) fn has_trailing_suppression_comment<T>(&self, node: T, source: &str) -> bool
where
T: Into<AnyNodeRef<'a>>,
{
fn has_trailing_suppression_comment(
node: AnyNodeRef,
comments: &Comments,
source: &str,
) -> bool {
comments.trailing_comments(node).iter().any(|comment| {
comment.line_position().is_end_of_line()
&& matches!(
comment.suppression_kind(source),
Some(SuppressionKind::Skip | SuppressionKind::Off)
)
})
}

has_trailing_suppression_comment(node.into(), self, source)
}

#[inline(always)]
#[cfg(not(debug_assertions))]
pub(crate) fn assert_formatted_all_comments(&self, _source_code: SourceCode) {}
Expand Down
2 changes: 1 addition & 1 deletion crates/ruff_python_formatter/src/comments/placement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -843,7 +843,7 @@ fn handle_leading_class_with_decorators_comment<'a>(
comment: DecoratedComment<'a>,
class_def: &'a ast::StmtClassDef,
) -> CommentPlacement<'a> {
if comment.start() < class_def.name.start() {
if comment.line_position().is_own_line() && comment.start() < class_def.name.start() {
if let Some(decorator) = class_def.decorator_list.last() {
if decorator.end() < comment.start() {
return CommentPlacement::dangling(class_def, comment);
Expand Down
2 changes: 1 addition & 1 deletion crates/ruff_python_formatter/src/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ impl FormatRule<Expr, PyFormatContext<'_>> for FormatExpr {
Expr::List(expr) => expr.format().fmt(f),
Expr::Tuple(expr) => expr.format().fmt(f),
Expr::Slice(expr) => expr.format().fmt(f),
Expr::IpyEscapeCommand(_) => todo!(),
Expr::IpyEscapeCommand(expr) => expr.format().fmt(f),
});

let parenthesize = match parentheses {
Expand Down
14 changes: 5 additions & 9 deletions crates/ruff_python_formatter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,16 +255,12 @@ if True:
#[ignore]
#[test]
fn quick_test() {
let src = r#"def test():
# fmt: off
a + b
# suppressed comments
let src = r#"
@MyDecorator(list = a) # fmt: skip
# trailing comment
class Test:
pass
a + b # formatted
"#;
// Tokenize once
Expand Down
65 changes: 37 additions & 28 deletions crates/ruff_python_formatter/src/statement/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use crate::prelude::*;
use ruff_formatter::{FormatOwnedWithRule, FormatRefWithRule};
use ruff_python_ast::Stmt;

use crate::prelude::*;
use crate::verbatim::suppressed_node;

pub(crate) mod stmt_ann_assign;
pub(crate) mod stmt_assert;
pub(crate) mod stmt_assign;
Expand Down Expand Up @@ -35,33 +37,40 @@ pub struct FormatStmt;

impl FormatRule<Stmt, PyFormatContext<'_>> for FormatStmt {
fn fmt(&self, item: &Stmt, f: &mut PyFormatter) -> FormatResult<()> {
match item {
Stmt::FunctionDef(x) => x.format().fmt(f),
Stmt::ClassDef(x) => x.format().fmt(f),
Stmt::Return(x) => x.format().fmt(f),
Stmt::Delete(x) => x.format().fmt(f),
Stmt::Assign(x) => x.format().fmt(f),
Stmt::AugAssign(x) => x.format().fmt(f),
Stmt::AnnAssign(x) => x.format().fmt(f),
Stmt::For(x) => x.format().fmt(f),
Stmt::While(x) => x.format().fmt(f),
Stmt::If(x) => x.format().fmt(f),
Stmt::With(x) => x.format().fmt(f),
Stmt::Match(x) => x.format().fmt(f),
Stmt::Raise(x) => x.format().fmt(f),
Stmt::Try(x) => x.format().fmt(f),
Stmt::TryStar(x) => x.format().fmt(f),
Stmt::Assert(x) => x.format().fmt(f),
Stmt::Import(x) => x.format().fmt(f),
Stmt::ImportFrom(x) => x.format().fmt(f),
Stmt::Global(x) => x.format().fmt(f),
Stmt::Nonlocal(x) => x.format().fmt(f),
Stmt::Expr(x) => x.format().fmt(f),
Stmt::Pass(x) => x.format().fmt(f),
Stmt::Break(x) => x.format().fmt(f),
Stmt::Continue(x) => x.format().fmt(f),
Stmt::TypeAlias(x) => x.format().fmt(f),
Stmt::IpyEscapeCommand(_) => todo!(),
if f.context()
.comments()
.has_trailing_suppression_comment(item, f.context().source())
{
suppressed_node(item).fmt(f)
} else {
match item {
Stmt::FunctionDef(x) => x.format().fmt(f),
Stmt::ClassDef(x) => x.format().fmt(f),
Stmt::Return(x) => x.format().fmt(f),
Stmt::Delete(x) => x.format().fmt(f),
Stmt::Assign(x) => x.format().fmt(f),
Stmt::AugAssign(x) => x.format().fmt(f),
Stmt::AnnAssign(x) => x.format().fmt(f),
Stmt::For(x) => x.format().fmt(f),
Stmt::While(x) => x.format().fmt(f),
Stmt::If(x) => x.format().fmt(f),
Stmt::With(x) => x.format().fmt(f),
Stmt::Match(x) => x.format().fmt(f),
Stmt::Raise(x) => x.format().fmt(f),
Stmt::Try(x) => x.format().fmt(f),
Stmt::TryStar(x) => x.format().fmt(f),
Stmt::Assert(x) => x.format().fmt(f),
Stmt::Import(x) => x.format().fmt(f),
Stmt::ImportFrom(x) => x.format().fmt(f),
Stmt::Global(x) => x.format().fmt(f),
Stmt::Nonlocal(x) => x.format().fmt(f),
Stmt::Expr(x) => x.format().fmt(f),
Stmt::Pass(x) => x.format().fmt(f),
Stmt::Break(x) => x.format().fmt(f),
Stmt::Continue(x) => x.format().fmt(f),
Stmt::TypeAlias(x) => x.format().fmt(f),
Stmt::IpyEscapeCommand(x) => x.format().fmt(f),
}
}
}
}
Expand Down
84 changes: 58 additions & 26 deletions crates/ruff_python_formatter/src/statement/stmt_class_def.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use ruff_formatter::{write, Buffer};
use ruff_python_ast::{Ranged, StmtClassDef};
use ruff_python_ast::{Decorator, Ranged, StmtClassDef};
use ruff_python_trivia::lines_after_ignoring_trivia;

use crate::comments::{leading_comments, trailing_comments};
use crate::comments::{leading_comments, trailing_comments, SourceComment};
use crate::prelude::*;
use crate::statement::suite::SuiteKind;
use crate::verbatim::suppressed_node;
use crate::FormatNodeRule;

#[derive(Default)]
Expand All @@ -30,31 +31,11 @@ impl FormatNodeRule<StmtClassDef> for FormatStmtClassDef {
let (leading_definition_comments, trailing_definition_comments) =
dangling_comments.split_at(trailing_definition_comments_start);

if let Some(last_decorator) = decorator_list.last() {
f.join_with(hard_line_break())
.entries(decorator_list.iter().formatted())
.finish()?;

if leading_definition_comments.is_empty() {
write!(f, [hard_line_break()])?;
} else {
// Write any leading definition comments (between last decorator and the header)
// while maintaining the right amount of empty lines between the comment
// and the last decorator.
let leading_line =
if lines_after_ignoring_trivia(last_decorator.end(), f.context().source()) <= 1
{
hard_line_break()
} else {
empty_line()
};

write!(
f,
[leading_line, leading_comments(leading_definition_comments)]
)?;
}
FormatDecorators {
decorators: decorator_list,
leading_definition_comments,
}
.fmt(f)?;

write!(f, [text("class"), space(), name.format()])?;

Expand Down Expand Up @@ -136,3 +117,54 @@ impl FormatNodeRule<StmtClassDef> for FormatStmtClassDef {
Ok(())
}
}

pub(super) struct FormatDecorators<'a> {
pub(super) decorators: &'a [Decorator],
pub(super) leading_definition_comments: &'a [SourceComment],
}

impl Format<PyFormatContext<'_>> for FormatDecorators<'_> {
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
if let Some(last_decorator) = self.decorators.last() {
f.join_with(hard_line_break())
.entries(self.decorators.iter().map(|decorator| {
format_with(move |f: &mut PyFormatter| {
if f.context()
.comments()
.has_trailing_suppression_comment(decorator, f.context().source())
{
suppressed_node(decorator).fmt(f)
} else {
decorator.format().fmt(f)
}
})
}))
.finish()?;

if self.leading_definition_comments.is_empty() {
write!(f, [hard_line_break()])?;
} else {
// Write any leading definition comments (between last decorator and the header)
// while maintaining the right amount of empty lines between the comment
// and the last decorator.
let leading_line =
if lines_after_ignoring_trivia(last_decorator.end(), f.context().source()) <= 1
{
hard_line_break()
} else {
empty_line()
};

write!(
f,
[
leading_line,
leading_comments(self.leading_definition_comments)
]
)?;
}
}

Ok(())
}
}
58 changes: 25 additions & 33 deletions crates/ruff_python_formatter/src/statement/stmt_function_def.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use ruff_formatter::write;
use ruff_python_ast::{Parameters, Ranged, StmtFunctionDef};
use ruff_python_trivia::{lines_after_ignoring_trivia, SimpleTokenKind, SimpleTokenizer};
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};

use crate::comments::{leading_comments, trailing_comments};
use crate::comments::trailing_comments;
use crate::expression::maybe_parenthesize_expression;
use crate::expression::parentheses::{Parentheses, Parenthesize};
use crate::prelude::*;
use crate::statement::stmt_class_def::FormatDecorators;
use crate::statement::suite::SuiteKind;
use crate::FormatNodeRule;

Expand All @@ -14,6 +15,17 @@ pub struct FormatStmtFunctionDef;

impl FormatNodeRule<StmtFunctionDef> for FormatStmtFunctionDef {
fn fmt_fields(&self, item: &StmtFunctionDef, f: &mut PyFormatter) -> FormatResult<()> {
let StmtFunctionDef {
range: _,
is_async,
decorator_list,
name,
type_params,
parameters,
returns,
body,
} = item;

let comments = f.context().comments().clone();

let dangling_comments = comments.dangling_comments(item);
Expand All @@ -23,46 +35,26 @@ impl FormatNodeRule<StmtFunctionDef> for FormatStmtFunctionDef {
let (leading_definition_comments, trailing_definition_comments) =
dangling_comments.split_at(trailing_definition_comments_start);

if let Some(last_decorator) = item.decorator_list.last() {
f.join_with(hard_line_break())
.entries(item.decorator_list.iter().formatted())
.finish()?;

if leading_definition_comments.is_empty() {
write!(f, [hard_line_break()])?;
} else {
// Write any leading definition comments (between last decorator and the header)
// while maintaining the right amount of empty lines between the comment
// and the last decorator.
let leading_line =
if lines_after_ignoring_trivia(last_decorator.end(), f.context().source()) <= 1
{
hard_line_break()
} else {
empty_line()
};

write!(
f,
[leading_line, leading_comments(leading_definition_comments)]
)?;
}
FormatDecorators {
decorators: decorator_list,
leading_definition_comments,
}
.fmt(f)?;

if item.is_async {
if *is_async {
write!(f, [text("async"), space()])?;
}

write!(f, [text("def"), space(), item.name.format()])?;
write!(f, [text("def"), space(), name.format()])?;

if let Some(type_params) = item.type_params.as_ref() {
if let Some(type_params) = type_params.as_ref() {
write!(f, [type_params.format()])?;
}

let format_inner = format_with(|f: &mut PyFormatter| {
write!(f, [item.parameters.format()])?;
write!(f, [parameters.format()])?;

if let Some(return_annotation) = item.returns.as_ref() {
if let Some(return_annotation) = returns.as_ref() {
write!(f, [space(), text("->"), space()])?;

if return_annotation.is_tuple_expr() {
Expand Down Expand Up @@ -108,7 +100,7 @@ impl FormatNodeRule<StmtFunctionDef> for FormatStmtFunctionDef {
[maybe_parenthesize_expression(
return_annotation,
item,
if empty_parameters(&item.parameters, f.context().source()) {
if empty_parameters(parameters, f.context().source()) {
// If the parameters are empty, add parentheses if the return annotation
// breaks at all.
Parenthesize::IfBreaksOrIfRequired
Expand Down Expand Up @@ -140,7 +132,7 @@ impl FormatNodeRule<StmtFunctionDef> for FormatStmtFunctionDef {
[
text(":"),
trailing_comments(trailing_definition_comments),
block_indent(&item.body.format().with_options(SuiteKind::Function))
block_indent(&body.format().with_options(SuiteKind::Function))
]
)
}
Expand Down
Loading

0 comments on commit 8731120

Please sign in to comment.