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-print`](https://pypi.org/project/flake8-print/)
|
||||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/) (11/16)
|
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/) (11/16)
|
||||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (3/32)
|
- [`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)
|
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (8/34)
|
||||||
|
|
||||||
Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis Flake8:
|
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 | | |
|
| D213 | MultiLineSummarySecondLine | Multi-line docstring summary should start at the second line | | |
|
||||||
| D300 | UsesTripleQuotes | Use """triple double quotes""" | | |
|
| D300 | UsesTripleQuotes | Use """triple double quotes""" | | |
|
||||||
| D400 | EndsInPeriod | First line should end with a period | | |
|
| 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 | | |
|
| 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 | | |
|
| D415 | EndsInPunctuation | First line should end with a period, question mark, or exclamation point | | |
|
||||||
| D419 | NonEmpty | Docstring is empty | | |
|
| D419 | NonEmpty | Docstring is empty | | |
|
||||||
|
|
|
@ -1916,6 +1916,9 @@ impl<'a> Checker<'a> {
|
||||||
if self.settings.enabled.contains(&CheckCode::D400) {
|
if self.settings.enabled.contains(&CheckCode::D400) {
|
||||||
docstrings::ends_with_period(self, &docstring);
|
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) {
|
if self.settings.enabled.contains(&CheckCode::D403) {
|
||||||
docstrings::capitalized(self, &docstring);
|
docstrings::capitalized(self, &docstring);
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,6 +159,7 @@ pub enum CheckCode {
|
||||||
D213,
|
D213,
|
||||||
D300,
|
D300,
|
||||||
D400,
|
D400,
|
||||||
|
D402,
|
||||||
D403,
|
D403,
|
||||||
D415,
|
D415,
|
||||||
D419,
|
D419,
|
||||||
|
@ -270,6 +271,7 @@ pub enum CheckKind {
|
||||||
NoSurroundingWhitespace,
|
NoSurroundingWhitespace,
|
||||||
NonEmpty,
|
NonEmpty,
|
||||||
UsesTripleQuotes,
|
UsesTripleQuotes,
|
||||||
|
NoSignature,
|
||||||
// Meta
|
// Meta
|
||||||
UnusedNOQA(Option<Vec<String>>),
|
UnusedNOQA(Option<Vec<String>>),
|
||||||
}
|
}
|
||||||
|
@ -388,6 +390,7 @@ impl CheckCode {
|
||||||
CheckCode::D212 => CheckKind::MultiLineSummaryFirstLine,
|
CheckCode::D212 => CheckKind::MultiLineSummaryFirstLine,
|
||||||
CheckCode::D213 => CheckKind::MultiLineSummarySecondLine,
|
CheckCode::D213 => CheckKind::MultiLineSummarySecondLine,
|
||||||
CheckCode::D300 => CheckKind::UsesTripleQuotes,
|
CheckCode::D300 => CheckKind::UsesTripleQuotes,
|
||||||
|
CheckCode::D402 => CheckKind::NoSignature,
|
||||||
CheckCode::D403 => CheckKind::FirstLineCapitalized,
|
CheckCode::D403 => CheckKind::FirstLineCapitalized,
|
||||||
CheckCode::D415 => CheckKind::EndsInPunctuation,
|
CheckCode::D415 => CheckKind::EndsInPunctuation,
|
||||||
// Meta
|
// Meta
|
||||||
|
@ -487,6 +490,7 @@ impl CheckKind {
|
||||||
CheckKind::MultiLineSummaryFirstLine => &CheckCode::D212,
|
CheckKind::MultiLineSummaryFirstLine => &CheckCode::D212,
|
||||||
CheckKind::MultiLineSummarySecondLine => &CheckCode::D213,
|
CheckKind::MultiLineSummarySecondLine => &CheckCode::D213,
|
||||||
CheckKind::UsesTripleQuotes => &CheckCode::D300,
|
CheckKind::UsesTripleQuotes => &CheckCode::D300,
|
||||||
|
CheckKind::NoSignature => &CheckCode::D402,
|
||||||
CheckKind::FirstLineCapitalized => &CheckCode::D403,
|
CheckKind::FirstLineCapitalized => &CheckCode::D403,
|
||||||
CheckKind::EndsInPunctuation => &CheckCode::D415,
|
CheckKind::EndsInPunctuation => &CheckCode::D415,
|
||||||
// Meta
|
// Meta
|
||||||
|
@ -759,6 +763,9 @@ impl CheckKind {
|
||||||
CheckKind::MultiLineSummarySecondLine => {
|
CheckKind::MultiLineSummarySecondLine => {
|
||||||
"Multi-line docstring summary should start at the second line".to_string()
|
"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
|
// Meta
|
||||||
CheckKind::UnusedNOQA(codes) => match codes {
|
CheckKind::UnusedNOQA(codes) => match codes {
|
||||||
None => "Unused `noqa` directive".to_string(),
|
None => "Unused `noqa` directive".to_string(),
|
||||||
|
|
|
@ -5,22 +5,21 @@ use crate::check_ast::Checker;
|
||||||
use crate::checks::{Check, CheckCode, CheckKind};
|
use crate::checks::{Check, CheckCode, CheckKind};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum DocstringKind {
|
pub enum DocstringKind<'a> {
|
||||||
Module,
|
Module,
|
||||||
Function,
|
Function(&'a Stmt),
|
||||||
Class,
|
Class(&'a Stmt),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Docstring<'a> {
|
pub struct Docstring<'a> {
|
||||||
pub kind: DocstringKind,
|
pub kind: DocstringKind<'a>,
|
||||||
pub parent: Option<&'a Stmt>,
|
|
||||||
pub expr: &'a Expr,
|
pub expr: &'a Expr,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract a `Docstring` from an `Expr`.
|
/// Extract a `Docstring` from an `Expr`.
|
||||||
pub fn extract<'a, 'b>(
|
pub fn extract<'a, 'b>(
|
||||||
checker: &'a Checker,
|
checker: &'a Checker<'b>,
|
||||||
stmt: &'b Stmt,
|
stmt: &'b Stmt,
|
||||||
expr: &'b Expr,
|
expr: &'b Expr,
|
||||||
) -> Option<Docstring<'b>> {
|
) -> Option<Docstring<'b>> {
|
||||||
|
@ -33,7 +32,6 @@ pub fn extract<'a, 'b>(
|
||||||
if checker.initial {
|
if checker.initial {
|
||||||
return Some(Docstring {
|
return Some(Docstring {
|
||||||
kind: DocstringKind::Module,
|
kind: DocstringKind::Module,
|
||||||
parent: None,
|
|
||||||
expr,
|
expr,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -46,11 +44,10 @@ pub fn extract<'a, 'b>(
|
||||||
if body.first().map(|node| node == stmt).unwrap_or_default() {
|
if body.first().map(|node| node == stmt).unwrap_or_default() {
|
||||||
return Some(Docstring {
|
return Some(Docstring {
|
||||||
kind: if matches!(&parent.node, StmtKind::ClassDef { .. }) {
|
kind: if matches!(&parent.node, StmtKind::ClassDef { .. }) {
|
||||||
DocstringKind::Class
|
DocstringKind::Class(parent)
|
||||||
} else {
|
} else {
|
||||||
DocstringKind::Function
|
DocstringKind::Function(parent)
|
||||||
},
|
},
|
||||||
parent: None,
|
|
||||||
expr,
|
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
|
/// D403
|
||||||
pub fn capitalized(checker: &mut Checker, docstring: &Docstring) {
|
pub fn capitalized(checker: &mut Checker, docstring: &Docstring) {
|
||||||
if !matches!(docstring.kind, DocstringKind::Function) {
|
if !matches!(docstring.kind, DocstringKind::Function(_)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1104,6 +1104,18 @@ mod tests {
|
||||||
Ok(())
|
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]
|
#[test]
|
||||||
fn d403() -> Result<()> {
|
fn d403() -> Result<()> {
|
||||||
let mut checks = check_path(
|
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