Enable autofix for over- and under-indented docstrings (#451)

This commit is contained in:
Charlie Marsh 2022-10-17 21:43:38 -04:00 committed by GitHub
parent f832f88c75
commit 36fe8b76d4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 230 additions and 91 deletions

View file

@ -296,8 +296,8 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
| D204 | OneBlankLineAfterClass | 1 blank line required after class docstring | 🛠 | | D204 | OneBlankLineAfterClass | 1 blank line required after class docstring | 🛠 |
| D205 | BlankLineAfterSummary | 1 blank line required between summary line and description | 🛠 | | D205 | BlankLineAfterSummary | 1 blank line required between summary line and description | 🛠 |
| D206 | IndentWithSpaces | Docstring should be indented with spaces, not tabs | | | D206 | IndentWithSpaces | Docstring should be indented with spaces, not tabs | |
| D207 | NoUnderIndentation | Docstring is under-indented | | | D207 | NoUnderIndentation | Docstring is under-indented | 🛠 |
| D208 | NoOverIndentation | Docstring is over-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 | 🛠 |
@ -399,7 +399,6 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
| ---- | ---- | ------- | --- | | ---- | ---- | ------- | --- |
| M001 | UnusedNOQA | Unused `noqa` directive | 🛠 | | M001 | UnusedNOQA | Unused `noqa` directive | 🛠 |
## Editor Integrations ## Editor Integrations
### PyCharm ### PyCharm

View file

@ -1219,7 +1219,9 @@ impl CheckKind {
| CheckKind::NoBlankLineBeforeClass(_) | CheckKind::NoBlankLineBeforeClass(_)
| CheckKind::NoBlankLineBeforeFunction(_) | CheckKind::NoBlankLineBeforeFunction(_)
| CheckKind::NoBlankLinesBetweenHeaderAndContent(_) | CheckKind::NoBlankLinesBetweenHeaderAndContent(_)
| CheckKind::NoOverIndentation
| CheckKind::NoSurroundingWhitespace | CheckKind::NoSurroundingWhitespace
| CheckKind::NoUnderIndentation
| CheckKind::OneBlankLineAfterClass(_) | CheckKind::OneBlankLineAfterClass(_)
| CheckKind::OneBlankLineBeforeClass(_) | CheckKind::OneBlankLineBeforeClass(_)
| CheckKind::PPrintFound | CheckKind::PPrintFound

View file

@ -32,3 +32,11 @@ pub fn indentation<'a>(checker: &'a mut Checker, docstring: &Expr) -> &'a str {
end_location: Location::new(range.location.row(), range.location.column()), end_location: Location::new(range.location.row(), range.location.column()),
}) })
} }
/// Replace any non-whitespace characters from an indentation string.
pub fn clean(indentation: &str) -> String {
indentation
.chars()
.map(|char| if char.is_whitespace() { char } else { ' ' })
.collect()
}

View file

@ -385,23 +385,10 @@ pub fn indent(checker: &mut Checker, definition: &Definition) {
return; return;
} }
let mut has_seen_tab = false;
let mut has_seen_over_indent = false;
let mut has_seen_under_indent = false;
let docstring_indent = helpers::indentation(checker, docstring).to_string(); let docstring_indent = helpers::indentation(checker, docstring).to_string();
if !has_seen_tab { let mut has_seen_tab = docstring_indent.contains('\t');
if docstring_indent.contains('\t') { let mut is_over_indented = true;
if checker.settings.enabled.contains(&CheckCode::D206) { let mut over_indented_lines = vec![];
checker.add_check(Check::new(
CheckKind::IndentWithSpaces,
Range::from_located(docstring),
));
}
has_seen_tab = true;
}
}
for i in 0..lines.len() { for i in 0..lines.len() {
// First lines and continuations doesn't need any indentation. // First lines and continuations doesn't need any indentation.
if i == 0 || lines[i - 1].ends_with('\\') { if i == 0 || lines[i - 1].ends_with('\\') {
@ -415,39 +402,106 @@ pub fn indent(checker: &mut Checker, definition: &Definition) {
} }
let line_indent = helpers::leading_space(lines[i]); let line_indent = helpers::leading_space(lines[i]);
if !has_seen_tab {
if line_indent.contains('\t') { // We only report tab indentation once, so only check if we haven't seen a tab yet.
has_seen_tab = has_seen_tab || line_indent.contains('\t');
if checker.settings.enabled.contains(&CheckCode::D207) {
// We report under-indentation on every line. This isn't great, but enables
// autofix.
if line_indent.len() < docstring_indent.len() {
let mut check = Check::new(
CheckKind::NoUnderIndentation,
Range {
location: Location::new(docstring.location.row() + i, 1),
end_location: Location::new(docstring.location.row() + i, 1),
},
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
check.amend(Fix::replacement(
helpers::clean(&docstring_indent),
Location::new(docstring.location.row() + i, 1),
Location::new(docstring.location.row() + i, 1 + line_indent.len()),
));
}
checker.add_check(check);
}
}
// Like pydocstyle, we only report over-indentation if either: (1) every line
// (except, optionally, the last line) is over-indented, or (2) the last line (which
// contains the closing quotation marks) is over-indented. We can't know if we've
// achieved that condition until we've viewed all the lines, so for now, just track
// the over-indentation status of every line.
if i < lines.len() - 1 {
if line_indent.len() > docstring_indent.len() {
over_indented_lines.push(i);
} else {
is_over_indented = false;
}
}
}
if checker.settings.enabled.contains(&CheckCode::D206) { if checker.settings.enabled.contains(&CheckCode::D206) {
if has_seen_tab {
checker.add_check(Check::new( checker.add_check(Check::new(
CheckKind::IndentWithSpaces, CheckKind::IndentWithSpaces,
Range::from_located(docstring), Range::from_located(docstring),
)); ));
} }
has_seen_tab = true;
}
} }
if !has_seen_over_indent {
if line_indent.len() > docstring_indent.len() {
if checker.settings.enabled.contains(&CheckCode::D208) { if checker.settings.enabled.contains(&CheckCode::D208) {
checker.add_check(Check::new( // If every line (except the last) is over-indented...
if is_over_indented {
for i in over_indented_lines {
let line_indent = helpers::leading_space(lines[i]);
if line_indent.len() > docstring_indent.len() {
// We report over-indentation on every line. This isn't great, but
// enables autofix.
let mut check = Check::new(
CheckKind::NoOverIndentation, CheckKind::NoOverIndentation,
Range::from_located(docstring), Range {
location: Location::new(docstring.location.row() + i, 1),
end_location: Location::new(docstring.location.row() + i, 1),
},
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply)
{
check.amend(Fix::replacement(
helpers::clean(&docstring_indent),
Location::new(docstring.location.row() + i, 1),
Location::new(
docstring.location.row() + i,
1 + line_indent.len(),
),
)); ));
} }
has_seen_over_indent = true; checker.add_check(check);
}
} }
} }
if !has_seen_under_indent { // If the last line is over-indented...
if line_indent.len() < docstring_indent.len() { if !lines.is_empty() {
if checker.settings.enabled.contains(&CheckCode::D207) { let i = lines.len() - 1;
checker.add_check(Check::new( let line_indent = helpers::leading_space(lines[i]);
CheckKind::NoUnderIndentation, if line_indent.len() > docstring_indent.len() {
Range::from_located(docstring), let mut check = Check::new(
CheckKind::NoOverIndentation,
Range {
location: Location::new(docstring.location.row() + i, 1),
end_location: Location::new(docstring.location.row() + i, 1),
},
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
check.amend(Fix::replacement(
helpers::clean(&docstring_indent),
Location::new(docstring.location.row() + i, 1),
Location::new(docstring.location.row() + i, 1 + line_indent.len()),
)); ));
} }
has_seen_under_indent = true; checker.add_check(check);
} }
} }
} }
@ -481,8 +535,10 @@ pub fn newline_after_last_paragraph(checker: &mut Checker, definition: &Definiti
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply)
{ {
// Insert a newline just before the end-quote(s). // Insert a newline just before the end-quote(s).
let mut content = "\n".to_string(); let content = format!(
content.push_str(helpers::indentation(checker, docstring)); "\n{}",
helpers::clean(helpers::indentation(checker, docstring))
);
check.amend(Fix::insertion( check.amend(Fix::insertion(
content, content,
Location::new( Location::new(
@ -857,10 +913,11 @@ fn blanks_and_section_underline(
); );
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) { if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
// Add a dashed line (of the appropriate length) under the section header. // Add a dashed line (of the appropriate length) under the section header.
let mut content = "".to_string(); let content = format!(
content.push_str(helpers::indentation(checker, docstring)); "{}{}\n",
content.push_str(&"-".repeat(context.section_name.len())); helpers::clean(helpers::indentation(checker, docstring)),
content.push('\n'); "-".repeat(context.section_name.len())
);
check.amend(Fix::insertion( check.amend(Fix::insertion(
content, content,
Location::new(docstring.location.row() + context.original_index + 1, 1), Location::new(docstring.location.row() + context.original_index + 1, 1),
@ -890,10 +947,11 @@ fn blanks_and_section_underline(
); );
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) { if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
// Add a dashed line (of the appropriate length) under the section header. // Add a dashed line (of the appropriate length) under the section header.
let mut content = "".to_string(); let content = format!(
content.push_str(helpers::indentation(checker, docstring)); "{}{}\n",
content.push_str(&"-".repeat(context.section_name.len())); helpers::clean(helpers::indentation(checker, docstring)),
content.push('\n'); "-".repeat(context.section_name.len())
);
check.amend(Fix::insertion( check.amend(Fix::insertion(
content, content,
Location::new(docstring.location.row() + context.original_index + 1, 1), Location::new(docstring.location.row() + context.original_index + 1, 1),
@ -965,10 +1023,11 @@ fn blanks_and_section_underline(
); );
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) { if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
// Replace the existing underline with a line of the appropriate length. // Replace the existing underline with a line of the appropriate length.
let mut content = "".to_string(); let content = format!(
content.push_str(helpers::indentation(checker, docstring)); "{}{}\n",
content.push_str(&"-".repeat(context.section_name.len())); helpers::clean(helpers::indentation(checker, docstring)),
content.push('\n'); "-".repeat(context.section_name.len())
);
check.amend(Fix::replacement( check.amend(Fix::replacement(
content, content,
Location::new( Location::new(
@ -1003,7 +1062,7 @@ fn blanks_and_section_underline(
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) { if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
// Replace the existing indentation with whitespace of the appropriate length. // Replace the existing indentation with whitespace of the appropriate length.
check.amend(Fix::replacement( check.amend(Fix::replacement(
indentation, helpers::clean(&indentation),
Location::new( Location::new(
docstring.location.row() docstring.location.row()
+ context.original_index + context.original_index
@ -1144,7 +1203,7 @@ fn common_section(
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) { if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
// Replace the existing indentation with whitespace of the appropriate length. // Replace the existing indentation with whitespace of the appropriate length.
check.amend(Fix::replacement( check.amend(Fix::replacement(
indentation, helpers::clean(&indentation),
Location::new(docstring.location.row() + context.original_index, 1), Location::new(docstring.location.row() + context.original_index, 1),
Location::new( Location::new(
docstring.location.row() + context.original_index, docstring.location.row() + context.original_index,

View file

@ -4,26 +4,70 @@ expression: checks
--- ---
- kind: NoUnderIndentation - kind: NoUnderIndentation
location: location:
row: 225 row: 227
column: 5 column: 1
end_location: end_location:
row: 229 row: 227
column: 8 column: 1
fix: ~ fix:
patch:
content: " "
location:
row: 227
column: 1
end_location:
row: 227
column: 1
applied: false
- kind: NoUnderIndentation - kind: NoUnderIndentation
location: location:
row: 235 row: 238
column: 5 column: 1
end_location: end_location:
row: 239 row: 238
column: 4 column: 1
fix: ~ fix:
patch:
content: " "
location:
row: 238
column: 1
end_location:
row: 238
column: 1
applied: false
- kind: NoUnderIndentation - kind: NoUnderIndentation
location: location:
row: 433 row: 435
column: 37 column: 1
end_location:
row: 435
column: 1
fix:
patch:
content: " "
location:
row: 435
column: 1
end_location:
row: 435
column: 5
applied: false
- kind: NoUnderIndentation
location:
row: 436
column: 1
end_location: end_location:
row: 436 row: 436
column: 8 column: 1
fix: ~ fix:
patch:
content: " "
location:
row: 436
column: 1
end_location:
row: 436
column: 5
applied: false

View file

@ -4,26 +4,53 @@ expression: checks
--- ---
- kind: NoOverIndentation - kind: NoOverIndentation
location: location:
row: 245 row: 247
column: 5 column: 1
end_location: end_location:
row: 249 row: 247
column: 1
fix:
patch:
content: " "
location:
row: 247
column: 1
end_location:
row: 247
column: 8 column: 8
fix: ~ applied: false
- kind: NoOverIndentation - kind: NoOverIndentation
location: location:
row: 255 row: 259
column: 5 column: 1
end_location: end_location:
row: 259 row: 259
column: 12 column: 1
fix: ~ fix:
patch:
content: " "
location:
row: 259
column: 1
end_location:
row: 259
column: 9
applied: false
- kind: NoOverIndentation - kind: NoOverIndentation
location: location:
row: 265 row: 267
column: 5 column: 1
end_location: end_location:
row: 269 row: 267
column: 8 column: 1
fix: ~ fix:
patch:
content: " "
location:
row: 267
column: 1
end_location:
row: 267
column: 9
applied: false