Implement D410 (EmptyDocstring) (#395)

This commit is contained in:
Charlie Marsh 2022-10-10 15:15:38 -04:00 committed by GitHub
parent 323a5c857c
commit 71d3a84b14
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 95 additions and 4 deletions

13
resources/test/fixtures/D419.py vendored Normal file
View file

@ -0,0 +1,13 @@
""""""
def f1() -> None:
""""""
def f2() -> None:
""
def f3() -> None:
"""Hello, world!"""

View file

@ -1888,6 +1888,12 @@ impl<'a> Checker<'a> {
} }
} }
fn check_docstrings(&mut self) {
while let Some(docstring) = self.docstrings.pop() {
docstrings::docstring_empty(self, &docstring);
}
}
fn check_builtin_shadowing(&mut self, name: &str, location: Range, is_attribute: bool) { fn check_builtin_shadowing(&mut self, name: &str, location: Range, is_attribute: bool) {
let scope = self.current_scope(); let scope = self.current_scope();
@ -1955,5 +1961,8 @@ pub fn check_ast(
checker.pop_scope(); checker.pop_scope();
checker.check_dead_scopes(); checker.check_dead_scopes();
// Check docstrings.
checker.check_docstrings();
checker.checks checker.checks
} }

View file

@ -150,6 +150,8 @@ pub enum CheckCode {
U006, U006,
U007, U007,
U008, U008,
// pydocstyle
D419,
// Meta // Meta
M001, M001,
} }
@ -246,6 +248,8 @@ pub enum CheckKind {
UsePEP585Annotation(String), UsePEP585Annotation(String),
UsePEP604Annotation, UsePEP604Annotation,
SuperCallWithParameters, SuperCallWithParameters,
// pydocstyle
EmptyDocstring,
// Meta // Meta
UnusedNOQA(Option<Vec<String>>), UnusedNOQA(Option<Vec<String>>),
} }
@ -354,6 +358,8 @@ impl CheckCode {
CheckCode::U006 => CheckKind::UsePEP585Annotation("List".to_string()), CheckCode::U006 => CheckKind::UsePEP585Annotation("List".to_string()),
CheckCode::U007 => CheckKind::UsePEP604Annotation, CheckCode::U007 => CheckKind::UsePEP604Annotation,
CheckCode::U008 => CheckKind::SuperCallWithParameters, CheckCode::U008 => CheckKind::SuperCallWithParameters,
// pydocstyle
CheckCode::D419 => CheckKind::EmptyDocstring,
// Meta // Meta
CheckCode::M001 => CheckKind::UnusedNOQA(None), CheckCode::M001 => CheckKind::UnusedNOQA(None),
} }
@ -441,6 +447,8 @@ impl CheckKind {
CheckKind::UsePEP604Annotation => &CheckCode::U007, CheckKind::UsePEP604Annotation => &CheckCode::U007,
CheckKind::UselessObjectInheritance(_) => &CheckCode::U004, CheckKind::UselessObjectInheritance(_) => &CheckCode::U004,
CheckKind::SuperCallWithParameters => &CheckCode::U008, CheckKind::SuperCallWithParameters => &CheckCode::U008,
// pydocstyle
CheckKind::EmptyDocstring => &CheckCode::D419,
// Meta // Meta
CheckKind::UnusedNOQA(_) => &CheckCode::M001, CheckKind::UnusedNOQA(_) => &CheckCode::M001,
} }
@ -684,6 +692,8 @@ impl CheckKind {
CheckKind::SuperCallWithParameters => { CheckKind::SuperCallWithParameters => {
"Use `super()` instead of `super(__class__, self)`".to_string() "Use `super()` instead of `super(__class__, self)`".to_string()
} }
// pydocstyle
CheckKind::EmptyDocstring => "Docstring is empty".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(),

View file

@ -1,5 +1,7 @@
use crate::ast::types::Range;
use crate::check_ast::Checker; use crate::check_ast::Checker;
use rustpython_ast::{Expr, Stmt, StmtKind}; use crate::checks::{Check, CheckKind};
use rustpython_ast::{Constant, Expr, ExprKind, Stmt, StmtKind};
#[derive(Debug)] #[derive(Debug)]
pub enum DocstringKind { pub enum DocstringKind {
@ -12,7 +14,7 @@ pub enum DocstringKind {
pub struct Docstring<'a> { pub struct Docstring<'a> {
pub kind: DocstringKind, pub kind: DocstringKind,
pub parent: Option<&'a Stmt>, pub parent: Option<&'a Stmt>,
pub node: &'a Expr, pub expr: &'a Expr,
} }
/// Extract a docstring from an expression. /// Extract a docstring from an expression.
@ -32,7 +34,7 @@ pub fn extract<'a, 'b>(
return Some(Docstring { return Some(Docstring {
kind: DocstringKind::Module, kind: DocstringKind::Module,
parent: None, parent: None,
node: expr, expr,
}); });
} }
} }
@ -49,7 +51,7 @@ pub fn extract<'a, 'b>(
DocstringKind::Function DocstringKind::Function
}, },
parent: None, parent: None,
node: expr, expr,
}); });
} }
} }
@ -58,3 +60,19 @@ pub fn extract<'a, 'b>(
None None
} }
pub fn docstring_empty(checker: &mut Checker, docstring: &Docstring) {
// Extract the source.
if let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.expr.node
{
if string.trim().is_empty() {
checker.add_check(Check::new(
CheckKind::EmptyDocstring,
Range::from_located(docstring.expr),
));
}
}
}

View file

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

View file

@ -0,0 +1,29 @@
---
source: src/linter.rs
expression: checks
---
- kind: EmptyDocstring
location:
row: 1
column: 2
end_location:
row: 1
column: 7
fix: ~
- kind: EmptyDocstring
location:
row: 5
column: 6
end_location:
row: 5
column: 11
fix: ~
- kind: EmptyDocstring
location:
row: 9
column: 6
end_location:
row: 9
column: 7
fix: ~