Skip to content

Commit 80fc02e

Browse files
authored
Don't trim last empty line in docstrings (#9813)
1 parent 55d0e11 commit 80fc02e

File tree

3 files changed

+186
-2
lines changed

3 files changed

+186
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Tests that Ruff correctly preserves newlines before the closing quote
2+
3+
def test():
4+
"""a, b
5+
c"""
6+
7+
8+
def test2():
9+
"""a, b
10+
c
11+
"""
12+
13+
def test3():
14+
"""a, b
15+
16+
"""
17+
18+
19+
def test4():
20+
"""
21+
a, b
22+
23+
"""
24+
25+
26+
def test5():
27+
"""
28+
a, b
29+
a"""
30+
31+
def test6():
32+
"""
33+
a, b
34+
35+
c
36+
"""
37+
38+
39+
40+
def test7():
41+
"""
42+
a, b
43+
44+
45+
46+
47+
"""
48+
49+
def test7():
50+
"""
51+
a, b
52+
53+
54+
55+
"""

crates/ruff_python_formatter/src/string/docstring.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::{borrow::Cow, collections::VecDeque};
66

77
use ruff_formatter::printer::SourceMapGeneration;
88
use ruff_python_parser::ParseError;
9+
910
use {once_cell::sync::Lazy, regex::Regex};
1011
use {
1112
ruff_formatter::{write, FormatOptions, IndentStyle, LineWidth, Printed},
@@ -114,7 +115,10 @@ pub(crate) fn format(normalized: &NormalizedString, f: &mut PyFormatter) -> Form
114115
// is_borrowed is unstable :/
115116
let already_normalized = matches!(docstring, Cow::Borrowed(_));
116117

117-
let mut lines = docstring.lines().peekable();
118+
// Use `split` instead of `lines` to preserve the closing quotes on their own line
119+
// if they have no indentation (in which case the last line is `\n` which
120+
// `lines` omit for the last element).
121+
let mut lines = docstring.split('\n').peekable();
118122

119123
// Start the string
120124
write!(f, [normalized.prefix, normalized.quotes])?;
@@ -259,7 +263,7 @@ impl<'ast, 'buf, 'fmt, 'src> DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> {
259263
/// iterator given contains all lines except for the first.
260264
fn add_iter(
261265
&mut self,
262-
mut lines: std::iter::Peekable<std::str::Lines<'src>>,
266+
mut lines: std::iter::Peekable<std::str::Split<'src, char>>,
263267
) -> FormatResult<()> {
264268
while let Some(line) = lines.next() {
265269
let line = InputDocstringLine {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
---
2+
source: crates/ruff_python_formatter/tests/fixtures.rs
3+
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring_newlines.py
4+
---
5+
## Input
6+
```python
7+
# Tests that Ruff correctly preserves newlines before the closing quote
8+
9+
def test():
10+
"""a, b
11+
c"""
12+
13+
14+
def test2():
15+
"""a, b
16+
c
17+
"""
18+
19+
def test3():
20+
"""a, b
21+
22+
"""
23+
24+
25+
def test4():
26+
"""
27+
a, b
28+
29+
"""
30+
31+
32+
def test5():
33+
"""
34+
a, b
35+
a"""
36+
37+
def test6():
38+
"""
39+
a, b
40+
41+
c
42+
"""
43+
44+
45+
46+
def test7():
47+
"""
48+
a, b
49+
50+
51+
52+
53+
"""
54+
55+
def test7():
56+
"""
57+
a, b
58+
59+
60+
61+
"""
62+
```
63+
64+
## Output
65+
```python
66+
# Tests that Ruff correctly preserves newlines before the closing quote
67+
68+
69+
def test():
70+
"""a, b
71+
c"""
72+
73+
74+
def test2():
75+
"""a, b
76+
c
77+
"""
78+
79+
80+
def test3():
81+
"""a, b"""
82+
83+
84+
def test4():
85+
"""
86+
a, b
87+
88+
"""
89+
90+
91+
def test5():
92+
"""
93+
a, b
94+
a"""
95+
96+
97+
def test6():
98+
"""
99+
a, b
100+
101+
c
102+
"""
103+
104+
105+
def test7():
106+
"""
107+
a, b
108+
109+
110+
111+
112+
"""
113+
114+
115+
def test7():
116+
"""
117+
a, b
118+
119+
120+
121+
"""
122+
```
123+
124+
125+

0 commit comments

Comments
 (0)