Implement D206, D207, and D208 (#429)

This commit is contained in:
Charlie Marsh 2022-10-14 13:26:36 -04:00 committed by GitHub
parent 6a8e31b2ff
commit 3c15c578a7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 179 additions and 1 deletions

View file

@ -216,12 +216,13 @@ variables.)
ruff also implements some of the most popular Flake8 plugins natively, including: ruff also implements some of the most popular Flake8 plugins natively, including:
- [`pydocstyle`](https://pypi.org/project/pydocstyle/)
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
- [`flake8-builtins`](https://pypi.org/project/flake8-builtins/) - [`flake8-builtins`](https://pypi.org/project/flake8-builtins/)
- [`flake8-super`](https://pypi.org/project/flake8-super/) - [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-print`](https://pypi.org/project/flake8-print/) - [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/) - [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
- [`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/) (41/48)
- [`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:
@ -327,6 +328,9 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
| D203 | OneBlankLineBeforeClass | 1 blank line required before class docstring | | | | D203 | OneBlankLineBeforeClass | 1 blank line required before class docstring | | |
| D204 | OneBlankLineAfterClass | 1 blank line required after class docstring | | | | D204 | OneBlankLineAfterClass | 1 blank line required after class docstring | | |
| D205 | NoBlankLineAfterSummary | 1 blank line required between summary line and description | | | | D205 | NoBlankLineAfterSummary | 1 blank line required between summary line and description | | |
| D206 | IndentWithSpaces | Docstring should be indented with spaces, not tabs | | |
| D207 | NoUnderIndentation | Docstring is under-indented | | |
| D208 | NoOverIndentation | Docstring is over-indented | | |
| D209 | NewLineAfterLastParagraph | Multi-line docstring closing quotes should be on a separate line | | | | D209 | NewLineAfterLastParagraph | Multi-line docstring closing quotes should be on a separate line | | |
| D210 | NoSurroundingWhitespace | No whitespaces allowed surrounding docstring text | | | | D210 | NoSurroundingWhitespace | No whitespaces allowed surrounding docstring text | | |
| D211 | NoBlankLineBeforeClass | No blank lines allowed before class docstring | | | | D211 | NoBlankLineBeforeClass | No blank lines allowed before class docstring | | |

View file

@ -1969,6 +1969,12 @@ impl<'a> Checker<'a> {
if self.settings.enabled.contains(&CheckCode::D205) { if self.settings.enabled.contains(&CheckCode::D205) {
docstring_plugins::blank_after_summary(self, &docstring); docstring_plugins::blank_after_summary(self, &docstring);
} }
if self.settings.enabled.contains(&CheckCode::D206)
|| self.settings.enabled.contains(&CheckCode::D207)
|| self.settings.enabled.contains(&CheckCode::D208)
{
docstring_plugins::indent(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D209) { if self.settings.enabled.contains(&CheckCode::D209) {
docstring_plugins::newline_after_last_paragraph(self, &docstring); docstring_plugins::newline_after_last_paragraph(self, &docstring);
} }

View file

@ -170,6 +170,9 @@ pub enum CheckCode {
D203, D203,
D204, D204,
D205, D205,
D206,
D207,
D208,
D209, D209,
D210, D210,
D211, D211,
@ -309,6 +312,7 @@ pub enum CheckKind {
EndsInPunctuation, EndsInPunctuation,
FirstLineCapitalized, FirstLineCapitalized,
FitsOnOneLine, FitsOnOneLine,
IndentWithSpaces,
MagicMethod, MagicMethod,
MultiLineSummaryFirstLine, MultiLineSummaryFirstLine,
MultiLineSummarySecondLine, MultiLineSummarySecondLine,
@ -319,9 +323,11 @@ pub enum CheckKind {
NoBlankLineBeforeClass(usize), NoBlankLineBeforeClass(usize),
NoBlankLineBeforeFunction(usize), NoBlankLineBeforeFunction(usize),
NoBlankLinesBetweenHeaderAndContent(String), NoBlankLinesBetweenHeaderAndContent(String),
NoOverIndentation,
NoSignature, NoSignature,
NoSurroundingWhitespace, NoSurroundingWhitespace,
NoThisPrefix, NoThisPrefix,
NoUnderIndentation,
NonEmpty, NonEmpty,
NonEmptySection(String), NonEmptySection(String),
OneBlankLineAfterClass(usize), OneBlankLineAfterClass(usize),
@ -473,6 +479,9 @@ impl CheckCode {
CheckCode::D203 => CheckKind::OneBlankLineBeforeClass(0), CheckCode::D203 => CheckKind::OneBlankLineBeforeClass(0),
CheckCode::D204 => CheckKind::OneBlankLineAfterClass(0), CheckCode::D204 => CheckKind::OneBlankLineAfterClass(0),
CheckCode::D205 => CheckKind::NoBlankLineAfterSummary, CheckCode::D205 => CheckKind::NoBlankLineAfterSummary,
CheckCode::D206 => CheckKind::IndentWithSpaces,
CheckCode::D207 => CheckKind::NoUnderIndentation,
CheckCode::D208 => CheckKind::NoOverIndentation,
CheckCode::D209 => CheckKind::NewLineAfterLastParagraph, CheckCode::D209 => CheckKind::NewLineAfterLastParagraph,
CheckCode::D210 => CheckKind::NoSurroundingWhitespace, CheckCode::D210 => CheckKind::NoSurroundingWhitespace,
CheckCode::D211 => CheckKind::NoBlankLineBeforeClass(1), CheckCode::D211 => CheckKind::NoBlankLineBeforeClass(1),
@ -609,6 +618,7 @@ impl CheckKind {
CheckKind::EndsInPunctuation => &CheckCode::D415, CheckKind::EndsInPunctuation => &CheckCode::D415,
CheckKind::FirstLineCapitalized => &CheckCode::D403, CheckKind::FirstLineCapitalized => &CheckCode::D403,
CheckKind::FitsOnOneLine => &CheckCode::D200, CheckKind::FitsOnOneLine => &CheckCode::D200,
CheckKind::IndentWithSpaces => &CheckCode::D206,
CheckKind::MagicMethod => &CheckCode::D105, CheckKind::MagicMethod => &CheckCode::D105,
CheckKind::MultiLineSummaryFirstLine => &CheckCode::D212, CheckKind::MultiLineSummaryFirstLine => &CheckCode::D212,
CheckKind::MultiLineSummarySecondLine => &CheckCode::D213, CheckKind::MultiLineSummarySecondLine => &CheckCode::D213,
@ -619,9 +629,11 @@ impl CheckKind {
CheckKind::NoBlankLineBeforeClass(_) => &CheckCode::D211, CheckKind::NoBlankLineBeforeClass(_) => &CheckCode::D211,
CheckKind::NoBlankLineBeforeFunction(_) => &CheckCode::D201, CheckKind::NoBlankLineBeforeFunction(_) => &CheckCode::D201,
CheckKind::NoBlankLinesBetweenHeaderAndContent(_) => &CheckCode::D412, CheckKind::NoBlankLinesBetweenHeaderAndContent(_) => &CheckCode::D412,
CheckKind::NoOverIndentation => &CheckCode::D208,
CheckKind::NoSignature => &CheckCode::D402, CheckKind::NoSignature => &CheckCode::D402,
CheckKind::NoSurroundingWhitespace => &CheckCode::D210, CheckKind::NoSurroundingWhitespace => &CheckCode::D210,
CheckKind::NoThisPrefix => &CheckCode::D404, CheckKind::NoThisPrefix => &CheckCode::D404,
CheckKind::NoUnderIndentation => &CheckCode::D207,
CheckKind::NonEmpty => &CheckCode::D419, CheckKind::NonEmpty => &CheckCode::D419,
CheckKind::NonEmptySection(_) => &CheckCode::D414, CheckKind::NonEmptySection(_) => &CheckCode::D414,
CheckKind::OneBlankLineAfterClass(_) => &CheckCode::D204, CheckKind::OneBlankLineAfterClass(_) => &CheckCode::D204,
@ -1009,6 +1021,11 @@ impl CheckKind {
format!("Missing argument descriptions in the docstring: {names}") format!("Missing argument descriptions in the docstring: {names}")
} }
} }
CheckKind::IndentWithSpaces => {
"Docstring should be indented with spaces, not tabs".to_string()
}
CheckKind::NoUnderIndentation => "Docstring is under-indented".to_string(),
CheckKind::NoOverIndentation => "Docstring is over-indented".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

@ -9,6 +9,7 @@ use crate::check_ast::Checker;
use crate::checks::{Check, CheckCode, CheckKind}; use crate::checks::{Check, CheckCode, CheckKind};
use crate::docstrings::google::check_google_section; use crate::docstrings::google::check_google_section;
use crate::docstrings::helpers; use crate::docstrings::helpers;
use crate::docstrings::helpers::{indentation, leading_space};
use crate::docstrings::numpy::check_numpy_section; use crate::docstrings::numpy::check_numpy_section;
use crate::docstrings::sections::section_contexts; use crate::docstrings::sections::section_contexts;
use crate::docstrings::styles::SectionStyle; use crate::docstrings::styles::SectionStyle;
@ -303,6 +304,89 @@ pub fn blank_after_summary(checker: &mut Checker, definition: &Definition) {
} }
} }
/// D206, D207, D208
pub fn indent(checker: &mut Checker, definition: &Definition) {
if let Some(docstring) = definition.docstring {
if let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.node
{
let lines: Vec<&str> = string.lines().collect();
if lines.len() <= 1 {
return;
}
let mut has_seen_tab = false;
let mut has_seen_over_indent = false;
let mut has_seen_under_indent = false;
let docstring_indent = indentation(checker, docstring).to_string();
if !has_seen_tab {
if docstring_indent.contains('\t') {
if checker.settings.enabled.contains(&CheckCode::D206) {
checker.add_check(Check::new(
CheckKind::IndentWithSpaces,
helpers::range_for(docstring),
));
}
has_seen_tab = true;
}
}
for i in 0..lines.len() {
// First lines and continuations doesn't need any indentation.
if i == 0 || lines[i - 1].ends_with('\\') {
continue;
}
// Omit empty lines, except for the last line, which is non-empty by way of
// containing the closing quotation marks.
if i < lines.len() - 1 && lines[i].trim().is_empty() {
continue;
}
let line_indent = leading_space(lines[i]);
if !has_seen_tab {
if line_indent.contains('\t') {
if checker.settings.enabled.contains(&CheckCode::D206) {
checker.add_check(Check::new(
CheckKind::IndentWithSpaces,
helpers::range_for(docstring),
));
}
has_seen_tab = true;
}
}
if !has_seen_over_indent {
if line_indent.len() > docstring_indent.len() {
if checker.settings.enabled.contains(&CheckCode::D208) {
checker.add_check(Check::new(
CheckKind::NoOverIndentation,
helpers::range_for(docstring),
));
}
has_seen_over_indent = true;
}
}
if !has_seen_under_indent {
if line_indent.len() < docstring_indent.len() {
if checker.settings.enabled.contains(&CheckCode::D207) {
checker.add_check(Check::new(
CheckKind::NoUnderIndentation,
helpers::range_for(docstring),
));
}
has_seen_under_indent = true;
}
}
}
}
}
}
/// D209 /// D209
pub fn newline_after_last_paragraph(checker: &mut Checker, definition: &Definition) { pub fn newline_after_last_paragraph(checker: &mut Checker, definition: &Definition) {
if let Some(docstring) = definition.docstring { if let Some(docstring) = definition.docstring {

View file

@ -273,6 +273,9 @@ mod tests {
#[test_case(CheckCode::D203, Path::new("D.py"); "D203")] #[test_case(CheckCode::D203, Path::new("D.py"); "D203")]
#[test_case(CheckCode::D204, Path::new("D.py"); "D204")] #[test_case(CheckCode::D204, Path::new("D.py"); "D204")]
#[test_case(CheckCode::D205, Path::new("D.py"); "D205")] #[test_case(CheckCode::D205, Path::new("D.py"); "D205")]
#[test_case(CheckCode::D206, Path::new("D.py"); "D206")]
#[test_case(CheckCode::D207, Path::new("D.py"); "D207")]
#[test_case(CheckCode::D208, Path::new("D.py"); "D208")]
#[test_case(CheckCode::D209, Path::new("D.py"); "D209")] #[test_case(CheckCode::D209, Path::new("D.py"); "D209")]
#[test_case(CheckCode::D210, Path::new("D.py"); "D210")] #[test_case(CheckCode::D210, Path::new("D.py"); "D210")]
#[test_case(CheckCode::D211, Path::new("D.py"); "D211")] #[test_case(CheckCode::D211, Path::new("D.py"); "D211")]

View file

@ -0,0 +1,6 @@
---
source: src/linter.rs
expression: checks
---
[]

View file

@ -0,0 +1,29 @@
---
source: src/linter.rs
expression: checks
---
- kind: NoUnderIndentation
location:
row: 225
column: 5
end_location:
row: 229
column: 8
fix: ~
- kind: NoUnderIndentation
location:
row: 235
column: 5
end_location:
row: 239
column: 4
fix: ~
- kind: NoUnderIndentation
location:
row: 433
column: 37
end_location:
row: 436
column: 8
fix: ~

View file

@ -0,0 +1,29 @@
---
source: src/linter.rs
expression: checks
---
- kind: NoOverIndentation
location:
row: 245
column: 5
end_location:
row: 249
column: 8
fix: ~
- kind: NoOverIndentation
location:
row: 255
column: 5
end_location:
row: 259
column: 12
fix: ~
- kind: NoOverIndentation
location:
row: 265
column: 5
end_location:
row: 269
column: 8
fix: ~