Format Slice Expressions (#5047)

This formats slice expressions and subscript expressions.

Spaces around the colons follows the same rules as black
(https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#slices):
```python
e00 = "e"[:]
e01 = "e"[:1]
e02 = "e"[: a()]
e10 = "e"[1:]
e11 = "e"[1:1]
e12 = "e"[1 : a()]
e20 = "e"[a() :]
e21 = "e"[a() : 1]
e22 = "e"[a() : a()]
e200 = "e"[a() : :]
e201 = "e"[a() :: 1]
e202 = "e"[a() :: a()]
e210 = "e"[a() : 1 :]
```

Comment placement is different due to our very different infrastructure.
If we have explicit bounds (e.g. `x[1:2]`) all comments get assigned as
leading or trailing to the bound expression. If a bound is missing
`[:]`, comments get marked as dangling and placed in the same section as
they were originally in:
```python
x = "x"[ # a
      # b
    :  # c
      # d
]
```
to
```python
x = "x"[
    # a
    # b
    :
    # c
    # d
]
```
Except for the potential trailing end-of-line comments, all comments get
formatted on their own line. This can be improved by keeping end-of-line
comments after the opening bracket or after a colon as such but the
changes were already complex enough.

I added tests for comment placement and spaces.
This commit is contained in:
konstin 2023-06-21 17:09:39 +02:00 committed by GitHub
parent 4634560c80
commit 6155fd647d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 1065 additions and 430 deletions

View file

@ -1,24 +1,52 @@
use crate::comments::{trailing_comments, Comments};
use crate::expression::parentheses::{
default_expression_needs_parentheses, NeedsParentheses, Parentheses, Parenthesize,
};
use crate::{not_yet_implemented_custom_text, FormatNodeRule, PyFormatter};
use crate::comments::Comments;
use ruff_formatter::{write, Buffer, FormatResult};
use crate::{AsFormat, FormatNodeRule, PyFormatter};
use ruff_formatter::prelude::{group, soft_block_indent, text};
use ruff_formatter::{format_args, write, Buffer, FormatResult};
use ruff_python_ast::node::AstNode;
use rustpython_parser::ast::ExprSubscript;
#[derive(Default)]
pub struct FormatExprSubscript;
impl FormatNodeRule<ExprSubscript> for FormatExprSubscript {
fn fmt_fields(&self, _item: &ExprSubscript, f: &mut PyFormatter) -> FormatResult<()> {
fn fmt_fields(&self, item: &ExprSubscript, f: &mut PyFormatter) -> FormatResult<()> {
let ExprSubscript {
range: _,
value,
slice,
ctx: _,
} = item;
let comments = f.context().comments().clone();
let dangling_comments = comments.dangling_comments(item.as_any_node_ref());
debug_assert!(
dangling_comments.len() <= 1,
"The subscript expression must have at most a single comment, the one after the bracket"
);
write!(
f,
[not_yet_implemented_custom_text(
"NOT_IMPLEMENTED_value[NOT_IMPLEMENTED_key]"
)]
[group(&format_args![
value.format(),
text("["),
trailing_comments(dangling_comments),
soft_block_indent(&slice.format()),
text("]")
])]
)
}
fn fmt_dangling_comments(
&self,
_node: &ExprSubscript,
_f: &mut PyFormatter,
) -> FormatResult<()> {
// Handled inside of `fmt_fields`
Ok(())
}
}
impl NeedsParentheses for ExprSubscript {
@ -28,6 +56,9 @@ impl NeedsParentheses for ExprSubscript {
source: &str,
comments: &Comments,
) -> Parentheses {
default_expression_needs_parentheses(self.into(), parenthesize, source, comments)
match default_expression_needs_parentheses(self.into(), parenthesize, source, comments) {
Parentheses::Optional => Parentheses::Never,
parentheses => parentheses,
}
}
}