Skip to content

Commit

Permalink
[pydocstyle] Split on first whitespace character (D403) (astral-s…
Browse files Browse the repository at this point in the history
…h#15082)

## Summary

This PR fixes an issue where Ruff's `D403` rule
(`first-word-uncapitalized`) was not detecting some single-word edge
cases that are picked up by `pydocstyle`.

The change involves extracting the first word of the docstring by
identifying the first whitespace character. This is consistent with
`pydocstyle` which uses `.split()` - see
https://github.com/PyCQA/pydocstyle/blob/8d0cdfc93e86e096bb5753f07dc5c7c373e63837/src/pydocstyle/checker.py#L581C13-L581C64

## Example

Here is a playground example -
https://play.ruff.rs/eab9ea59-92cf-4e44-b1a9-b54b7f69b178

```py
def example1():
    """foo"""

def example2():
    """foo
    
    Hello world!
    """

def example3():
    """foo bar

    Hello world!
    """

def example4():
    """
    foo
    """

def example5():
    """
    foo bar
    """
```

`pydocstyle` detects all five cases:
```bash
$ pydocstyle test.py --select D403
dev/test.py:2 in public function `example1`:
        D403: First word of the first line should be properly capitalized ('Foo', not 'foo')
dev/test.py:5 in public function `example2`:
        D403: First word of the first line should be properly capitalized ('Foo', not 'foo')
dev/test.py:11 in public function `example3`:
        D403: First word of the first line should be properly capitalized ('Foo', not 'foo')
dev/test.py:17 in public function `example4`:
        D403: First word of the first line should be properly capitalized ('Foo', not 'foo')
dev/test.py:22 in public function `example5`:
        D403: First word of the first line should be properly capitalized ('Foo', not 'foo')
```
Ruff (`0.8.4`) fails to catch example2 and example4.

## Test Plan

* Added two new test cases to cover the previously missed single-word
docstring cases.
  • Loading branch information
my1e5 authored Dec 20, 2024
1 parent d47fba1 commit d3f51cf
Show file tree
Hide file tree
Showing 3 changed files with 241 additions and 8 deletions.
46 changes: 46 additions & 0 deletions crates/ruff_linter/resources/test/fixtures/pydocstyle/D403.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,49 @@ def first_word_lots_of_whitespace():
What do you think?
"""

def single_word_newline():
"""singleword
"""

def single_word_dot_newline():
"""singleword.
"""

def single_word_second_line():
"""
singleword
"""

def single_word_dot_second_line():
"""
singleword.
"""

def single_word_then_more_text():
"""singleword
This is more text.
"""

def single_word_dot_then_more_text():
"""singleword.
This is more text.
"""

def single_word_second_line_then_more_text():
"""
singleword
This is more text.
"""

def single_word_dot_second_line_then_more_text():
"""
singleword.
This is more text.
"""
12 changes: 4 additions & 8 deletions crates/ruff_linter/src/rules/pydocstyle/rules/capitalized.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,10 @@ pub(crate) fn capitalized(checker: &mut Checker, docstring: &Docstring) {

let body = docstring.body();
let trim_start_body = body.trim_start();
let first_word = trim_start_body.split_once(' ').map_or_else(
|| {
// If the docstring is a single word, trim the punctuation marks because
// it makes the ASCII test below fail.
body.trim_end_matches(['.', '!', '?'])
},
|(first_word, _)| first_word,
);
let first_word = trim_start_body
.find(char::is_whitespace)
.map_or(trim_start_body, |idx| &trim_start_body[..idx])
.trim_end_matches(['.', '!', '?']);

let mut first_word_chars = first_word.chars();
let Some(first_char) = first_word_chars.next() else {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
snapshot_kind: text
---
D403.py:2:5: D403 [*] First word of the docstring should be capitalized: `this` -> `This`
|
Expand Down Expand Up @@ -72,6 +73,8 @@ D403.py:36:5: D403 [*] First word of the docstring should be capitalized: `here`
42 | | What do you think?
43 | | """
| |_______^ D403
44 |
45 | def single_word_newline():
|
= help: Capitalize `here` to `Here`

Expand All @@ -84,3 +87,191 @@ D403.py:36:5: D403 [*] First word of the docstring should be capitalized: `here`
41 41 |
42 42 | What do you think?
43 43 | """
D403.py:46:5: D403 [*] First word of the docstring should be capitalized: `singleword` -> `Singleword`
|
45 | def single_word_newline():
46 | """singleword
| _____^
47 | |
48 | | """
| |_______^ D403
49 |
50 | def single_word_dot_newline():
|
= help: Capitalize `singleword` to `Singleword`

Safe fix
43 43 | """
44 44 |
45 45 | def single_word_newline():
46 |- """singleword
46 |+ """Singleword
47 47 |
48 48 | """
49 49 |

D403.py:51:5: D403 [*] First word of the docstring should be capitalized: `singleword` -> `Singleword`
|
50 | def single_word_dot_newline():
51 | """singleword.
| _____^
52 | |
53 | | """
| |_______^ D403
54 |
55 | def single_word_second_line():
|
= help: Capitalize `singleword` to `Singleword`

Safe fix
48 48 | """
49 49 |
50 50 | def single_word_dot_newline():
51 |- """singleword.
51 |+ """Singleword.
52 52 |
53 53 | """
54 54 |

D403.py:56:5: D403 [*] First word of the docstring should be capitalized: `singleword` -> `Singleword`
|
55 | def single_word_second_line():
56 | """
| _____^
57 | | singleword
58 | | """
| |_______^ D403
59 |
60 | def single_word_dot_second_line():
|
= help: Capitalize `singleword` to `Singleword`

Safe fix
54 54 |
55 55 | def single_word_second_line():
56 56 | """
57 |- singleword
57 |+ Singleword
58 58 | """
59 59 |
60 60 | def single_word_dot_second_line():

D403.py:61:5: D403 [*] First word of the docstring should be capitalized: `singleword` -> `Singleword`
|
60 | def single_word_dot_second_line():
61 | """
| _____^
62 | | singleword.
63 | | """
| |_______^ D403
64 |
65 | def single_word_then_more_text():
|
= help: Capitalize `singleword` to `Singleword`

Safe fix
59 59 |
60 60 | def single_word_dot_second_line():
61 61 | """
62 |- singleword.
62 |+ Singleword.
63 63 | """
64 64 |
65 65 | def single_word_then_more_text():

D403.py:66:5: D403 [*] First word of the docstring should be capitalized: `singleword` -> `Singleword`
|
65 | def single_word_then_more_text():
66 | """singleword
| _____^
67 | |
68 | | This is more text.
69 | | """
| |_______^ D403
70 |
71 | def single_word_dot_then_more_text():
|
= help: Capitalize `singleword` to `Singleword`

Safe fix
63 63 | """
64 64 |
65 65 | def single_word_then_more_text():
66 |- """singleword
66 |+ """Singleword
67 67 |
68 68 | This is more text.
69 69 | """
D403.py:72:5: D403 [*] First word of the docstring should be capitalized: `singleword` -> `Singleword`
|
71 | def single_word_dot_then_more_text():
72 | """singleword.
| _____^
73 | |
74 | | This is more text.
75 | | """
| |_______^ D403
76 |
77 | def single_word_second_line_then_more_text():
|
= help: Capitalize `singleword` to `Singleword`

Safe fix
69 69 | """
70 70 |
71 71 | def single_word_dot_then_more_text():
72 |- """singleword.
72 |+ """Singleword.
73 73 |
74 74 | This is more text.
75 75 | """
D403.py:78:5: D403 [*] First word of the docstring should be capitalized: `singleword` -> `Singleword`
|
77 | def single_word_second_line_then_more_text():
78 | """
| _____^
79 | | singleword
80 | |
81 | | This is more text.
82 | | """
| |_______^ D403
83 |
84 | def single_word_dot_second_line_then_more_text():
|
= help: Capitalize `singleword` to `Singleword`

Safe fix
76 76 |
77 77 | def single_word_second_line_then_more_text():
78 78 | """
79 |- singleword
79 |+ Singleword
80 80 |
81 81 | This is more text.
82 82 | """
D403.py:85:5: D403 [*] First word of the docstring should be capitalized: `singleword` -> `Singleword`
|
84 | def single_word_dot_second_line_then_more_text():
85 | """
| _____^
86 | | singleword.
87 | |
88 | | This is more text.
89 | | """
| |_______^ D403
|
= help: Capitalize `singleword` to `Singleword`

Safe fix
83 83 |
84 84 | def single_word_dot_second_line_then_more_text():
85 85 | """
86 |- singleword.
86 |+ Singleword.
87 87 |
88 88 | This is more text.
89 89 | """

0 comments on commit d3f51cf

Please sign in to comment.