Don't add a magic trailing comma for a single entry (#5463)

## Summary

If a comma separated list has only one entry, black will respect the
magic trailing comma, but it will not add a new one.

The following code will remain as is:

```python
b1 = [
    aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa
]
b2 = [
    aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa,
]
b3 = [
    aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa,
    aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa
]
```

## Test Plan

This was first discovered in
7eeadc82c2/django/contrib/admin/checks.py (L674-L681),
which i've minimized into a call test.

I've added tests for the three cases (one entry + no comma, one entry +
comma, more than one entry) to the list tests.

The diffs from the black tests get smaller.
This commit is contained in:
konsti 2023-07-03 21:48:44 +02:00 committed by GitHub
parent 3992c47c00
commit a647f31600
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 119 additions and 149 deletions

View file

@ -182,7 +182,10 @@ impl<'fmt, 'ast, 'buf> JoinNodesBuilder<'fmt, 'ast, 'buf> {
pub(crate) struct JoinCommaSeparatedBuilder<'fmt, 'ast, 'buf> {
result: FormatResult<()>,
fmt: &'fmt mut PyFormatter<'ast, 'buf>,
last_end: Option<TextSize>,
end_of_last_entry: Option<TextSize>,
/// We need to track whether we have more than one entry since a sole entry doesn't get a
/// magic trailing comma even when expanded
len: usize,
}
impl<'fmt, 'ast, 'buf> JoinCommaSeparatedBuilder<'fmt, 'ast, 'buf> {
@ -190,7 +193,8 @@ impl<'fmt, 'ast, 'buf> JoinCommaSeparatedBuilder<'fmt, 'ast, 'buf> {
Self {
fmt: f,
result: Ok(()),
last_end: None,
end_of_last_entry: None,
len: 0,
}
}
@ -203,11 +207,12 @@ impl<'fmt, 'ast, 'buf> JoinCommaSeparatedBuilder<'fmt, 'ast, 'buf> {
T: Ranged,
{
self.result = self.result.and_then(|_| {
if self.last_end.is_some() {
if self.end_of_last_entry.is_some() {
write!(self.fmt, [text(","), soft_line_break_or_space()])?;
}
self.last_end = Some(node.end());
self.end_of_last_entry = Some(node.end());
self.len += 1;
content.fmt(self.fmt)
});
@ -243,18 +248,23 @@ impl<'fmt, 'ast, 'buf> JoinCommaSeparatedBuilder<'fmt, 'ast, 'buf> {
pub(crate) fn finish(&mut self) -> FormatResult<()> {
self.result.and_then(|_| {
if let Some(last_end) = self.last_end.take() {
if_group_breaks(&text(",")).fmt(self.fmt)?;
if self.fmt.options().magic_trailing_comma().is_respect()
if let Some(last_end) = self.end_of_last_entry.take() {
let magic_trailing_comma = self.fmt.options().magic_trailing_comma().is_respect()
&& matches!(
first_non_trivia_token(last_end, self.fmt.context().contents()),
Some(Token {
kind: TokenKind::Comma,
..
})
)
{
);
// If there is a single entry, only keep the magic trailing comma, don't add it if
// it wasn't there. If there is more than one entry, always add it.
if magic_trailing_comma || self.len > 1 {
if_group_breaks(&text(",")).fmt(self.fmt)?;
}
if magic_trailing_comma {
expand_parent().fmt(self.fmt)?;
}
}