diff --git a/crates/ruff_linter/resources/test/fixtures/pydocstyle/D403.py b/crates/ruff_linter/resources/test/fixtures/pydocstyle/D403.py index d90acb358efab..bd90d60bc84c2 100644 --- a/crates/ruff_linter/resources/test/fixtures/pydocstyle/D403.py +++ b/crates/ruff_linter/resources/test/fixtures/pydocstyle/D403.py @@ -25,3 +25,9 @@ def non_ascii(): def all_caps(): """th•s is not capitalized.""" + +def single_word(): + """singleword.""" + +def single_word_no_dot(): + """singleword""" diff --git a/crates/ruff_linter/src/rules/pydocstyle/rules/capitalized.rs b/crates/ruff_linter/src/rules/pydocstyle/rules/capitalized.rs index 7988490f6c18f..88f02716764d0 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/rules/capitalized.rs +++ b/crates/ruff_linter/src/rules/pydocstyle/rules/capitalized.rs @@ -59,26 +59,36 @@ pub(crate) fn capitalized(checker: &mut Checker, docstring: &Docstring) { } let body = docstring.body(); - let Some(first_word) = body.split(' ').next() else { - return; - }; - - // Like pydocstyle, we only support ASCII for now. - for char in first_word.chars() { - if !char.is_ascii_alphabetic() && char != '\'' { - return; - } - } + let first_word = 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 mut first_word_chars = first_word.chars(); let Some(first_char) = first_word_chars.next() else { return; }; + + if !first_char.is_ascii() { + return; + } + let uppercase_first_char = first_char.to_ascii_uppercase(); if first_char == uppercase_first_char { return; } + // Like pydocstyle, we only support ASCII for now. + for char in first_word.chars().skip(1) { + if !char.is_ascii_alphabetic() && char != '\'' { + return; + } + } + let capitalized_word = uppercase_first_char.to_string() + first_word_chars.as_str(); let mut diagnostic = Diagnostic::new( diff --git a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D403_D403.py.snap b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D403_D403.py.snap index 0ac0eed67f2ff..07a0589fb11fc 100644 --- a/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D403_D403.py.snap +++ b/crates/ruff_linter/src/rules/pydocstyle/snapshots/ruff_linter__rules__pydocstyle__tests__D403_D403.py.snap @@ -19,4 +19,37 @@ 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` + | +29 | def single_word(): +30 | """singleword.""" + | ^^^^^^^^^^^^^^^^^ D403 +31 | +32 | def single_word_no_dot(): + | + = help: Capitalize `singleword` to `Singleword` +ℹ Safe fix +27 27 | """th•s is not capitalized.""" +28 28 | +29 29 | def single_word(): +30 |- """singleword.""" + 30 |+ """Singleword.""" +31 31 | +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` + | +32 | def single_word_no_dot(): +33 | """singleword""" + | ^^^^^^^^^^^^^^^^ D403 + | + = help: Capitalize `singleword` to `Singleword` + +ℹ Safe fix +30 30 | """singleword.""" +31 31 | +32 32 | def single_word_no_dot(): +33 |- """singleword""" + 33 |+ """Singleword"""