Compare commits

..

7 Commits

Author SHA1 Message Date
Charlie Marsh
c72b8e8d1e Bump version to 0.0.82 2022-10-21 12:15:19 -04:00
Harutaka Kawamura
b108c693fa Implement N811, 812, 813, 814, and 817 (#457) 2022-10-20 12:20:44 -04:00
Harutaka Kawamura
aac1912ea7 Implement N807 (#456) 2022-10-20 11:12:02 -04:00
Charlie Marsh
3dcd26aac3 Add SectionUnderlineAfterName to autofix list 2022-10-17 22:08:49 -04:00
Charlie Marsh
e53b9807f6 Bump version to 0.0.81 2022-10-17 21:43:49 -04:00
Charlie Marsh
36fe8b76d4 Enable autofix for over- and under-indented docstrings (#451) 2022-10-17 21:43:38 -04:00
Charlie Marsh
f832f88c75 Implement autofix support for D214, D405, D406, and D416 (#450) 2022-10-17 17:37:20 -04:00
26 changed files with 753 additions and 124 deletions

2
Cargo.lock generated
View File

@@ -2045,7 +2045,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.80"
version = "0.0.82"
dependencies = [
"anyhow",
"assert_cmd",

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.80"
version = "0.0.82"
edition = "2021"
[lib]

View File

@@ -77,7 +77,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.80
rev: v0.0.82
hooks:
- id: lint
```
@@ -296,24 +296,24 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
| D204 | OneBlankLineAfterClass | 1 blank line required after class docstring | 🛠 |
| D205 | BlankLineAfterSummary | 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 | |
| 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 | 🛠 |
| D210 | NoSurroundingWhitespace | No whitespaces allowed surrounding docstring text | 🛠 |
| D211 | NoBlankLineBeforeClass | No blank lines allowed before class docstring | 🛠 |
| D212 | MultiLineSummaryFirstLine | Multi-line docstring summary should start at the first 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") | 🛠 |
| D300 | UsesTripleQuotes | Use """triple double quotes""" | |
| 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 | |
| D404 | NoThisPrefix | First word of the docstring should not be `This` | |
| D405 | CapitalizeSectionName | Section name should be properly capitalized ("returns") | |
| D406 | NewLineAfterSectionName | Section name should end with a newline ("Returns") | |
| D405 | CapitalizeSectionName | Section name should be properly capitalized ("returns") | 🛠 |
| D406 | NewLineAfterSectionName | Section name should end with a newline ("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") | 🛠 |
| D410 | BlankLineAfterSection | Missing blank line after section ("Returns") | 🛠 |
| D411 | BlankLineBeforeSection | Missing blank line before section ("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") | 🛠 |
| D414 | NonEmptySection | Section has no content ("Returns") | |
| 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` | |
| D418 | SkipDocstring | Function decorated with @overload shouldn't contain a docstring | |
| D419 | NonEmpty | Docstring is empty | |
@@ -348,6 +348,12 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
| N803 | InvalidArgumentName | Argument name `...` should be lowercase | |
| N804 | InvalidFirstArgumentNameForClassMethod | First argument of a class method should be named `cls` | |
| N805 | InvalidFirstArgumentNameForMethod | First argument of a method should be named `self` | |
| N807 | DunderFunctionName | function name should not start and end with '__' | |
| N811 | ConstantImportedAsNonConstant | constant '...' imported as non constant '...' | |
| N812 | LowercaseImportedAsNonLowercase | lowercase '...' imported as non lowercase '...' | |
| N813 | CamelcaseImportedAsLowercase | camelcase '...' imported as lowercase '...' | |
| N814 | CamelcaseImportedAsConstant | camelcase '...' imported as constant '...' | |
| N817 | CamelcaseImportedAsAcronym | camelcase '...' imported as acronym '...' | |
### flake8-comprehensions

15
resources/test/fixtures/N807.py vendored Normal file
View File

@@ -0,0 +1,15 @@
def __bad__():
pass
def __good():
pass
def good__():
pass
class Class:
def __good__(self):
pass

1
resources/test/fixtures/N811.py vendored Normal file
View File

@@ -0,0 +1 @@
from mod import BAD as bad

1
resources/test/fixtures/N812.py vendored Normal file
View File

@@ -0,0 +1 @@
from mod import bad as Bad

1
resources/test/fixtures/N813.py vendored Normal file
View File

@@ -0,0 +1 @@
from mod import CamelCase as camelcase

1
resources/test/fixtures/N814.py vendored Normal file
View File

@@ -0,0 +1 @@
from mod import CamelCase as CAMELCASE

1
resources/test/fixtures/N817.py vendored Normal file
View File

@@ -0,0 +1 @@
from mod import CamelCase as CC

View File

@@ -245,6 +245,14 @@ where
}
}
if self.settings.enabled.contains(&CheckCode::N807) {
if let Some(check) =
pep8_naming::checks::dunder_function_name(stmt, self.current_scope(), name)
{
self.checks.push(check);
}
}
self.check_builtin_shadowing(name, Range::from_located(stmt), true);
// Visit the decorators and arguments, but avoid the body, which will be deferred.
@@ -525,6 +533,64 @@ where
},
)
}
if let Some(asname) = &alias.node.asname {
if self.settings.enabled.contains(&CheckCode::N811) {
if let Some(check) =
pep8_naming::checks::constant_imported_as_non_constant(
stmt,
&alias.node.name,
asname,
)
{
self.checks.push(check);
}
}
if self.settings.enabled.contains(&CheckCode::N812) {
if let Some(check) =
pep8_naming::checks::lowercase_imported_as_non_lowercase(
stmt,
&alias.node.name,
asname,
)
{
self.checks.push(check);
}
}
if self.settings.enabled.contains(&CheckCode::N813) {
if let Some(check) =
pep8_naming::checks::camelcase_imported_as_lowercase(
stmt,
&alias.node.name,
asname,
)
{
self.checks.push(check);
}
}
if self.settings.enabled.contains(&CheckCode::N814) {
if let Some(check) = pep8_naming::checks::camelcase_imported_as_constant(
stmt,
&alias.node.name,
asname,
) {
self.checks.push(check);
}
}
if self.settings.enabled.contains(&CheckCode::N817) {
if let Some(check) = pep8_naming::checks::camelcase_imported_as_acronym(
stmt,
&alias.node.name,
asname,
) {
self.checks.push(check);
}
}
}
}
}
StmtKind::Raise { exc, .. } => {

View File

@@ -158,6 +158,12 @@ pub enum CheckCode {
N803,
N804,
N805,
N807,
N811,
N812,
N813,
N814,
N817,
// Meta
M001,
}
@@ -342,6 +348,12 @@ pub enum CheckKind {
InvalidArgumentName(String),
InvalidFirstArgumentNameForClassMethod,
InvalidFirstArgumentNameForMethod,
DunderFunctionName,
ConstantImportedAsNonConstant(String, String),
LowercaseImportedAsNonLowercase(String, String),
CamelcaseImportedAsLowercase(String, String),
CamelcaseImportedAsConstant(String, String),
CamelcaseImportedAsAcronym(String, String),
// Meta
UnusedNOQA(Option<Vec<String>>),
}
@@ -517,6 +529,22 @@ impl CheckCode {
CheckCode::N803 => CheckKind::InvalidArgumentName("...".to_string()),
CheckCode::N804 => CheckKind::InvalidFirstArgumentNameForClassMethod,
CheckCode::N805 => CheckKind::InvalidFirstArgumentNameForMethod,
CheckCode::N807 => CheckKind::DunderFunctionName,
CheckCode::N811 => {
CheckKind::ConstantImportedAsNonConstant("...".to_string(), "...".to_string())
}
CheckCode::N812 => {
CheckKind::LowercaseImportedAsNonLowercase("...".to_string(), "...".to_string())
}
CheckCode::N813 => {
CheckKind::CamelcaseImportedAsLowercase("...".to_string(), "...".to_string())
}
CheckCode::N814 => {
CheckKind::CamelcaseImportedAsConstant("...".to_string(), "...".to_string())
}
CheckCode::N817 => {
CheckKind::CamelcaseImportedAsAcronym("...".to_string(), "...".to_string())
}
// Meta
CheckCode::M001 => CheckKind::UnusedNOQA(None),
}
@@ -648,6 +676,12 @@ impl CheckCode {
CheckCode::N803 => CheckCategory::PEP8Naming,
CheckCode::N804 => CheckCategory::PEP8Naming,
CheckCode::N805 => CheckCategory::PEP8Naming,
CheckCode::N807 => CheckCategory::PEP8Naming,
CheckCode::N811 => CheckCategory::PEP8Naming,
CheckCode::N812 => CheckCategory::PEP8Naming,
CheckCode::N813 => CheckCategory::PEP8Naming,
CheckCode::N814 => CheckCategory::PEP8Naming,
CheckCode::N817 => CheckCategory::PEP8Naming,
CheckCode::M001 => CheckCategory::Meta,
}
}
@@ -790,6 +824,12 @@ impl CheckKind {
CheckKind::InvalidArgumentName(_) => &CheckCode::N803,
CheckKind::InvalidFirstArgumentNameForClassMethod => &CheckCode::N804,
CheckKind::InvalidFirstArgumentNameForMethod => &CheckCode::N805,
CheckKind::DunderFunctionName => &CheckCode::N807,
CheckKind::ConstantImportedAsNonConstant(..) => &CheckCode::N811,
CheckKind::LowercaseImportedAsNonLowercase(..) => &CheckCode::N812,
CheckKind::CamelcaseImportedAsLowercase(..) => &CheckCode::N813,
CheckKind::CamelcaseImportedAsConstant(..) => &CheckCode::N814,
CheckKind::CamelcaseImportedAsAcronym(..) => &CheckCode::N817,
// Meta
CheckKind::UnusedNOQA(_) => &CheckCode::M001,
}
@@ -1080,7 +1120,7 @@ impl CheckKind {
"Multi-line docstring summary should start at the second line".to_string()
}
CheckKind::NoSignature => {
"First line should not be the function's 'signature'".to_string()
"First line should not be the function's signature".to_string()
}
CheckKind::NoBlankLineBeforeFunction(num_lines) => {
format!("No blank lines allowed before function docstring (found {num_lines})")
@@ -1180,6 +1220,24 @@ impl CheckKind {
CheckKind::InvalidFirstArgumentNameForMethod => {
"First argument of a method should be named `self`".to_string()
}
CheckKind::DunderFunctionName => {
"function name should not start and end with '__'".to_string()
}
CheckKind::ConstantImportedAsNonConstant(name, asname) => {
format!("constant '{name}' imported as non constant '{asname}'")
}
CheckKind::LowercaseImportedAsNonLowercase(name, asname) => {
format!("lowercase '{name}' imported as non lowercase '{asname}'")
}
CheckKind::CamelcaseImportedAsLowercase(name, asname) => {
format!("camelcase '{name}' imported as lowercase '{asname}'")
}
CheckKind::CamelcaseImportedAsConstant(name, asname) => {
format!("camelcase '{name}' imported as constant '{asname}'")
}
CheckKind::CamelcaseImportedAsAcronym(name, asname) => {
format!("camelcase '{name}' imported as acronym '{asname}'")
}
// Meta
CheckKind::UnusedNOQA(codes) => match codes {
None => "Unused `noqa` directive".to_string(),
@@ -1208,20 +1266,27 @@ impl CheckKind {
| CheckKind::BlankLineAfterSection(_)
| CheckKind::BlankLineAfterSummary
| CheckKind::BlankLineBeforeSection(_)
| CheckKind::CapitalizeSectionName(_)
| CheckKind::DashedUnderlineAfterSection(_)
| CheckKind::DeprecatedUnittestAlias(_, _)
| CheckKind::DoNotAssertFalse
| CheckKind::DuplicateHandlerException(_)
| CheckKind::NewLineAfterLastParagraph
| CheckKind::NewLineAfterSectionName(_)
| CheckKind::NoBlankLineAfterFunction(_)
| CheckKind::NoBlankLineBeforeClass(_)
| CheckKind::NoBlankLineBeforeFunction(_)
| CheckKind::NoBlankLinesBetweenHeaderAndContent(_)
| CheckKind::NoOverIndentation
| CheckKind::NoSurroundingWhitespace
| CheckKind::NoUnderIndentation
| CheckKind::OneBlankLineAfterClass(_)
| CheckKind::OneBlankLineBeforeClass(_)
| CheckKind::PPrintFound
| CheckKind::PrintFound
| CheckKind::SectionNameEndsInColon(_)
| CheckKind::SectionNotOverIndented(_)
| CheckKind::SectionUnderlineAfterName(_)
| CheckKind::SectionUnderlineMatchesSectionLength(_)
| CheckKind::SectionUnderlineNotOverIndented(_)
| CheckKind::SuperCallWithParameters

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()),
})
}
/// 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

@@ -358,6 +358,12 @@ mod tests {
#[test_case(CheckCode::N803, Path::new("N803.py"); "N803")]
#[test_case(CheckCode::N804, Path::new("N804.py"); "N804")]
#[test_case(CheckCode::N805, Path::new("N805.py"); "N805")]
#[test_case(CheckCode::N807, Path::new("N807.py"); "N807")]
#[test_case(CheckCode::N811, Path::new("N811.py"); "N811")]
#[test_case(CheckCode::N812, Path::new("N812.py"); "N812")]
#[test_case(CheckCode::N813, Path::new("N813.py"); "N813")]
#[test_case(CheckCode::N814, Path::new("N814.py"); "N814")]
#[test_case(CheckCode::N817, Path::new("N817.py"); "N817")]
#[test_case(CheckCode::T201, Path::new("T201.py"); "T201")]
#[test_case(CheckCode::T203, Path::new("T203.py"); "T203")]
#[test_case(CheckCode::U001, Path::new("U001.py"); "U001")]

View File

@@ -1,3 +1,4 @@
use itertools::Itertools;
use rustpython_ast::{Arguments, Expr, ExprKind, Stmt};
use crate::ast::types::{Range, Scope, ScopeKind};
@@ -97,3 +98,96 @@ pub fn invalid_first_argument_name_for_method(
}
None
}
pub fn dunder_function_name(func_def: &Stmt, scope: &Scope, name: &str) -> Option<Check> {
if matches!(scope.kind, ScopeKind::Class) {
return None;
}
if name.starts_with("__") && name.ends_with("__") {
return Some(Check::new(
CheckKind::DunderFunctionName,
Range::from_located(func_def),
));
}
None
}
pub fn constant_imported_as_non_constant(
import_from: &Stmt,
name: &str,
asname: &str,
) -> Option<Check> {
if name.chars().all(|c| c.is_uppercase()) && !asname.chars().all(|c| c.is_uppercase()) {
return Some(Check::new(
CheckKind::ConstantImportedAsNonConstant(name.to_string(), asname.to_string()),
Range::from_located(import_from),
));
}
None
}
pub fn lowercase_imported_as_non_lowercase(
import_from: &Stmt,
name: &str,
asname: &str,
) -> Option<Check> {
if name.chars().all(|c| c.is_lowercase()) && asname.to_lowercase() != asname {
return Some(Check::new(
CheckKind::LowercaseImportedAsNonLowercase(name.to_string(), asname.to_string()),
Range::from_located(import_from),
));
}
None
}
fn is_camelcase(name: &str) -> bool {
!name.chars().all(|c| c.is_uppercase()) && !name.chars().all(|c| c.is_lowercase())
}
fn is_acronym(name: &str, asname: &str) -> bool {
name.chars().filter(|c| c.is_uppercase()).join("") == asname
}
pub fn camelcase_imported_as_lowercase(
import_from: &Stmt,
name: &str,
asname: &str,
) -> Option<Check> {
if is_camelcase(name) && asname.chars().all(|c| c.is_lowercase()) {
return Some(Check::new(
CheckKind::CamelcaseImportedAsLowercase(name.to_string(), asname.to_string()),
Range::from_located(import_from),
));
}
None
}
pub fn camelcase_imported_as_constant(
import_from: &Stmt,
name: &str,
asname: &str,
) -> Option<Check> {
if is_camelcase(name) && asname.chars().all(|c| c.is_uppercase()) && !is_acronym(name, asname) {
return Some(Check::new(
CheckKind::CamelcaseImportedAsConstant(name.to_string(), asname.to_string()),
Range::from_located(import_from),
));
}
None
}
pub fn camelcase_imported_as_acronym(
import_from: &Stmt,
name: &str,
asname: &str,
) -> Option<Check> {
if is_camelcase(name) && asname.chars().all(|c| c.is_uppercase()) && is_acronym(name, asname) {
return Some(Check::new(
CheckKind::CamelcaseImportedAsAcronym(name.to_string(), asname.to_string()),
Range::from_located(import_from),
));
}
None
}

View File

@@ -4,7 +4,6 @@ use itertools::Itertools;
use once_cell::sync::Lazy;
use regex::Regex;
use rustpython_ast::{Arg, Constant, ExprKind, Location, StmtKind};
use titlecase::titlecase;
use crate::ast::types::Range;
use crate::autofix::fixer;
@@ -386,23 +385,10 @@ pub fn indent(checker: &mut Checker, definition: &Definition) {
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();
if !has_seen_tab {
if docstring_indent.contains('\t') {
if checker.settings.enabled.contains(&CheckCode::D206) {
checker.add_check(Check::new(
CheckKind::IndentWithSpaces,
Range::from_located(docstring),
));
}
has_seen_tab = true;
}
}
let mut has_seen_tab = docstring_indent.contains('\t');
let mut is_over_indented = true;
let mut over_indented_lines = vec![];
for i in 0..lines.len() {
// First lines and continuations doesn't need any indentation.
if i == 0 || lines[i - 1].ends_with('\\') {
@@ -416,39 +402,106 @@ pub fn indent(checker: &mut Checker, definition: &Definition) {
}
let line_indent = helpers::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,
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) {
checker.add_check(Check::new(
CheckKind::NoOverIndentation,
Range::from_located(docstring),
));
}
has_seen_over_indent = true;
}
}
// 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 !has_seen_under_indent {
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() {
if checker.settings.enabled.contains(&CheckCode::D207) {
checker.add_check(Check::new(
CheckKind::NoUnderIndentation,
Range::from_located(docstring),
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()),
));
}
has_seen_under_indent = true;
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 has_seen_tab {
checker.add_check(Check::new(
CheckKind::IndentWithSpaces,
Range::from_located(docstring),
));
}
}
if checker.settings.enabled.contains(&CheckCode::D208) {
// 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,
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);
}
}
}
// If the last line is over-indented...
if !lines.is_empty() {
let i = lines.len() - 1;
let line_indent = helpers::leading_space(lines[i]);
if line_indent.len() > docstring_indent.len() {
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()),
));
}
checker.add_check(check);
}
}
}
@@ -482,8 +535,10 @@ pub fn newline_after_last_paragraph(checker: &mut Checker, definition: &Definiti
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply)
{
// Insert a newline just before the end-quote(s).
let mut content = "\n".to_string();
content.push_str(helpers::indentation(checker, docstring));
let content = format!(
"\n{}",
helpers::clean(helpers::indentation(checker, docstring))
);
check.amend(Fix::insertion(
content,
Location::new(
@@ -858,10 +913,11 @@ fn blanks_and_section_underline(
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
// Add a dashed line (of the appropriate length) under the section header.
let mut content = "".to_string();
content.push_str(helpers::indentation(checker, docstring));
content.push_str(&"-".repeat(context.section_name.len()));
content.push('\n');
let content = format!(
"{}{}\n",
helpers::clean(helpers::indentation(checker, docstring)),
"-".repeat(context.section_name.len())
);
check.amend(Fix::insertion(
content,
Location::new(docstring.location.row() + context.original_index + 1, 1),
@@ -891,10 +947,11 @@ fn blanks_and_section_underline(
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
// Add a dashed line (of the appropriate length) under the section header.
let mut content = "".to_string();
content.push_str(helpers::indentation(checker, docstring));
content.push_str(&"-".repeat(context.section_name.len()));
content.push('\n');
let content = format!(
"{}{}\n",
helpers::clean(helpers::indentation(checker, docstring)),
"-".repeat(context.section_name.len())
);
check.amend(Fix::insertion(
content,
Location::new(docstring.location.row() + context.original_index + 1, 1),
@@ -966,10 +1023,11 @@ fn blanks_and_section_underline(
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
// Replace the existing underline with a line of the appropriate length.
let mut content = "".to_string();
content.push_str(helpers::indentation(checker, docstring));
content.push_str(&"-".repeat(context.section_name.len()));
content.push('\n');
let content = format!(
"{}{}\n",
helpers::clean(helpers::indentation(checker, docstring)),
"-".repeat(context.section_name.len())
);
check.amend(Fix::replacement(
content,
Location::new(
@@ -1004,7 +1062,7 @@ fn blanks_and_section_underline(
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,
helpers::clean(&indentation),
Location::new(
docstring.location.row()
+ context.original_index
@@ -1099,25 +1157,61 @@ fn common_section(
if !style
.section_names()
.contains(&context.section_name.as_str())
&& style
.section_names()
.contains(titlecase(&context.section_name).as_str())
{
checker.add_check(Check::new(
CheckKind::CapitalizeSectionName(context.section_name.to_string()),
Range::from_located(docstring),
))
let capitalized_section_name = titlecase::titlecase(&context.section_name);
if style
.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 helpers::leading_space(context.line).len()
> helpers::indentation(checker, docstring).len()
{
checker.add_check(Check::new(
let leading_space = helpers::leading_space(context.line);
let indentation = helpers::indentation(checker, docstring).to_string();
if leading_space.len() > indentation.len() {
let mut check = Check::new(
CheckKind::SectionNotOverIndented(context.section_name.to_string()),
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(
helpers::clean(&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 +1238,7 @@ fn common_section(
+ context.following_lines.len(),
1,
),
))
));
}
checker.add_check(check);
}
@@ -1334,10 +1428,31 @@ fn numpy_section(checker: &mut Checker, definition: &Definition, context: &Secti
let docstring = definition
.docstring
.expect("Sections are only available for docstrings.");
checker.add_check(Check::new(
let mut check = Check::new(
CheckKind::NewLineAfterSectionName(context.section_name.to_string()),
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 +1477,32 @@ fn google_section(checker: &mut Checker, definition: &Definition, context: &Sect
let docstring = definition
.docstring
.expect("Sections are only available for docstrings.");
checker.add_check(Check::new(
let mut check = Check::new(
CheckKind::SectionNameEndsInColon(context.section_name.to_string()),
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

@@ -4,26 +4,70 @@ expression: checks
---
- kind: NoUnderIndentation
location:
row: 225
column: 5
row: 227
column: 1
end_location:
row: 229
column: 8
fix: ~
row: 227
column: 1
fix:
patch:
content: " "
location:
row: 227
column: 1
end_location:
row: 227
column: 1
applied: false
- kind: NoUnderIndentation
location:
row: 235
column: 5
row: 238
column: 1
end_location:
row: 239
column: 4
fix: ~
row: 238
column: 1
fix:
patch:
content: " "
location:
row: 238
column: 1
end_location:
row: 238
column: 1
applied: false
- kind: NoUnderIndentation
location:
row: 433
column: 37
row: 435
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:
row: 436
column: 8
fix: ~
column: 1
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
location:
row: 245
column: 5
row: 247
column: 1
end_location:
row: 249
column: 8
fix: ~
row: 247
column: 1
fix:
patch:
content: " "
location:
row: 247
column: 1
end_location:
row: 247
column: 8
applied: false
- kind: NoOverIndentation
location:
row: 255
column: 5
row: 259
column: 1
end_location:
row: 259
column: 12
fix: ~
column: 1
fix:
patch:
content: " "
location:
row: 259
column: 1
end_location:
row: 259
column: 9
applied: false
- kind: NoOverIndentation
location:
row: 265
column: 5
row: 267
column: 1
end_location:
row: 269
column: 8
fix: ~
row: 267
column: 1
fix:
patch:
content: " "
location:
row: 267
column: 1
end_location:
row: 267
column: 9
applied: false

View File

@@ -10,5 +10,14 @@ expression: checks
end_location:
row: 141
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:
row: 23
column: 8
fix: ~
fix:
patch:
content: Returns
location:
row: 19
column: 5
end_location:
row: 19
column: 12
applied: false
- kind:
CapitalizeSectionName: Short summary
location:
@@ -19,5 +28,14 @@ expression: checks
end_location:
row: 221
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:
row: 36
column: 8
fix: ~
fix:
patch:
content: ""
location:
row: 32
column: 12
end_location:
row: 32
column: 13
applied: false
- kind:
NewLineAfterSectionName: Raises
location:
@@ -19,7 +28,16 @@ expression: checks
end_location:
row: 221
column: 8
fix: ~
fix:
patch:
content: ""
location:
row: 218
column: 11
end_location:
row: 218
column: 12
applied: false
- kind:
NewLineAfterSectionName: Returns
location:
@@ -28,7 +46,16 @@ expression: checks
end_location:
row: 262
column: 8
fix: ~
fix:
patch:
content: ""
location:
row: 257
column: 12
end_location:
row: 257
column: 13
applied: false
- kind:
NewLineAfterSectionName: Raises
location:
@@ -37,5 +64,14 @@ expression: checks
end_location:
row: 262
column: 8
fix: ~
fix:
patch:
content: ""
location:
row: 259
column: 11
end_location:
row: 259
column: 12
applied: false

View File

@@ -0,0 +1,13 @@
---
source: src/linter.rs
expression: checks
---
- kind: DunderFunctionName
location:
row: 1
column: 1
end_location:
row: 5
column: 1
fix: ~

View File

@@ -0,0 +1,16 @@
---
source: src/linter.rs
expression: checks
---
- kind:
ConstantImportedAsNonConstant:
- BAD
- bad
location:
row: 1
column: 1
end_location:
row: 1
column: 27
fix: ~

View File

@@ -0,0 +1,16 @@
---
source: src/linter.rs
expression: checks
---
- kind:
LowercaseImportedAsNonLowercase:
- bad
- Bad
location:
row: 1
column: 1
end_location:
row: 1
column: 27
fix: ~

View File

@@ -0,0 +1,16 @@
---
source: src/linter.rs
expression: checks
---
- kind:
CamelcaseImportedAsLowercase:
- CamelCase
- camelcase
location:
row: 1
column: 1
end_location:
row: 1
column: 39
fix: ~

View File

@@ -0,0 +1,16 @@
---
source: src/linter.rs
expression: checks
---
- kind:
CamelcaseImportedAsConstant:
- CamelCase
- CAMELCASE
location:
row: 1
column: 1
end_location:
row: 1
column: 39
fix: ~

View File

@@ -0,0 +1,16 @@
---
source: src/linter.rs
expression: checks
---
- kind:
CamelcaseImportedAsAcronym:
- CamelCase
- CC
location:
row: 1
column: 1
end_location:
row: 1
column: 32
fix: ~