Implement autofix support for D214, D405, D406, and D416 (#450)

This commit is contained in:
Charlie Marsh 2022-10-17 17:37:20 -04:00 committed by GitHub
parent 659a28de02
commit f832f88c75
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 175 additions and 29 deletions

View file

@ -303,15 +303,15 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
| D211 | NoBlankLineBeforeClass | No blank lines allowed before class docstring | 🛠 | | D211 | NoBlankLineBeforeClass | No blank lines allowed before class docstring | 🛠 |
| D212 | MultiLineSummaryFirstLine | Multi-line docstring summary should start at the first line | | | D212 | MultiLineSummaryFirstLine | Multi-line docstring summary should start at the first line | |
| D213 | MultiLineSummarySecondLine | Multi-line docstring summary should start at the second line | | | D213 | MultiLineSummarySecondLine | Multi-line docstring summary should start at the second line | |
| D214 | SectionNotOverIndented | Section is over-indented ("Returns") | | | D214 | SectionNotOverIndented | Section is over-indented ("Returns") | 🛠 |
| D215 | SectionUnderlineNotOverIndented | Section underline is over-indented ("Returns") | 🛠 | | D215 | SectionUnderlineNotOverIndented | Section underline is over-indented ("Returns") | 🛠 |
| D300 | UsesTripleQuotes | Use """triple double quotes""" | | | D300 | UsesTripleQuotes | Use """triple double quotes""" | |
| D400 | EndsInPeriod | First line should end with a period | | | D400 | EndsInPeriod | First line should end with a period | |
| D402 | NoSignature | First line should not be the function's 'signature' | | | D402 | NoSignature | First line should not be the function's 'signature' | |
| D403 | FirstLineCapitalized | First word of the first line should be properly capitalized | | | D403 | FirstLineCapitalized | First word of the first line should be properly capitalized | |
| D404 | NoThisPrefix | First word of the docstring should not be `This` | | | D404 | NoThisPrefix | First word of the docstring should not be `This` | |
| D405 | CapitalizeSectionName | Section name should be properly capitalized ("returns") | | | D405 | CapitalizeSectionName | Section name should be properly capitalized ("returns") | 🛠 |
| D406 | NewLineAfterSectionName | Section name should end with a newline ("Returns") | | | D406 | NewLineAfterSectionName | Section name should end with a newline ("Returns") | 🛠 |
| D407 | DashedUnderlineAfterSection | Missing dashed underline after section ("Returns") | 🛠 | | D407 | DashedUnderlineAfterSection | Missing dashed underline after section ("Returns") | 🛠 |
| D408 | SectionUnderlineAfterName | Section underline should be in the line following the section's name ("Returns") | | | D408 | SectionUnderlineAfterName | Section underline should be in the line following the section's name ("Returns") | |
| D409 | SectionUnderlineMatchesSectionLength | Section underline should match the length of its name ("Returns") | 🛠 | | D409 | SectionUnderlineMatchesSectionLength | Section underline should match the length of its name ("Returns") | 🛠 |
@ -321,7 +321,7 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
| D413 | BlankLineAfterLastSection | Missing blank line after last section ("Returns") | 🛠 | | D413 | BlankLineAfterLastSection | Missing blank line after last section ("Returns") | 🛠 |
| D414 | NonEmptySection | Section has no content ("Returns") | | | D414 | NonEmptySection | Section has no content ("Returns") | |
| D415 | EndsInPunctuation | First line should end with a period, question mark, or exclamation point | | | D415 | EndsInPunctuation | First line should end with a period, question mark, or exclamation point | |
| D416 | SectionNameEndsInColon | Section name should end with a colon ("Returns") | | | D416 | SectionNameEndsInColon | Section name should end with a colon ("Returns") | 🛠 |
| D417 | DocumentAllArguments | Missing argument descriptions in the docstring: `x`, `y` | | | D417 | DocumentAllArguments | Missing argument descriptions in the docstring: `x`, `y` | |
| D418 | SkipDocstring | Function decorated with @overload shouldn't contain a docstring | | | D418 | SkipDocstring | Function decorated with @overload shouldn't contain a docstring | |
| D419 | NonEmpty | Docstring is empty | | | D419 | NonEmpty | Docstring is empty | |
@ -399,6 +399,7 @@ 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

@ -1208,11 +1208,13 @@ impl CheckKind {
| CheckKind::BlankLineAfterSection(_) | CheckKind::BlankLineAfterSection(_)
| CheckKind::BlankLineAfterSummary | CheckKind::BlankLineAfterSummary
| CheckKind::BlankLineBeforeSection(_) | CheckKind::BlankLineBeforeSection(_)
| CheckKind::CapitalizeSectionName(_)
| CheckKind::DashedUnderlineAfterSection(_) | CheckKind::DashedUnderlineAfterSection(_)
| CheckKind::DeprecatedUnittestAlias(_, _) | CheckKind::DeprecatedUnittestAlias(_, _)
| CheckKind::DoNotAssertFalse | CheckKind::DoNotAssertFalse
| CheckKind::DuplicateHandlerException(_) | CheckKind::DuplicateHandlerException(_)
| CheckKind::NewLineAfterLastParagraph | CheckKind::NewLineAfterLastParagraph
| CheckKind::NewLineAfterSectionName(_)
| CheckKind::NoBlankLineAfterFunction(_) | CheckKind::NoBlankLineAfterFunction(_)
| CheckKind::NoBlankLineBeforeClass(_) | CheckKind::NoBlankLineBeforeClass(_)
| CheckKind::NoBlankLineBeforeFunction(_) | CheckKind::NoBlankLineBeforeFunction(_)
@ -1222,6 +1224,8 @@ impl CheckKind {
| CheckKind::OneBlankLineBeforeClass(_) | CheckKind::OneBlankLineBeforeClass(_)
| CheckKind::PPrintFound | CheckKind::PPrintFound
| CheckKind::PrintFound | CheckKind::PrintFound
| CheckKind::SectionNameEndsInColon(_)
| CheckKind::SectionNotOverIndented(_)
| CheckKind::SectionUnderlineMatchesSectionLength(_) | CheckKind::SectionUnderlineMatchesSectionLength(_)
| CheckKind::SectionUnderlineNotOverIndented(_) | CheckKind::SectionUnderlineNotOverIndented(_)
| CheckKind::SuperCallWithParameters | CheckKind::SuperCallWithParameters

View file

@ -4,7 +4,6 @@ use itertools::Itertools;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use regex::Regex; use regex::Regex;
use rustpython_ast::{Arg, Constant, ExprKind, Location, StmtKind}; use rustpython_ast::{Arg, Constant, ExprKind, Location, StmtKind};
use titlecase::titlecase;
use crate::ast::types::Range; use crate::ast::types::Range;
use crate::autofix::fixer; use crate::autofix::fixer;
@ -1099,25 +1098,61 @@ fn common_section(
if !style if !style
.section_names() .section_names()
.contains(&context.section_name.as_str()) .contains(&context.section_name.as_str())
&& style
.section_names()
.contains(titlecase(&context.section_name).as_str())
{ {
checker.add_check(Check::new( let capitalized_section_name = titlecase::titlecase(&context.section_name);
CheckKind::CapitalizeSectionName(context.section_name.to_string()), if style
Range::from_located(docstring), .section_names()
)) .contains(capitalized_section_name.as_str())
{
let mut check = Check::new(
CheckKind::CapitalizeSectionName(context.section_name.to_string()),
Range::from_located(docstring),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
// Replace the section title with the capitalized variant. This requires
// locating the start and end of the section name.
if let Some(index) = context.line.find(&context.section_name) {
// Map from bytes to characters.
let section_name_start = &context.line[..index].chars().count();
let section_name_length = &context.section_name.chars().count();
check.amend(Fix::replacement(
capitalized_section_name,
Location::new(
docstring.location.row() + context.original_index,
1 + section_name_start,
),
Location::new(
docstring.location.row() + context.original_index,
1 + section_name_start + section_name_length,
),
))
}
}
checker.add_check(check);
}
} }
} }
if checker.settings.enabled.contains(&CheckCode::D214) { if checker.settings.enabled.contains(&CheckCode::D214) {
if helpers::leading_space(context.line).len() let leading_space = helpers::leading_space(context.line);
> helpers::indentation(checker, docstring).len() let indentation = helpers::indentation(checker, docstring).to_string();
{ if leading_space.len() > indentation.len() {
checker.add_check(Check::new( let mut check = Check::new(
CheckKind::SectionNotOverIndented(context.section_name.to_string()), CheckKind::SectionNotOverIndented(context.section_name.to_string()),
Range::from_located(docstring), Range::from_located(docstring),
)) );
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
// Replace the existing indentation with whitespace of the appropriate length.
check.amend(Fix::replacement(
indentation,
Location::new(docstring.location.row() + context.original_index, 1),
Location::new(
docstring.location.row() + context.original_index,
1 + leading_space.len(),
),
));
};
checker.add_check(check);
} }
} }
@ -1144,7 +1179,7 @@ fn common_section(
+ context.following_lines.len(), + context.following_lines.len(),
1, 1,
), ),
)) ));
} }
checker.add_check(check); checker.add_check(check);
} }
@ -1334,10 +1369,31 @@ fn numpy_section(checker: &mut Checker, definition: &Definition, context: &Secti
let docstring = definition let docstring = definition
.docstring .docstring
.expect("Sections are only available for docstrings."); .expect("Sections are only available for docstrings.");
checker.add_check(Check::new( let mut check = Check::new(
CheckKind::NewLineAfterSectionName(context.section_name.to_string()), CheckKind::NewLineAfterSectionName(context.section_name.to_string()),
Range::from_located(docstring), Range::from_located(docstring),
)) );
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
// Delete the suffix. This requires locating the end of the section name.
if let Some(index) = context.line.find(&context.section_name) {
// Map from bytes to characters.
let suffix_start = &context.line[..index + context.section_name.len()]
.chars()
.count();
let suffix_length = suffix.chars().count();
check.amend(Fix::deletion(
Location::new(
docstring.location.row() + context.original_index,
1 + suffix_start,
),
Location::new(
docstring.location.row() + context.original_index,
1 + suffix_start + suffix_length,
),
));
}
}
checker.add_check(check)
} }
} }
@ -1362,10 +1418,32 @@ fn google_section(checker: &mut Checker, definition: &Definition, context: &Sect
let docstring = definition let docstring = definition
.docstring .docstring
.expect("Sections are only available for docstrings."); .expect("Sections are only available for docstrings.");
checker.add_check(Check::new( let mut check = Check::new(
CheckKind::SectionNameEndsInColon(context.section_name.to_string()), CheckKind::SectionNameEndsInColon(context.section_name.to_string()),
Range::from_located(docstring), Range::from_located(docstring),
)) );
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
// Replace the suffix. This requires locating the end of the section name.
if let Some(index) = context.line.find(&context.section_name) {
// Map from bytes to characters.
let suffix_start = &context.line[..index + context.section_name.len()]
.chars()
.count();
let suffix_length = suffix.chars().count();
check.amend(Fix::replacement(
":".to_string(),
Location::new(
docstring.location.row() + context.original_index,
1 + suffix_start,
),
Location::new(
docstring.location.row() + context.original_index,
1 + suffix_start + suffix_length,
),
));
}
}
checker.add_check(check);
} }
} }

View file

@ -10,5 +10,14 @@ expression: checks
end_location: end_location:
row: 141 row: 141
column: 8 column: 8
fix: ~ fix:
patch:
content: " "
location:
row: 137
column: 1
end_location:
row: 137
column: 9
applied: false

View file

@ -10,7 +10,16 @@ expression: checks
end_location: end_location:
row: 23 row: 23
column: 8 column: 8
fix: ~ fix:
patch:
content: Returns
location:
row: 19
column: 5
end_location:
row: 19
column: 12
applied: false
- kind: - kind:
CapitalizeSectionName: Short summary CapitalizeSectionName: Short summary
location: location:
@ -19,5 +28,14 @@ expression: checks
end_location: end_location:
row: 221 row: 221
column: 8 column: 8
fix: ~ fix:
patch:
content: Short Summary
location:
row: 209
column: 5
end_location:
row: 209
column: 18
applied: false

View file

@ -10,7 +10,16 @@ expression: checks
end_location: end_location:
row: 36 row: 36
column: 8 column: 8
fix: ~ fix:
patch:
content: ""
location:
row: 32
column: 12
end_location:
row: 32
column: 13
applied: false
- kind: - kind:
NewLineAfterSectionName: Raises NewLineAfterSectionName: Raises
location: location:
@ -19,7 +28,16 @@ expression: checks
end_location: end_location:
row: 221 row: 221
column: 8 column: 8
fix: ~ fix:
patch:
content: ""
location:
row: 218
column: 11
end_location:
row: 218
column: 12
applied: false
- kind: - kind:
NewLineAfterSectionName: Returns NewLineAfterSectionName: Returns
location: location:
@ -28,7 +46,16 @@ expression: checks
end_location: end_location:
row: 262 row: 262
column: 8 column: 8
fix: ~ fix:
patch:
content: ""
location:
row: 257
column: 12
end_location:
row: 257
column: 13
applied: false
- kind: - kind:
NewLineAfterSectionName: Raises NewLineAfterSectionName: Raises
location: location:
@ -37,5 +64,14 @@ expression: checks
end_location: end_location:
row: 262 row: 262
column: 8 column: 8
fix: ~ fix:
patch:
content: ""
location:
row: 259
column: 11
end_location:
row: 259
column: 12
applied: false