mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-01 14:21:53 +00:00
Implement D402 for pydocstyle (#403)
This commit is contained in:
parent
71802f8861
commit
8868f57a74
6 changed files with 64 additions and 12 deletions
|
@ -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 | | |
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
13
src/snapshots/ruff__linter__tests__d402.snap
Normal file
13
src/snapshots/ruff__linter__tests__d402.snap
Normal 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: ~
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue