mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-01 22:31:47 +00:00
Implement D206, D207, and D208 (#429)
This commit is contained in:
parent
6a8e31b2ff
commit
3c15c578a7
8 changed files with 179 additions and 1 deletions
|
@ -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 | | |
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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")]
|
||||||
|
|
6
src/snapshots/ruff__linter__tests__D206_D.py.snap
Normal file
6
src/snapshots/ruff__linter__tests__D206_D.py.snap
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
source: src/linter.rs
|
||||||
|
expression: checks
|
||||||
|
---
|
||||||
|
[]
|
||||||
|
|
29
src/snapshots/ruff__linter__tests__D207_D.py.snap
Normal file
29
src/snapshots/ruff__linter__tests__D207_D.py.snap
Normal 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: ~
|
||||||
|
|
29
src/snapshots/ruff__linter__tests__D208_D.py.snap
Normal file
29
src/snapshots/ruff__linter__tests__D208_D.py.snap
Normal 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: ~
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue