Skip to content

Commit

Permalink
[pydocstyle] Skip leading whitespace for D403 (astral-sh#14963)
Browse files Browse the repository at this point in the history
This PR introduces three changes to `D403`, which has to do with
capitalizing the first word in a docstring.

1. The diagnostic and fix now skip leading whitespace when determining
what counts as "the first word".
2. The name has been changed to `first-word-uncapitalized` from
`first-line-capitalized`, for both clarity and compliance with our rule
naming policy.
3. The diagnostic message and documentation has been modified slightly
to reflect this.

Closes astral-sh#14890
  • Loading branch information
dylwil3 authored Dec 16, 2024
1 parent a623d8f commit 6a5eff6
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 17 deletions.
10 changes: 10 additions & 0 deletions crates/ruff_linter/resources/test/fixtures/pydocstyle/D403.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,13 @@ def single_word():

def single_word_no_dot():
"""singleword"""

def first_word_lots_of_whitespace():
"""
here is the start of my docstring!
What do you think?
"""
4 changes: 2 additions & 2 deletions crates/ruff_linter/src/checkers/ast/analyze/definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
Rule::EndsInPeriod,
Rule::EndsInPunctuation,
Rule::EscapeSequenceInDocstring,
Rule::FirstLineCapitalized,
Rule::FirstWordUncapitalized,
Rule::FitsOnOneLine,
Rule::IndentWithSpaces,
Rule::MultiLineSummaryFirstLine,
Expand Down Expand Up @@ -277,7 +277,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
if checker.enabled(Rule::NoSignature) {
pydocstyle::rules::no_signature(checker, &docstring);
}
if checker.enabled(Rule::FirstLineCapitalized) {
if checker.enabled(Rule::FirstWordUncapitalized) {
pydocstyle::rules::capitalized(checker, &docstring);
}
if checker.enabled(Rule::DocstringStartsWithThis) {
Expand Down
2 changes: 1 addition & 1 deletion crates/ruff_linter/src/codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pydocstyle, "400") => (RuleGroup::Stable, rules::pydocstyle::rules::EndsInPeriod),
(Pydocstyle, "401") => (RuleGroup::Stable, rules::pydocstyle::rules::NonImperativeMood),
(Pydocstyle, "402") => (RuleGroup::Stable, rules::pydocstyle::rules::NoSignature),
(Pydocstyle, "403") => (RuleGroup::Stable, rules::pydocstyle::rules::FirstLineCapitalized),
(Pydocstyle, "403") => (RuleGroup::Stable, rules::pydocstyle::rules::FirstWordUncapitalized),
(Pydocstyle, "404") => (RuleGroup::Stable, rules::pydocstyle::rules::DocstringStartsWithThis),
(Pydocstyle, "405") => (RuleGroup::Stable, rules::pydocstyle::rules::CapitalizeSectionName),
(Pydocstyle, "406") => (RuleGroup::Stable, rules::pydocstyle::rules::NewLineAfterSectionName),
Expand Down
4 changes: 2 additions & 2 deletions crates/ruff_linter/src/rules/pydocstyle/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ mod tests {
#[test_case(Rule::EndsInPeriod, Path::new("D400_415.py"))]
#[test_case(Rule::EndsInPunctuation, Path::new("D.py"))]
#[test_case(Rule::EndsInPunctuation, Path::new("D400_415.py"))]
#[test_case(Rule::FirstLineCapitalized, Path::new("D.py"))]
#[test_case(Rule::FirstLineCapitalized, Path::new("D403.py"))]
#[test_case(Rule::FirstWordUncapitalized, Path::new("D.py"))]
#[test_case(Rule::FirstWordUncapitalized, Path::new("D403.py"))]
#[test_case(Rule::FitsOnOneLine, Path::new("D.py"))]
#[test_case(Rule::IndentWithSpaces, Path::new("D.py"))]
#[test_case(Rule::UndocumentedMagicMethod, Path::new("D.py"))]
Expand Down
19 changes: 11 additions & 8 deletions crates/ruff_linter/src/rules/pydocstyle/rules/capitalized.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use crate::docstrings::Docstring;
/// Checks for docstrings that do not start with a capital letter.
///
/// ## Why is this bad?
/// The first character in a docstring should be capitalized for, grammatical
/// correctness and consistency.
/// The first non-whitespace character in a docstring should be
/// capitalized for grammatical correctness and consistency.
///
/// ## Example
/// ```python
Expand All @@ -30,16 +30,16 @@ use crate::docstrings::Docstring;
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
#[derive(ViolationMetadata)]
pub(crate) struct FirstLineCapitalized {
pub(crate) struct FirstWordUncapitalized {
first_word: String,
capitalized_word: String,
}

impl AlwaysFixableViolation for FirstLineCapitalized {
impl AlwaysFixableViolation for FirstWordUncapitalized {
#[derive_message_formats]
fn message(&self) -> String {
format!(
"First word of the first line should be capitalized: `{}` -> `{}`",
"First word of the docstring should be capitalized: `{}` -> `{}`",
self.first_word, self.capitalized_word
)
}
Expand All @@ -59,7 +59,8 @@ pub(crate) fn capitalized(checker: &mut Checker, docstring: &Docstring) {
}

let body = docstring.body();
let first_word = body.split_once(' ').map_or_else(
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.
Expand Down Expand Up @@ -91,8 +92,10 @@ pub(crate) fn capitalized(checker: &mut Checker, docstring: &Docstring) {

let capitalized_word = uppercase_first_char.to_string() + first_word_chars.as_str();

let leading_whitespace_len = body.text_len() - trim_start_body.text_len();

let mut diagnostic = Diagnostic::new(
FirstLineCapitalized {
FirstWordUncapitalized {
first_word: first_word.to_string(),
capitalized_word: capitalized_word.to_string(),
},
Expand All @@ -101,7 +104,7 @@ pub(crate) fn capitalized(checker: &mut Checker, docstring: &Docstring) {

diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
capitalized_word,
TextRange::at(body.start(), first_word.text_len()),
TextRange::at(body.start() + leading_whitespace_len, first_word.text_len()),
)));

checker.diagnostics.push(diagnostic);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
snapshot_kind: text
---
D403.py:2:5: D403 [*] First word of the first line should be capitalized: `this` -> `This`
D403.py:2:5: D403 [*] First word of the docstring should be capitalized: `this` -> `This`
|
1 | def bad_function():
2 | """this docstring is not capitalized"""
Expand All @@ -20,7 +19,7 @@ D403.py:2:5: D403 [*] First word of the first line should be capitalized: `this`
4 4 | def good_function():
5 5 | """This docstring is capitalized."""

D403.py:30:5: D403 [*] First word of the first line should be capitalized: `singleword` -> `Singleword`
D403.py:30:5: D403 [*] First word of the docstring should be capitalized: `singleword` -> `Singleword`
|
29 | def single_word():
30 | """singleword."""
Expand All @@ -40,11 +39,13 @@ D403.py:30:5: D403 [*] First word of the first line should be capitalized: `sing
32 32 | def single_word_no_dot():
33 33 | """singleword"""

D403.py:33:5: D403 [*] First word of the first line should be capitalized: `singleword` -> `Singleword`
D403.py:33:5: D403 [*] First word of the docstring should be capitalized: `singleword` -> `Singleword`
|
32 | def single_word_no_dot():
33 | """singleword"""
| ^^^^^^^^^^^^^^^^ D403
34 |
35 | def first_word_lots_of_whitespace():
|
= help: Capitalize `singleword` to `Singleword`

Expand All @@ -54,3 +55,32 @@ D403.py:33:5: D403 [*] First word of the first line should be capitalized: `sing
32 32 | def single_word_no_dot():
33 |- """singleword"""
33 |+ """Singleword"""
34 34 |
35 35 | def first_word_lots_of_whitespace():
36 36 | """
D403.py:36:5: D403 [*] First word of the docstring should be capitalized: `here` -> `Here`
|
35 | def first_word_lots_of_whitespace():
36 | """
| _____^
37 | |
38 | |
39 | |
40 | | here is the start of my docstring!
41 | |
42 | | What do you think?
43 | | """
| |_______^ D403
|
= help: Capitalize `here` to `Here`

Safe fix
37 37 |
38 38 |
39 39 |
40 |- here is the start of my docstring!
40 |+ Here is the start of my docstring!
41 41 |
42 42 | What do you think?
43 43 | """

0 comments on commit 6a5eff6

Please sign in to comment.