diff --git a/resources/test/fixtures/D419.py b/resources/test/fixtures/D419.py new file mode 100644 index 0000000000..7effbdf16f --- /dev/null +++ b/resources/test/fixtures/D419.py @@ -0,0 +1,13 @@ +"""""" + + +def f1() -> None: + """""" + + +def f2() -> None: + "" + + +def f3() -> None: + """Hello, world!""" diff --git a/src/check_ast.rs b/src/check_ast.rs index 85f3f07141..345b09511f 100644 --- a/src/check_ast.rs +++ b/src/check_ast.rs @@ -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) { let scope = self.current_scope(); @@ -1955,5 +1961,8 @@ pub fn check_ast( checker.pop_scope(); checker.check_dead_scopes(); + // Check docstrings. + checker.check_docstrings(); + checker.checks } diff --git a/src/checks.rs b/src/checks.rs index b0ca898458..7b5668408f 100644 --- a/src/checks.rs +++ b/src/checks.rs @@ -150,6 +150,8 @@ pub enum CheckCode { U006, U007, U008, + // pydocstyle + D419, // Meta M001, } @@ -246,6 +248,8 @@ pub enum CheckKind { UsePEP585Annotation(String), UsePEP604Annotation, SuperCallWithParameters, + // pydocstyle + EmptyDocstring, // Meta UnusedNOQA(Option>), } @@ -354,6 +358,8 @@ impl CheckCode { CheckCode::U006 => CheckKind::UsePEP585Annotation("List".to_string()), CheckCode::U007 => CheckKind::UsePEP604Annotation, CheckCode::U008 => CheckKind::SuperCallWithParameters, + // pydocstyle + CheckCode::D419 => CheckKind::EmptyDocstring, // Meta CheckCode::M001 => CheckKind::UnusedNOQA(None), } @@ -441,6 +447,8 @@ impl CheckKind { CheckKind::UsePEP604Annotation => &CheckCode::U007, CheckKind::UselessObjectInheritance(_) => &CheckCode::U004, CheckKind::SuperCallWithParameters => &CheckCode::U008, + // pydocstyle + CheckKind::EmptyDocstring => &CheckCode::D419, // Meta CheckKind::UnusedNOQA(_) => &CheckCode::M001, } @@ -684,6 +692,8 @@ impl CheckKind { CheckKind::SuperCallWithParameters => { "Use `super()` instead of `super(__class__, self)`".to_string() } + // pydocstyle + CheckKind::EmptyDocstring => "Docstring is empty".to_string(), // Meta CheckKind::UnusedNOQA(codes) => match codes { None => "Unused `noqa` directive".to_string(), diff --git a/src/docstrings.rs b/src/docstrings.rs index 482340e7fb..40aea47515 100644 --- a/src/docstrings.rs +++ b/src/docstrings.rs @@ -1,5 +1,7 @@ +use crate::ast::types::Range; 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)] pub enum DocstringKind { @@ -12,7 +14,7 @@ pub enum DocstringKind { pub struct Docstring<'a> { pub kind: DocstringKind, pub parent: Option<&'a Stmt>, - pub node: &'a Expr, + pub expr: &'a Expr, } /// Extract a docstring from an expression. @@ -32,7 +34,7 @@ pub fn extract<'a, 'b>( return Some(Docstring { kind: DocstringKind::Module, parent: None, - node: expr, + expr, }); } } @@ -49,7 +51,7 @@ pub fn extract<'a, 'b>( DocstringKind::Function }, parent: None, - node: expr, + expr, }); } } @@ -58,3 +60,19 @@ pub fn extract<'a, 'b>( 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), + )); + } + } +} diff --git a/src/linter.rs b/src/linter.rs index 9cb9989bc1..4c4e37d74e 100644 --- a/src/linter.rs +++ b/src/linter.rs @@ -978,6 +978,18 @@ mod tests { 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] fn u008() -> Result<()> { let mut checks = check_path( diff --git a/src/snapshots/ruff__linter__tests__d419.snap b/src/snapshots/ruff__linter__tests__d419.snap new file mode 100644 index 0000000000..b258ea38b1 --- /dev/null +++ b/src/snapshots/ruff__linter__tests__d419.snap @@ -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: ~ +