Implement D402 for pydocstyle (#403)

This commit is contained in:
Charlie Marsh 2022-10-11 13:19:56 -04:00 committed by GitHub
parent 71802f8861
commit 8868f57a74
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 64 additions and 12 deletions

View file

@ -217,7 +217,7 @@ ruff also implements some of the most popular Flake8 plugins natively, including
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/) (11/16)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (3/32)
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/) (11/47)
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/) (12/47)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (8/34)
Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis Flake8:
@ -312,6 +312,7 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
| D213 | MultiLineSummarySecondLine | Multi-line docstring summary should start at the second line | | |
| D300 | UsesTripleQuotes | Use """triple double quotes""" | | |
| D400 | EndsInPeriod | First line should end with a period | | |
| D402 | NoSignature | First line should not be the function's 'signature' | | |
| D403 | FirstLineCapitalized | First word of the first line should be properly capitalized | | |
| D415 | EndsInPunctuation | First line should end with a period, question mark, or exclamation point | | |
| D419 | NonEmpty | Docstring is empty | | |

View file

@ -1916,6 +1916,9 @@ impl<'a> Checker<'a> {
if self.settings.enabled.contains(&CheckCode::D400) {
docstrings::ends_with_period(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D402) {
docstrings::no_signature(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D403) {
docstrings::capitalized(self, &docstring);
}

View file

@ -159,6 +159,7 @@ pub enum CheckCode {
D213,
D300,
D400,
D402,
D403,
D415,
D419,
@ -270,6 +271,7 @@ pub enum CheckKind {
NoSurroundingWhitespace,
NonEmpty,
UsesTripleQuotes,
NoSignature,
// Meta
UnusedNOQA(Option<Vec<String>>),
}
@ -388,6 +390,7 @@ impl CheckCode {
CheckCode::D212 => CheckKind::MultiLineSummaryFirstLine,
CheckCode::D213 => CheckKind::MultiLineSummarySecondLine,
CheckCode::D300 => CheckKind::UsesTripleQuotes,
CheckCode::D402 => CheckKind::NoSignature,
CheckCode::D403 => CheckKind::FirstLineCapitalized,
CheckCode::D415 => CheckKind::EndsInPunctuation,
// Meta
@ -487,6 +490,7 @@ impl CheckKind {
CheckKind::MultiLineSummaryFirstLine => &CheckCode::D212,
CheckKind::MultiLineSummarySecondLine => &CheckCode::D213,
CheckKind::UsesTripleQuotes => &CheckCode::D300,
CheckKind::NoSignature => &CheckCode::D402,
CheckKind::FirstLineCapitalized => &CheckCode::D403,
CheckKind::EndsInPunctuation => &CheckCode::D415,
// Meta
@ -759,6 +763,9 @@ impl CheckKind {
CheckKind::MultiLineSummarySecondLine => {
"Multi-line docstring summary should start at the second line".to_string()
}
CheckKind::NoSignature => {
"First line should not be the function's 'signature'".to_string()
}
// Meta
CheckKind::UnusedNOQA(codes) => match codes {
None => "Unused `noqa` directive".to_string(),

View file

@ -5,22 +5,21 @@ use crate::check_ast::Checker;
use crate::checks::{Check, CheckCode, CheckKind};
#[derive(Debug)]
pub enum DocstringKind {
pub enum DocstringKind<'a> {
Module,
Function,
Class,
Function(&'a Stmt),
Class(&'a Stmt),
}
#[derive(Debug)]
pub struct Docstring<'a> {
pub kind: DocstringKind,
pub parent: Option<&'a Stmt>,
pub kind: DocstringKind<'a>,
pub expr: &'a Expr,
}
/// Extract a `Docstring` from an `Expr`.
pub fn extract<'a, 'b>(
checker: &'a Checker,
checker: &'a Checker<'b>,
stmt: &'b Stmt,
expr: &'b Expr,
) -> Option<Docstring<'b>> {
@ -33,7 +32,6 @@ pub fn extract<'a, 'b>(
if checker.initial {
return Some(Docstring {
kind: DocstringKind::Module,
parent: None,
expr,
});
}
@ -46,11 +44,10 @@ pub fn extract<'a, 'b>(
if body.first().map(|node| node == stmt).unwrap_or_default() {
return Some(Docstring {
kind: if matches!(&parent.node, StmtKind::ClassDef { .. }) {
DocstringKind::Class
DocstringKind::Class(parent)
} else {
DocstringKind::Function
DocstringKind::Function(parent)
},
parent: None,
expr,
});
}
@ -257,9 +254,28 @@ pub fn ends_with_period(checker: &mut Checker, docstring: &Docstring) {
}
}
/// D402
pub fn no_signature(checker: &mut Checker, docstring: &Docstring) {
if let DocstringKind::Function(parent) = docstring.kind {
if let StmtKind::FunctionDef { name, .. } = &parent.node {
if let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.expr.node
{
if let Some(first_line) = string.lines().next() {
if first_line.contains(&format!("{name}(")) {
checker.add_check(Check::new(CheckKind::NoSignature, range_for(docstring)));
}
}
}
}
}
}
/// D403
pub fn capitalized(checker: &mut Checker, docstring: &Docstring) {
if !matches!(docstring.kind, DocstringKind::Function) {
if !matches!(docstring.kind, DocstringKind::Function(_)) {
return;
}

View file

@ -1104,6 +1104,18 @@ mod tests {
Ok(())
}
#[test]
fn d402() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/D.py"),
&settings::Settings::for_rule(CheckCode::D402),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn d403() -> Result<()> {
let mut checks = check_path(

View file

@ -0,0 +1,13 @@
---
source: src/linter.rs
expression: checks
---
- kind: NoSignature
location:
row: 373
column: 5
end_location:
row: 373
column: 31
fix: ~