Implement D400 (DocstringEndsInNonPeriod) (#396)

This commit is contained in:
Charlie Marsh 2022-10-10 15:35:23 -04:00 committed by GitHub
parent 8b66bbdc9b
commit 30877127bc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 115 additions and 4 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/) (1/47)
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/) (2/47)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (partial)
Beyond rule-set parity, ruff suffers from the following limitations vis-à-vis Flake8:
@ -304,6 +304,7 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
| U006 | UsePEP585Annotation | Use `list` instead of `List` for type annotations | | 🛠 |
| U007 | UsePEP604Annotation | Use `X \| Y` for type annotations | | 🛠 |
| U008 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` | | 🛠 |
| D400 | DocstringEndsInNonPeriod | First line should end with a period | | |
| D419 | EmptyDocstring | Docstring is empty | | |
| M001 | UnusedNOQA | Unused `noqa` directive | | 🛠 |

27
resources/test/fixtures/D400.py vendored Normal file
View file

@ -0,0 +1,27 @@
def f1() -> None:
"""Hello, world.
More content here.
"""
def f2() -> None:
"""Hello, world!
More content here.
"""
def f3() -> None:
"""Hello, world
More content here.
"""
def f4() -> None:
"""."""
def f5() -> None:
""""""

View file

@ -1890,7 +1890,12 @@ impl<'a> Checker<'a> {
fn check_docstrings(&mut self) {
while let Some(docstring) = self.docstrings.pop() {
docstrings::docstring_empty(self, &docstring);
if self.settings.enabled.contains(&CheckCode::D400) {
docstrings::ends_with_period(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D419) {
docstrings::not_empty(self, &docstring);
}
}
}

View file

@ -151,6 +151,7 @@ pub enum CheckCode {
U007,
U008,
// pydocstyle
D400,
D419,
// Meta
M001,
@ -250,6 +251,7 @@ pub enum CheckKind {
SuperCallWithParameters,
// pydocstyle
EmptyDocstring,
DocstringEndsInNonPeriod,
// Meta
UnusedNOQA(Option<Vec<String>>),
}
@ -359,6 +361,7 @@ impl CheckCode {
CheckCode::U007 => CheckKind::UsePEP604Annotation,
CheckCode::U008 => CheckKind::SuperCallWithParameters,
// pydocstyle
CheckCode::D400 => CheckKind::DocstringEndsInNonPeriod,
CheckCode::D419 => CheckKind::EmptyDocstring,
// Meta
CheckCode::M001 => CheckKind::UnusedNOQA(None),
@ -448,6 +451,7 @@ impl CheckKind {
CheckKind::UselessObjectInheritance(_) => &CheckCode::U004,
CheckKind::SuperCallWithParameters => &CheckCode::U008,
// pydocstyle
CheckKind::DocstringEndsInNonPeriod => &CheckCode::D400,
CheckKind::EmptyDocstring => &CheckCode::D419,
// Meta
CheckKind::UnusedNOQA(_) => &CheckCode::M001,
@ -693,6 +697,9 @@ impl CheckKind {
"Use `super()` instead of `super(__class__, self)`".to_string()
}
// pydocstyle
CheckKind::DocstringEndsInNonPeriod => {
"First line should end with a period".to_string()
}
CheckKind::EmptyDocstring => "Docstring is empty".to_string(),
// Meta
CheckKind::UnusedNOQA(codes) => match codes {

View file

@ -61,8 +61,7 @@ pub fn extract<'a, 'b>(
None
}
pub fn docstring_empty(checker: &mut Checker, docstring: &Docstring) {
// Extract the source.
pub fn not_empty(checker: &mut Checker, docstring: &Docstring) {
if let ExprKind::Constant {
value: Constant::Str(string),
..
@ -76,3 +75,20 @@ pub fn docstring_empty(checker: &mut Checker, docstring: &Docstring) {
}
}
}
pub fn ends_with_period(checker: &mut Checker, docstring: &Docstring) {
if let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.expr.node
{
if let Some(string) = string.lines().next() {
if !string.ends_with('.') {
checker.add_check(Check::new(
CheckKind::DocstringEndsInNonPeriod,
Range::from_located(docstring.expr),
));
}
}
}
}

View file

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

View file

@ -0,0 +1,21 @@
---
source: src/linter.rs
expression: checks
---
- kind: DocstringEndsInPeriod
location:
row: 9
column: 6
end_location:
row: 12
column: 8
fix: ~
- kind: DocstringEndsInPeriod
location:
row: 16
column: 6
end_location:
row: 19
column: 8
fix: ~

View file

@ -0,0 +1,22 @@
---
source: src/linter.rs
assertion_line: 989
expression: checks
---
- kind: DocstringEndsInNonPeriod
location:
row: 9
column: 6
end_location:
row: 12
column: 8
fix: ~
- kind: DocstringEndsInNonPeriod
location:
row: 16
column: 6
end_location:
row: 19
column: 8
fix: ~