Enable autofix for `FitsOnOneLine` (`D200`) (#2006)

Closes #1965.
This commit is contained in:
Charlie Marsh 2023-01-19 19:24:50 -05:00 committed by GitHub
parent de54ff114e
commit f6a93a4c3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 124 additions and 12 deletions

View File

@ -653,7 +653,7 @@ For more, see [pydocstyle](https://pypi.org/project/pydocstyle/6.1.1/) on PyPI.
| D105 | MagicMethod | Missing docstring in magic method | |
| D106 | PublicNestedClass | Missing docstring in public nested class | |
| D107 | PublicInit | Missing docstring in `__init__` | |
| D200 | FitsOnOneLine | One-line docstring should fit on one line | |
| D200 | FitsOnOneLine | One-line docstring should fit on one line | 🛠 |
| D201 | NoBlankLineBeforeFunction | No blank lines allowed before function docstring (found {num_lines}) | 🛠 |
| D202 | NoBlankLineAfterFunction | No blank lines allowed after function docstring (found {num_lines}) | 🛠 |
| D203 | OneBlankLineBeforeClass | 1 blank line required before class docstring | 🛠 |

View File

@ -588,3 +588,21 @@ def asdfljdjgf24():
"""Summary.
Description. """
@expect('D200: One-line docstring should fit on one line with quotes '
'(found 3)')
@expect('D212: Multi-line docstring summary should start at the first line')
def one_liner():
"""
Wrong."""
@expect('D200: One-line docstring should fit on one line with quotes '
'(found 3)')
@expect('D212: Multi-line docstring summary should start at the first line')
def one_liner():
r"""Wrong.
"""

View File

@ -4560,18 +4560,18 @@ impl<'a> Checker<'a> {
// Extract a `Docstring` from a `Definition`.
let expr = definition.docstring.unwrap();
let content = self
let contents = self
.locator
.slice_source_code_range(&Range::from_located(expr));
let indentation = self.locator.slice_source_code_range(&Range::new(
Location::new(expr.location.row(), 0),
Location::new(expr.location.row(), expr.location.column()),
));
let body = pydocstyle::helpers::raw_contents(&content);
let body = pydocstyle::helpers::raw_contents(&contents);
let docstring = Docstring {
kind: definition.kind,
expr,
contents: &content,
contents: &contents,
indentation: &indentation,
body,
};

View File

@ -24,6 +24,7 @@ mod tests {
#[test_case(Rule::MagicMethod, Path::new("D.py"); "D105")]
#[test_case(Rule::PublicNestedClass, Path::new("D.py"); "D106")]
#[test_case(Rule::PublicInit, Path::new("D.py"); "D107")]
#[test_case(Rule::FitsOnOneLine, Path::new("D.py"); "D200")]
#[test_case(Rule::NoBlankLineBeforeFunction, Path::new("D.py"); "D201")]
#[test_case(Rule::NoBlankLineAfterFunction, Path::new("D.py"); "D202")]
#[test_case(Rule::OneBlankLineBeforeClass, Path::new("D.py"); "D203")]

View File

@ -2,29 +2,42 @@ use crate::ast::types::Range;
use crate::ast::whitespace::LinesWithTrailingNewline;
use crate::checkers::ast::Checker;
use crate::docstrings::definition::Docstring;
use crate::registry::Diagnostic;
use crate::fix::Fix;
use crate::registry::{Diagnostic, Rule};
use crate::rules::pydocstyle::helpers;
use crate::violations;
/// D200
pub fn one_liner(checker: &mut Checker, docstring: &Docstring) {
let body = docstring.body;
let mut line_count = 0;
let mut non_empty_line_count = 0;
for line in LinesWithTrailingNewline::from(body) {
for line in LinesWithTrailingNewline::from(docstring.body) {
line_count += 1;
if !line.trim().is_empty() {
non_empty_line_count += 1;
}
if non_empty_line_count > 1 {
break;
return;
}
}
if non_empty_line_count == 1 && line_count > 1 {
checker.diagnostics.push(Diagnostic::new(
let mut diagnostic = Diagnostic::new(
violations::FitsOnOneLine,
Range::from_located(docstring.expr),
));
);
if checker.patch(&Rule::FitsOnOneLine) {
if let (Some(leading), Some(trailing)) = (
helpers::leading_quote(docstring.contents),
helpers::trailing_quote(docstring.contents),
) {
diagnostic.amend(Fix::replacement(
format!("{leading}{}{trailing}", docstring.body.trim()),
docstring.expr.location,
docstring.expr.end_location.unwrap(),
));
}
}
checker.diagnostics.push(diagnostic);
}
}

View File

@ -0,0 +1,56 @@
---
source: src/rules/pydocstyle/mod.rs
expression: diagnostics
---
- kind:
FitsOnOneLine: ~
location:
row: 129
column: 4
end_location:
row: 131
column: 7
fix:
content: "\"\"\"Wrong.\"\"\""
location:
row: 129
column: 4
end_location:
row: 131
column: 7
parent: ~
- kind:
FitsOnOneLine: ~
location:
row: 597
column: 4
end_location:
row: 599
column: 13
fix:
content: "\"\"\"Wrong.\"\"\""
location:
row: 597
column: 4
end_location:
row: 599
column: 13
parent: ~
- kind:
FitsOnOneLine: ~
location:
row: 606
column: 4
end_location:
row: 608
column: 7
fix:
content: "r\"\"\"Wrong.\"\"\""
location:
row: 606
column: 4
end_location:
row: 608
column: 7
parent: ~

View File

@ -12,4 +12,14 @@ expression: diagnostics
column: 7
fix: ~
parent: ~
- kind:
MultiLineSummaryFirstLine: ~
location:
row: 597
column: 4
end_location:
row: 599
column: 13
fix: ~
parent: ~

View File

@ -202,4 +202,14 @@ expression: diagnostics
column: 21
fix: ~
parent: ~
- kind:
MultiLineSummarySecondLine: ~
location:
row: 606
column: 4
end_location:
row: 608
column: 7
fix: ~
parent: ~

View File

@ -3265,11 +3265,15 @@ impl Violation for PublicInit {
define_violation!(
pub struct FitsOnOneLine;
);
impl Violation for FitsOnOneLine {
impl AlwaysAutofixableViolation for FitsOnOneLine {
#[derive_message_formats]
fn message(&self) -> String {
format!("One-line docstring should fit on one line")
}
fn autofix_title(&self) -> String {
"Reformat to one line".to_string()
}
}
define_violation!(