mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-03 07:04:53 +00:00
[pydocstyle
] Skip leading whitespace for D403
(#14963)
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 #14890
This commit is contained in:
parent
a623d8f7c4
commit
6a5eff6017
6 changed files with 60 additions and 17 deletions
|
@ -31,3 +31,13 @@ def single_word():
|
||||||
|
|
||||||
def single_word_no_dot():
|
def single_word_no_dot():
|
||||||
"""singleword"""
|
"""singleword"""
|
||||||
|
|
||||||
|
def first_word_lots_of_whitespace():
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
here is the start of my docstring!
|
||||||
|
|
||||||
|
What do you think?
|
||||||
|
"""
|
||||||
|
|
|
@ -46,7 +46,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||||
Rule::EndsInPeriod,
|
Rule::EndsInPeriod,
|
||||||
Rule::EndsInPunctuation,
|
Rule::EndsInPunctuation,
|
||||||
Rule::EscapeSequenceInDocstring,
|
Rule::EscapeSequenceInDocstring,
|
||||||
Rule::FirstLineCapitalized,
|
Rule::FirstWordUncapitalized,
|
||||||
Rule::FitsOnOneLine,
|
Rule::FitsOnOneLine,
|
||||||
Rule::IndentWithSpaces,
|
Rule::IndentWithSpaces,
|
||||||
Rule::MultiLineSummaryFirstLine,
|
Rule::MultiLineSummaryFirstLine,
|
||||||
|
@ -277,7 +277,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||||
if checker.enabled(Rule::NoSignature) {
|
if checker.enabled(Rule::NoSignature) {
|
||||||
pydocstyle::rules::no_signature(checker, &docstring);
|
pydocstyle::rules::no_signature(checker, &docstring);
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::FirstLineCapitalized) {
|
if checker.enabled(Rule::FirstWordUncapitalized) {
|
||||||
pydocstyle::rules::capitalized(checker, &docstring);
|
pydocstyle::rules::capitalized(checker, &docstring);
|
||||||
}
|
}
|
||||||
if checker.enabled(Rule::DocstringStartsWithThis) {
|
if checker.enabled(Rule::DocstringStartsWithThis) {
|
||||||
|
|
|
@ -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, "400") => (RuleGroup::Stable, rules::pydocstyle::rules::EndsInPeriod),
|
||||||
(Pydocstyle, "401") => (RuleGroup::Stable, rules::pydocstyle::rules::NonImperativeMood),
|
(Pydocstyle, "401") => (RuleGroup::Stable, rules::pydocstyle::rules::NonImperativeMood),
|
||||||
(Pydocstyle, "402") => (RuleGroup::Stable, rules::pydocstyle::rules::NoSignature),
|
(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, "404") => (RuleGroup::Stable, rules::pydocstyle::rules::DocstringStartsWithThis),
|
||||||
(Pydocstyle, "405") => (RuleGroup::Stable, rules::pydocstyle::rules::CapitalizeSectionName),
|
(Pydocstyle, "405") => (RuleGroup::Stable, rules::pydocstyle::rules::CapitalizeSectionName),
|
||||||
(Pydocstyle, "406") => (RuleGroup::Stable, rules::pydocstyle::rules::NewLineAfterSectionName),
|
(Pydocstyle, "406") => (RuleGroup::Stable, rules::pydocstyle::rules::NewLineAfterSectionName),
|
||||||
|
|
|
@ -32,8 +32,8 @@ mod tests {
|
||||||
#[test_case(Rule::EndsInPeriod, Path::new("D400_415.py"))]
|
#[test_case(Rule::EndsInPeriod, Path::new("D400_415.py"))]
|
||||||
#[test_case(Rule::EndsInPunctuation, Path::new("D.py"))]
|
#[test_case(Rule::EndsInPunctuation, Path::new("D.py"))]
|
||||||
#[test_case(Rule::EndsInPunctuation, Path::new("D400_415.py"))]
|
#[test_case(Rule::EndsInPunctuation, Path::new("D400_415.py"))]
|
||||||
#[test_case(Rule::FirstLineCapitalized, Path::new("D.py"))]
|
#[test_case(Rule::FirstWordUncapitalized, Path::new("D.py"))]
|
||||||
#[test_case(Rule::FirstLineCapitalized, Path::new("D403.py"))]
|
#[test_case(Rule::FirstWordUncapitalized, Path::new("D403.py"))]
|
||||||
#[test_case(Rule::FitsOnOneLine, Path::new("D.py"))]
|
#[test_case(Rule::FitsOnOneLine, Path::new("D.py"))]
|
||||||
#[test_case(Rule::IndentWithSpaces, Path::new("D.py"))]
|
#[test_case(Rule::IndentWithSpaces, Path::new("D.py"))]
|
||||||
#[test_case(Rule::UndocumentedMagicMethod, Path::new("D.py"))]
|
#[test_case(Rule::UndocumentedMagicMethod, Path::new("D.py"))]
|
||||||
|
|
|
@ -10,8 +10,8 @@ use crate::docstrings::Docstring;
|
||||||
/// Checks for docstrings that do not start with a capital letter.
|
/// Checks for docstrings that do not start with a capital letter.
|
||||||
///
|
///
|
||||||
/// ## Why is this bad?
|
/// ## Why is this bad?
|
||||||
/// The first character in a docstring should be capitalized for, grammatical
|
/// The first non-whitespace character in a docstring should be
|
||||||
/// correctness and consistency.
|
/// capitalized for grammatical correctness and consistency.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```python
|
/// ```python
|
||||||
|
@ -30,16 +30,16 @@ use crate::docstrings::Docstring;
|
||||||
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
|
/// - [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)
|
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
|
||||||
#[derive(ViolationMetadata)]
|
#[derive(ViolationMetadata)]
|
||||||
pub(crate) struct FirstLineCapitalized {
|
pub(crate) struct FirstWordUncapitalized {
|
||||||
first_word: String,
|
first_word: String,
|
||||||
capitalized_word: String,
|
capitalized_word: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AlwaysFixableViolation for FirstLineCapitalized {
|
impl AlwaysFixableViolation for FirstWordUncapitalized {
|
||||||
#[derive_message_formats]
|
#[derive_message_formats]
|
||||||
fn message(&self) -> String {
|
fn message(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
"First word of the first line should be capitalized: `{}` -> `{}`",
|
"First word of the docstring should be capitalized: `{}` -> `{}`",
|
||||||
self.first_word, self.capitalized_word
|
self.first_word, self.capitalized_word
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,8 @@ pub(crate) fn capitalized(checker: &mut Checker, docstring: &Docstring) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let body = docstring.body();
|
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
|
// If the docstring is a single word, trim the punctuation marks because
|
||||||
// it makes the ASCII test below fail.
|
// it makes the ASCII test below fail.
|
||||||
|
@ -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 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(
|
let mut diagnostic = Diagnostic::new(
|
||||||
FirstLineCapitalized {
|
FirstWordUncapitalized {
|
||||||
first_word: first_word.to_string(),
|
first_word: first_word.to_string(),
|
||||||
capitalized_word: capitalized_word.to_string(),
|
capitalized_word: capitalized_word.to_string(),
|
||||||
},
|
},
|
||||||
|
@ -101,7 +104,7 @@ pub(crate) fn capitalized(checker: &mut Checker, docstring: &Docstring) {
|
||||||
|
|
||||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||||
capitalized_word,
|
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);
|
checker.diagnostics.push(diagnostic);
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
|
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():
|
1 | def bad_function():
|
||||||
2 | """this docstring is not capitalized"""
|
2 | """this docstring is not capitalized"""
|
||||||
|
@ -20,7 +19,7 @@ D403.py:2:5: D403 [*] First word of the first line should be capitalized: `this`
|
||||||
4 4 | def good_function():
|
4 4 | def good_function():
|
||||||
5 5 | """This docstring is capitalized."""
|
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():
|
29 | def single_word():
|
||||||
30 | """singleword."""
|
30 | """singleword."""
|
||||||
|
@ -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():
|
32 32 | def single_word_no_dot():
|
||||||
33 33 | """singleword"""
|
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():
|
32 | def single_word_no_dot():
|
||||||
33 | """singleword"""
|
33 | """singleword"""
|
||||||
| ^^^^^^^^^^^^^^^^ D403
|
| ^^^^^^^^^^^^^^^^ D403
|
||||||
|
34 |
|
||||||
|
35 | def first_word_lots_of_whitespace():
|
||||||
|
|
|
|
||||||
= help: Capitalize `singleword` to `Singleword`
|
= help: Capitalize `singleword` to `Singleword`
|
||||||
|
|
||||||
|
@ -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():
|
32 32 | def single_word_no_dot():
|
||||||
33 |- """singleword"""
|
33 |- """singleword"""
|
||||||
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 | """
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue