diff --git a/crates/ruff_formatter/src/lib.rs b/crates/ruff_formatter/src/lib.rs index 11fee92fab..7e5f047aff 100644 --- a/crates/ruff_formatter/src/lib.rs +++ b/crates/ruff_formatter/src/lib.rs @@ -440,7 +440,7 @@ pub type FormatResult = Result; /// let paragraph = Paragraph(String::from("test")); /// let formatted = format!(SimpleFormatContext::default(), [paragraph])?; /// -/// assert_eq!("test\n", formatted.print()?.as_code()); +/// assert_eq!("\ntest\n", formatted.print()?.as_code()); /// # Ok(()) /// # } /// ``` diff --git a/crates/ruff_formatter/src/printer/mod.rs b/crates/ruff_formatter/src/printer/mod.rs index 3d8d58be62..b081dfe9ec 100644 --- a/crates/ruff_formatter/src/printer/mod.rs +++ b/crates/ruff_formatter/src/printer/mod.rs @@ -124,7 +124,7 @@ impl<'a> Printer<'a> { self.flush_line_suffixes(queue, stack, Some(element)); } else { // Only print a newline if the current line isn't already empty - if self.state.line_width > 0 { + if self.state.line_width > 0 || self.state.buffer.is_empty() { self.print_char('\n'); } diff --git a/crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/module_comment.py b/crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/module_comment.py new file mode 100644 index 0000000000..fcf1bfa436 --- /dev/null +++ b/crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/module_comment.py @@ -0,0 +1,4 @@ + + + +# hehehe >:)a diff --git a/crates/ruff_python_formatter/src/comments/placement.rs b/crates/ruff_python_formatter/src/comments/placement.rs index 1d4ebcce99..dad69e7779 100644 --- a/crates/ruff_python_formatter/src/comments/placement.rs +++ b/crates/ruff_python_formatter/src/comments/placement.rs @@ -2,7 +2,7 @@ use std::cmp::Ordering; use ruff_python_ast::node::AnyNodeRef; use ruff_python_ast::whitespace::indentation; -use ruff_python_ast::{self as ast, Comprehension, Expr, MatchCase, Parameters}; +use ruff_python_ast::{self as ast, Comprehension, Expr, MatchCase, ModModule, Parameters}; use ruff_python_trivia::{ find_only_token_in_range, indentation_at_offset, BackwardsTokenizer, CommentRanges, SimpleToken, SimpleTokenKind, SimpleTokenizer, @@ -229,8 +229,12 @@ fn handle_enclosed_comment<'a>( CommentPlacement::Default(comment) } } - AnyNodeRef::ModModule(_) => { - handle_module_level_own_line_comment_before_class_or_function_comment(comment, locator) + AnyNodeRef::ModModule(module) => { + handle_trailing_module_comment(module, comment).or_else(|comment| { + handle_module_level_own_line_comment_before_class_or_function_comment( + comment, locator, + ) + }) } AnyNodeRef::WithItem(_) => handle_with_item_comment(comment, locator), AnyNodeRef::PatternMatchSequence(pattern_match_sequence) => { @@ -882,6 +886,35 @@ fn handle_trailing_binary_like_comment<'a>( } } +/// Handles trailing comments after the last statement in a module. +/// Ruff's parser sets the module range to exclude trailing comments and the result is that +/// [`CommentPlacement::Default`] makes these comments dangling comments. +/// +/// This method overrides the handling to make these comments trailing comments of the last +/// statement instead. +/// +/// ```python +/// a +/// +/// # trailing comment +/// ``` +/// +/// Comments of an all empty module are leading module comments +fn handle_trailing_module_comment<'a>( + module: &'a ModModule, + comment: DecoratedComment<'a>, +) -> CommentPlacement<'a> { + if comment.preceding_node().is_none() && comment.following_node().is_none() { + if let Some(last_statement) = module.body.last() { + CommentPlacement::trailing(last_statement, comment) + } else { + CommentPlacement::leading(comment.enclosing_node(), comment) + } + } else { + CommentPlacement::Default(comment) + } +} + /// Handles own line comments on the module level before a class or function statement. /// A comment only becomes the leading comment of a class or function if it isn't separated by an empty /// line from the class. Comments that are separated by at least one empty line from the header of the diff --git a/crates/ruff_python_formatter/src/comments/snapshots/ruff_python_formatter__comments__tests__only_comments.snap b/crates/ruff_python_formatter/src/comments/snapshots/ruff_python_formatter__comments__tests__only_comments.snap index c471113557..49f7ca154b 100644 --- a/crates/ruff_python_formatter/src/comments/snapshots/ruff_python_formatter__comments__tests__only_comments.snap +++ b/crates/ruff_python_formatter/src/comments/snapshots/ruff_python_formatter__comments__tests__only_comments.snap @@ -8,8 +8,7 @@ expression: comments.debug(test_case.source_code) range: 0..0, source: ``, }: { - "leading": [], - "dangling": [ + "leading": [ SourceComment { text: "# Some comment", position: OwnLine, @@ -21,6 +20,7 @@ expression: comments.debug(test_case.source_code) formatted: false, }, ], + "dangling": [], "trailing": [], }, } diff --git a/crates/ruff_python_formatter/src/module/mod_module.rs b/crates/ruff_python_formatter/src/module/mod_module.rs index 54b1dbd51d..44337350e5 100644 --- a/crates/ruff_python_formatter/src/module/mod_module.rs +++ b/crates/ruff_python_formatter/src/module/mod_module.rs @@ -1,10 +1,10 @@ -use ruff_formatter::prelude::hard_line_break; -use ruff_formatter::{Buffer, FormatResult}; +use ruff_formatter::write; use ruff_python_ast::ModModule; -use crate::comments::{trailing_comments, SourceComment}; +use crate::comments::SourceComment; +use crate::prelude::*; use crate::statement::suite::SuiteKind; -use crate::{write, AsFormat, FormatNodeRule, PyFormatter}; +use crate::FormatNodeRule; #[derive(Default)] pub struct FormatModModule; @@ -12,13 +12,11 @@ pub struct FormatModModule; impl FormatNodeRule for FormatModModule { fn fmt_fields(&self, item: &ModModule, f: &mut PyFormatter) -> FormatResult<()> { let ModModule { range: _, body } = item; - let comments = f.context().comments().clone(); write!( f, [ body.format().with_options(SuiteKind::TopLevel), - trailing_comments(comments.dangling(item)), // Trailing newline at the end of the file hard_line_break() ] diff --git a/crates/ruff_python_formatter/tests/snapshots/format@fmt_on_off__indent.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@fmt_on_off__indent.py.snap index 5330ab331e..3a3392f35e 100644 --- a/crates/ruff_python_formatter/tests/snapshots/format@fmt_on_off__indent.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@fmt_on_off__indent.py.snap @@ -17,6 +17,7 @@ magic-trailing-comma = Respect ``` ```py + ``` @@ -30,6 +31,7 @@ magic-trailing-comma = Respect ``` ```py + ``` @@ -43,6 +45,7 @@ magic-trailing-comma = Respect ``` ```py + ``` diff --git a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__whitespace.py.snap b/crates/ruff_python_formatter/tests/snapshots/format@statement__module_comment.py.snap similarity index 52% rename from crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__whitespace.py.snap rename to crates/ruff_python_formatter/tests/snapshots/format@statement__module_comment.py.snap index 70636c22b7..1ba55d8a77 100644 --- a/crates/ruff_python_formatter/tests/snapshots/black_compatibility@simple_cases__whitespace.py.snap +++ b/crates/ruff_python_formatter/tests/snapshots/format@statement__module_comment.py.snap @@ -1,31 +1,19 @@ --- source: crates/ruff_python_formatter/tests/fixtures.rs -input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/whitespace.py +input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/module_comment.py --- ## Input - ```py + + +# hehehe >:)a ``` -## Black Differences - -```diff ---- Black -+++ Ruff -@@ -1 +0,0 @@ -- -``` - -## Ruff Output - +## Output ```py -``` - -## Black Output - -```py - +# hehehe >:)a ``` +