Formatter: Add EmptyWithDanglingComments helper (#5951)

**Summary** Add a `EmptyWithDanglingComments` format helper that formats
comments inside empty parentheses, brackets or curly braces. Previously,
this was implemented separately, and partially incorrectly, for each use
case.

Empty `()`, `[]` and `{}` are special because there can be dangling
comments, and they can be in
two positions:
```python
x = [  # end-of-line
    # own line
]
```
These comments are dangling because they can't be assigned to any
element inside as they would
in all other cases.

**Test Plan** Added a regression test.

145 (from previously 149) instances of unstable formatting remaining.

```
$ cargo run --bin ruff_dev --release -- format-dev --stability-check --error-file formatter-ecosystem-errors.txt --multi-project target/checkouts > formatter-ecosystem-progress.txt
$ rg "Unstable formatting" target/formatter-ecosystem-errors.txt | wc -l
145
```
This commit is contained in:
konsti 2023-07-23 14:32:16 +02:00 committed by GitHub
parent f886b58c92
commit 46f8961292
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 94 additions and 56 deletions

View file

@ -1,6 +1,7 @@
use ruff_text_size::{TextRange, TextSize};
use rustpython_parser::ast::Ranged;
use crate::comments::{dangling_comments, SourceComment};
use ruff_formatter::{format_args, write, Argument, Arguments};
use ruff_python_trivia::{
lines_after, skip_trailing_trivia, SimpleToken, SimpleTokenKind, SimpleTokenizer,
@ -323,6 +324,57 @@ impl<'fmt, 'ast, 'buf> JoinCommaSeparatedBuilder<'fmt, 'ast, 'buf> {
}
}
/// Format comments inside empty parentheses, brackets or curly braces.
///
/// Empty `()`, `[]` and `{}` are special because there can be dangling comments, and they can be in
/// two positions:
/// ```python
/// x = [ # end-of-line
/// # own line
/// ]
/// ```
/// These comments are dangling because they can't be assigned to any element inside as they would
/// in all other cases.
pub(crate) fn empty_parenthesized_with_dangling_comments(
opening: StaticText,
comments: &[SourceComment],
closing: StaticText,
) -> EmptyWithDanglingComments {
EmptyWithDanglingComments {
opening,
comments,
closing,
}
}
pub(crate) struct EmptyWithDanglingComments<'a> {
opening: StaticText,
comments: &'a [SourceComment],
closing: StaticText,
}
impl<'ast> Format<PyFormatContext<'ast>> for EmptyWithDanglingComments<'_> {
fn fmt(&self, f: &mut Formatter<PyFormatContext>) -> FormatResult<()> {
let end_of_line_split = self
.comments
.partition_point(|comment| comment.line_position().is_end_of_line());
debug_assert!(self.comments[end_of_line_split..]
.iter()
.all(|comment| comment.line_position().is_own_line()));
write!(
f,
[group(&format_args![
self.opening,
// end-of-line comments
dangling_comments(&self.comments[..end_of_line_split]),
// own line comments, which need to be indented
soft_block_indent(&dangling_comments(&self.comments[end_of_line_split..])),
self.closing
])]
)
}
}
#[cfg(test)]
mod tests {
use rustpython_parser::ast::ModModule;