Compare commits

..

13 Commits

Author SHA1 Message Date
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
Charlie Marsh
659a28de02 Bump version to 0.0.80 2022-10-17 17:02:44 -04:00
Charlie Marsh
583149a472 Break up autofix/fixes.rs (#449) 2022-10-17 17:01:50 -04:00
Charlie Marsh
206e6463be Implement autofix for more docstring-related rules (#448) 2022-10-17 16:56:47 -04:00
Charlie Marsh
118a9feec8 Split checks and plugins into source-related modules (#447) 2022-10-17 15:38:49 -04:00
Charlie Marsh
1f2ccb059a Break up some nested if statements 2022-10-17 13:08:33 -04:00
Charlie Marsh
f4d9d6c858 Fix typo in prev_visible_scope 2022-10-17 13:03:50 -04:00
Charlie Marsh
3477f5664a Fix README link to near-parity 2022-10-17 11:57:07 -04:00
Charlie Marsh
edefa5219c Update RustPython to get main versions of end_location etc. (#445) 2022-10-17 11:52:40 -04:00
Charlie Marsh
cf0d198365 Bump version to 0.0.79 2022-10-16 21:39:01 -04:00
Charlie Marsh
08b14ed77e Remove leading 'or' from fixable match 2022-10-16 21:38:22 -04:00
99 changed files with 5045 additions and 4245 deletions

10
Cargo.lock generated
View File

@@ -2045,7 +2045,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.78"
version = "0.0.81"
dependencies = [
"anyhow",
"assert_cmd",
@@ -2101,7 +2101,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.1.0"
source = "git+https://github.com/charliermarsh/RustPython.git?rev=210db77e4274787028dc3ebc0b4841d1334c8c10#210db77e4274787028dc3ebc0b4841d1334c8c10"
source = "git+https://github.com/charliermarsh/RustPython.git?rev=1b253a12705f84972cd76e8dc1cdaaccb233e5a5#1b253a12705f84972cd76e8dc1cdaaccb233e5a5"
dependencies = [
"num-bigint",
"rustpython-common",
@@ -2111,7 +2111,7 @@ dependencies = [
[[package]]
name = "rustpython-common"
version = "0.0.0"
source = "git+https://github.com/charliermarsh/RustPython.git?rev=210db77e4274787028dc3ebc0b4841d1334c8c10#210db77e4274787028dc3ebc0b4841d1334c8c10"
source = "git+https://github.com/charliermarsh/RustPython.git?rev=1b253a12705f84972cd76e8dc1cdaaccb233e5a5#1b253a12705f84972cd76e8dc1cdaaccb233e5a5"
dependencies = [
"ascii",
"cfg-if 1.0.0",
@@ -2134,7 +2134,7 @@ dependencies = [
[[package]]
name = "rustpython-compiler-core"
version = "0.1.2"
source = "git+https://github.com/charliermarsh/RustPython.git?rev=210db77e4274787028dc3ebc0b4841d1334c8c10#210db77e4274787028dc3ebc0b4841d1334c8c10"
source = "git+https://github.com/charliermarsh/RustPython.git?rev=1b253a12705f84972cd76e8dc1cdaaccb233e5a5#1b253a12705f84972cd76e8dc1cdaaccb233e5a5"
dependencies = [
"bincode",
"bitflags",
@@ -2151,7 +2151,7 @@ dependencies = [
[[package]]
name = "rustpython-parser"
version = "0.1.2"
source = "git+https://github.com/charliermarsh/RustPython.git?rev=210db77e4274787028dc3ebc0b4841d1334c8c10#210db77e4274787028dc3ebc0b4841d1334c8c10"
source = "git+https://github.com/charliermarsh/RustPython.git?rev=1b253a12705f84972cd76e8dc1cdaaccb233e5a5#1b253a12705f84972cd76e8dc1cdaaccb233e5a5"
dependencies = [
"ahash",
"anyhow",

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.78"
version = "0.0.81"
edition = "2021"
[lib]
@@ -26,9 +26,9 @@ once_cell = { version = "1.13.1" }
path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] }
rayon = { version = "1.5.3" }
regex = { version = "1.6.0" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/charliermarsh/RustPython.git", rev = "210db77e4274787028dc3ebc0b4841d1334c8c10" }
rustpython-common = { git = "https://github.com/charliermarsh/RustPython.git", rev = "210db77e4274787028dc3ebc0b4841d1334c8c10" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/charliermarsh/RustPython.git", rev = "210db77e4274787028dc3ebc0b4841d1334c8c10" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/charliermarsh/RustPython.git", rev = "1b253a12705f84972cd76e8dc1cdaaccb233e5a5" }
rustpython-common = { git = "https://github.com/charliermarsh/RustPython.git", rev = "1b253a12705f84972cd76e8dc1cdaaccb233e5a5" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/charliermarsh/RustPython.git", rev = "1b253a12705f84972cd76e8dc1cdaaccb233e5a5" }
serde = { version = "1.0.143", features = ["derive"] }
serde_json = { version = "1.0.83" }
strum = { version = "0.24.1", features = ["strum_macros"] }

View File

@@ -22,7 +22,7 @@ An extremely fast Python linter, written in Rust.
- 📦 [ESLint](https://eslint.org/docs/latest/user-guide/command-line-interface#caching)-inspired cache support
- 🔧 [ESLint](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix)-inspired autofix support (e.g., automatically remove unused imports)
- 👀 [TypeScript](https://www.typescriptlang.org/docs/handbook/configuring-watch.html)-inspired `--watch` support, for continuous file monitoring
- ⚖️ [Near-parity](#Parity-with-Flake8) with the built-in Flake8 rule set
- ⚖️ [Near-parity](#how-does-ruff-compare-to-flake8) with the built-in Flake8 rule set
- 🔌 Native re-implementations of popular Flake8 plugins, like [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/) ([`pydocstyle`](https://pypi.org/project/pydocstyle/))
Ruff aims to be orders of magnitude faster than alternative tools while integrating more
@@ -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.78
rev: v0.0.81
hooks:
- id: lint
```
@@ -294,34 +294,34 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
| D202 | NoBlankLineAfterFunction | No blank lines allowed after function docstring (found 1) | 🛠 |
| D203 | OneBlankLineBeforeClass | 1 blank line required before class docstring | 🛠 |
| D204 | OneBlankLineAfterClass | 1 blank line required after class docstring | 🛠 |
| D205 | NoBlankLineAfterSummary | 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 | |
| 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") | |
| D215 | SectionUnderlineNotOverIndented | Section underline 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' | |
| 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") | |
| D407 | DashedUnderlineAfterSection | Missing dashed underline after section ("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") | |
| D409 | SectionUnderlineMatchesSectionLength | Section underline should match the length of its 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") | |
| D412 | NoBlankLinesBetweenHeaderAndContent | No blank lines allowed between a section header and its content ("Returns") | |
| D411 | BlankLineBeforeSection | Missing blank line before section ("Returns") | 🛠 |
| D412 | NoBlankLinesBetweenHeaderAndContent | No blank lines allowed between a section header and its content ("Returns") | 🛠 |
| 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 | |

View File

@@ -1,4 +1,3 @@
pub mod checkers;
pub mod helpers;
pub mod operations;
pub mod relocate;

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
use once_cell::sync::Lazy;
use regex::Regex;
use rustpython_ast::{Excepthandler, ExcepthandlerKind, Expr, ExprKind, StmtKind};
use rustpython_ast::{Excepthandler, ExcepthandlerKind, Expr, ExprKind, Location, StmtKind};
use crate::python::typing;
@@ -131,3 +131,15 @@ pub fn is_super_call_with_arguments(func: &Expr, args: &[Expr]) -> bool {
false
}
}
/// Convert a location within a file (relative to `base`) to an absolute position.
pub fn to_absolute(relative: &Location, base: &Location) -> Location {
if relative.row() == 1 {
Location::new(
relative.row() + base.row() - 1,
relative.column() + base.column() - 1,
)
} else {
Location::new(relative.row() + base.row() - 1, relative.column())
}
}

View File

@@ -4,14 +4,14 @@ use crate::ast::types::Range;
fn relocate_keyword(keyword: &mut Keyword, location: Range) {
keyword.location = location.location;
keyword.end_location = location.end_location;
keyword.end_location = Some(location.end_location);
relocate_expr(&mut keyword.node.value, location);
}
/// Change an expression's location (recursively) to match a desired, fixed location.
pub fn relocate_expr(expr: &mut Expr, location: Range) {
expr.location = location.location;
expr.end_location = location.end_location;
expr.end_location = Some(location.end_location);
match &mut expr.node {
ExprKind::BoolOp { values, .. } => {
for expr in values {

View File

@@ -18,7 +18,9 @@ impl Range {
pub fn from_located<T>(located: &Located<T>) -> Self {
Range {
location: located.location,
end_location: located.end_location,
end_location: located
.end_location
.expect("AST nodes should have end_location."),
}
}
}

View File

@@ -1,2 +0,0 @@
pub mod fixer;
pub mod fixes;

View File

@@ -1,7 +1,10 @@
use crate::autofix::Fix;
use crate::autofix::Patch;
use itertools::Itertools;
use rustpython_parser::ast::Location;
use std::collections::BTreeSet;
use crate::checks::{Check, Fix};
use crate::checks::Check;
#[derive(Hash)]
pub enum Mode {
@@ -35,34 +38,44 @@ pub fn fix_file(checks: &mut [Check], contents: &str) -> Option<String> {
fn apply_fixes<'a>(fixes: impl Iterator<Item = &'a mut Fix>, contents: &str) -> String {
let lines: Vec<&str> = contents.lines().collect();
let mut output = "".to_string();
let mut last_pos: Location = Location::new(0, 0);
let mut output: String = Default::default();
let mut last_pos: Location = Default::default();
let mut applied: BTreeSet<&Patch> = Default::default();
for fix in fixes.sorted_by_key(|fix| fix.location) {
// Best-effort approach: if this fix overlaps with a fix we've already applied, skip it.
if last_pos > fix.location {
for fix in fixes.sorted_by_key(|fix| fix.patch.location) {
// If we already applied an identical fix as part of another correction, skip any
// re-application.
if applied.contains(&fix.patch) {
fix.applied = true;
continue;
}
if fix.location.row() > last_pos.row() {
// Best-effort approach: if this fix overlaps with a fix we've already applied, skip it.
if last_pos > fix.patch.location {
continue;
}
if fix.patch.location.row() > last_pos.row() {
if last_pos.row() > 0 || last_pos.column() > 0 {
output.push_str(&lines[last_pos.row() - 1][last_pos.column() - 1..]);
output.push('\n');
}
for line in &lines[last_pos.row()..fix.location.row() - 1] {
for line in &lines[last_pos.row()..fix.patch.location.row() - 1] {
output.push_str(line);
output.push('\n');
}
output.push_str(&lines[fix.location.row() - 1][..fix.location.column() - 1]);
output.push_str(&fix.content);
output
.push_str(&lines[fix.patch.location.row() - 1][..fix.patch.location.column() - 1]);
output.push_str(&fix.patch.content);
} else {
output.push_str(
&lines[last_pos.row() - 1][last_pos.column() - 1..fix.location.column() - 1],
&lines[last_pos.row() - 1][last_pos.column() - 1..fix.patch.location.column() - 1],
);
output.push_str(&fix.content);
output.push_str(&fix.patch.content);
}
last_pos = fix.patch.end_location;
last_pos = fix.end_location;
applied.insert(&fix.patch);
fix.applied = true;
}
@@ -89,7 +102,8 @@ mod tests {
use rustpython_parser::ast::Location;
use crate::autofix::fixer::apply_fixes;
use crate::checks::Fix;
use crate::autofix::Fix;
use crate::autofix::Patch;
#[test]
fn empty_file() -> Result<()> {
@@ -105,9 +119,11 @@ mod tests {
#[test]
fn apply_single_replacement() -> Result<()> {
let mut fixes = vec![Fix {
content: "Bar".to_string(),
location: Location::new(1, 9),
end_location: Location::new(1, 15),
patch: Patch {
content: "Bar".to_string(),
location: Location::new(1, 9),
end_location: Location::new(1, 15),
},
applied: false,
}];
let actual = apply_fixes(
@@ -129,9 +145,11 @@ mod tests {
#[test]
fn apply_single_removal() -> Result<()> {
let mut fixes = vec![Fix {
content: "".to_string(),
location: Location::new(1, 8),
end_location: Location::new(1, 16),
patch: Patch {
content: "".to_string(),
location: Location::new(1, 8),
end_location: Location::new(1, 16),
},
applied: false,
}];
let actual = apply_fixes(
@@ -154,15 +172,19 @@ mod tests {
fn apply_double_removal() -> Result<()> {
let mut fixes = vec![
Fix {
content: "".to_string(),
location: Location::new(1, 8),
end_location: Location::new(1, 17),
patch: Patch {
content: "".to_string(),
location: Location::new(1, 8),
end_location: Location::new(1, 17),
},
applied: false,
},
Fix {
content: "".to_string(),
location: Location::new(1, 17),
end_location: Location::new(1, 24),
patch: Patch {
content: "".to_string(),
location: Location::new(1, 17),
end_location: Location::new(1, 24),
},
applied: false,
},
];
@@ -186,15 +208,19 @@ mod tests {
fn ignore_overlapping_fixes() -> Result<()> {
let mut fixes = vec![
Fix {
content: "".to_string(),
location: Location::new(1, 8),
end_location: Location::new(1, 16),
patch: Patch {
content: "".to_string(),
location: Location::new(1, 8),
end_location: Location::new(1, 16),
},
applied: false,
},
Fix {
content: "ignored".to_string(),
location: Location::new(1, 10),
end_location: Location::new(1, 12),
patch: Patch {
content: "ignored".to_string(),
location: Location::new(1, 10),
end_location: Location::new(1, 12),
},
applied: false,
},
];

View File

@@ -1,392 +0,0 @@
use anyhow::Result;
use itertools::Itertools;
use libcst_native::ImportNames::Aliases;
use libcst_native::NameOrAttribute::N;
use libcst_native::{Codegen, Expression, SmallStatement, Statement};
use rustpython_parser::ast::{ExcepthandlerKind, Expr, Keyword, Location, Stmt, StmtKind};
use rustpython_parser::lexer;
use rustpython_parser::token::Tok;
use crate::ast::operations::SourceCodeLocator;
use crate::ast::types::Range;
use crate::checks::Fix;
/// Convert a location within a file (relative to `base`) to an absolute position.
fn to_absolute(relative: &Location, base: &Location) -> Location {
if relative.row() == 1 {
Location::new(
relative.row() + base.row() - 1,
relative.column() + base.column() - 1,
)
} else {
Location::new(relative.row() + base.row() - 1, relative.column())
}
}
/// Generate a fix to remove a base from a ClassDef statement.
pub fn remove_class_def_base(
locator: &mut SourceCodeLocator,
stmt_at: &Location,
expr_at: Location,
bases: &[Expr],
keywords: &[Keyword],
) -> Option<Fix> {
let content = locator.slice_source_code_at(stmt_at);
// Case 1: `object` is the only base.
if bases.len() == 1 && keywords.is_empty() {
let mut fix_start = None;
let mut fix_end = None;
let mut count: usize = 0;
for (start, tok, end) in lexer::make_tokenizer(content).flatten() {
if matches!(tok, Tok::Lpar) {
if count == 0 {
fix_start = Some(to_absolute(&start, stmt_at));
}
count += 1;
}
if matches!(tok, Tok::Rpar) {
count -= 1;
if count == 0 {
fix_end = Some(to_absolute(&end, stmt_at));
break;
}
}
}
return match (fix_start, fix_end) {
(Some(start), Some(end)) => Some(Fix {
content: "".to_string(),
location: start,
end_location: end,
applied: false,
}),
_ => None,
};
}
if bases
.iter()
.map(|node| node.location)
.chain(keywords.iter().map(|node| node.location))
.any(|location| location > expr_at)
{
// Case 2: `object` is _not_ the last node.
let mut fix_start: Option<Location> = None;
let mut fix_end: Option<Location> = None;
let mut seen_comma = false;
for (start, tok, end) in lexer::make_tokenizer(content).flatten() {
let start = to_absolute(&start, stmt_at);
if seen_comma {
if matches!(tok, Tok::Newline) {
fix_end = Some(end);
} else {
fix_end = Some(start);
}
break;
}
if start == expr_at {
fix_start = Some(start);
}
if fix_start.is_some() && matches!(tok, Tok::Comma) {
seen_comma = true;
}
}
match (fix_start, fix_end) {
(Some(start), Some(end)) => Some(Fix {
content: "".to_string(),
location: start,
end_location: end,
applied: false,
}),
_ => None,
}
} else {
// Case 3: `object` is the last node, so we have to find the last token that isn't a comma.
let mut fix_start: Option<Location> = None;
let mut fix_end: Option<Location> = None;
for (start, tok, end) in lexer::make_tokenizer(content).flatten() {
let start = to_absolute(&start, stmt_at);
let end = to_absolute(&end, stmt_at);
if start == expr_at {
fix_end = Some(end);
break;
}
if matches!(tok, Tok::Comma) {
fix_start = Some(start);
}
}
match (fix_start, fix_end) {
(Some(start), Some(end)) => Some(Fix {
content: "".to_string(),
location: start,
end_location: end,
applied: false,
}),
_ => None,
}
}
}
pub fn remove_super_arguments(locator: &mut SourceCodeLocator, expr: &Expr) -> Option<Fix> {
let range = Range::from_located(expr);
let contents = locator.slice_source_code_range(&range);
let mut tree = match libcst_native::parse_module(contents, None) {
Ok(m) => m,
Err(_) => return None,
};
if let Some(Statement::Simple(body)) = tree.body.first_mut() {
if let Some(SmallStatement::Expr(body)) = body.body.first_mut() {
if let Expression::Call(body) = &mut body.value {
body.args = vec![];
body.whitespace_before_args = Default::default();
body.whitespace_after_func = Default::default();
let mut state = Default::default();
tree.codegen(&mut state);
return Some(Fix {
content: state.to_string(),
location: range.location,
end_location: range.end_location,
applied: false,
});
}
}
}
None
}
/// Determine if a body contains only a single statement, taking into account deleted.
fn has_single_child(body: &[Stmt], deleted: &[&Stmt]) -> bool {
body.iter().filter(|child| !deleted.contains(child)).count() == 1
}
/// Determine if a child is the only statement in its body.
fn is_lone_child(child: &Stmt, parent: &Stmt, deleted: &[&Stmt]) -> Result<bool> {
match &parent.node {
StmtKind::FunctionDef { body, .. }
| StmtKind::AsyncFunctionDef { body, .. }
| StmtKind::ClassDef { body, .. }
| StmtKind::With { body, .. }
| StmtKind::AsyncWith { body, .. } => {
if body.iter().contains(child) {
Ok(has_single_child(body, deleted))
} else {
Err(anyhow::anyhow!("Unable to find child in parent body."))
}
}
StmtKind::For { body, orelse, .. }
| StmtKind::AsyncFor { body, orelse, .. }
| StmtKind::While { body, orelse, .. }
| StmtKind::If { body, orelse, .. } => {
if body.iter().contains(child) {
Ok(has_single_child(body, deleted))
} else if orelse.iter().contains(child) {
Ok(has_single_child(orelse, deleted))
} else {
Err(anyhow::anyhow!("Unable to find child in parent body."))
}
}
StmtKind::Try {
body,
handlers,
orelse,
finalbody,
} => {
if body.iter().contains(child) {
Ok(has_single_child(body, deleted))
} else if orelse.iter().contains(child) {
Ok(has_single_child(orelse, deleted))
} else if finalbody.iter().contains(child) {
Ok(has_single_child(finalbody, deleted))
} else if let Some(body) = handlers.iter().find_map(|handler| match &handler.node {
ExcepthandlerKind::ExceptHandler { body, .. } => {
if body.iter().contains(child) {
Some(body)
} else {
None
}
}
}) {
Ok(has_single_child(body, deleted))
} else {
Err(anyhow::anyhow!("Unable to find child in parent body."))
}
}
_ => Err(anyhow::anyhow!("Unable to find child in parent body.")),
}
}
pub fn remove_stmt(stmt: &Stmt, parent: Option<&Stmt>, deleted: &[&Stmt]) -> Result<Fix> {
if parent
.map(|parent| is_lone_child(stmt, parent, deleted))
.map_or(Ok(None), |v| v.map(Some))?
.unwrap_or_default()
{
// If removing this node would lead to an invalid syntax tree, replace
// it with a `pass`.
Ok(Fix {
location: stmt.location,
end_location: stmt.end_location,
content: "pass".to_string(),
applied: false,
})
} else {
// Otherwise, nuke the entire line.
// TODO(charlie): This logic assumes that there are no multi-statement physical lines.
Ok(Fix {
location: Location::new(stmt.location.row(), 1),
end_location: Location::new(stmt.end_location.row() + 1, 1),
content: "".to_string(),
applied: false,
})
}
}
/// Generate a Fix to remove any unused imports from an `import` statement.
pub fn remove_unused_imports(
locator: &mut SourceCodeLocator,
full_names: &[&str],
stmt: &Stmt,
parent: Option<&Stmt>,
deleted: &[&Stmt],
) -> Result<Fix> {
let mut tree = match libcst_native::parse_module(
locator.slice_source_code_range(&Range::from_located(stmt)),
None,
) {
Ok(m) => m,
Err(_) => return Err(anyhow::anyhow!("Failed to extract CST from source.")),
};
let body = if let Some(Statement::Simple(body)) = tree.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!("Expected node to be: Statement::Simple."));
};
let body = if let Some(SmallStatement::Import(body)) = body.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!(
"Expected node to be: SmallStatement::ImportFrom."
));
};
let aliases = &mut body.names;
// Preserve the trailing comma (or not) from the last entry.
let trailing_comma = aliases.last().and_then(|alias| alias.comma.clone());
// Identify unused imports from within the `import from`.
let mut removable = vec![];
for (index, alias) in aliases.iter().enumerate() {
if let N(import_name) = &alias.name {
if full_names.contains(&import_name.value) {
removable.push(index);
}
}
}
// TODO(charlie): This is quadratic.
for index in removable.iter().rev() {
aliases.remove(*index);
}
if let Some(alias) = aliases.last_mut() {
alias.comma = trailing_comma;
}
if aliases.is_empty() {
remove_stmt(stmt, parent, deleted)
} else {
let mut state = Default::default();
tree.codegen(&mut state);
Ok(Fix {
content: state.to_string(),
location: stmt.location,
end_location: stmt.end_location,
applied: false,
})
}
}
/// Generate a Fix to remove any unused imports from an `import from` statement.
pub fn remove_unused_import_froms(
locator: &mut SourceCodeLocator,
full_names: &[&str],
stmt: &Stmt,
parent: Option<&Stmt>,
deleted: &[&Stmt],
) -> Result<Fix> {
let mut tree = match libcst_native::parse_module(
locator.slice_source_code_range(&Range::from_located(stmt)),
None,
) {
Ok(m) => m,
Err(_) => return Err(anyhow::anyhow!("Failed to extract CST from source.")),
};
let body = if let Some(Statement::Simple(body)) = tree.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!("Expected node to be: Statement::Simple."));
};
let body = if let Some(SmallStatement::ImportFrom(body)) = body.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!(
"Expected node to be: SmallStatement::ImportFrom."
));
};
let aliases = if let Aliases(aliases) = &mut body.names {
aliases
} else {
return Err(anyhow::anyhow!("Expected node to be: Aliases."));
};
// Preserve the trailing comma (or not) from the last entry.
let trailing_comma = aliases.last().and_then(|alias| alias.comma.clone());
// Identify unused imports from within the `import from`.
let mut removable = vec![];
for (index, alias) in aliases.iter().enumerate() {
if let N(name) = &alias.name {
let import_name = if let Some(N(module_name)) = &body.module {
format!("{}.{}", module_name.value, name.value)
} else {
name.value.to_string()
};
if full_names.contains(&import_name.as_str()) {
removable.push(index);
}
}
}
// TODO(charlie): This is quadratic.
for index in removable.iter().rev() {
aliases.remove(*index);
}
if let Some(alias) = aliases.last_mut() {
alias.comma = trailing_comma;
}
if aliases.is_empty() {
remove_stmt(stmt, parent, deleted)
} else {
let mut state = Default::default();
tree.codegen(&mut state);
Ok(Fix {
content: state.to_string(),
location: stmt.location,
end_location: stmt.end_location,
applied: false,
})
}
}

89
src/autofix/helpers.rs Normal file
View File

@@ -0,0 +1,89 @@
use anyhow::Result;
use itertools::Itertools;
use rustpython_parser::ast::{ExcepthandlerKind, Location, Stmt, StmtKind};
use crate::autofix::Fix;
/// Determine if a body contains only a single statement, taking into account deleted.
fn has_single_child(body: &[Stmt], deleted: &[&Stmt]) -> bool {
body.iter().filter(|child| !deleted.contains(child)).count() == 1
}
/// Determine if a child is the only statement in its body.
fn is_lone_child(child: &Stmt, parent: &Stmt, deleted: &[&Stmt]) -> Result<bool> {
match &parent.node {
StmtKind::FunctionDef { body, .. }
| StmtKind::AsyncFunctionDef { body, .. }
| StmtKind::ClassDef { body, .. }
| StmtKind::With { body, .. }
| StmtKind::AsyncWith { body, .. } => {
if body.iter().contains(child) {
Ok(has_single_child(body, deleted))
} else {
Err(anyhow::anyhow!("Unable to find child in parent body."))
}
}
StmtKind::For { body, orelse, .. }
| StmtKind::AsyncFor { body, orelse, .. }
| StmtKind::While { body, orelse, .. }
| StmtKind::If { body, orelse, .. } => {
if body.iter().contains(child) {
Ok(has_single_child(body, deleted))
} else if orelse.iter().contains(child) {
Ok(has_single_child(orelse, deleted))
} else {
Err(anyhow::anyhow!("Unable to find child in parent body."))
}
}
StmtKind::Try {
body,
handlers,
orelse,
finalbody,
} => {
if body.iter().contains(child) {
Ok(has_single_child(body, deleted))
} else if orelse.iter().contains(child) {
Ok(has_single_child(orelse, deleted))
} else if finalbody.iter().contains(child) {
Ok(has_single_child(finalbody, deleted))
} else if let Some(body) = handlers.iter().find_map(|handler| match &handler.node {
ExcepthandlerKind::ExceptHandler { body, .. } => {
if body.iter().contains(child) {
Some(body)
} else {
None
}
}
}) {
Ok(has_single_child(body, deleted))
} else {
Err(anyhow::anyhow!("Unable to find child in parent body."))
}
}
_ => Err(anyhow::anyhow!("Unable to find child in parent body.")),
}
}
pub fn remove_stmt(stmt: &Stmt, parent: Option<&Stmt>, deleted: &[&Stmt]) -> Result<Fix> {
if parent
.map(|parent| is_lone_child(stmt, parent, deleted))
.map_or(Ok(None), |v| v.map(Some))?
.unwrap_or_default()
{
// If removing this node would lead to an invalid syntax tree, replace
// it with a `pass`.
Ok(Fix::replacement(
"pass".to_string(),
stmt.location,
stmt.end_location.unwrap(),
))
} else {
// Otherwise, nuke the entire line.
// TODO(charlie): This logic assumes that there are no multi-statement physical lines.
Ok(Fix::deletion(
Location::new(stmt.location.row(), 1),
Location::new(stmt.end_location.unwrap().row() + 1, 1),
))
}
}

53
src/autofix/mod.rs Normal file
View File

@@ -0,0 +1,53 @@
use rustpython_ast::Location;
use serde::{Deserialize, Serialize};
pub mod fixer;
pub mod helpers;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Patch {
pub content: String,
pub location: Location,
pub end_location: Location,
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Fix {
pub patch: Patch,
pub applied: bool,
}
impl Fix {
pub fn deletion(start: Location, end: Location) -> Self {
Self {
patch: Patch {
content: "".to_string(),
location: start,
end_location: end,
},
applied: false,
}
}
pub fn replacement(content: String, start: Location, end: Location) -> Self {
Self {
patch: Patch {
content,
location: start,
end_location: end,
},
applied: false,
}
}
pub fn insertion(content: String, at: Location) -> Self {
Self {
patch: Patch {
content,
location: at,
end_location: at,
},
applied: false,
}
}
}

View File

@@ -17,15 +17,18 @@ use crate::ast::types::{
ScopeKind,
};
use crate::ast::visitor::{walk_excepthandler, Visitor};
use crate::ast::{checkers, helpers, operations, visitor};
use crate::autofix::{fixer, fixes};
use crate::ast::{helpers, operations, visitor};
use crate::autofix::fixer;
use crate::checks::{Check, CheckCode, CheckKind};
use crate::docstrings::definition::{Definition, DefinitionKind, Documentable};
use crate::python::builtins::{BUILTINS, MAGIC_GLOBALS};
use crate::python::future::ALL_FEATURE_NAMES;
use crate::settings::{PythonVersion, Settings};
use crate::visibility::{module_visibility, transition_scope, Modifier, Visibility, VisibleScope};
use crate::{docstrings, plugins};
use crate::{
docstrings, flake8_bugbear, flake8_builtins, flake8_comprehensions, flake8_print, pep8_naming,
pycodestyle, pydocstyle, pyflakes, pyupgrade,
};
pub const GLOBAL_SCOPE_INDEX: usize = 0;
@@ -162,25 +165,26 @@ where
if self.settings.enabled.contains(&CheckCode::E741) {
let location = self.locate_check(Range::from_located(stmt));
self.checks.extend(
names
.iter()
.filter_map(|name| checkers::ambiguous_variable_name(name, location)),
);
self.checks.extend(names.iter().filter_map(|name| {
pycodestyle::checks::ambiguous_variable_name(name, location)
}));
}
}
StmtKind::Break => {
if self.settings.enabled.contains(&CheckCode::F701) {
if let Some(check) =
checkers::break_outside_loop(stmt, &self.parents, &self.parent_stack, self)
{
if let Some(check) = pyflakes::checks::break_outside_loop(
stmt,
&self.parents,
&self.parent_stack,
self,
) {
self.checks.push(check);
}
}
}
StmtKind::Continue => {
if self.settings.enabled.contains(&CheckCode::F702) {
if let Some(check) = checkers::continue_outside_loop(
if let Some(check) = pyflakes::checks::continue_outside_loop(
stmt,
&self.parents,
&self.parent_stack,
@@ -205,7 +209,7 @@ where
..
} => {
if self.settings.enabled.contains(&CheckCode::E743) {
if let Some(check) = checkers::ambiguous_function_name(
if let Some(check) = pycodestyle::checks::ambiguous_function_name(
name,
self.locate_check(Range::from_located(stmt)),
) {
@@ -214,23 +218,25 @@ where
}
if self.settings.enabled.contains(&CheckCode::N802) {
if let Some(check) = checkers::invalid_function_name(stmt, name) {
if let Some(check) = pep8_naming::checks::invalid_function_name(stmt, name) {
self.checks.push(check);
}
}
if self.settings.enabled.contains(&CheckCode::N804) {
if let Some(check) = checkers::invalid_first_argument_name_for_class_method(
self.current_scope(),
decorator_list,
args,
) {
if let Some(check) =
pep8_naming::checks::invalid_first_argument_name_for_class_method(
self.current_scope(),
decorator_list,
args,
)
{
self.checks.push(check);
}
}
if self.settings.enabled.contains(&CheckCode::N805) {
if let Some(check) = checkers::invalid_first_argument_name_for_method(
if let Some(check) = pep8_naming::checks::invalid_first_argument_name_for_method(
self.current_scope(),
decorator_list,
args,
@@ -311,11 +317,13 @@ where
..
} => {
if self.settings.enabled.contains(&CheckCode::U004) {
plugins::useless_object_inheritance(self, stmt, name, bases, keywords);
pyupgrade::plugins::useless_object_inheritance(
self, stmt, name, bases, keywords,
);
}
if self.settings.enabled.contains(&CheckCode::E742) {
if let Some(check) = checkers::ambiguous_class_name(
if let Some(check) = pycodestyle::checks::ambiguous_class_name(
name,
self.locate_check(Range::from_located(stmt)),
) {
@@ -324,7 +332,7 @@ where
}
if self.settings.enabled.contains(&CheckCode::N801) {
if let Some(check) = checkers::invalid_class_name(stmt, name) {
if let Some(check) = pep8_naming::checks::invalid_class_name(stmt, name) {
self.checks.push(check);
}
}
@@ -347,8 +355,8 @@ where
self.push_scope(Scope::new(ScopeKind::Class))
}
StmtKind::Import { names } => {
if self.seen_import_boundary && stmt.location.column() == 1 {
if self.settings.enabled.contains(&CheckCode::E402) {
if self.settings.enabled.contains(&CheckCode::E402) {
if self.seen_import_boundary && stmt.location.column() == 1 {
self.checks.push(Check::new(
CheckKind::ModuleImportNotAtTopOfFile,
self.locate_check(Range::from_located(stmt)),
@@ -404,8 +412,8 @@ where
module,
level,
} => {
if self.seen_import_boundary && stmt.location.column() == 1 {
if self.settings.enabled.contains(&CheckCode::E402) {
if self.settings.enabled.contains(&CheckCode::E402) {
if self.seen_import_boundary && stmt.location.column() == 1 {
self.checks.push(Check::new(
CheckKind::ModuleImportNotAtTopOfFile,
self.locate_check(Range::from_located(stmt)),
@@ -436,13 +444,13 @@ where
self.annotations_future_enabled = true;
}
if self.settings.enabled.contains(&CheckCode::F407)
&& !ALL_FEATURE_NAMES.contains(&alias.node.name.deref())
{
self.checks.push(Check::new(
CheckKind::FutureFeatureNotDefined(alias.node.name.to_string()),
self.locate_check(Range::from_located(stmt)),
));
if self.settings.enabled.contains(&CheckCode::F407) {
if !ALL_FEATURE_NAMES.contains(&alias.node.name.deref()) {
self.checks.push(Check::new(
CheckKind::FutureFeatureNotDefined(alias.node.name.to_string()),
self.locate_check(Range::from_located(stmt)),
));
}
}
if self.settings.enabled.contains(&CheckCode::F404) && !self.futures_allowed
@@ -522,7 +530,7 @@ where
StmtKind::Raise { exc, .. } => {
if self.settings.enabled.contains(&CheckCode::F901) {
if let Some(expr) = exc {
if let Some(check) = checkers::raise_not_implemented(expr) {
if let Some(check) = pyflakes::checks::raise_not_implemented(expr) {
self.checks.push(check);
}
}
@@ -533,32 +541,32 @@ where
}
StmtKind::If { test, .. } => {
if self.settings.enabled.contains(&CheckCode::F634) {
plugins::if_tuple(self, stmt, test);
pyflakes::plugins::if_tuple(self, stmt, test);
}
}
StmtKind::Assert { test, msg } => {
if self.settings.enabled.contains(&CheckCode::F631) {
plugins::assert_tuple(self, stmt, test);
pyflakes::plugins::assert_tuple(self, stmt, test);
}
if self.settings.enabled.contains(&CheckCode::B011) {
plugins::assert_false(self, stmt, test, msg);
flake8_bugbear::plugins::assert_false(self, stmt, test, msg);
}
}
StmtKind::Try { handlers, .. } => {
if self.settings.enabled.contains(&CheckCode::F707) {
if let Some(check) = checkers::default_except_not_last(handlers) {
if let Some(check) = pyflakes::checks::default_except_not_last(handlers) {
self.checks.push(check);
}
}
if self.settings.enabled.contains(&CheckCode::B014)
|| self.settings.enabled.contains(&CheckCode::B025)
{
plugins::duplicate_exceptions(self, stmt, handlers);
flake8_bugbear::plugins::duplicate_exceptions(self, stmt, handlers);
}
}
StmtKind::Assign { targets, value, .. } => {
if self.settings.enabled.contains(&CheckCode::E731) {
if let Some(check) = checkers::do_not_assign_lambda(
if let Some(check) = pycodestyle::checks::do_not_assign_lambda(
value,
self.locate_check(Range::from_located(stmt)),
) {
@@ -566,13 +574,13 @@ where
}
}
if self.settings.enabled.contains(&CheckCode::U001) {
plugins::useless_metaclass_type(self, stmt, value, targets);
pyupgrade::plugins::useless_metaclass_type(self, stmt, value, targets);
}
}
StmtKind::AnnAssign { value, .. } => {
if self.settings.enabled.contains(&CheckCode::E731) {
if let Some(value) = value {
if let Some(check) = checkers::do_not_assign_lambda(
if let Some(check) = pycodestyle::checks::do_not_assign_lambda(
value,
self.locate_check(Range::from_located(stmt)),
) {
@@ -586,7 +594,7 @@ where
}
// Recurse.
let prev_visibile_scope = self.visible_scope.clone();
let prev_visible_scope = self.visible_scope.clone();
match &stmt.node {
StmtKind::FunctionDef { body, .. } | StmtKind::AsyncFunctionDef { body, .. } => {
let definition = docstrings::extraction::extract(
@@ -644,7 +652,7 @@ where
}
_ => visitor::walk_stmt(self, stmt),
};
self.visible_scope = prev_visibile_scope;
self.visible_scope = prev_visible_scope;
// Post-visit.
if let StmtKind::ClassDef { name, .. } = &stmt.node {
@@ -699,7 +707,7 @@ where
if self.settings.enabled.contains(&CheckCode::U007)
&& self.settings.target_version >= PythonVersion::Py39
{
plugins::use_pep604_annotation(self, expr, value, slice);
pyupgrade::plugins::use_pep604_annotation(self, expr, value, slice);
}
if match_name_or_attr(value, "Literal") {
@@ -712,7 +720,7 @@ where
self.settings.enabled.contains(&CheckCode::F621);
let check_two_starred_expressions =
self.settings.enabled.contains(&CheckCode::F622);
if let Some(check) = checkers::starred_expressions(
if let Some(check) = pyflakes::checks::starred_expressions(
elts,
check_too_many_expressions,
check_two_starred_expressions,
@@ -728,14 +736,14 @@ where
if self.settings.enabled.contains(&CheckCode::U006)
&& self.settings.target_version >= PythonVersion::Py39
{
plugins::use_pep585_annotation(self, expr, id);
pyupgrade::plugins::use_pep585_annotation(self, expr, id);
}
self.handle_node_load(expr);
}
ExprContext::Store => {
if self.settings.enabled.contains(&CheckCode::E741) {
if let Some(check) = checkers::ambiguous_variable_name(
if let Some(check) = pycodestyle::checks::ambiguous_variable_name(
id,
self.locate_check(Range::from_located(expr)),
) {
@@ -756,7 +764,7 @@ where
{
if let ExprKind::Name { id, .. } = &value.node {
if id == "typing" {
plugins::use_pep585_annotation(self, expr, attr);
pyupgrade::plugins::use_pep585_annotation(self, expr, attr);
}
}
}
@@ -768,43 +776,51 @@ where
..
} => {
if self.settings.enabled.contains(&CheckCode::U005) {
plugins::deprecated_unittest_alias(self, func);
pyupgrade::plugins::deprecated_unittest_alias(self, func);
}
// flake8-super
if self.settings.enabled.contains(&CheckCode::U008) {
plugins::super_call_with_parameters(self, expr, func, args);
pyupgrade::plugins::super_call_with_parameters(self, expr, func, args);
}
// flake8-print
if self.settings.enabled.contains(&CheckCode::T201)
|| self.settings.enabled.contains(&CheckCode::T203)
{
plugins::print_call(self, expr, func);
flake8_print::plugins::print_call(self, expr, func);
}
// flake8-comprehensions
if self.settings.enabled.contains(&CheckCode::C400) {
if let Some(check) = checkers::unnecessary_generator_list(expr, func, args) {
if let Some(check) =
flake8_comprehensions::checks::unnecessary_generator_list(expr, func, args)
{
self.checks.push(check);
};
}
if self.settings.enabled.contains(&CheckCode::C401) {
if let Some(check) = checkers::unnecessary_generator_set(expr, func, args) {
if let Some(check) =
flake8_comprehensions::checks::unnecessary_generator_set(expr, func, args)
{
self.checks.push(check);
};
}
if self.settings.enabled.contains(&CheckCode::C402) {
if let Some(check) = checkers::unnecessary_generator_dict(expr, func, args) {
if let Some(check) =
flake8_comprehensions::checks::unnecessary_generator_dict(expr, func, args)
{
self.checks.push(check);
};
}
if self.settings.enabled.contains(&CheckCode::C403) {
if let Some(check) =
checkers::unnecessary_list_comprehension_set(expr, func, args)
flake8_comprehensions::checks::unnecessary_list_comprehension_set(
expr, func, args,
)
{
self.checks.push(check);
};
@@ -812,35 +828,43 @@ where
if self.settings.enabled.contains(&CheckCode::C404) {
if let Some(check) =
checkers::unnecessary_list_comprehension_dict(expr, func, args)
flake8_comprehensions::checks::unnecessary_list_comprehension_dict(
expr, func, args,
)
{
self.checks.push(check);
};
}
if self.settings.enabled.contains(&CheckCode::C405) {
if let Some(check) = checkers::unnecessary_literal_set(expr, func, args) {
if let Some(check) =
flake8_comprehensions::checks::unnecessary_literal_set(expr, func, args)
{
self.checks.push(check);
};
}
if self.settings.enabled.contains(&CheckCode::C406) {
if let Some(check) = checkers::unnecessary_literal_dict(expr, func, args) {
if let Some(check) =
flake8_comprehensions::checks::unnecessary_literal_dict(expr, func, args)
{
self.checks.push(check);
};
}
if self.settings.enabled.contains(&CheckCode::C408) {
if let Some(check) =
checkers::unnecessary_collection_call(expr, func, args, keywords)
{
if let Some(check) = flake8_comprehensions::checks::unnecessary_collection_call(
expr, func, args, keywords,
) {
self.checks.push(check);
};
}
if self.settings.enabled.contains(&CheckCode::C409) {
if let Some(check) =
checkers::unnecessary_literal_within_tuple_call(expr, func, args)
flake8_comprehensions::checks::unnecessary_literal_within_tuple_call(
expr, func, args,
)
{
self.checks.push(check);
};
@@ -848,20 +872,27 @@ where
if self.settings.enabled.contains(&CheckCode::C410) {
if let Some(check) =
checkers::unnecessary_literal_within_list_call(expr, func, args)
flake8_comprehensions::checks::unnecessary_literal_within_list_call(
expr, func, args,
)
{
self.checks.push(check);
};
}
if self.settings.enabled.contains(&CheckCode::C411) {
if let Some(check) = checkers::unnecessary_list_call(expr, func, args) {
if let Some(check) =
flake8_comprehensions::checks::unnecessary_list_call(expr, func, args)
{
self.checks.push(check);
};
}
if self.settings.enabled.contains(&CheckCode::C413) {
if let Some(check) = checkers::unnecessary_call_around_sorted(expr, func, args)
if let Some(check) =
flake8_comprehensions::checks::unnecessary_call_around_sorted(
expr, func, args,
)
{
self.checks.push(check);
};
@@ -869,21 +900,28 @@ where
if self.settings.enabled.contains(&CheckCode::C414) {
if let Some(check) =
checkers::unnecessary_double_cast_or_process(expr, func, args)
flake8_comprehensions::checks::unnecessary_double_cast_or_process(
expr, func, args,
)
{
self.checks.push(check);
};
}
if self.settings.enabled.contains(&CheckCode::C415) {
if let Some(check) = checkers::unnecessary_subscript_reversal(expr, func, args)
if let Some(check) =
flake8_comprehensions::checks::unnecessary_subscript_reversal(
expr, func, args,
)
{
self.checks.push(check);
};
}
if self.settings.enabled.contains(&CheckCode::C417) {
if let Some(check) = checkers::unnecessary_map(expr, func, args) {
if let Some(check) =
flake8_comprehensions::checks::unnecessary_map(expr, func, args)
{
self.checks.push(check);
};
}
@@ -892,11 +930,11 @@ where
if self.settings.enabled.contains(&CheckCode::U002)
&& self.settings.target_version >= PythonVersion::Py310
{
plugins::unnecessary_abspath(self, expr, func, args);
pyupgrade::plugins::unnecessary_abspath(self, expr, func, args);
}
if self.settings.enabled.contains(&CheckCode::U003) {
plugins::type_of_primitive(self, expr, func, args);
pyupgrade::plugins::type_of_primitive(self, expr, func, args);
}
if let ExprKind::Name { id, ctx } = &func.node {
@@ -918,7 +956,7 @@ where
let check_repeated_literals = self.settings.enabled.contains(&CheckCode::F601);
let check_repeated_variables = self.settings.enabled.contains(&CheckCode::F602);
if check_repeated_literals || check_repeated_variables {
self.checks.extend(checkers::repeated_keys(
self.checks.extend(pyflakes::checks::repeated_keys(
keys,
check_repeated_literals,
check_repeated_variables,
@@ -958,14 +996,14 @@ where
..
} => {
if self.settings.enabled.contains(&CheckCode::F633) {
plugins::invalid_print_syntax(self, left);
pyflakes::plugins::invalid_print_syntax(self, left);
}
}
ExprKind::UnaryOp { op, operand } => {
let check_not_in = self.settings.enabled.contains(&CheckCode::E713);
let check_not_is = self.settings.enabled.contains(&CheckCode::E714);
if check_not_in || check_not_is {
self.checks.extend(checkers::not_tests(
self.checks.extend(pycodestyle::checks::not_tests(
op,
operand,
check_not_in,
@@ -982,7 +1020,7 @@ where
let check_none_comparisons = self.settings.enabled.contains(&CheckCode::E711);
let check_true_false_comparisons = self.settings.enabled.contains(&CheckCode::E712);
if check_none_comparisons || check_true_false_comparisons {
self.checks.extend(checkers::literal_comparisons(
self.checks.extend(pycodestyle::checks::literal_comparisons(
left,
ops,
comparators,
@@ -993,7 +1031,7 @@ where
}
if self.settings.enabled.contains(&CheckCode::F632) {
self.checks.extend(checkers::is_literal(
self.checks.extend(pyflakes::checks::is_literal(
left,
ops,
comparators,
@@ -1002,7 +1040,7 @@ where
}
if self.settings.enabled.contains(&CheckCode::E721) {
self.checks.extend(checkers::type_comparison(
self.checks.extend(pycodestyle::checks::type_comparison(
ops,
comparators,
self.locate_check(Range::from_located(expr)),
@@ -1055,8 +1093,9 @@ where
ExprKind::ListComp { elt, generators } | ExprKind::SetComp { elt, generators } => {
if self.settings.enabled.contains(&CheckCode::C416) {
if let Some(check) = checkers::unnecessary_comprehension(expr, elt, generators)
{
if let Some(check) = flake8_comprehensions::checks::unnecessary_comprehension(
expr, elt, generators,
) {
self.checks.push(check);
};
}
@@ -1239,7 +1278,7 @@ where
match name {
Some(name) => {
if self.settings.enabled.contains(&CheckCode::E741) {
if let Some(check) = checkers::ambiguous_variable_name(
if let Some(check) = pycodestyle::checks::ambiguous_variable_name(
name,
self.locate_check(Range::from_located(excepthandler)),
) {
@@ -1257,7 +1296,7 @@ where
self.handle_node_store(
&Expr::new(
excepthandler.location,
excepthandler.end_location,
excepthandler.end_location.unwrap(),
ExprKind::Name {
id: name.to_string(),
ctx: ExprContext::Store,
@@ -1271,7 +1310,7 @@ where
self.handle_node_store(
&Expr::new(
excepthandler.location,
excepthandler.end_location,
excepthandler.end_location.unwrap(),
ExprKind::Name {
id: name.to_string(),
ctx: ExprContext::Store,
@@ -1285,13 +1324,13 @@ where
let scope = &mut self.scopes
[*(self.scope_stack.last().expect("No current scope found."))];
if let Some(binding) = &scope.values.remove(name) {
if self.settings.enabled.contains(&CheckCode::F841)
&& binding.used.is_none()
{
self.checks.push(Check::new(
CheckKind::UnusedVariable(name.to_string()),
Range::from_located(excepthandler),
));
if binding.used.is_none() {
if self.settings.enabled.contains(&CheckCode::F841) {
self.checks.push(Check::new(
CheckKind::UnusedVariable(name.to_string()),
Range::from_located(excepthandler),
));
}
}
}
@@ -1307,7 +1346,8 @@ where
fn visit_arguments(&mut self, arguments: &'b Arguments) {
if self.settings.enabled.contains(&CheckCode::F831) {
self.checks.extend(checkers::duplicate_arguments(arguments));
self.checks
.extend(pyflakes::checks::duplicate_arguments(arguments));
}
// Bind, but intentionally avoid walking default expressions, as we handle them upstream.
@@ -1340,7 +1380,7 @@ where
);
if self.settings.enabled.contains(&CheckCode::E741) {
if let Some(check) = checkers::ambiguous_variable_name(
if let Some(check) = pycodestyle::checks::ambiguous_variable_name(
&arg.node.arg,
self.locate_check(Range::from_located(arg)),
) {
@@ -1350,7 +1390,7 @@ where
if self.settings.enabled.contains(&CheckCode::N803) {
if let Some(check) =
checkers::invalid_argument_name(Range::from_located(arg), &arg.node.arg)
pep8_naming::checks::invalid_argument_name(Range::from_located(arg), &arg.node.arg)
{
self.checks.push(check);
}
@@ -1486,24 +1526,25 @@ impl<'a> Checker<'a> {
let binding = match scope.values.get(&name) {
None => binding,
Some(existing) => {
if self.settings.enabled.contains(&CheckCode::F402)
&& matches!(binding.kind, BindingKind::LoopVar)
&& matches!(
existing.kind,
BindingKind::Importation(_, _, _)
| BindingKind::FromImportation(_, _, _)
| BindingKind::SubmoduleImportation(_, _, _)
| BindingKind::StarImportation
| BindingKind::FutureImportation
)
{
self.checks.push(Check::new(
CheckKind::ImportShadowedByLoopVar(
name.clone(),
existing.range.location.row(),
),
binding.range,
));
if self.settings.enabled.contains(&CheckCode::F402) {
if matches!(binding.kind, BindingKind::LoopVar)
&& matches!(
existing.kind,
BindingKind::Importation(_, _, _)
| BindingKind::FromImportation(_, _, _)
| BindingKind::SubmoduleImportation(_, _, _)
| BindingKind::StarImportation
| BindingKind::FutureImportation
)
{
self.checks.push(Check::new(
CheckKind::ImportShadowedByLoopVar(
name.clone(),
existing.range.location.row(),
),
binding.range,
));
}
}
Binding {
kind: binding.kind,
@@ -1590,19 +1631,20 @@ impl<'a> Checker<'a> {
let current =
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
if self.settings.enabled.contains(&CheckCode::F823)
&& matches!(current.kind, ScopeKind::Function(_))
&& !current.values.contains_key(id)
{
for scope in self.scopes.iter().rev().skip(1) {
if matches!(scope.kind, ScopeKind::Function(_) | ScopeKind::Module) {
if let Some(binding) = scope.values.get(id) {
if let Some((scope_id, location)) = binding.used {
if scope_id == current.id {
self.checks.push(Check::new(
CheckKind::UndefinedLocal(id.clone()),
self.locate_check(location),
));
if self.settings.enabled.contains(&CheckCode::F823) {
if matches!(current.kind, ScopeKind::Function(_))
&& !current.values.contains_key(id)
{
for scope in self.scopes.iter().rev().skip(1) {
if matches!(scope.kind, ScopeKind::Function(_) | ScopeKind::Module) {
if let Some(binding) = scope.values.get(id) {
if let Some((scope_id, location)) = binding.used {
if scope_id == current.id {
self.checks.push(Check::new(
CheckKind::UndefinedLocal(id.clone()),
self.locate_check(location),
));
}
}
}
}
@@ -1734,11 +1776,13 @@ impl<'a> Checker<'a> {
if let Ok(mut expr) = parser::parse_expression(expression, "<filename>") {
relocate_expr(&mut expr, range);
allocator.push(expr);
} else if self.settings.enabled.contains(&CheckCode::F722) {
self.checks.push(Check::new(
CheckKind::ForwardAnnotationSyntaxError(expression.to_string()),
self.locate_check(range),
));
} else {
if self.settings.enabled.contains(&CheckCode::F722) {
self.checks.push(Check::new(
CheckKind::ForwardAnnotationSyntaxError(expression.to_string()),
self.locate_check(range),
));
}
}
}
for expr in allocator {
@@ -1792,7 +1836,7 @@ impl<'a> Checker<'a> {
fn check_deferred_assignments(&mut self) {
if self.settings.enabled.contains(&CheckCode::F841) {
while let Some(index) = self.deferred_assignments.pop() {
self.checks.extend(checkers::unused_variables(
self.checks.extend(pyflakes::checks::unused_variables(
&self.scopes[index],
self,
&self.settings.dummy_variable_rgx,
@@ -1818,18 +1862,17 @@ impl<'a> Checker<'a> {
_ => None,
});
if self.settings.enabled.contains(&CheckCode::F822)
&& !scope.import_starred
&& !self.path.ends_with("__init__.py")
{
if let Some(all_binding) = all_binding {
if let Some(names) = all_names {
for name in names {
if !scope.values.contains_key(name) {
self.checks.push(Check::new(
CheckKind::UndefinedExport(name.to_string()),
self.locate_check(all_binding.range),
));
if self.settings.enabled.contains(&CheckCode::F822) {
if !scope.import_starred && !self.path.ends_with("__init__.py") {
if let Some(all_binding) = all_binding {
if let Some(names) = all_names {
for name in names {
if !scope.values.contains_key(name) {
self.checks.push(Check::new(
CheckKind::UndefinedExport(name.to_string()),
self.locate_check(all_binding.range),
));
}
}
}
}
@@ -1912,8 +1955,8 @@ impl<'a> Checker<'a> {
.collect();
let removal_fn = match kind {
ImportKind::Import => fixes::remove_unused_imports,
ImportKind::ImportFrom => fixes::remove_unused_import_froms,
ImportKind::Import => pyflakes::fixes::remove_unused_imports,
ImportKind::ImportFrom => pyflakes::fixes::remove_unused_import_froms,
};
match removal_fn(&mut self.locator, &full_names, child, parent, &deleted) {
@@ -1943,66 +1986,66 @@ impl<'a> Checker<'a> {
fn check_docstrings(&mut self) {
while let Some((docstring, visibility)) = self.docstrings.pop() {
if !docstrings::plugins::not_empty(self, &docstring) {
if !pydocstyle::plugins::not_empty(self, &docstring) {
continue;
}
if !docstrings::plugins::not_missing(self, &docstring, &visibility) {
if !pydocstyle::plugins::not_missing(self, &docstring, &visibility) {
continue;
}
if self.settings.enabled.contains(&CheckCode::D200) {
docstrings::plugins::one_liner(self, &docstring);
pydocstyle::plugins::one_liner(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D201)
|| self.settings.enabled.contains(&CheckCode::D202)
{
docstrings::plugins::blank_before_after_function(self, &docstring);
pydocstyle::plugins::blank_before_after_function(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D203)
|| self.settings.enabled.contains(&CheckCode::D204)
|| self.settings.enabled.contains(&CheckCode::D211)
{
docstrings::plugins::blank_before_after_class(self, &docstring);
pydocstyle::plugins::blank_before_after_class(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D205) {
docstrings::plugins::blank_after_summary(self, &docstring);
pydocstyle::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)
{
docstrings::plugins::indent(self, &docstring);
pydocstyle::plugins::indent(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D209) {
docstrings::plugins::newline_after_last_paragraph(self, &docstring);
pydocstyle::plugins::newline_after_last_paragraph(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D210) {
docstrings::plugins::no_surrounding_whitespace(self, &docstring);
pydocstyle::plugins::no_surrounding_whitespace(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D212)
|| self.settings.enabled.contains(&CheckCode::D213)
{
docstrings::plugins::multi_line_summary_start(self, &docstring);
pydocstyle::plugins::multi_line_summary_start(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D300) {
docstrings::plugins::triple_quotes(self, &docstring);
pydocstyle::plugins::triple_quotes(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D400) {
docstrings::plugins::ends_with_period(self, &docstring);
pydocstyle::plugins::ends_with_period(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D402) {
docstrings::plugins::no_signature(self, &docstring);
pydocstyle::plugins::no_signature(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D403) {
docstrings::plugins::capitalized(self, &docstring);
pydocstyle::plugins::capitalized(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D404) {
docstrings::plugins::starts_with_this(self, &docstring);
pydocstyle::plugins::starts_with_this(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D415) {
docstrings::plugins::ends_with_punctuation(self, &docstring);
pydocstyle::plugins::ends_with_punctuation(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D418) {
docstrings::plugins::if_needed(self, &docstring);
pydocstyle::plugins::if_needed(self, &docstring);
}
if self.settings.enabled.contains(&CheckCode::D212)
|| self.settings.enabled.contains(&CheckCode::D214)
@@ -2020,41 +2063,41 @@ impl<'a> Checker<'a> {
|| self.settings.enabled.contains(&CheckCode::D416)
|| self.settings.enabled.contains(&CheckCode::D417)
{
docstrings::plugins::sections(self, &docstring);
pydocstyle::plugins::sections(self, &docstring);
}
}
}
fn check_builtin_shadowing(&mut self, name: &str, location: Range, is_attribute: bool) {
// flake8-builtins
if is_attribute && matches!(self.current_scope().kind, ScopeKind::Class) {
if self.settings.enabled.contains(&CheckCode::A003) {
if let Some(check) = checkers::builtin_shadowing(
if let Some(check) = flake8_builtins::checks::builtin_shadowing(
name,
self.locate_check(location),
checkers::ShadowingType::Attribute,
flake8_builtins::types::ShadowingType::Attribute,
) {
self.checks.push(check);
}
}
} else if self.settings.enabled.contains(&CheckCode::A001) {
if let Some(check) = checkers::builtin_shadowing(
name,
self.locate_check(location),
checkers::ShadowingType::Variable,
) {
self.checks.push(check);
} else {
if self.settings.enabled.contains(&CheckCode::A001) {
if let Some(check) = flake8_builtins::checks::builtin_shadowing(
name,
self.locate_check(location),
flake8_builtins::types::ShadowingType::Variable,
) {
self.checks.push(check);
}
}
}
}
fn check_builtin_arg_shadowing(&mut self, name: &str, location: Range) {
// flake8-builtins
if self.settings.enabled.contains(&CheckCode::A002) {
if let Some(check) = checkers::builtin_shadowing(
if let Some(check) = flake8_builtins::checks::builtin_shadowing(
name,
self.locate_check(location),
checkers::ShadowingType::Argument,
flake8_builtins::types::ShadowingType::Argument,
) {
self.checks.push(check);
}

View File

@@ -4,7 +4,8 @@ use rustpython_parser::ast::Location;
use crate::ast::types::Range;
use crate::autofix::fixer;
use crate::checks::{Check, CheckCode, CheckKind, Fix};
use crate::autofix::Fix;
use crate::checks::{Check, CheckCode, CheckKind};
use crate::noqa;
use crate::noqa::Directive;
use crate::settings::Settings;
@@ -166,15 +167,10 @@ pub fn check_lines(
},
);
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
check.amend(Fix {
content: "".to_string(),
location: Location::new(row + 1, start + 1),
end_location: Location::new(
row + 1,
lines[row].chars().count() + 1,
),
applied: false,
});
check.amend(Fix::deletion(
Location::new(row + 1, start + 1),
Location::new(row + 1, lines[row].chars().count() + 1),
));
}
line_checks.push(check);
}
@@ -200,25 +196,16 @@ pub fn check_lines(
);
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if valid_codes.is_empty() {
check.amend(Fix {
content: "".to_string(),
location: Location::new(row + 1, start + 1),
end_location: Location::new(
row + 1,
lines[row].chars().count() + 1,
),
applied: false,
});
check.amend(Fix::deletion(
Location::new(row + 1, start + 1),
Location::new(row + 1, lines[row].chars().count() + 1),
));
} else {
check.amend(Fix {
content: format!(" # noqa: {}", valid_codes.join(", ")),
location: Location::new(row + 1, start + 1),
end_location: Location::new(
row + 1,
lines[row].chars().count() + 1,
),
applied: false,
});
check.amend(Fix::replacement(
format!(" # noqa: {}", valid_codes.join(", ")),
Location::new(row + 1, start + 1),
Location::new(row + 1, lines[row].chars().count() + 1),
));
}
}
line_checks.push(check);

View File

@@ -5,8 +5,9 @@ use rustpython_parser::ast::Location;
use serde::{Deserialize, Serialize};
use strum_macros::{AsRefStr, EnumIter, EnumString};
use crate::ast::checkers::Primitive;
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::pyupgrade::types::Primitive;
#[derive(
AsRefStr,
@@ -211,18 +212,29 @@ pub enum CheckKind {
AmbiguousClassName(String),
AmbiguousFunctionName(String),
AmbiguousVariableName(String),
DoNotAssignLambda,
DoNotUseBareExcept,
IOError(String),
LineTooLong(usize, usize),
ModuleImportNotAtTopOfFile,
NoneComparison(RejectedCmpop),
NotInTest,
NotIsTest,
SyntaxError(String),
TrueFalseComparison(bool, RejectedCmpop),
TypeComparison,
// pycodestyle warnings
NoNewLineAtEndOfFile,
// pyflakes
AssertTuple,
BreakOutsideLoop,
ContinueOutsideLoop,
DefaultExceptNotLast,
DoNotAssignLambda,
DoNotUseBareExcept,
DuplicateArgumentName,
ExpressionsInStarAssignment,
FStringMissingPlaceholders,
ForwardAnnotationSyntaxError(String),
FutureFeatureNotDefined(String),
IOError(String),
IfTuple,
ImportShadowedByLoopVar(String, usize),
ImportStarNotPermitted(String),
@@ -231,28 +243,18 @@ pub enum CheckKind {
InvalidPrintSyntax,
IsLiteral,
LateFutureImport,
LineTooLong(usize, usize),
ModuleImportNotAtTopOfFile,
MultiValueRepeatedKeyLiteral,
MultiValueRepeatedKeyVariable(String),
NoneComparison(RejectedCmpop),
NotInTest,
NotIsTest,
RaiseNotImplemented,
ReturnOutsideFunction,
SyntaxError(String),
TrueFalseComparison(bool, RejectedCmpop),
TwoStarredExpressions,
TypeComparison,
UndefinedExport(String),
UndefinedLocal(String),
UndefinedName(String),
UnusedImport(Vec<String>),
UnusedVariable(String),
YieldOutsideFunction,
// pycodestyle warnings
NoNewLineAtEndOfFile,
// flake8-builtin
// flake8-builtins
BuiltinVariableShadowing(String),
BuiltinArgumentShadowing(String),
BuiltinAttributeShadowing(String),
@@ -292,6 +294,7 @@ pub enum CheckKind {
// pydocstyle
BlankLineAfterLastSection(String),
BlankLineAfterSection(String),
BlankLineAfterSummary,
BlankLineBeforeSection(String),
CapitalizeSectionName(String),
DashedUnderlineAfterSection(String),
@@ -307,7 +310,6 @@ pub enum CheckKind {
NewLineAfterLastParagraph,
NewLineAfterSectionName(String),
NoBlankLineAfterFunction(usize),
NoBlankLineAfterSummary,
NoBlankLineBeforeClass(usize),
NoBlankLineBeforeFunction(usize),
NoBlankLinesBetweenHeaderAndContent(String),
@@ -472,7 +474,7 @@ impl CheckCode {
CheckCode::D202 => CheckKind::NoBlankLineAfterFunction(1),
CheckCode::D203 => CheckKind::OneBlankLineBeforeClass(0),
CheckCode::D204 => CheckKind::OneBlankLineAfterClass(0),
CheckCode::D205 => CheckKind::NoBlankLineAfterSummary,
CheckCode::D205 => CheckKind::BlankLineAfterSummary,
CheckCode::D206 => CheckKind::IndentWithSpaces,
CheckCode::D207 => CheckKind::NoUnderIndentation,
CheckCode::D208 => CheckKind::NoOverIndentation,
@@ -755,7 +757,7 @@ impl CheckKind {
CheckKind::NewLineAfterLastParagraph => &CheckCode::D209,
CheckKind::NewLineAfterSectionName(_) => &CheckCode::D406,
CheckKind::NoBlankLineAfterFunction(_) => &CheckCode::D202,
CheckKind::NoBlankLineAfterSummary => &CheckCode::D205,
CheckKind::BlankLineAfterSummary => &CheckCode::D205,
CheckKind::NoBlankLineBeforeClass(_) => &CheckCode::D211,
CheckKind::NoBlankLineBeforeFunction(_) => &CheckCode::D201,
CheckKind::NoBlankLinesBetweenHeaderAndContent(_) => &CheckCode::D412,
@@ -1052,7 +1054,7 @@ impl CheckKind {
}
// pydocstyle
CheckKind::FitsOnOneLine => "One-line docstring should fit on one line".to_string(),
CheckKind::NoBlankLineAfterSummary => {
CheckKind::BlankLineAfterSummary => {
"1 blank line required between summary line and description".to_string()
}
CheckKind::NewLineAfterLastParagraph => {
@@ -1202,20 +1204,32 @@ impl CheckKind {
pub fn fixable(&self) -> bool {
matches!(
self,
|CheckKind::BlankLineAfterLastSection(_)| CheckKind::BlankLineAfterSection(_)
CheckKind::BlankLineAfterLastSection(_)
| CheckKind::BlankLineAfterSection(_)
| CheckKind::BlankLineAfterSummary
| CheckKind::BlankLineBeforeSection(_)
| CheckKind::CapitalizeSectionName(_)
| CheckKind::DashedUnderlineAfterSection(_)
| CheckKind::DeprecatedUnittestAlias(_, _)
| CheckKind::DoNotAssertFalse
| CheckKind::DuplicateHandlerException(_)
| CheckKind::NewLineAfterLastParagraph
| CheckKind::NewLineAfterSectionName(_)
| CheckKind::NoBlankLineAfterFunction(_)
| CheckKind::NoBlankLineAfterSummary
| 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::SectionUnderlineMatchesSectionLength(_)
| CheckKind::SectionUnderlineNotOverIndented(_)
| CheckKind::SuperCallWithParameters
| CheckKind::TypeOfPrimitive(_)
| CheckKind::UnnecessaryAbspath
@@ -1229,43 +1243,6 @@ impl CheckKind {
}
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Fix {
pub content: String,
pub location: Location,
pub end_location: Location,
pub applied: bool,
}
impl Fix {
pub fn deletion(start: Location, end: Location) -> Self {
Self {
content: "".to_string(),
location: start,
end_location: end,
applied: false,
}
}
pub fn replacement(content: String, start: Location, end: Location) -> Self {
Self {
content,
location: start,
end_location: end,
applied: false,
}
}
pub fn insertion(content: String, at: Location) -> Self {
Self {
content,
location: at,
end_location: at,
applied: false,
}
}
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Check {
pub kind: CheckKind,
@@ -1275,11 +1252,11 @@ pub struct Check {
}
impl Check {
pub fn new(kind: CheckKind, rage: Range) -> Self {
pub fn new(kind: CheckKind, range: Range) -> Self {
Self {
kind,
location: rage.location,
end_location: rage.end_location,
location: range.location,
end_location: range.end_location,
fix: None,
}
}

View File

@@ -1,8 +0,0 @@
pub mod definition;
pub mod extraction;
mod google;
mod helpers;
mod numpy;
pub mod plugins;
pub mod sections;
mod styles;

View File

@@ -2,16 +2,7 @@
use std::collections::BTreeSet;
use crate::ast::types::Range;
use once_cell::sync::Lazy;
use regex::Regex;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckCode, CheckKind};
use crate::docstrings::definition::Definition;
use crate::docstrings::sections;
use crate::docstrings::sections::SectionContext;
use crate::docstrings::styles::SectionStyle;
pub(crate) static GOOGLE_SECTION_NAMES: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
BTreeSet::from([
@@ -78,73 +69,3 @@ pub(crate) static LOWERCASE_GOOGLE_SECTION_NAMES: Lazy<BTreeSet<&'static str>> =
"yields",
])
});
// See: `GOOGLE_ARGS_REGEX` in `pydocstyle/checker.py`.
static GOOGLE_ARGS_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^\s*(\w+)\s*(\(.*?\))?\s*:\n?\s*.+").expect("Invalid regex"));
fn check_args_section(checker: &mut Checker, definition: &Definition, context: &SectionContext) {
let mut args_sections: Vec<String> = vec![];
for line in textwrap::dedent(&context.following_lines.join("\n")).lines() {
if line
.chars()
.next()
.map(|char| char.is_whitespace())
.unwrap_or(true)
{
// This is a continuation of documentation for the last
// parameter because it does start with whitespace.
if let Some(current) = args_sections.last_mut() {
current.push_str(line);
}
} else {
// This line is the start of documentation for the next
// parameter because it doesn't start with any whitespace.
args_sections.push(line.to_string());
}
}
sections::check_missing_args(
checker,
definition,
// Collect the list of arguments documented in the docstring.
&BTreeSet::from_iter(args_sections.iter().filter_map(|section| {
match GOOGLE_ARGS_REGEX.captures(section.as_str()) {
Some(caps) => caps.get(1).map(|arg_name| arg_name.as_str()),
None => None,
}
})),
)
}
pub(crate) fn check_google_section(
checker: &mut Checker,
definition: &Definition,
context: &SectionContext,
) {
sections::check_common_section(checker, definition, context, &SectionStyle::Google);
if checker.settings.enabled.contains(&CheckCode::D416) {
let suffix = context
.line
.trim()
.strip_prefix(&context.section_name)
.unwrap();
if suffix != ":" {
let docstring = definition
.docstring
.expect("Sections are only available for docstrings.");
checker.add_check(Check::new(
CheckKind::SectionNameEndsInColon(context.section_name.to_string()),
Range::from_located(docstring),
))
}
}
if checker.settings.enabled.contains(&CheckCode::D417) {
let capitalized_section_name = titlecase::titlecase(&context.section_name);
if capitalized_section_name == "Args" || capitalized_section_name == "Arguments" {
check_args_section(checker, definition, context);
}
}
}

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

7
src/docstrings/mod.rs Normal file
View File

@@ -0,0 +1,7 @@
pub mod definition;
pub mod extraction;
pub mod google;
pub mod helpers;
pub mod numpy;
pub mod sections;
pub mod styles;

View File

@@ -2,16 +2,8 @@
use std::collections::BTreeSet;
use crate::ast::types::Range;
use once_cell::sync::Lazy;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckCode, CheckKind};
use crate::docstrings::definition::Definition;
use crate::docstrings::sections::SectionContext;
use crate::docstrings::styles::SectionStyle;
use crate::docstrings::{helpers, sections};
pub(crate) static LOWERCASE_NUMPY_SECTION_NAMES: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
BTreeSet::from([
"short summary",
@@ -47,68 +39,3 @@ pub(crate) static NUMPY_SECTION_NAMES: Lazy<BTreeSet<&'static str>> = Lazy::new(
"Methods",
])
});
fn check_parameters_section(
checker: &mut Checker,
definition: &Definition,
context: &SectionContext,
) {
// Collect the list of arguments documented in the docstring.
let mut docstring_args: BTreeSet<&str> = Default::default();
let section_level_indent = helpers::leading_space(context.line);
for i in 1..context.following_lines.len() {
let current_line = context.following_lines[i - 1];
let current_leading_space = helpers::leading_space(current_line);
let next_line = context.following_lines[i];
if current_leading_space == section_level_indent
&& (helpers::leading_space(next_line).len() > current_leading_space.len())
&& !next_line.trim().is_empty()
{
let parameters = if let Some(semi_index) = current_line.find(':') {
// If the parameter has a type annotation, exclude it.
&current_line[..semi_index]
} else {
// Otherwise, it's just a list of parameters on the current line.
current_line.trim()
};
// Notably, NumPy lets you put multiple parameters of the same type on the same line.
for parameter in parameters.split(',') {
docstring_args.insert(parameter.trim());
}
}
}
// Validate that all arguments were documented.
sections::check_missing_args(checker, definition, &docstring_args);
}
pub(crate) fn check_numpy_section(
checker: &mut Checker,
definition: &Definition,
context: &SectionContext,
) {
sections::check_common_section(checker, definition, context, &SectionStyle::NumPy);
if checker.settings.enabled.contains(&CheckCode::D406) {
let suffix = context
.line
.trim()
.strip_prefix(&context.section_name)
.unwrap();
if !suffix.is_empty() {
let docstring = definition
.docstring
.expect("Sections are only available for docstrings.");
checker.add_check(Check::new(
CheckKind::NewLineAfterSectionName(context.section_name.to_string()),
Range::from_located(docstring),
))
}
}
if checker.settings.enabled.contains(&CheckCode::D417) {
let capitalized_section_name = titlecase::titlecase(&context.section_name);
if capitalized_section_name == "Parameters" {
check_parameters_section(checker, definition, context);
}
}
}

View File

@@ -1,826 +0,0 @@
//! Abstractions for tracking and validating docstrings in Python code.
use once_cell::sync::Lazy;
use regex::Regex;
use rustpython_ast::{Constant, ExprKind, Location, StmtKind};
use crate::ast::types::Range;
use crate::autofix::fixer;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckCode, CheckKind, Fix};
use crate::docstrings::definition::{Definition, DefinitionKind};
use crate::docstrings::google::check_google_section;
use crate::docstrings::helpers::{
indentation, leading_space, SINGLE_QUOTE_PREFIXES, TRIPLE_QUOTE_PREFIXES,
};
use crate::docstrings::numpy::check_numpy_section;
use crate::docstrings::sections::section_contexts;
use crate::docstrings::styles::SectionStyle;
use crate::visibility::{is_init, is_magic, is_overload, Visibility};
/// D100, D101, D102, D103, D104, D105, D106, D107
pub fn not_missing(
checker: &mut Checker,
definition: &Definition,
visibility: &Visibility,
) -> bool {
if matches!(visibility, Visibility::Private) {
return true;
}
if definition.docstring.is_some() {
return true;
}
match definition.kind {
DefinitionKind::Module => {
if checker.settings.enabled.contains(&CheckCode::D100) {
checker.add_check(Check::new(
CheckKind::PublicModule,
Range {
location: Location::new(1, 1),
end_location: Location::new(1, 1),
},
));
}
false
}
DefinitionKind::Package => {
if checker.settings.enabled.contains(&CheckCode::D104) {
checker.add_check(Check::new(
CheckKind::PublicPackage,
Range {
location: Location::new(1, 1),
end_location: Location::new(1, 1),
},
));
}
false
}
DefinitionKind::Class(stmt) => {
if checker.settings.enabled.contains(&CheckCode::D101) {
checker.add_check(Check::new(
CheckKind::PublicClass,
Range::from_located(stmt),
));
}
false
}
DefinitionKind::NestedClass(stmt) => {
if checker.settings.enabled.contains(&CheckCode::D106) {
checker.add_check(Check::new(
CheckKind::PublicNestedClass,
Range::from_located(stmt),
));
}
false
}
DefinitionKind::Function(stmt) | DefinitionKind::NestedFunction(stmt) => {
if is_overload(stmt) {
true
} else {
if checker.settings.enabled.contains(&CheckCode::D103) {
checker.add_check(Check::new(
CheckKind::PublicFunction,
Range::from_located(stmt),
));
}
false
}
}
DefinitionKind::Method(stmt) => {
if is_overload(stmt) {
true
} else if is_magic(stmt) {
if checker.settings.enabled.contains(&CheckCode::D105) {
checker.add_check(Check::new(
CheckKind::MagicMethod,
Range::from_located(stmt),
));
}
true
} else if is_init(stmt) {
if checker.settings.enabled.contains(&CheckCode::D107) {
checker.add_check(Check::new(CheckKind::PublicInit, Range::from_located(stmt)));
}
true
} else {
if checker.settings.enabled.contains(&CheckCode::D102) {
checker.add_check(Check::new(
CheckKind::PublicMethod,
Range::from_located(stmt),
));
}
true
}
}
}
}
/// D200
pub fn one_liner(checker: &mut Checker, definition: &Definition) {
if let Some(docstring) = &definition.docstring {
if let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.node
{
let mut line_count = 0;
let mut non_empty_line_count = 0;
for line in string.lines() {
line_count += 1;
if !line.trim().is_empty() {
non_empty_line_count += 1;
}
if non_empty_line_count > 1 {
break;
}
}
if non_empty_line_count == 1 && line_count > 1 {
checker.add_check(Check::new(
CheckKind::FitsOnOneLine,
Range::from_located(docstring),
));
}
}
}
}
static COMMENT_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^\s*#").unwrap());
static INNER_FUNCTION_OR_CLASS_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^\s+(?:(?:class|def|async def)\s|@)").unwrap());
/// D201, D202
pub fn blank_before_after_function(checker: &mut Checker, definition: &Definition) {
if let Some(docstring) = definition.docstring {
if let DefinitionKind::Function(parent)
| DefinitionKind::NestedFunction(parent)
| DefinitionKind::Method(parent) = &definition.kind
{
if let ExprKind::Constant {
value: Constant::Str(_),
..
} = &docstring.node
{
let (before, _, after) = checker.locator.partition_source_code_at(
&Range::from_located(parent),
&Range::from_located(docstring),
);
if checker.settings.enabled.contains(&CheckCode::D201) {
let blank_lines_before = before
.lines()
.rev()
.skip(1)
.take_while(|line| line.trim().is_empty())
.count();
if blank_lines_before != 0 {
let mut check = Check::new(
CheckKind::NoBlankLineBeforeFunction(blank_lines_before),
Range::from_located(docstring),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
check.amend(Fix::deletion(
Location::new(docstring.location.row() - blank_lines_before, 1),
Location::new(docstring.location.row(), 1),
));
}
checker.add_check(check);
}
}
if checker.settings.enabled.contains(&CheckCode::D202) {
let all_blank_after = after
.lines()
.skip(1)
.all(|line| line.trim().is_empty() || COMMENT_REGEX.is_match(line));
if all_blank_after {
return;
}
let blank_lines_after = after
.lines()
.skip(1)
.take_while(|line| line.trim().is_empty())
.count();
// Report a D202 violation if the docstring is followed by a blank line and the
// blank line is not itself followed by an inner function or class.
let expected_blank_lines_after =
if INNER_FUNCTION_OR_CLASS_REGEX.is_match(after) {
1
} else {
0
};
if blank_lines_after != expected_blank_lines_after {
let mut check = Check::new(
CheckKind::NoBlankLineAfterFunction(blank_lines_after),
Range::from_located(docstring),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
check.amend(Fix::deletion(
Location::new(
docstring.location.row() + 1 + expected_blank_lines_after,
1,
),
Location::new(docstring.location.row() + 1 + blank_lines_after, 1),
));
}
checker.add_check(check);
}
}
}
}
}
}
/// D203, D204, D211
pub fn blank_before_after_class(checker: &mut Checker, definition: &Definition) {
if let Some(docstring) = &definition.docstring {
if let DefinitionKind::Class(parent) | DefinitionKind::NestedClass(parent) =
&definition.kind
{
if let ExprKind::Constant {
value: Constant::Str(_),
..
} = &docstring.node
{
let (before, _, after) = checker.locator.partition_source_code_at(
&Range::from_located(parent),
&Range::from_located(docstring),
);
if checker.settings.enabled.contains(&CheckCode::D203)
|| checker.settings.enabled.contains(&CheckCode::D211)
{
let blank_lines_before = before
.lines()
.rev()
.skip(1)
.take_while(|line| line.trim().is_empty())
.count();
if checker.settings.enabled.contains(&CheckCode::D211) {
if blank_lines_before != 0 {
let mut check = Check::new(
CheckKind::NoBlankLineBeforeClass(blank_lines_before),
Range::from_located(docstring),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply)
{
check.amend(Fix::deletion(
Location::new(docstring.location.row() - blank_lines_before, 1),
Location::new(docstring.location.row(), 1),
));
}
checker.add_check(check);
}
}
if checker.settings.enabled.contains(&CheckCode::D203) {
if blank_lines_before != 1 {
let mut check = Check::new(
CheckKind::OneBlankLineBeforeClass(blank_lines_before),
Range::from_located(docstring),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply)
{
check.amend(Fix::replacement(
"\n".to_string(),
Location::new(docstring.location.row() - blank_lines_before, 1),
Location::new(docstring.location.row(), 1),
));
}
checker.add_check(check);
}
}
}
if checker.settings.enabled.contains(&CheckCode::D204) {
let all_blank_after = after
.lines()
.skip(1)
.all(|line| line.trim().is_empty() || COMMENT_REGEX.is_match(line));
if all_blank_after {
return;
}
let blank_lines_after = after
.lines()
.skip(1)
.take_while(|line| line.trim().is_empty())
.count();
if blank_lines_after != 1 {
let mut check = Check::new(
CheckKind::OneBlankLineAfterClass(blank_lines_after),
Range::from_located(docstring),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
check.amend(Fix::replacement(
"\n".to_string(),
Location::new(docstring.end_location.row() + 1, 1),
Location::new(
docstring.end_location.row() + 1 + blank_lines_after,
1,
),
));
}
checker.add_check(check);
}
}
}
}
}
}
/// D205
pub fn blank_after_summary(checker: &mut Checker, definition: &Definition) {
if let Some(docstring) = definition.docstring {
if let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.node
{
let mut lines_count = 1;
let mut blanks_count = 0;
for line in string.trim().lines().skip(1) {
lines_count += 1;
if line.trim().is_empty() {
blanks_count += 1;
} else {
break;
}
}
if lines_count > 1 && blanks_count != 1 {
let mut check = Check::new(
CheckKind::NoBlankLineAfterSummary,
Range::from_located(docstring),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
check.amend(Fix::replacement(
"\n".to_string(),
Location::new(docstring.location.row() + 1, 1),
Location::new(docstring.location.row() + 1 + blanks_count, 1),
));
}
checker.add_check(check);
}
}
}
}
/// 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,
Range::from_located(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,
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;
}
}
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,
Range::from_located(docstring),
));
}
has_seen_under_indent = true;
}
}
}
}
}
}
/// D209
pub fn newline_after_last_paragraph(checker: &mut Checker, definition: &Definition) {
if let Some(docstring) = definition.docstring {
if let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.node
{
let mut line_count = 0;
for line in string.lines() {
if !line.trim().is_empty() {
line_count += 1;
}
if line_count > 1 {
let content = checker
.locator
.slice_source_code_range(&Range::from_located(docstring));
if let Some(last_line) = content.lines().last().map(|line| line.trim()) {
if last_line != "\"\"\"" && last_line != "'''" {
let mut check = Check::new(
CheckKind::NewLineAfterLastParagraph,
Range::from_located(docstring),
);
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(indentation(checker, docstring));
check.amend(Fix::insertion(
content,
Location::new(
docstring.end_location.row(),
docstring.end_location.column() - "\"\"\"".len(),
),
));
}
checker.add_check(check);
}
}
return;
}
}
}
}
}
/// D210
pub fn no_surrounding_whitespace(checker: &mut Checker, definition: &Definition) {
if let Some(docstring) = definition.docstring {
if let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.node
{
let mut lines = string.lines();
if let Some(line) = lines.next() {
let trimmed = line.trim();
if trimmed.is_empty() {
return;
}
if line != trimmed {
let mut check = Check::new(
CheckKind::NoSurroundingWhitespace,
Range::from_located(docstring),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if let Some(first_line) = checker
.locator
.slice_source_code_range(&Range::from_located(docstring))
.lines()
.next()
.map(|line| line.to_lowercase())
{
for pattern in TRIPLE_QUOTE_PREFIXES.iter().chain(SINGLE_QUOTE_PREFIXES)
{
if first_line.starts_with(pattern) {
check.amend(Fix::replacement(
trimmed.to_string(),
Location::new(
docstring.location.row(),
docstring.location.column() + pattern.len(),
),
Location::new(
docstring.location.row(),
docstring.location.column()
+ pattern.len()
+ line.chars().count(),
),
));
break;
}
}
}
}
checker.add_check(check);
}
}
}
}
}
/// D212, D213
pub fn multi_line_summary_start(checker: &mut Checker, definition: &Definition) {
if let Some(docstring) = definition.docstring {
if let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.node
{
if string.lines().nth(1).is_some() {
if let Some(first_line) = checker
.locator
.slice_source_code_range(&Range::from_located(docstring))
.lines()
.next()
.map(|line| line.to_lowercase())
{
if TRIPLE_QUOTE_PREFIXES.contains(&first_line.as_str()) {
if checker.settings.enabled.contains(&CheckCode::D212) {
checker.add_check(Check::new(
CheckKind::MultiLineSummaryFirstLine,
Range::from_located(docstring),
));
}
} else {
if checker.settings.enabled.contains(&CheckCode::D213) {
checker.add_check(Check::new(
CheckKind::MultiLineSummarySecondLine,
Range::from_located(docstring),
));
}
}
}
}
}
}
}
/// D300
pub fn triple_quotes(checker: &mut Checker, definition: &Definition) {
if let Some(docstring) = definition.docstring {
if let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.node
{
if let Some(first_line) = checker
.locator
.slice_source_code_range(&Range::from_located(docstring))
.lines()
.next()
.map(|line| line.to_lowercase())
{
let starts_with_triple = if string.contains("\"\"\"") {
first_line.starts_with("'''")
|| first_line.starts_with("u'''")
|| first_line.starts_with("r'''")
|| first_line.starts_with("ur'''")
} else {
first_line.starts_with("\"\"\"")
|| first_line.starts_with("u\"\"\"")
|| first_line.starts_with("r\"\"\"")
|| first_line.starts_with("ur\"\"\"")
};
if !starts_with_triple {
checker.add_check(Check::new(
CheckKind::UsesTripleQuotes,
Range::from_located(docstring),
));
}
}
}
}
}
/// D400
pub fn ends_with_period(checker: &mut Checker, definition: &Definition) {
if let Some(docstring) = definition.docstring {
if let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.node
{
if let Some(string) = string.lines().next() {
if !string.ends_with('.') {
checker.add_check(Check::new(
CheckKind::EndsInPeriod,
Range::from_located(docstring),
));
}
}
}
}
}
/// D402
pub fn no_signature(checker: &mut Checker, definition: &Definition) {
if let Some(docstring) = definition.docstring {
if let DefinitionKind::Function(parent)
| DefinitionKind::NestedFunction(parent)
| DefinitionKind::Method(parent) = definition.kind
{
if let StmtKind::FunctionDef { name, .. } = &parent.node {
if let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.node
{
if let Some(first_line) = string.lines().next() {
if first_line.contains(&format!("{name}(")) {
checker.add_check(Check::new(
CheckKind::NoSignature,
Range::from_located(docstring),
));
}
}
}
}
}
}
}
/// D403
pub fn capitalized(checker: &mut Checker, definition: &Definition) {
if !matches!(definition.kind, DefinitionKind::Function(_)) {
return;
}
if let Some(docstring) = definition.docstring {
if let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.node
{
if let Some(first_word) = string.split(' ').next() {
if first_word == first_word.to_uppercase() {
return;
}
for char in first_word.chars() {
if !char.is_ascii_alphabetic() && char != '\'' {
return;
}
}
if let Some(first_char) = first_word.chars().next() {
if !first_char.is_uppercase() {
checker.add_check(Check::new(
CheckKind::FirstLineCapitalized,
Range::from_located(docstring),
));
}
}
}
}
}
}
/// D404
pub fn starts_with_this(checker: &mut Checker, definition: &Definition) {
if let Some(docstring) = definition.docstring {
if let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.node
{
let trimmed = string.trim();
if trimmed.is_empty() {
return;
}
if let Some(first_word) = string.split(' ').next() {
if first_word
.replace(|c: char| !c.is_alphanumeric(), "")
.to_lowercase()
== "this"
{
checker.add_check(Check::new(
CheckKind::NoThisPrefix,
Range::from_located(docstring),
));
}
}
}
}
}
/// D415
pub fn ends_with_punctuation(checker: &mut Checker, definition: &Definition) {
if let Some(docstring) = definition.docstring {
if let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.node
{
if let Some(string) = string.lines().next() {
if !(string.ends_with('.') || string.ends_with('!') || string.ends_with('?')) {
checker.add_check(Check::new(
CheckKind::EndsInPunctuation,
Range::from_located(docstring),
));
}
}
}
}
}
/// D418
pub fn if_needed(checker: &mut Checker, definition: &Definition) {
if definition.docstring.is_some() {
if let DefinitionKind::Function(stmt)
| DefinitionKind::NestedFunction(stmt)
| DefinitionKind::Method(stmt) = definition.kind
{
if is_overload(stmt) {
checker.add_check(Check::new(
CheckKind::SkipDocstring,
Range::from_located(stmt),
));
}
}
}
}
/// D419
pub fn not_empty(checker: &mut Checker, definition: &Definition) -> bool {
if let Some(docstring) = definition.docstring {
if let ExprKind::Constant {
value: Constant::Str(string),
..
} = &docstring.node
{
if string.trim().is_empty() {
if checker.settings.enabled.contains(&CheckCode::D419) {
checker.add_check(Check::new(
CheckKind::NonEmpty,
Range::from_located(docstring),
));
}
return false;
}
}
}
true
}
/// D212, D214, D215, D405, D406, D407, D408, D409, D410, D411, D412, D413, D414, D416, D417
pub fn sections(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() < 2 {
return;
}
// First, interpret as NumPy-style sections.
let mut found_numpy_section = false;
for context in &section_contexts(&lines, &SectionStyle::NumPy) {
found_numpy_section = true;
check_numpy_section(checker, definition, context);
}
// If no such sections were identified, interpret as Google-style sections.
if !found_numpy_section {
for context in &section_contexts(&lines, &SectionStyle::Google) {
check_google_section(checker, definition, context);
}
}
}
}
}

View File

@@ -1,17 +1,5 @@
use std::collections::BTreeSet;
use itertools::Itertools;
use rustpython_ast::{Arg, Location, StmtKind};
use titlecase::titlecase;
use crate::ast::types::Range;
use crate::autofix::fixer;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckCode, CheckKind, Fix};
use crate::docstrings::definition::{Definition, DefinitionKind};
use crate::docstrings::helpers;
use crate::docstrings::styles::SectionStyle;
use crate::visibility::is_static;
#[derive(Debug)]
pub(crate) struct SectionContext<'a> {
@@ -20,7 +8,7 @@ pub(crate) struct SectionContext<'a> {
pub(crate) line: &'a str,
pub(crate) following_lines: &'a [&'a str],
pub(crate) is_last_section: bool,
original_index: usize,
pub(crate) original_index: usize,
}
fn suspected_as_section(line: &str, style: &SectionStyle) -> bool {
@@ -109,295 +97,3 @@ pub(crate) fn section_contexts<'a>(
truncated_contexts.reverse();
truncated_contexts
}
fn check_blanks_and_section_underline(
checker: &mut Checker,
definition: &Definition,
context: &SectionContext,
) {
let docstring = definition
.docstring
.expect("Sections are only available for docstrings.");
let mut blank_lines_after_header = 0;
for line in context.following_lines {
if !line.trim().is_empty() {
break;
}
blank_lines_after_header += 1;
}
// Nothing but blank lines after the section header.
if blank_lines_after_header == context.following_lines.len() {
if checker.settings.enabled.contains(&CheckCode::D407) {
checker.add_check(Check::new(
CheckKind::DashedUnderlineAfterSection(context.section_name.to_string()),
Range::from_located(docstring),
));
}
if checker.settings.enabled.contains(&CheckCode::D414) {
checker.add_check(Check::new(
CheckKind::NonEmptySection(context.section_name.to_string()),
Range::from_located(docstring),
));
}
return;
}
let non_empty_line = context.following_lines[blank_lines_after_header];
let dash_line_found = non_empty_line
.chars()
.all(|char| char.is_whitespace() || char == '-');
if !dash_line_found {
if checker.settings.enabled.contains(&CheckCode::D407) {
checker.add_check(Check::new(
CheckKind::DashedUnderlineAfterSection(context.section_name.to_string()),
Range::from_located(docstring),
));
}
if blank_lines_after_header > 0 {
if checker.settings.enabled.contains(&CheckCode::D212) {
checker.add_check(Check::new(
CheckKind::NoBlankLinesBetweenHeaderAndContent(
context.section_name.to_string(),
),
Range::from_located(docstring),
));
}
}
} else {
if blank_lines_after_header > 0 {
if checker.settings.enabled.contains(&CheckCode::D408) {
checker.add_check(Check::new(
CheckKind::SectionUnderlineAfterName(context.section_name.to_string()),
Range::from_located(docstring),
));
}
}
if non_empty_line
.trim()
.chars()
.filter(|char| *char == '-')
.count()
!= context.section_name.len()
{
if checker.settings.enabled.contains(&CheckCode::D409) {
checker.add_check(Check::new(
CheckKind::SectionUnderlineMatchesSectionLength(
context.section_name.to_string(),
),
Range::from_located(docstring),
));
}
}
if checker.settings.enabled.contains(&CheckCode::D215) {
if helpers::leading_space(non_empty_line).len()
> helpers::indentation(checker, docstring).len()
{
checker.add_check(Check::new(
CheckKind::SectionUnderlineNotOverIndented(context.section_name.to_string()),
Range::from_located(docstring),
));
}
}
let line_after_dashes_index = blank_lines_after_header + 1;
if line_after_dashes_index < context.following_lines.len() {
let line_after_dashes = context.following_lines[line_after_dashes_index];
if line_after_dashes.trim().is_empty() {
let rest_of_lines = &context.following_lines[line_after_dashes_index..];
if rest_of_lines.iter().all(|line| line.trim().is_empty()) {
if checker.settings.enabled.contains(&CheckCode::D414) {
checker.add_check(Check::new(
CheckKind::NonEmptySection(context.section_name.to_string()),
Range::from_located(docstring),
));
}
} else {
if checker.settings.enabled.contains(&CheckCode::D412) {
checker.add_check(Check::new(
CheckKind::NoBlankLinesBetweenHeaderAndContent(
context.section_name.to_string(),
),
Range::from_located(docstring),
));
}
}
}
} else {
if checker.settings.enabled.contains(&CheckCode::D414) {
checker.add_check(Check::new(
CheckKind::NonEmptySection(context.section_name.to_string()),
Range::from_located(docstring),
));
}
}
}
}
pub(crate) fn check_common_section(
checker: &mut Checker,
definition: &Definition,
context: &SectionContext,
style: &SectionStyle,
) {
let docstring = definition
.docstring
.expect("Sections are only available for docstrings.");
if checker.settings.enabled.contains(&CheckCode::D405) {
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),
))
}
}
if checker.settings.enabled.contains(&CheckCode::D214) {
if helpers::leading_space(context.line).len()
> helpers::indentation(checker, docstring).len()
{
checker.add_check(Check::new(
CheckKind::SectionNotOverIndented(context.section_name.to_string()),
Range::from_located(docstring),
))
}
}
if context
.following_lines
.last()
.map(|line| !line.trim().is_empty())
.unwrap_or(true)
{
if context.is_last_section {
if checker.settings.enabled.contains(&CheckCode::D413) {
let mut check = Check::new(
CheckKind::BlankLineAfterLastSection(context.section_name.to_string()),
Range::from_located(docstring),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
check.amend(Fix::insertion(
"\n".to_string(),
Location::new(
docstring.location.row()
+ context.original_index
+ 1
+ context.following_lines.len(),
1,
),
))
}
checker.add_check(check);
}
} else {
if checker.settings.enabled.contains(&CheckCode::D410) {
let mut check = Check::new(
CheckKind::BlankLineAfterSection(context.section_name.to_string()),
Range::from_located(docstring),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
check.amend(Fix::insertion(
"\n".to_string(),
Location::new(
docstring.location.row()
+ context.original_index
+ 1
+ context.following_lines.len(),
1,
),
))
}
checker.add_check(check);
}
}
}
if checker.settings.enabled.contains(&CheckCode::D411) {
if !context.previous_line.is_empty() {
checker.add_check(Check::new(
CheckKind::BlankLineBeforeSection(context.section_name.to_string()),
Range::from_located(docstring),
))
}
}
check_blanks_and_section_underline(checker, definition, context);
}
pub(crate) fn check_missing_args(
checker: &mut Checker,
definition: &Definition,
docstrings_args: &BTreeSet<&str>,
) {
if let DefinitionKind::Function(parent)
| DefinitionKind::NestedFunction(parent)
| DefinitionKind::Method(parent) = definition.kind
{
if let StmtKind::FunctionDef {
args: arguments, ..
}
| StmtKind::AsyncFunctionDef {
args: arguments, ..
} = &parent.node
{
// Collect all the arguments into a single vector.
let mut all_arguments: Vec<&Arg> = arguments
.args
.iter()
.chain(arguments.posonlyargs.iter())
.chain(arguments.kwonlyargs.iter())
.skip(
// If this is a non-static method, skip `cls` or `self`.
if matches!(definition.kind, DefinitionKind::Method(_)) && !is_static(parent) {
1
} else {
0
},
)
.collect();
if let Some(arg) = &arguments.vararg {
all_arguments.push(arg);
}
if let Some(arg) = &arguments.kwarg {
all_arguments.push(arg);
}
// Look for arguments that weren't included in the docstring.
let mut missing_args: BTreeSet<&str> = Default::default();
for arg in all_arguments {
let arg_name = arg.node.arg.as_str();
if arg_name.starts_with('_') {
continue;
}
if docstrings_args.contains(&arg_name) {
continue;
}
missing_args.insert(arg_name);
}
if !missing_args.is_empty() {
let names = missing_args
.into_iter()
.map(String::from)
.sorted()
.collect();
checker.add_check(Check::new(
CheckKind::DocumentAllArguments(names),
Range::from_located(parent),
));
}
}
}
}

View File

@@ -0,0 +1 @@
pub mod plugins;

View File

@@ -2,8 +2,9 @@ use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::autofix::fixer;
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind, Fix};
use crate::checks::{Check, CheckKind};
use crate::code_gen::SourceGenerator;
fn assertion_error(msg: &Option<Box<Expr>>) -> Stmt {
@@ -47,12 +48,11 @@ pub fn assert_false(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg: &Optio
let mut generator = SourceGenerator::new();
if let Ok(()) = generator.unparse_stmt(&assertion_error(msg)) {
if let Ok(content) = generator.generate() {
check.amend(Fix {
check.amend(Fix::replacement(
content,
location: stmt.location,
end_location: stmt.end_location,
applied: false,
})
stmt.location,
stmt.end_location.unwrap(),
));
}
}
}

View File

@@ -6,8 +6,9 @@ use rustpython_ast::{Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKi
use crate::ast::helpers;
use crate::ast::types::{CheckLocator, Range};
use crate::autofix::fixer;
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckCode, CheckKind, Fix};
use crate::checks::{Check, CheckCode, CheckKind};
use crate::code_gen::SourceGenerator;
fn type_pattern(elts: Vec<&Expr>) -> Expr {
@@ -54,12 +55,11 @@ pub fn duplicate_handler_exceptions(
let mut generator = SourceGenerator::new();
if let Ok(()) = generator.unparse_expr(&type_pattern(unique_elts), 0) {
if let Ok(content) = generator.generate() {
check.amend(Fix {
check.amend(Fix::replacement(
content,
location: expr.location,
end_location: expr.end_location,
applied: false,
})
expr.location,
expr.end_location.unwrap(),
))
}
}
}

View File

@@ -0,0 +1,6 @@
pub use assert_false::assert_false;
pub use duplicate_exceptions::duplicate_exceptions;
pub use duplicate_exceptions::duplicate_handler_exceptions;
mod assert_false;
mod duplicate_exceptions;

View File

@@ -0,0 +1,20 @@
use crate::ast::types::Range;
use crate::checks::{Check, CheckKind};
use crate::flake8_builtins::types::ShadowingType;
use crate::python::builtins::BUILTINS;
/// Check builtin name shadowing.
pub fn builtin_shadowing(name: &str, location: Range, node_type: ShadowingType) -> Option<Check> {
if BUILTINS.contains(&name) {
Some(Check::new(
match node_type {
ShadowingType::Variable => CheckKind::BuiltinVariableShadowing(name.to_string()),
ShadowingType::Argument => CheckKind::BuiltinArgumentShadowing(name.to_string()),
ShadowingType::Attribute => CheckKind::BuiltinAttributeShadowing(name.to_string()),
},
location,
))
} else {
None
}
}

View File

@@ -0,0 +1,2 @@
pub mod checks;
pub mod types;

View File

@@ -0,0 +1,5 @@
pub enum ShadowingType {
Variable,
Argument,
Attribute,
}

View File

@@ -0,0 +1,505 @@
use num_bigint::BigInt;
use rustpython_ast::{Comprehension, Constant, Expr, ExprKind, KeywordData, Located, Unaryop};
use crate::ast::types::Range;
use crate::checks::{Check, CheckKind};
/// Check `list(generator)` compliance.
pub fn unnecessary_generator_list(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
if args.len() == 1 {
if let ExprKind::Name { id, .. } = &func.node {
if id == "list" {
if let ExprKind::GeneratorExp { .. } = &args[0].node {
return Some(Check::new(
CheckKind::UnnecessaryGeneratorList,
Range::from_located(expr),
));
}
}
}
}
None
}
/// Check `set(generator)` compliance.
pub fn unnecessary_generator_set(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
if args.len() == 1 {
if let ExprKind::Name { id, .. } = &func.node {
if id == "set" {
if let ExprKind::GeneratorExp { .. } = &args[0].node {
return Some(Check::new(
CheckKind::UnnecessaryGeneratorSet,
Range::from_located(expr),
));
}
}
}
}
None
}
/// Check `dict((x, y) for x, y in iterable)` compliance.
pub fn unnecessary_generator_dict(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
if args.len() == 1 {
if let ExprKind::Name { id, .. } = &func.node {
if id == "dict" {
if let ExprKind::GeneratorExp { elt, .. } = &args[0].node {
match &elt.node {
ExprKind::Tuple { elts, .. } if elts.len() == 2 => {
return Some(Check::new(
CheckKind::UnnecessaryGeneratorDict,
Range::from_located(expr),
));
}
_ => {}
}
}
}
}
}
None
}
/// Check `set([...])` compliance.
pub fn unnecessary_list_comprehension_set(
expr: &Expr,
func: &Expr,
args: &[Expr],
) -> Option<Check> {
if args.len() == 1 {
if let ExprKind::Name { id, .. } = &func.node {
if id == "set" {
if let ExprKind::ListComp { .. } = &args[0].node {
return Some(Check::new(
CheckKind::UnnecessaryListComprehensionSet,
Range::from_located(expr),
));
}
}
}
}
None
}
/// Check `dict([...])` compliance.
pub fn unnecessary_list_comprehension_dict(
expr: &Expr,
func: &Expr,
args: &[Expr],
) -> Option<Check> {
if args.len() == 1 {
if let ExprKind::Name { id, .. } = &func.node {
if id == "dict" {
if let ExprKind::ListComp { elt, .. } = &args[0].node {
match &elt.node {
ExprKind::Tuple { elts, .. } if elts.len() == 2 => {
return Some(Check::new(
CheckKind::UnnecessaryListComprehensionDict,
Range::from_located(expr),
));
}
_ => {}
}
}
}
}
}
None
}
/// Check `set([1, 2])` compliance.
pub fn unnecessary_literal_set(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
if args.len() == 1 {
if let ExprKind::Name { id, .. } = &func.node {
if id == "set" {
match &args[0].node {
ExprKind::List { .. } => {
return Some(Check::new(
CheckKind::UnnecessaryLiteralSet("list".to_string()),
Range::from_located(expr),
));
}
ExprKind::Tuple { .. } => {
return Some(Check::new(
CheckKind::UnnecessaryLiteralSet("tuple".to_string()),
Range::from_located(expr),
));
}
_ => {}
}
}
}
}
None
}
/// Check `dict([(1, 2)])` compliance.
pub fn unnecessary_literal_dict(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
if args.len() == 1 {
if let ExprKind::Name { id, .. } = &func.node {
if id == "dict" {
match &args[0].node {
ExprKind::Tuple { elts, .. } => {
if let Some(elt) = elts.first() {
match &elt.node {
// dict((1, 2), ...))
ExprKind::Tuple { elts, .. } if elts.len() == 2 => {
return Some(Check::new(
CheckKind::UnnecessaryLiteralDict("tuple".to_string()),
Range::from_located(expr),
));
}
_ => {}
}
} else {
// dict(())
return Some(Check::new(
CheckKind::UnnecessaryLiteralDict("tuple".to_string()),
Range::from_located(expr),
));
}
}
ExprKind::List { elts, .. } => {
if let Some(elt) = elts.first() {
match &elt.node {
// dict([(1, 2), ...])
ExprKind::Tuple { elts, .. } if elts.len() == 2 => {
return Some(Check::new(
CheckKind::UnnecessaryLiteralDict("list".to_string()),
Range::from_located(expr),
));
}
_ => {}
}
} else {
// dict([])
return Some(Check::new(
CheckKind::UnnecessaryLiteralDict("list".to_string()),
Range::from_located(expr),
));
}
}
_ => {}
}
}
}
}
None
}
pub fn unnecessary_collection_call(
expr: &Expr,
func: &Expr,
args: &[Expr],
keywords: &[Located<KeywordData>],
) -> Option<Check> {
if args.is_empty() {
if let ExprKind::Name { id, .. } = &func.node {
if id == "list" || id == "tuple" {
// list() or tuple()
return Some(Check::new(
CheckKind::UnnecessaryCollectionCall(id.to_string()),
Range::from_located(expr),
));
} else if id == "dict" {
// dict() or dict(a=1)
if keywords.is_empty() || keywords.iter().all(|kw| kw.node.arg.is_some()) {
return Some(Check::new(
CheckKind::UnnecessaryCollectionCall(id.to_string()),
Range::from_located(expr),
));
}
}
}
}
None
}
pub fn unnecessary_literal_within_tuple_call(
expr: &Expr,
func: &Expr,
args: &[Expr],
) -> Option<Check> {
if let ExprKind::Name { id, .. } = &func.node {
if id == "tuple" {
if let Some(arg) = args.first() {
match &arg.node {
ExprKind::Tuple { .. } => {
return Some(Check::new(
CheckKind::UnnecessaryLiteralWithinTupleCall("tuple".to_string()),
Range::from_located(expr),
));
}
ExprKind::List { .. } => {
return Some(Check::new(
CheckKind::UnnecessaryLiteralWithinTupleCall("list".to_string()),
Range::from_located(expr),
));
}
_ => {}
}
}
}
}
None
}
pub fn unnecessary_literal_within_list_call(
expr: &Expr,
func: &Expr,
args: &[Expr],
) -> Option<Check> {
if let ExprKind::Name { id, .. } = &func.node {
if id == "list" {
if let Some(arg) = args.first() {
match &arg.node {
ExprKind::Tuple { .. } => {
return Some(Check::new(
CheckKind::UnnecessaryLiteralWithinListCall("tuple".to_string()),
Range::from_located(expr),
));
}
ExprKind::List { .. } => {
return Some(Check::new(
CheckKind::UnnecessaryLiteralWithinListCall("list".to_string()),
Range::from_located(expr),
));
}
_ => {}
}
}
}
}
None
}
pub fn unnecessary_list_call(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
if let ExprKind::Name { id, .. } = &func.node {
if id == "list" {
if let Some(arg) = args.first() {
if let ExprKind::ListComp { .. } = &arg.node {
return Some(Check::new(
CheckKind::UnnecessaryListCall,
Range::from_located(expr),
));
}
}
}
}
None
}
pub fn unnecessary_call_around_sorted(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
if let ExprKind::Name { id: outer, .. } = &func.node {
if outer == "list" || outer == "reversed" {
if let Some(arg) = args.first() {
if let ExprKind::Call { func, .. } = &arg.node {
if let ExprKind::Name { id: inner, .. } = &func.node {
if inner == "sorted" {
return Some(Check::new(
CheckKind::UnnecessaryCallAroundSorted(outer.to_string()),
Range::from_located(expr),
));
}
}
}
}
}
}
None
}
pub fn unnecessary_double_cast_or_process(
expr: &Expr,
func: &Expr,
args: &[Expr],
) -> Option<Check> {
if let ExprKind::Name { id: outer, .. } = &func.node {
if outer == "list"
|| outer == "tuple"
|| outer == "set"
|| outer == "reversed"
|| outer == "sorted"
{
if let Some(arg) = args.first() {
if let ExprKind::Call { func, .. } = &arg.node {
if let ExprKind::Name { id: inner, .. } = &func.node {
// Ex) set(tuple(...))
if (outer == "set" || outer == "sorted")
&& (inner == "list"
|| inner == "tuple"
|| inner == "reversed"
|| inner == "sorted")
{
return Some(Check::new(
CheckKind::UnnecessaryDoubleCastOrProcess(
inner.to_string(),
outer.to_string(),
),
Range::from_located(expr),
));
}
// Ex) list(tuple(...))
if (outer == "list" || outer == "tuple")
&& (inner == "list" || inner == "tuple")
{
return Some(Check::new(
CheckKind::UnnecessaryDoubleCastOrProcess(
inner.to_string(),
outer.to_string(),
),
Range::from_located(expr),
));
}
// Ex) set(set(...))
if outer == "set" && inner == "set" {
return Some(Check::new(
CheckKind::UnnecessaryDoubleCastOrProcess(
inner.to_string(),
outer.to_string(),
),
Range::from_located(expr),
));
}
}
}
}
}
}
None
}
pub fn unnecessary_subscript_reversal(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
if let Some(first_arg) = args.first() {
if let ExprKind::Name { id, .. } = &func.node {
if id == "set" || id == "sorted" || id == "reversed" {
if let ExprKind::Subscript { slice, .. } = &first_arg.node {
if let ExprKind::Slice { lower, upper, step } = &slice.node {
if lower.is_none() && upper.is_none() {
if let Some(step) = step {
if let ExprKind::UnaryOp {
op: Unaryop::USub,
operand,
} = &step.node
{
if let ExprKind::Constant {
value: Constant::Int(val),
..
} = &operand.node
{
if *val == BigInt::from(1) {
return Some(Check::new(
CheckKind::UnnecessarySubscriptReversal(
id.to_string(),
),
Range::from_located(expr),
));
}
}
}
}
}
}
}
}
}
}
None
}
pub fn unnecessary_comprehension(
expr: &Expr,
elt: &Expr,
generators: &[Comprehension],
) -> Option<Check> {
if generators.len() == 1 {
let generator = &generators[0];
if generator.ifs.is_empty() && generator.is_async == 0 {
if let ExprKind::Name { id: elt_id, .. } = &elt.node {
if let ExprKind::Name { id: target_id, .. } = &generator.target.node {
if elt_id == target_id {
match &expr.node {
ExprKind::ListComp { .. } => {
return Some(Check::new(
CheckKind::UnnecessaryComprehension("list".to_string()),
Range::from_located(expr),
))
}
ExprKind::SetComp { .. } => {
return Some(Check::new(
CheckKind::UnnecessaryComprehension("set".to_string()),
Range::from_located(expr),
))
}
_ => {}
};
}
}
}
}
}
None
}
pub fn unnecessary_map(expr: &Expr, func: &Expr, args: &[Expr]) -> Option<Check> {
if let ExprKind::Name { id, .. } = &func.node {
if id == "map" {
if args.len() == 2 {
if let ExprKind::Lambda { .. } = &args[0].node {
return Some(Check::new(
CheckKind::UnnecessaryMap("generator".to_string()),
Range::from_located(expr),
));
}
}
} else if id == "list" || id == "set" {
if let Some(arg) = args.first() {
if let ExprKind::Call { func, args, .. } = &arg.node {
if let ExprKind::Name { id: f, .. } = &func.node {
if f == "map" {
if let Some(arg) = args.first() {
if let ExprKind::Lambda { .. } = &arg.node {
return Some(Check::new(
CheckKind::UnnecessaryMap(id.to_string()),
Range::from_located(expr),
));
}
}
}
}
}
}
} else if id == "dict" {
if args.len() == 1 {
if let ExprKind::Call { func, args, .. } = &args[0].node {
if let ExprKind::Name { id: f, .. } = &func.node {
if f == "map" {
if let Some(arg) = args.first() {
if let ExprKind::Lambda { body, .. } = &arg.node {
match &body.node {
ExprKind::Tuple { elts, .. }
| ExprKind::List { elts, .. }
if elts.len() == 2 =>
{
return Some(Check::new(
CheckKind::UnnecessaryMap(id.to_string()),
Range::from_located(expr),
))
}
_ => {}
}
}
}
}
}
}
}
}
}
None
}

View File

@@ -0,0 +1 @@
pub mod checks;

View File

@@ -0,0 +1,36 @@
use rustpython_ast::{Expr, ExprKind};
use crate::ast::types::Range;
use crate::checks::{Check, CheckKind};
/// Check whether a function call is a `print` or `pprint` invocation
pub fn print_call(
expr: &Expr,
func: &Expr,
check_print: bool,
check_pprint: bool,
) -> Option<Check> {
if let ExprKind::Name { id, .. } = &func.node {
if check_print && id == "print" {
return Some(Check::new(CheckKind::PrintFound, Range::from_located(expr)));
} else if check_pprint && id == "pprint" {
return Some(Check::new(
CheckKind::PPrintFound,
Range::from_located(expr),
));
}
}
if let ExprKind::Attribute { value, attr, .. } = &func.node {
if let ExprKind::Name { id, .. } = &value.node {
if check_pprint && id == "pprint" && attr == "pprint" {
return Some(Check::new(
CheckKind::PPrintFound,
Range::from_located(expr),
));
}
}
}
None
}

2
src/flake8_print/mod.rs Normal file
View File

@@ -0,0 +1,2 @@
mod checks;
pub mod plugins;

View File

@@ -0,0 +1,3 @@
pub use print_call::print_call;
mod print_call;

View File

@@ -1,13 +1,13 @@
use log::error;
use rustpython_ast::{Expr, Stmt, StmtKind};
use crate::ast::checkers;
use crate::autofix::{fixer, fixes};
use crate::autofix::{fixer, helpers};
use crate::check_ast::Checker;
use crate::checks::CheckCode;
use crate::flake8_print::checks;
pub fn print_call(checker: &mut Checker, expr: &Expr, func: &Expr) {
if let Some(mut check) = checkers::print_call(
if let Some(mut check) = checks::print_call(
expr,
func,
checker.settings.enabled.contains(&CheckCode::T201),
@@ -25,13 +25,13 @@ pub fn print_call(checker: &mut Checker, expr: &Expr, func: &Expr) {
.map(|index| checker.parents[*index])
.collect();
match fixes::remove_stmt(
match helpers::remove_stmt(
checker.parents[context.defined_by],
context.defined_in.map(|index| checker.parents[index]),
&deleted,
) {
Ok(fix) => {
if fix.content.is_empty() || fix.content == "pass" {
if fix.patch.content.is_empty() || fix.patch.content == "pass" {
checker.deletions.insert(context.defined_by);
}
check.amend(fix)

View File

@@ -20,15 +20,23 @@ pub mod checks;
pub mod cli;
pub mod code_gen;
mod docstrings;
mod flake8_bugbear;
mod flake8_builtins;
mod flake8_comprehensions;
mod flake8_print;
pub mod fs;
pub mod linter;
pub mod logging;
pub mod message;
mod noqa;
mod plugins;
mod pep8_naming;
pub mod printer;
mod pycodestyle;
mod pydocstyle;
mod pyflakes;
pub mod pyproject;
mod python;
mod pyupgrade;
pub mod settings;
pub mod visibility;

99
src/pep8_naming/checks.rs Normal file
View File

@@ -0,0 +1,99 @@
use rustpython_ast::{Arguments, Expr, ExprKind, Stmt};
use crate::ast::types::{Range, Scope, ScopeKind};
use crate::checks::{Check, CheckKind};
pub fn invalid_class_name(class_def: &Stmt, name: &str) -> Option<Check> {
let stripped = name.strip_prefix('_').unwrap_or(name);
if !stripped
.chars()
.next()
.map(|c| c.is_uppercase())
.unwrap_or(false)
|| stripped.contains('_')
{
return Some(Check::new(
CheckKind::InvalidClassName(name.to_string()),
Range::from_located(class_def),
));
}
None
}
pub fn invalid_function_name(func_def: &Stmt, name: &str) -> Option<Check> {
if name.chars().any(|c| c.is_uppercase()) {
return Some(Check::new(
CheckKind::InvalidFunctionName(name.to_string()),
Range::from_located(func_def),
));
}
None
}
pub fn invalid_argument_name(location: Range, name: &str) -> Option<Check> {
if name.chars().any(|c| c.is_uppercase()) {
return Some(Check::new(
CheckKind::InvalidArgumentName(name.to_string()),
location,
));
}
None
}
pub fn invalid_first_argument_name_for_class_method(
scope: &Scope,
decorator_list: &[Expr],
args: &Arguments,
) -> Option<Check> {
if !matches!(scope.kind, ScopeKind::Class) {
return None;
}
if decorator_list.iter().any(|decorator| {
if let ExprKind::Name { id, .. } = &decorator.node {
id == "classmethod"
} else {
false
}
}) {
if let Some(arg) = args.args.first() {
if arg.node.arg != "cls" {
return Some(Check::new(
CheckKind::InvalidFirstArgumentNameForClassMethod,
Range::from_located(arg),
));
}
}
}
None
}
pub fn invalid_first_argument_name_for_method(
scope: &Scope,
decorator_list: &[Expr],
args: &Arguments,
) -> Option<Check> {
if !matches!(scope.kind, ScopeKind::Class) {
return None;
}
if decorator_list.iter().any(|decorator| {
if let ExprKind::Name { id, .. } = &decorator.node {
id == "classmethod" || id == "staticmethod"
} else {
false
}
}) {
return None;
}
if let Some(arg) = args.args.first() {
if arg.node.arg != "self" {
return Some(Check::new(
CheckKind::InvalidFirstArgumentNameForMethod,
Range::from_located(arg),
));
}
}
None
}

1
src/pep8_naming/mod.rs Normal file
View File

@@ -0,0 +1 @@
pub mod checks;

238
src/pycodestyle/checks.rs Normal file
View File

@@ -0,0 +1,238 @@
use itertools::izip;
use rustpython_parser::ast::{Cmpop, Constant, Expr, ExprKind, Unaryop};
use crate::ast::types::{CheckLocator, Range};
use crate::checks::{Check, CheckKind, RejectedCmpop};
fn is_ambiguous_name(name: &str) -> bool {
name == "l" || name == "I" || name == "O"
}
/// Check AmbiguousVariableName compliance.
pub fn ambiguous_variable_name(name: &str, location: Range) -> Option<Check> {
if is_ambiguous_name(name) {
Some(Check::new(
CheckKind::AmbiguousVariableName(name.to_string()),
location,
))
} else {
None
}
}
/// Check AmbiguousClassName compliance.
pub fn ambiguous_class_name(name: &str, location: Range) -> Option<Check> {
if is_ambiguous_name(name) {
Some(Check::new(
CheckKind::AmbiguousClassName(name.to_string()),
location,
))
} else {
None
}
}
/// Check AmbiguousFunctionName compliance.
pub fn ambiguous_function_name(name: &str, location: Range) -> Option<Check> {
if is_ambiguous_name(name) {
Some(Check::new(
CheckKind::AmbiguousFunctionName(name.to_string()),
location,
))
} else {
None
}
}
/// Check DoNotAssignLambda compliance.
pub fn do_not_assign_lambda(value: &Expr, location: Range) -> Option<Check> {
if let ExprKind::Lambda { .. } = &value.node {
Some(Check::new(CheckKind::DoNotAssignLambda, location))
} else {
None
}
}
/// Check NotInTest and NotIsTest compliance.
pub fn not_tests(
op: &Unaryop,
operand: &Expr,
check_not_in: bool,
check_not_is: bool,
locator: &dyn CheckLocator,
) -> Vec<Check> {
let mut checks: Vec<Check> = vec![];
if matches!(op, Unaryop::Not) {
if let ExprKind::Compare { ops, .. } = &operand.node {
for op in ops {
match op {
Cmpop::In => {
if check_not_in {
checks.push(Check::new(
CheckKind::NotInTest,
locator.locate_check(Range::from_located(operand)),
));
}
}
Cmpop::Is => {
if check_not_is {
checks.push(Check::new(
CheckKind::NotIsTest,
locator.locate_check(Range::from_located(operand)),
));
}
}
_ => {}
}
}
}
}
checks
}
/// Check TrueFalseComparison and NoneComparison compliance.
pub fn literal_comparisons(
left: &Expr,
ops: &[Cmpop],
comparators: &[Expr],
check_none_comparisons: bool,
check_true_false_comparisons: bool,
locator: &dyn CheckLocator,
) -> Vec<Check> {
let mut checks: Vec<Check> = vec![];
let op = ops.first().unwrap();
let comparator = left;
// Check `left`.
if check_none_comparisons
&& matches!(
comparator.node,
ExprKind::Constant {
value: Constant::None,
kind: None
}
)
{
if matches!(op, Cmpop::Eq) {
checks.push(Check::new(
CheckKind::NoneComparison(RejectedCmpop::Eq),
locator.locate_check(Range::from_located(comparator)),
));
}
if matches!(op, Cmpop::NotEq) {
checks.push(Check::new(
CheckKind::NoneComparison(RejectedCmpop::NotEq),
locator.locate_check(Range::from_located(comparator)),
));
}
}
if check_true_false_comparisons {
if let ExprKind::Constant {
value: Constant::Bool(value),
kind: None,
} = comparator.node
{
if matches!(op, Cmpop::Eq) {
checks.push(Check::new(
CheckKind::TrueFalseComparison(value, RejectedCmpop::Eq),
locator.locate_check(Range::from_located(comparator)),
));
}
if matches!(op, Cmpop::NotEq) {
checks.push(Check::new(
CheckKind::TrueFalseComparison(value, RejectedCmpop::NotEq),
locator.locate_check(Range::from_located(comparator)),
));
}
}
}
// Check each comparator in order.
for (op, comparator) in izip!(ops, comparators) {
if check_none_comparisons
&& matches!(
comparator.node,
ExprKind::Constant {
value: Constant::None,
kind: None
}
)
{
if matches!(op, Cmpop::Eq) {
checks.push(Check::new(
CheckKind::NoneComparison(RejectedCmpop::Eq),
locator.locate_check(Range::from_located(comparator)),
));
}
if matches!(op, Cmpop::NotEq) {
checks.push(Check::new(
CheckKind::NoneComparison(RejectedCmpop::NotEq),
locator.locate_check(Range::from_located(comparator)),
));
}
}
if check_true_false_comparisons {
if let ExprKind::Constant {
value: Constant::Bool(value),
kind: None,
} = comparator.node
{
if matches!(op, Cmpop::Eq) {
checks.push(Check::new(
CheckKind::TrueFalseComparison(value, RejectedCmpop::Eq),
locator.locate_check(Range::from_located(comparator)),
));
}
if matches!(op, Cmpop::NotEq) {
checks.push(Check::new(
CheckKind::TrueFalseComparison(value, RejectedCmpop::NotEq),
locator.locate_check(Range::from_located(comparator)),
));
}
}
}
}
checks
}
/// Check TypeComparison compliance.
pub fn type_comparison(ops: &[Cmpop], comparators: &[Expr], location: Range) -> Vec<Check> {
let mut checks: Vec<Check> = vec![];
for (op, right) in izip!(ops, comparators) {
if matches!(op, Cmpop::Is | Cmpop::IsNot | Cmpop::Eq | Cmpop::NotEq) {
match &right.node {
ExprKind::Call { func, args, .. } => {
if let ExprKind::Name { id, .. } = &func.node {
// Ex) type(False)
if id == "type" {
if let Some(arg) = args.first() {
// Allow comparison for types which are not obvious.
if !matches!(arg.node, ExprKind::Name { .. }) {
checks.push(Check::new(CheckKind::TypeComparison, location));
}
}
}
}
}
ExprKind::Attribute { value, .. } => {
if let ExprKind::Name { id, .. } = &value.node {
// Ex) types.IntType
if id == "types" {
checks.push(Check::new(CheckKind::TypeComparison, location));
}
}
}
_ => {}
}
}
}
checks
}

1
src/pycodestyle/mod.rs Normal file
View File

@@ -0,0 +1 @@
pub mod checks;

1
src/pydocstyle/mod.rs Normal file
View File

@@ -0,0 +1 @@
pub mod plugins;

1515
src/pydocstyle/plugins.rs Normal file

File diff suppressed because it is too large Load Diff

345
src/pyflakes/checks.rs Normal file
View File

@@ -0,0 +1,345 @@
use std::collections::BTreeSet;
use itertools::izip;
use regex::Regex;
use rustpython_parser::ast::{
Arg, Arguments, Cmpop, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Stmt,
StmtKind,
};
use crate::ast::types::{BindingKind, CheckLocator, FunctionScope, Range, Scope, ScopeKind};
use crate::checks::{Check, CheckKind};
/// Check IfTuple compliance.
pub fn if_tuple(test: &Expr, location: Range) -> Option<Check> {
if let ExprKind::Tuple { elts, .. } = &test.node {
if !elts.is_empty() {
return Some(Check::new(CheckKind::IfTuple, location));
}
}
None
}
/// Check AssertTuple compliance.
pub fn assert_tuple(test: &Expr, location: Range) -> Option<Check> {
if let ExprKind::Tuple { elts, .. } = &test.node {
if !elts.is_empty() {
return Some(Check::new(CheckKind::AssertTuple, location));
}
}
None
}
/// Check UnusedVariable compliance.
pub fn unused_variables(
scope: &Scope,
locator: &dyn CheckLocator,
dummy_variable_rgx: &Regex,
) -> Vec<Check> {
let mut checks: Vec<Check> = vec![];
if matches!(
scope.kind,
ScopeKind::Function(FunctionScope { uses_locals: true })
) {
return checks;
}
for (name, binding) in scope.values.iter() {
if binding.used.is_none()
&& matches!(binding.kind, BindingKind::Assignment)
&& !dummy_variable_rgx.is_match(name)
&& name != "__tracebackhide__"
&& name != "__traceback_info__"
&& name != "__traceback_supplement__"
{
checks.push(Check::new(
CheckKind::UnusedVariable(name.to_string()),
locator.locate_check(binding.range),
));
}
}
checks
}
/// Check DefaultExceptNotLast compliance.
pub fn default_except_not_last(handlers: &[Excepthandler]) -> Option<Check> {
for (idx, handler) in handlers.iter().enumerate() {
let ExcepthandlerKind::ExceptHandler { type_, .. } = &handler.node;
if type_.is_none() && idx < handlers.len() - 1 {
return Some(Check::new(
CheckKind::DefaultExceptNotLast,
Range::from_located(handler),
));
}
}
None
}
/// Check RaiseNotImplemented compliance.
pub fn raise_not_implemented(expr: &Expr) -> Option<Check> {
match &expr.node {
ExprKind::Call { func, .. } => {
if let ExprKind::Name { id, .. } = &func.node {
if id == "NotImplemented" {
return Some(Check::new(
CheckKind::RaiseNotImplemented,
Range::from_located(expr),
));
}
}
}
ExprKind::Name { id, .. } => {
if id == "NotImplemented" {
return Some(Check::new(
CheckKind::RaiseNotImplemented,
Range::from_located(expr),
));
}
}
_ => {}
}
None
}
/// Check DuplicateArgumentName compliance.
pub fn duplicate_arguments(arguments: &Arguments) -> Vec<Check> {
let mut checks: Vec<Check> = vec![];
// Collect all the arguments into a single vector.
let mut all_arguments: Vec<&Arg> = arguments
.args
.iter()
.chain(arguments.posonlyargs.iter())
.chain(arguments.kwonlyargs.iter())
.collect();
if let Some(arg) = &arguments.vararg {
all_arguments.push(arg);
}
if let Some(arg) = &arguments.kwarg {
all_arguments.push(arg);
}
// Search for duplicates.
let mut idents: BTreeSet<&str> = BTreeSet::new();
for arg in all_arguments {
let ident = &arg.node.arg;
if idents.contains(ident.as_str()) {
checks.push(Check::new(
CheckKind::DuplicateArgumentName,
Range::from_located(arg),
));
}
idents.insert(ident);
}
checks
}
#[derive(Debug, PartialEq)]
enum DictionaryKey<'a> {
Constant(&'a Constant),
Variable(&'a String),
}
fn convert_to_value(expr: &Expr) -> Option<DictionaryKey> {
match &expr.node {
ExprKind::Constant { value, .. } => Some(DictionaryKey::Constant(value)),
ExprKind::Name { id, .. } => Some(DictionaryKey::Variable(id)),
_ => None,
}
}
/// Check MultiValueRepeatedKeyLiteral and MultiValueRepeatedKeyVariable compliance.
pub fn repeated_keys(
keys: &[Expr],
check_repeated_literals: bool,
check_repeated_variables: bool,
locator: &dyn CheckLocator,
) -> Vec<Check> {
let mut checks: Vec<Check> = vec![];
let num_keys = keys.len();
for i in 0..num_keys {
let k1 = &keys[i];
let v1 = convert_to_value(k1);
for k2 in keys.iter().take(num_keys).skip(i + 1) {
let v2 = convert_to_value(k2);
match (&v1, &v2) {
(Some(DictionaryKey::Constant(v1)), Some(DictionaryKey::Constant(v2))) => {
if check_repeated_literals && v1 == v2 {
checks.push(Check::new(
CheckKind::MultiValueRepeatedKeyLiteral,
locator.locate_check(Range::from_located(k2)),
))
}
}
(Some(DictionaryKey::Variable(v1)), Some(DictionaryKey::Variable(v2))) => {
if check_repeated_variables && v1 == v2 {
checks.push(Check::new(
CheckKind::MultiValueRepeatedKeyVariable((*v2).to_string()),
locator.locate_check(Range::from_located(k2)),
))
}
}
_ => {}
}
}
}
checks
}
fn is_constant(expr: &Expr) -> bool {
match &expr.node {
ExprKind::Constant { .. } => true,
ExprKind::Tuple { elts, .. } => elts.iter().all(is_constant),
_ => false,
}
}
fn is_singleton(expr: &Expr) -> bool {
matches!(
expr.node,
ExprKind::Constant {
value: Constant::None | Constant::Bool(_) | Constant::Ellipsis,
..
}
)
}
fn is_constant_non_singleton(expr: &Expr) -> bool {
is_constant(expr) && !is_singleton(expr)
}
/// Check IsLiteral compliance.
pub fn is_literal(left: &Expr, ops: &[Cmpop], comparators: &[Expr], location: Range) -> Vec<Check> {
let mut checks: Vec<Check> = vec![];
let mut left = left;
for (op, right) in izip!(ops, comparators) {
if matches!(op, Cmpop::Is | Cmpop::IsNot)
&& (is_constant_non_singleton(left) || is_constant_non_singleton(right))
{
checks.push(Check::new(CheckKind::IsLiteral, location));
}
left = right;
}
checks
}
/// Check TwoStarredExpressions and TooManyExpressionsInStarredAssignment compliance.
pub fn starred_expressions(
elts: &[Expr],
check_too_many_expressions: bool,
check_two_starred_expressions: bool,
location: Range,
) -> Option<Check> {
let mut has_starred: bool = false;
let mut starred_index: Option<usize> = None;
for (index, elt) in elts.iter().enumerate() {
if matches!(elt.node, ExprKind::Starred { .. }) {
if has_starred && check_two_starred_expressions {
return Some(Check::new(CheckKind::TwoStarredExpressions, location));
}
has_starred = true;
starred_index = Some(index);
}
}
if check_too_many_expressions {
if let Some(starred_index) = starred_index {
if starred_index >= 1 << 8 || elts.len() - starred_index > 1 << 24 {
return Some(Check::new(CheckKind::ExpressionsInStarAssignment, location));
}
}
}
None
}
/// Check BreakOutsideLoop compliance.
pub fn break_outside_loop(
stmt: &Stmt,
parents: &[&Stmt],
parent_stack: &[usize],
locator: &dyn CheckLocator,
) -> Option<Check> {
let mut allowed: bool = false;
let mut parent = stmt;
for index in parent_stack.iter().rev() {
let child = parent;
parent = parents[*index];
match &parent.node {
StmtKind::For { orelse, .. }
| StmtKind::AsyncFor { orelse, .. }
| StmtKind::While { orelse, .. } => {
if !orelse.contains(child) {
allowed = true;
break;
}
}
StmtKind::FunctionDef { .. }
| StmtKind::AsyncFunctionDef { .. }
| StmtKind::ClassDef { .. } => {
break;
}
_ => {}
}
}
if !allowed {
Some(Check::new(
CheckKind::BreakOutsideLoop,
locator.locate_check(Range::from_located(stmt)),
))
} else {
None
}
}
/// Check ContinueOutsideLoop compliance.
pub fn continue_outside_loop(
stmt: &Stmt,
parents: &[&Stmt],
parent_stack: &[usize],
locator: &dyn CheckLocator,
) -> Option<Check> {
let mut allowed: bool = false;
let mut parent = stmt;
for index in parent_stack.iter().rev() {
let child = parent;
parent = parents[*index];
match &parent.node {
StmtKind::For { orelse, .. }
| StmtKind::AsyncFor { orelse, .. }
| StmtKind::While { orelse, .. } => {
if !orelse.contains(child) {
allowed = true;
break;
}
}
StmtKind::FunctionDef { .. }
| StmtKind::AsyncFunctionDef { .. }
| StmtKind::ClassDef { .. } => {
break;
}
_ => {}
}
}
if !allowed {
Some(Check::new(
CheckKind::ContinueOutsideLoop,
locator.locate_check(Range::from_located(stmt)),
))
} else {
None
}
}

147
src/pyflakes/fixes.rs Normal file
View File

@@ -0,0 +1,147 @@
use libcst_native::ImportNames::Aliases;
use libcst_native::NameOrAttribute::N;
use libcst_native::{Codegen, SmallStatement, Statement};
use rustpython_ast::Stmt;
use crate::ast::operations::SourceCodeLocator;
use crate::ast::types::Range;
use crate::autofix::{helpers, Fix};
/// Generate a Fix to remove any unused imports from an `import` statement.
pub fn remove_unused_imports(
locator: &mut SourceCodeLocator,
full_names: &[&str],
stmt: &Stmt,
parent: Option<&Stmt>,
deleted: &[&Stmt],
) -> anyhow::Result<Fix> {
let mut tree = match libcst_native::parse_module(
locator.slice_source_code_range(&Range::from_located(stmt)),
None,
) {
Ok(m) => m,
Err(_) => return Err(anyhow::anyhow!("Failed to extract CST from source.")),
};
let body = if let Some(Statement::Simple(body)) = tree.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!("Expected node to be: Statement::Simple."));
};
let body = if let Some(SmallStatement::Import(body)) = body.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!(
"Expected node to be: SmallStatement::ImportFrom."
));
};
let aliases = &mut body.names;
// Preserve the trailing comma (or not) from the last entry.
let trailing_comma = aliases.last().and_then(|alias| alias.comma.clone());
// Identify unused imports from within the `import from`.
let mut removable = vec![];
for (index, alias) in aliases.iter().enumerate() {
if let N(import_name) = &alias.name {
if full_names.contains(&import_name.value) {
removable.push(index);
}
}
}
// TODO(charlie): This is quadratic.
for index in removable.iter().rev() {
aliases.remove(*index);
}
if let Some(alias) = aliases.last_mut() {
alias.comma = trailing_comma;
}
if aliases.is_empty() {
helpers::remove_stmt(stmt, parent, deleted)
} else {
let mut state = Default::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
state.to_string(),
stmt.location,
stmt.end_location.unwrap(),
))
}
}
/// Generate a Fix to remove any unused imports from an `import from` statement.
pub fn remove_unused_import_froms(
locator: &mut SourceCodeLocator,
full_names: &[&str],
stmt: &Stmt,
parent: Option<&Stmt>,
deleted: &[&Stmt],
) -> anyhow::Result<Fix> {
let mut tree = match libcst_native::parse_module(
locator.slice_source_code_range(&Range::from_located(stmt)),
None,
) {
Ok(m) => m,
Err(_) => return Err(anyhow::anyhow!("Failed to extract CST from source.")),
};
let body = if let Some(Statement::Simple(body)) = tree.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!("Expected node to be: Statement::Simple."));
};
let body = if let Some(SmallStatement::ImportFrom(body)) = body.body.first_mut() {
body
} else {
return Err(anyhow::anyhow!(
"Expected node to be: SmallStatement::ImportFrom."
));
};
let aliases = if let Aliases(aliases) = &mut body.names {
aliases
} else {
return Err(anyhow::anyhow!("Expected node to be: Aliases."));
};
// Preserve the trailing comma (or not) from the last entry.
let trailing_comma = aliases.last().and_then(|alias| alias.comma.clone());
// Identify unused imports from within the `import from`.
let mut removable = vec![];
for (index, alias) in aliases.iter().enumerate() {
if let N(name) = &alias.name {
let import_name = if let Some(N(module_name)) = &body.module {
format!("{}.{}", module_name.value, name.value)
} else {
name.value.to_string()
};
if full_names.contains(&import_name.as_str()) {
removable.push(index);
}
}
}
// TODO(charlie): This is quadratic.
for index in removable.iter().rev() {
aliases.remove(*index);
}
if let Some(alias) = aliases.last_mut() {
alias.comma = trailing_comma;
}
if aliases.is_empty() {
helpers::remove_stmt(stmt, parent, deleted)
} else {
let mut state = Default::default();
tree.codegen(&mut state);
Ok(Fix::replacement(
state.to_string(),
stmt.location,
stmt.end_location.unwrap(),
))
}
}

3
src/pyflakes/mod.rs Normal file
View File

@@ -0,0 +1,3 @@
pub mod checks;
pub mod fixes;
pub mod plugins;

View File

@@ -1,12 +1,11 @@
use rustpython_ast::{Expr, Stmt};
use crate::ast::checkers;
use crate::ast::types::{CheckLocator, Range};
use crate::check_ast::Checker;
use crate::pyflakes::checks;
pub fn assert_tuple(checker: &mut Checker, stmt: &Stmt, test: &Expr) {
if let Some(check) =
checkers::assert_tuple(test, checker.locate_check(Range::from_located(stmt)))
if let Some(check) = checks::assert_tuple(test, checker.locate_check(Range::from_located(stmt)))
{
checker.add_check(check);
}

View File

@@ -1,11 +1,11 @@
use rustpython_ast::{Expr, Stmt};
use crate::ast::checkers;
use crate::ast::types::{CheckLocator, Range};
use crate::check_ast::Checker;
use crate::pyflakes::checks;
pub fn if_tuple(checker: &mut Checker, stmt: &Stmt, test: &Expr) {
if let Some(check) = checkers::if_tuple(test, checker.locate_check(Range::from_located(stmt))) {
if let Some(check) = checks::if_tuple(test, checker.locate_check(Range::from_located(stmt))) {
checker.add_check(check);
}
}

View File

@@ -0,0 +1,7 @@
pub use assert_tuple::assert_tuple;
pub use if_tuple::if_tuple;
pub use invalid_print_syntax::invalid_print_syntax;
mod assert_tuple;
mod if_tuple;
mod invalid_print_syntax;

156
src/pyupgrade/checks.rs Normal file
View File

@@ -0,0 +1,156 @@
use rustpython_parser::ast::{ArgData, Expr, ExprKind, Stmt, StmtKind};
use crate::ast::helpers;
use crate::ast::types::{Binding, BindingKind, Range, Scope, ScopeKind};
use crate::checks::{Check, CheckKind};
use crate::pyupgrade::types::Primitive;
/// Check that `super()` has no args
pub fn super_args(
scope: &Scope,
parents: &[&Stmt],
expr: &Expr,
func: &Expr,
args: &[Expr],
) -> Option<Check> {
if !helpers::is_super_call_with_arguments(func, args) {
return None;
}
// Check: are we in a Function scope?
if !matches!(scope.kind, ScopeKind::Function { .. }) {
return None;
}
let mut parents = parents.iter().rev();
// For a `super` invocation to be unnecessary, the first argument needs to match the enclosing
// class, and the second argument needs to match the first argument to the enclosing function.
if let [first_arg, second_arg] = args {
// Find the enclosing function definition (if any).
if let Some(StmtKind::FunctionDef {
args: parent_args, ..
}) = parents
.find(|stmt| matches!(stmt.node, StmtKind::FunctionDef { .. }))
.map(|stmt| &stmt.node)
{
// Extract the name of the first argument to the enclosing function.
if let Some(ArgData {
arg: parent_arg, ..
}) = parent_args.args.first().map(|expr| &expr.node)
{
// Find the enclosing class definition (if any).
if let Some(StmtKind::ClassDef {
name: parent_name, ..
}) = parents
.find(|stmt| matches!(stmt.node, StmtKind::ClassDef { .. }))
.map(|stmt| &stmt.node)
{
if let (
ExprKind::Name {
id: first_arg_id, ..
},
ExprKind::Name {
id: second_arg_id, ..
},
) = (&first_arg.node, &second_arg.node)
{
if first_arg_id == parent_name && second_arg_id == parent_arg {
return Some(Check::new(
CheckKind::SuperCallWithParameters,
Range::from_located(expr),
));
}
}
}
}
}
}
None
}
/// Check UselessMetaclassType compliance.
pub fn useless_metaclass_type(targets: &[Expr], value: &Expr, location: Range) -> Option<Check> {
if targets.len() == 1 {
if let ExprKind::Name { id, .. } = targets.first().map(|expr| &expr.node).unwrap() {
if id == "__metaclass__" {
if let ExprKind::Name { id, .. } = &value.node {
if id == "type" {
return Some(Check::new(CheckKind::UselessMetaclassType, location));
}
}
}
}
}
None
}
/// Check UnnecessaryAbspath compliance.
pub fn unnecessary_abspath(func: &Expr, args: &[Expr], location: Range) -> Option<Check> {
// Validate the arguments.
if args.len() == 1 {
if let ExprKind::Name { id, .. } = &args[0].node {
if id == "__file__" {
match &func.node {
ExprKind::Attribute { attr: id, .. } | ExprKind::Name { id, .. } => {
if id == "abspath" {
return Some(Check::new(CheckKind::UnnecessaryAbspath, location));
}
}
_ => {}
}
}
}
}
None
}
/// Check UselessObjectInheritance compliance.
pub fn useless_object_inheritance(name: &str, bases: &[Expr], scope: &Scope) -> Option<Check> {
for expr in bases {
if let ExprKind::Name { id, .. } = &expr.node {
if id == "object" {
match scope.values.get(id) {
None
| Some(Binding {
kind: BindingKind::Builtin,
..
}) => {
return Some(Check::new(
CheckKind::UselessObjectInheritance(name.to_string()),
Range::from_located(expr),
));
}
_ => {}
}
}
}
}
None
}
/// Check TypeOfPrimitive compliance.
pub fn type_of_primitive(func: &Expr, args: &[Expr], location: Range) -> Option<Check> {
// Validate the arguments.
if args.len() == 1 {
match &func.node {
ExprKind::Attribute { attr: id, .. } | ExprKind::Name { id, .. } => {
if id == "type" {
if let ExprKind::Constant { value, .. } = &args[0].node {
if let Some(primitive) = Primitive::from_constant(value) {
return Some(Check::new(
CheckKind::TypeOfPrimitive(primitive),
location,
));
}
}
}
}
_ => {}
}
}
None
}

133
src/pyupgrade/fixes.rs Normal file
View File

@@ -0,0 +1,133 @@
use libcst_native::{Codegen, Expression, SmallStatement, Statement};
use rustpython_ast::{Expr, Keyword, Location};
use rustpython_parser::lexer;
use rustpython_parser::lexer::Tok;
use crate::ast::helpers;
use crate::ast::operations::SourceCodeLocator;
use crate::ast::types::Range;
use crate::autofix::Fix;
/// Generate a fix to remove a base from a ClassDef statement.
pub fn remove_class_def_base(
locator: &mut SourceCodeLocator,
stmt_at: &Location,
expr_at: Location,
bases: &[Expr],
keywords: &[Keyword],
) -> Option<Fix> {
let content = locator.slice_source_code_at(stmt_at);
// Case 1: `object` is the only base.
if bases.len() == 1 && keywords.is_empty() {
let mut fix_start = None;
let mut fix_end = None;
let mut count: usize = 0;
for (start, tok, end) in lexer::make_tokenizer(content).flatten() {
if matches!(tok, Tok::Lpar) {
if count == 0 {
fix_start = Some(helpers::to_absolute(&start, stmt_at));
}
count += 1;
}
if matches!(tok, Tok::Rpar) {
count -= 1;
if count == 0 {
fix_end = Some(helpers::to_absolute(&end, stmt_at));
break;
}
}
}
return match (fix_start, fix_end) {
(Some(start), Some(end)) => Some(Fix::replacement("".to_string(), start, end)),
_ => None,
};
}
if bases
.iter()
.map(|node| node.location)
.chain(keywords.iter().map(|node| node.location))
.any(|location| location > expr_at)
{
// Case 2: `object` is _not_ the last node.
let mut fix_start: Option<Location> = None;
let mut fix_end: Option<Location> = None;
let mut seen_comma = false;
for (start, tok, end) in lexer::make_tokenizer(content).flatten() {
let start = helpers::to_absolute(&start, stmt_at);
if seen_comma {
if matches!(tok, Tok::Newline) {
fix_end = Some(end);
} else {
fix_end = Some(start);
}
break;
}
if start == expr_at {
fix_start = Some(start);
}
if fix_start.is_some() && matches!(tok, Tok::Comma) {
seen_comma = true;
}
}
match (fix_start, fix_end) {
(Some(start), Some(end)) => Some(Fix::replacement("".to_string(), start, end)),
_ => None,
}
} else {
// Case 3: `object` is the last node, so we have to find the last token that isn't a comma.
let mut fix_start: Option<Location> = None;
let mut fix_end: Option<Location> = None;
for (start, tok, end) in lexer::make_tokenizer(content).flatten() {
let start = helpers::to_absolute(&start, stmt_at);
let end = helpers::to_absolute(&end, stmt_at);
if start == expr_at {
fix_end = Some(end);
break;
}
if matches!(tok, Tok::Comma) {
fix_start = Some(start);
}
}
match (fix_start, fix_end) {
(Some(start), Some(end)) => Some(Fix::replacement("".to_string(), start, end)),
_ => None,
}
}
}
pub fn remove_super_arguments(locator: &mut SourceCodeLocator, expr: &Expr) -> Option<Fix> {
let range = Range::from_located(expr);
let contents = locator.slice_source_code_range(&range);
let mut tree = match libcst_native::parse_module(contents, None) {
Ok(m) => m,
Err(_) => return None,
};
if let Some(Statement::Simple(body)) = tree.body.first_mut() {
if let Some(SmallStatement::Expr(body)) = body.body.first_mut() {
if let Expression::Call(body) = &mut body.value {
body.args = vec![];
body.whitespace_before_args = Default::default();
body.whitespace_after_func = Default::default();
let mut state = Default::default();
tree.codegen(&mut state);
return Some(Fix::replacement(
state.to_string(),
range.location,
range.end_location,
));
}
}
}
None
}

4
src/pyupgrade/mod.rs Normal file
View File

@@ -0,0 +1,4 @@
mod checks;
pub mod fixes;
pub mod plugins;
pub mod types;

View File

@@ -1,12 +1,13 @@
use std::collections::BTreeMap;
use once_cell::sync::Lazy;
use rustpython_ast::{Expr, ExprKind, Location};
use rustpython_ast::{Expr, ExprKind};
use crate::ast::types::Range;
use crate::autofix::fixer;
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind, Fix};
use crate::checks::{Check, CheckKind};
static DEPRECATED_ALIASES: Lazy<BTreeMap<&'static str, &'static str>> = Lazy::new(|| {
BTreeMap::from([
@@ -38,15 +39,11 @@ pub fn deprecated_unittest_alias(checker: &mut Checker, expr: &Expr) {
Range::from_located(expr),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
check.amend(Fix {
content: format!("self.{}", target),
location: Location::new(expr.location.row(), expr.location.column()),
end_location: Location::new(
expr.end_location.row(),
expr.end_location.column(),
),
applied: false,
});
check.amend(Fix::replacement(
format!("self.{}", target),
expr.location,
expr.end_location.unwrap(),
));
}
checker.add_check(check);
}

View File

@@ -1,10 +1,4 @@
pub use assert_false::assert_false;
pub use assert_tuple::assert_tuple;
pub use deprecated_unittest_alias::deprecated_unittest_alias;
pub use duplicate_exceptions::duplicate_exceptions;
pub use if_tuple::if_tuple;
pub use invalid_print_syntax::invalid_print_syntax;
pub use print_call::print_call;
pub use super_call_with_parameters::super_call_with_parameters;
pub use type_of_primitive::type_of_primitive;
pub use unnecessary_abspath::unnecessary_abspath;
@@ -13,13 +7,7 @@ pub use use_pep604_annotation::use_pep604_annotation;
pub use useless_metaclass_type::useless_metaclass_type;
pub use useless_object_inheritance::useless_object_inheritance;
mod assert_false;
mod assert_tuple;
mod deprecated_unittest_alias;
mod duplicate_exceptions;
mod if_tuple;
mod invalid_print_syntax;
mod print_call;
mod super_call_with_parameters;
mod type_of_primitive;
mod unnecessary_abspath;

View File

@@ -1,8 +1,10 @@
use rustpython_ast::{Expr, Stmt};
use crate::ast::{checkers, helpers};
use crate::autofix::{fixer, fixes};
use crate::ast::helpers;
use crate::autofix::fixer;
use crate::check_ast::Checker;
use crate::pyupgrade;
use crate::pyupgrade::checks;
pub fn super_call_with_parameters(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) {
// Only bother going through the super check at all if we're in a `super` call.
@@ -14,9 +16,11 @@ pub fn super_call_with_parameters(checker: &mut Checker, expr: &Expr, func: &Exp
.iter()
.map(|index| checker.parents[*index])
.collect();
if let Some(mut check) = checkers::super_args(scope, &parents, expr, func, args) {
if let Some(mut check) = checks::super_args(scope, &parents, expr, func, args) {
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if let Some(fix) = fixes::remove_super_arguments(&mut checker.locator, expr) {
if let Some(fix) =
pyupgrade::fixes::remove_super_arguments(&mut checker.locator, expr)
{
check.amend(fix);
}
}

View File

@@ -1,23 +1,23 @@
use rustpython_ast::Expr;
use crate::ast::checkers;
use crate::ast::types::{CheckLocator, Range};
use crate::autofix::fixer;
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checks::{CheckKind, Fix};
use crate::checks::CheckKind;
use crate::pyupgrade::checks;
pub fn type_of_primitive(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) {
if let Some(mut check) =
checkers::type_of_primitive(func, args, checker.locate_check(Range::from_located(expr)))
checks::type_of_primitive(func, args, checker.locate_check(Range::from_located(expr)))
{
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if let CheckKind::TypeOfPrimitive(primitive) = &check.kind {
check.amend(Fix {
content: primitive.builtin(),
location: expr.location,
end_location: expr.end_location,
applied: false,
});
check.amend(Fix::replacement(
primitive.builtin(),
expr.location,
expr.end_location.unwrap(),
));
}
}
checker.add_check(check);

View File

@@ -1,22 +1,21 @@
use rustpython_ast::Expr;
use crate::ast::checkers;
use crate::ast::types::{CheckLocator, Range};
use crate::autofix::fixer;
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checks::Fix;
use crate::pyupgrade::checks;
pub fn unnecessary_abspath(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) {
if let Some(mut check) =
checkers::unnecessary_abspath(func, args, checker.locate_check(Range::from_located(expr)))
checks::unnecessary_abspath(func, args, checker.locate_check(Range::from_located(expr)))
{
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
check.amend(Fix {
content: "__file__".to_string(),
location: expr.location,
end_location: expr.end_location,
applied: false,
});
check.amend(Fix::replacement(
"__file__".to_string(),
expr.location,
expr.end_location.unwrap(),
));
}
checker.add_check(check);
}

View File

@@ -2,8 +2,9 @@ use rustpython_ast::Expr;
use crate::ast::types::Range;
use crate::autofix::fixer;
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind, Fix};
use crate::checks::{Check, CheckKind};
use crate::python::typing;
pub fn use_pep585_annotation(checker: &mut Checker, expr: &Expr, id: &str) {
@@ -14,12 +15,11 @@ pub fn use_pep585_annotation(checker: &mut Checker, expr: &Expr, id: &str) {
Range::from_located(expr),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
check.amend(Fix {
content: id.to_lowercase(),
location: expr.location,
end_location: expr.end_location,
applied: false,
})
check.amend(Fix::replacement(
id.to_lowercase(),
expr.location,
expr.end_location.unwrap(),
));
}
checker.add_check(check);
}

View File

@@ -3,8 +3,9 @@ use rustpython_ast::{Constant, Expr, ExprKind, Operator};
use crate::ast::helpers::match_name_or_attr;
use crate::ast::types::Range;
use crate::autofix::fixer;
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind, Fix};
use crate::checks::{Check, CheckKind};
use crate::code_gen::SourceGenerator;
fn optional(expr: &Expr) -> Expr {
@@ -49,12 +50,11 @@ pub fn use_pep604_annotation(checker: &mut Checker, expr: &Expr, value: &Expr, s
let mut generator = SourceGenerator::new();
if let Ok(()) = generator.unparse_expr(&optional(slice), 0) {
if let Ok(content) = generator.generate() {
check.amend(Fix {
check.amend(Fix::replacement(
content,
location: expr.location,
end_location: expr.end_location,
applied: false,
})
expr.location,
expr.end_location.unwrap(),
))
}
}
}
@@ -70,12 +70,11 @@ pub fn use_pep604_annotation(checker: &mut Checker, expr: &Expr, value: &Expr, s
let mut generator = SourceGenerator::new();
if let Ok(()) = generator.unparse_expr(&union(elts), 0) {
if let Ok(content) = generator.generate() {
check.amend(Fix {
check.amend(Fix::replacement(
content,
location: expr.location,
end_location: expr.end_location,
applied: false,
})
expr.location,
expr.end_location.unwrap(),
))
}
}
}
@@ -84,12 +83,11 @@ pub fn use_pep604_annotation(checker: &mut Checker, expr: &Expr, value: &Expr, s
let mut generator = SourceGenerator::new();
if let Ok(()) = generator.unparse_expr(slice, 0) {
if let Ok(content) = generator.generate() {
check.amend(Fix {
check.amend(Fix::replacement(
content,
location: expr.location,
end_location: expr.end_location,
applied: false,
});
expr.location,
expr.end_location.unwrap(),
));
}
}
}

View File

@@ -1,13 +1,13 @@
use log::error;
use rustpython_ast::{Expr, Stmt};
use crate::ast::checkers;
use crate::ast::types::{CheckLocator, Range};
use crate::autofix::{fixer, fixes};
use crate::autofix::{fixer, helpers};
use crate::check_ast::Checker;
use crate::pyupgrade::checks;
pub fn useless_metaclass_type(checker: &mut Checker, stmt: &Stmt, value: &Expr, targets: &[Expr]) {
if let Some(mut check) = checkers::useless_metaclass_type(
if let Some(mut check) = checks::useless_metaclass_type(
targets,
value,
checker.locate_check(Range::from_located(stmt)),
@@ -20,13 +20,13 @@ pub fn useless_metaclass_type(checker: &mut Checker, stmt: &Stmt, value: &Expr,
.map(|index| checker.parents[*index])
.collect();
match fixes::remove_stmt(
match helpers::remove_stmt(
checker.parents[context.defined_by],
context.defined_in.map(|index| checker.parents[index]),
&deleted,
) {
Ok(fix) => {
if fix.content.is_empty() || fix.content == "pass" {
if fix.patch.content.is_empty() || fix.patch.content == "pass" {
checker.deletions.insert(context.defined_by);
}
check.amend(fix)

View File

@@ -1,8 +1,9 @@
use rustpython_ast::{Expr, Keyword, Stmt};
use crate::ast::checkers;
use crate::autofix::{fixer, fixes};
use crate::autofix::fixer;
use crate::check_ast::Checker;
use crate::pyupgrade;
use crate::pyupgrade::checks;
pub fn useless_object_inheritance(
checker: &mut Checker,
@@ -12,9 +13,9 @@ pub fn useless_object_inheritance(
keywords: &[Keyword],
) {
let scope = checker.current_scope();
if let Some(mut check) = checkers::useless_object_inheritance(name, bases, scope) {
if let Some(mut check) = checks::useless_object_inheritance(name, bases, scope) {
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if let Some(fix) = fixes::remove_class_def_base(
if let Some(fix) = pyupgrade::fixes::remove_class_def_base(
&mut checker.locator,
&stmt.location,
check.location,

37
src/pyupgrade/types.rs Normal file
View File

@@ -0,0 +1,37 @@
use rustpython_ast::Constant;
use serde::{Deserialize, Serialize};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Primitive {
Bool,
Str,
Bytes,
Int,
Float,
Complex,
}
impl Primitive {
pub fn from_constant(constant: &Constant) -> Option<Self> {
match constant {
Constant::Bool(_) => Some(Primitive::Bool),
Constant::Str(_) => Some(Primitive::Str),
Constant::Bytes(_) => Some(Primitive::Bytes),
Constant::Int(_) => Some(Primitive::Int),
Constant::Float(_) => Some(Primitive::Float),
Constant::Complex { .. } => Some(Primitive::Complex),
_ => None,
}
}
pub fn builtin(&self) -> String {
match self {
Primitive::Bool => "bool".to_string(),
Primitive::Str => "str".to_string(),
Primitive::Bytes => "bytes".to_string(),
Primitive::Int => "int".to_string(),
Primitive::Float => "float".to_string(),
Primitive::Complex => "complex".to_string(),
}
}
}

View File

@@ -10,13 +10,14 @@ expression: checks
row: 8
column: 13
fix:
content: raise AssertionError()
location:
row: 8
column: 1
end_location:
row: 8
column: 13
patch:
content: raise AssertionError()
location:
row: 8
column: 1
end_location:
row: 8
column: 13
applied: false
- kind: DoNotAssertFalse
location:
@@ -26,12 +27,13 @@ expression: checks
row: 10
column: 13
fix:
content: "raise AssertionError(\"message\")"
location:
row: 10
column: 1
end_location:
row: 10
column: 24
patch:
content: "raise AssertionError('message')"
location:
row: 10
column: 1
end_location:
row: 10
column: 24
applied: false

View File

@@ -12,13 +12,14 @@ expression: checks
row: 17
column: 25
fix:
content: "OSError,"
location:
row: 17
column: 9
end_location:
row: 17
column: 25
patch:
content: "OSError,"
location:
row: 17
column: 9
end_location:
row: 17
column: 25
applied: false
- kind:
DuplicateHandlerException:
@@ -30,13 +31,14 @@ expression: checks
row: 28
column: 25
fix:
content: "MyError,"
location:
row: 28
column: 9
end_location:
row: 28
column: 25
patch:
content: "MyError,"
location:
row: 28
column: 9
end_location:
row: 28
column: 25
applied: false
- kind:
DuplicateHandlerException:
@@ -48,12 +50,13 @@ expression: checks
row: 49
column: 27
fix:
content: "re.error,"
location:
row: 49
column: 9
end_location:
row: 49
column: 27
patch:
content: "re.error,"
location:
row: 49
column: 9
end_location:
row: 49
column: 27
applied: false

View File

@@ -11,13 +11,14 @@ expression: checks
row: 132
column: 25
fix:
content: ""
location:
row: 131
column: 1
end_location:
row: 132
column: 1
patch:
content: ""
location:
row: 131
column: 1
end_location:
row: 132
column: 1
applied: false
- kind:
NoBlankLineBeforeFunction: 1
@@ -28,12 +29,13 @@ expression: checks
row: 146
column: 38
fix:
content: ""
location:
row: 145
column: 1
end_location:
row: 146
column: 1
patch:
content: ""
location:
row: 145
column: 1
end_location:
row: 146
column: 1
applied: false

View File

@@ -11,13 +11,14 @@ expression: checks
row: 79
column: 33
fix:
content: ""
location:
row: 81
column: 1
end_location:
row: 80
column: 1
patch:
content: ""
location:
row: 81
column: 1
end_location:
row: 80
column: 1
applied: false
- kind:
NoBlankLineAfterFunction: 1
@@ -28,13 +29,14 @@ expression: checks
row: 137
column: 25
fix:
content: ""
location:
row: 138
column: 1
end_location:
row: 139
column: 1
patch:
content: ""
location:
row: 138
column: 1
end_location:
row: 139
column: 1
applied: false
- kind:
NoBlankLineAfterFunction: 1
@@ -45,13 +47,14 @@ expression: checks
row: 146
column: 38
fix:
content: ""
location:
row: 147
column: 1
end_location:
row: 148
column: 1
patch:
content: ""
location:
row: 147
column: 1
end_location:
row: 148
column: 1
applied: false
- kind:
NoBlankLineAfterFunction: 0
@@ -62,12 +65,13 @@ expression: checks
row: 453
column: 24
fix:
content: ""
location:
row: 455
column: 1
end_location:
row: 454
column: 1
patch:
content: ""
location:
row: 455
column: 1
end_location:
row: 454
column: 1
applied: false

View File

@@ -11,13 +11,14 @@ expression: checks
row: 156
column: 33
fix:
content: "\n"
location:
row: 156
column: 1
end_location:
row: 156
column: 1
patch:
content: "\n"
location:
row: 156
column: 1
end_location:
row: 156
column: 1
applied: false
- kind:
OneBlankLineBeforeClass: 0
@@ -28,13 +29,14 @@ expression: checks
row: 187
column: 46
fix:
content: "\n"
location:
row: 187
column: 1
end_location:
row: 187
column: 1
patch:
content: "\n"
location:
row: 187
column: 1
end_location:
row: 187
column: 1
applied: false
- kind:
OneBlankLineBeforeClass: 0
@@ -45,12 +47,13 @@ expression: checks
row: 527
column: 8
fix:
content: "\n"
location:
row: 521
column: 1
end_location:
row: 521
column: 1
patch:
content: "\n"
location:
row: 521
column: 1
end_location:
row: 521
column: 1
applied: false

View File

@@ -11,13 +11,14 @@ expression: checks
row: 176
column: 25
fix:
content: "\n"
location:
row: 177
column: 1
end_location:
row: 177
column: 1
patch:
content: "\n"
location:
row: 177
column: 1
end_location:
row: 177
column: 1
applied: false
- kind:
OneBlankLineAfterClass: 0
@@ -28,12 +29,13 @@ expression: checks
row: 187
column: 46
fix:
content: "\n"
location:
row: 188
column: 1
end_location:
row: 188
column: 1
patch:
content: "\n"
location:
row: 188
column: 1
end_location:
row: 188
column: 1
applied: false

View File

@@ -2,7 +2,7 @@
source: src/linter.rs
expression: checks
---
- kind: NoBlankLineAfterSummary
- kind: BlankLineAfterSummary
location:
row: 195
column: 5
@@ -10,15 +10,16 @@ expression: checks
row: 198
column: 8
fix:
content: "\n"
location:
row: 196
column: 1
end_location:
row: 196
column: 1
patch:
content: "\n"
location:
row: 196
column: 1
end_location:
row: 196
column: 1
applied: false
- kind: NoBlankLineAfterSummary
- kind: BlankLineAfterSummary
location:
row: 205
column: 5
@@ -26,12 +27,13 @@ expression: checks
row: 210
column: 8
fix:
content: "\n"
location:
row: 206
column: 1
end_location:
row: 208
column: 1
patch:
content: "\n"
location:
row: 206
column: 1
end_location:
row: 208
column: 1
applied: false

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,12 +10,13 @@ expression: checks
row: 278
column: 20
fix:
content: "\n "
location:
row: 278
column: 17
end_location:
row: 278
column: 17
patch:
content: "\n "
location:
row: 278
column: 17
end_location:
row: 278
column: 17
applied: false

View File

@@ -10,13 +10,14 @@ expression: checks
row: 283
column: 34
fix:
content: Whitespace at the end.
location:
row: 283
column: 8
end_location:
row: 283
column: 31
patch:
content: Whitespace at the end.
location:
row: 283
column: 8
end_location:
row: 283
column: 31
applied: false
- kind: NoSurroundingWhitespace
location:
@@ -26,13 +27,14 @@ expression: checks
row: 288
column: 38
fix:
content: Whitespace at everywhere.
location:
row: 288
column: 8
end_location:
row: 288
column: 35
patch:
content: Whitespace at everywhere.
location:
row: 288
column: 8
end_location:
row: 288
column: 35
applied: false
- kind: NoSurroundingWhitespace
location:
@@ -42,12 +44,13 @@ expression: checks
row: 297
column: 8
fix:
content: Whitespace at the beginning.
location:
row: 294
column: 8
end_location:
row: 294
column: 37
patch:
content: Whitespace at the beginning.
location:
row: 294
column: 8
end_location:
row: 294
column: 37
applied: false

View File

@@ -11,13 +11,14 @@ expression: checks
row: 165
column: 30
fix:
content: ""
location:
row: 164
column: 1
end_location:
row: 165
column: 1
patch:
content: ""
location:
row: 164
column: 1
end_location:
row: 165
column: 1
applied: false
- kind:
NoBlankLineBeforeClass: 1
@@ -28,12 +29,13 @@ expression: checks
row: 176
column: 25
fix:
content: ""
location:
row: 175
column: 1
end_location:
row: 176
column: 1
patch:
content: ""
location:
row: 175
column: 1
end_location:
row: 176
column: 1
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: 153
column: 8
fix: ~
fix:
patch:
content: " "
location:
row: 150
column: 1
end_location:
row: 150
column: 9
applied: false
- kind:
SectionUnderlineNotOverIndented: Returns
location:
@@ -19,5 +28,14 @@ expression: checks
end_location:
row: 165
column: 8
fix: ~
fix:
patch:
content: " "
location:
row: 164
column: 1
end_location:
row: 164
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

@@ -10,7 +10,16 @@ expression: checks
end_location:
row: 47
column: 8
fix: ~
fix:
patch:
content: " -------\n"
location:
row: 45
column: 1
end_location:
row: 45
column: 1
applied: false
- kind:
DashedUnderlineAfterSection: Returns
location:
@@ -19,7 +28,16 @@ expression: checks
end_location:
row: 58
column: 8
fix: ~
fix:
patch:
content: " -------\n"
location:
row: 57
column: 1
end_location:
row: 57
column: 1
applied: false
- kind:
DashedUnderlineAfterSection: Raises
location:
@@ -28,7 +46,16 @@ expression: checks
end_location:
row: 221
column: 8
fix: ~
fix:
patch:
content: " ------\n"
location:
row: 219
column: 1
end_location:
row: 219
column: 1
applied: false
- kind:
DashedUnderlineAfterSection: Returns
location:
@@ -37,7 +64,16 @@ expression: checks
end_location:
row: 262
column: 8
fix: ~
fix:
patch:
content: " -------\n"
location:
row: 258
column: 1
end_location:
row: 258
column: 1
applied: false
- kind:
DashedUnderlineAfterSection: Raises
location:
@@ -46,7 +82,16 @@ expression: checks
end_location:
row: 262
column: 8
fix: ~
fix:
patch:
content: " ------\n"
location:
row: 260
column: 1
end_location:
row: 260
column: 1
applied: false
- kind:
DashedUnderlineAfterSection: Args
location:
@@ -55,7 +100,16 @@ expression: checks
end_location:
row: 274
column: 8
fix: ~
fix:
patch:
content: " ----\n"
location:
row: 272
column: 1
end_location:
row: 272
column: 1
applied: false
- kind:
DashedUnderlineAfterSection: Args
location:
@@ -64,7 +118,16 @@ expression: checks
end_location:
row: 292
column: 12
fix: ~
fix:
patch:
content: " ----\n"
location:
row: 289
column: 1
end_location:
row: 289
column: 1
applied: false
- kind:
DashedUnderlineAfterSection: Args
location:
@@ -73,7 +136,16 @@ expression: checks
end_location:
row: 306
column: 8
fix: ~
fix:
patch:
content: " ----\n"
location:
row: 304
column: 1
end_location:
row: 304
column: 1
applied: false
- kind:
DashedUnderlineAfterSection: Args
location:
@@ -82,7 +154,16 @@ expression: checks
end_location:
row: 319
column: 12
fix: ~
fix:
patch:
content: " ----\n"
location:
row: 316
column: 1
end_location:
row: 316
column: 1
applied: false
- kind:
DashedUnderlineAfterSection: Args
location:
@@ -91,7 +172,16 @@ expression: checks
end_location:
row: 330
column: 12
fix: ~
fix:
patch:
content: " ----\n"
location:
row: 328
column: 1
end_location:
row: 328
column: 1
applied: false
- kind:
DashedUnderlineAfterSection: Args
location:
@@ -100,7 +190,16 @@ expression: checks
end_location:
row: 343
column: 12
fix: ~
fix:
patch:
content: " ----\n"
location:
row: 340
column: 1
end_location:
row: 340
column: 1
applied: false
- kind:
DashedUnderlineAfterSection: Args
location:
@@ -109,7 +208,16 @@ expression: checks
end_location:
row: 355
column: 12
fix: ~
fix:
patch:
content: " ----\n"
location:
row: 353
column: 1
end_location:
row: 353
column: 1
applied: false
- kind:
DashedUnderlineAfterSection: Args
location:
@@ -118,7 +226,16 @@ expression: checks
end_location:
row: 367
column: 12
fix: ~
fix:
patch:
content: " ----\n"
location:
row: 365
column: 1
end_location:
row: 365
column: 1
applied: false
- kind:
DashedUnderlineAfterSection: Args
location:
@@ -127,7 +244,16 @@ expression: checks
end_location:
row: 382
column: 12
fix: ~
fix:
patch:
content: " ----\n"
location:
row: 374
column: 1
end_location:
row: 374
column: 1
applied: false
- kind:
DashedUnderlineAfterSection: Args
location:
@@ -136,5 +262,14 @@ expression: checks
end_location:
row: 497
column: 12
fix: ~
fix:
patch:
content: " ----\n"
location:
row: 495
column: 1
end_location:
row: 495
column: 1
applied: false

View File

@@ -10,5 +10,14 @@ expression: checks
end_location:
row: 92
column: 8
fix: ~
fix:
patch:
content: ""
location:
row: 88
column: 1
end_location:
row: 89
column: 1
applied: false

View File

@@ -10,7 +10,16 @@ expression: checks
end_location:
row: 105
column: 8
fix: ~
fix:
patch:
content: " -------\n"
location:
row: 102
column: 1
end_location:
row: 103
column: 1
applied: false
- kind:
SectionUnderlineMatchesSectionLength: Returns
location:
@@ -19,5 +28,14 @@ expression: checks
end_location:
row: 221
column: 8
fix: ~
fix:
patch:
content: " -------\n"
location:
row: 216
column: 1
end_location:
row: 217
column: 1
applied: false

View File

@@ -11,13 +11,14 @@ expression: checks
row: 78
column: 8
fix:
content: "\n"
location:
row: 71
column: 1
end_location:
row: 71
column: 1
patch:
content: "\n"
location:
row: 71
column: 1
end_location:
row: 71
column: 1
applied: false
- kind:
BlankLineAfterSection: Returns
@@ -28,12 +29,13 @@ expression: checks
row: 221
column: 8
fix:
content: "\n"
location:
row: 218
column: 1
end_location:
row: 218
column: 1
patch:
content: "\n"
location:
row: 218
column: 1
end_location:
row: 218
column: 1
applied: false

View File

@@ -10,7 +10,16 @@ expression: checks
end_location:
row: 78
column: 8
fix: ~
fix:
patch:
content: "\n"
location:
row: 71
column: 1
end_location:
row: 71
column: 1
applied: false
- kind:
BlankLineBeforeSection: Returns
location:
@@ -19,7 +28,16 @@ expression: checks
end_location:
row: 129
column: 8
fix: ~
fix:
patch:
content: "\n"
location:
row: 125
column: 1
end_location:
row: 125
column: 1
applied: false
- kind:
BlankLineBeforeSection: Raises
location:
@@ -28,5 +46,14 @@ expression: checks
end_location:
row: 221
column: 8
fix: ~
fix:
patch:
content: "\n"
location:
row: 218
column: 1
end_location:
row: 218
column: 1
applied: false

View File

@@ -10,5 +10,14 @@ expression: checks
end_location:
row: 221
column: 8
fix: ~
fix:
patch:
content: ""
location:
row: 211
column: 1
end_location:
row: 212
column: 1
applied: false

View File

@@ -12,13 +12,14 @@ expression: checks
row: 2
column: 21
fix:
content: import os
location:
row: 2
column: 1
end_location:
row: 2
column: 21
patch:
content: import os
location:
row: 2
column: 1
end_location:
row: 2
column: 21
applied: false
- kind:
UnusedImport:
@@ -30,13 +31,14 @@ expression: checks
row: 8
column: 2
fix:
content: "from collections import (\n Counter,\n namedtuple,\n)"
location:
row: 4
column: 1
end_location:
row: 8
column: 2
patch:
content: "from collections import (\n Counter,\n namedtuple,\n)"
location:
row: 4
column: 1
end_location:
row: 8
column: 2
applied: false
- kind:
UnusedImport:
@@ -48,13 +50,14 @@ expression: checks
row: 12
column: 24
fix:
content: import logging.handlers
location:
row: 12
column: 1
end_location:
row: 12
column: 24
patch:
content: import logging.handlers
location:
row: 12
column: 1
end_location:
row: 12
column: 24
applied: false
- kind:
UnusedImport:
@@ -66,13 +69,14 @@ expression: checks
row: 33
column: 18
fix:
content: ""
location:
row: 33
column: 1
end_location:
row: 34
column: 1
patch:
content: ""
location:
row: 33
column: 1
end_location:
row: 34
column: 1
applied: false
- kind:
UnusedImport:
@@ -84,13 +88,14 @@ expression: checks
row: 34
column: 21
fix:
content: ""
location:
row: 34
column: 1
end_location:
row: 35
column: 1
patch:
content: ""
location:
row: 34
column: 1
end_location:
row: 35
column: 1
applied: false
- kind:
UnusedImport:
@@ -102,13 +107,14 @@ expression: checks
row: 38
column: 19
fix:
content: ""
location:
row: 38
column: 1
end_location:
row: 39
column: 1
patch:
content: ""
location:
row: 38
column: 1
end_location:
row: 39
column: 1
applied: false
- kind:
UnusedImport:
@@ -120,12 +126,13 @@ expression: checks
row: 53
column: 22
fix:
content: pass
location:
row: 53
column: 9
end_location:
row: 53
column: 22
patch:
content: pass
location:
row: 53
column: 9
end_location:
row: 53
column: 22
applied: false

View File

@@ -10,12 +10,13 @@ expression: checks
row: 1
column: 23
fix:
content: ""
location:
row: 1
column: 1
end_location:
row: 2
column: 1
patch:
content: ""
location:
row: 1
column: 1
end_location:
row: 2
column: 1
applied: false

View File

@@ -10,13 +10,14 @@ expression: checks
row: 3
column: 24
fix:
content: ""
location:
row: 3
column: 1
end_location:
row: 4
column: 1
patch:
content: ""
location:
row: 3
column: 1
end_location:
row: 4
column: 1
applied: false
- kind: PPrintFound
location:
@@ -26,12 +27,13 @@ expression: checks
row: 8
column: 31
fix:
content: ""
location:
row: 8
column: 1
end_location:
row: 9
column: 1
patch:
content: ""
location:
row: 8
column: 1
end_location:
row: 9
column: 1
applied: false

View File

@@ -10,13 +10,14 @@ expression: checks
row: 2
column: 25
fix:
content: pass
location:
row: 2
column: 5
end_location:
row: 2
column: 25
patch:
content: pass
location:
row: 2
column: 5
end_location:
row: 2
column: 25
applied: false
- kind: UselessMetaclassType
location:
@@ -26,12 +27,13 @@ expression: checks
row: 6
column: 25
fix:
content: ""
location:
row: 6
column: 1
end_location:
row: 7
column: 1
patch:
content: ""
location:
row: 6
column: 1
end_location:
row: 7
column: 1
applied: false

View File

@@ -10,13 +10,14 @@ expression: checks
row: 3
column: 22
fix:
content: __file__
location:
row: 3
column: 5
end_location:
row: 3
column: 22
patch:
content: __file__
location:
row: 3
column: 5
end_location:
row: 3
column: 22
applied: false
- kind: UnnecessaryAbspath
location:
@@ -26,13 +27,14 @@ expression: checks
row: 9
column: 30
fix:
content: __file__
location:
row: 9
column: 5
end_location:
row: 9
column: 30
patch:
content: __file__
location:
row: 9
column: 5
end_location:
row: 9
column: 30
applied: false
- kind: UnnecessaryAbspath
location:
@@ -42,12 +44,13 @@ expression: checks
row: 15
column: 27
fix:
content: __file__
location:
row: 15
column: 5
end_location:
row: 15
column: 27
patch:
content: __file__
location:
row: 15
column: 5
end_location:
row: 15
column: 27
applied: false

View File

@@ -11,13 +11,14 @@ expression: checks
row: 1
column: 9
fix:
content: str
location:
row: 1
column: 1
end_location:
row: 1
column: 9
patch:
content: str
location:
row: 1
column: 1
end_location:
row: 1
column: 9
applied: false
- kind:
TypeOfPrimitive: Bytes
@@ -28,13 +29,14 @@ expression: checks
row: 2
column: 10
fix:
content: bytes
location:
row: 2
column: 1
end_location:
row: 2
column: 10
patch:
content: bytes
location:
row: 2
column: 1
end_location:
row: 2
column: 10
applied: false
- kind:
TypeOfPrimitive: Int
@@ -45,13 +47,14 @@ expression: checks
row: 3
column: 8
fix:
content: int
location:
row: 3
column: 1
end_location:
row: 3
column: 8
patch:
content: int
location:
row: 3
column: 1
end_location:
row: 3
column: 8
applied: false
- kind:
TypeOfPrimitive: Float
@@ -62,13 +65,14 @@ expression: checks
row: 4
column: 9
fix:
content: float
location:
row: 4
column: 1
end_location:
row: 4
column: 9
patch:
content: float
location:
row: 4
column: 1
end_location:
row: 4
column: 9
applied: false
- kind:
TypeOfPrimitive: Complex
@@ -79,12 +83,13 @@ expression: checks
row: 5
column: 9
fix:
content: complex
location:
row: 5
column: 1
end_location:
row: 5
column: 9
patch:
content: complex
location:
row: 5
column: 1
end_location:
row: 5
column: 9
applied: false

View File

@@ -11,13 +11,14 @@ expression: checks
row: 5
column: 15
fix:
content: ""
location:
row: 5
column: 8
end_location:
row: 5
column: 16
patch:
content: ""
location:
row: 5
column: 8
end_location:
row: 5
column: 16
applied: false
- kind:
UselessObjectInheritance: A
@@ -28,13 +29,14 @@ expression: checks
row: 10
column: 11
fix:
content: ""
location:
row: 9
column: 8
end_location:
row: 11
column: 2
patch:
content: ""
location:
row: 9
column: 8
end_location:
row: 11
column: 2
applied: false
- kind:
UselessObjectInheritance: A
@@ -45,13 +47,14 @@ expression: checks
row: 16
column: 11
fix:
content: ""
location:
row: 15
column: 8
end_location:
row: 18
column: 2
patch:
content: ""
location:
row: 15
column: 8
end_location:
row: 18
column: 2
applied: false
- kind:
UselessObjectInheritance: A
@@ -62,13 +65,14 @@ expression: checks
row: 24
column: 11
fix:
content: ""
location:
row: 22
column: 8
end_location:
row: 25
column: 2
patch:
content: ""
location:
row: 22
column: 8
end_location:
row: 25
column: 2
applied: false
- kind:
UselessObjectInheritance: A
@@ -79,13 +83,14 @@ expression: checks
row: 31
column: 11
fix:
content: ""
location:
row: 29
column: 8
end_location:
row: 32
column: 2
patch:
content: ""
location:
row: 29
column: 8
end_location:
row: 32
column: 2
applied: false
- kind:
UselessObjectInheritance: A
@@ -96,13 +101,14 @@ expression: checks
row: 37
column: 11
fix:
content: ""
location:
row: 36
column: 8
end_location:
row: 39
column: 2
patch:
content: ""
location:
row: 36
column: 8
end_location:
row: 39
column: 2
applied: false
- kind:
UselessObjectInheritance: A
@@ -113,13 +119,14 @@ expression: checks
row: 45
column: 11
fix:
content: ""
location:
row: 43
column: 8
end_location:
row: 47
column: 2
patch:
content: ""
location:
row: 43
column: 8
end_location:
row: 47
column: 2
applied: false
- kind:
UselessObjectInheritance: A
@@ -130,13 +137,14 @@ expression: checks
row: 53
column: 11
fix:
content: ""
location:
row: 51
column: 8
end_location:
row: 55
column: 2
patch:
content: ""
location:
row: 51
column: 8
end_location:
row: 55
column: 2
applied: false
- kind:
UselessObjectInheritance: A
@@ -147,13 +155,14 @@ expression: checks
row: 61
column: 11
fix:
content: ""
location:
row: 59
column: 8
end_location:
row: 63
column: 2
patch:
content: ""
location:
row: 59
column: 8
end_location:
row: 63
column: 2
applied: false
- kind:
UselessObjectInheritance: A
@@ -164,13 +173,14 @@ expression: checks
row: 69
column: 11
fix:
content: ""
location:
row: 67
column: 8
end_location:
row: 71
column: 2
patch:
content: ""
location:
row: 67
column: 8
end_location:
row: 71
column: 2
applied: false
- kind:
UselessObjectInheritance: B
@@ -181,13 +191,14 @@ expression: checks
row: 75
column: 18
fix:
content: ""
location:
row: 75
column: 10
end_location:
row: 75
column: 18
patch:
content: ""
location:
row: 75
column: 10
end_location:
row: 75
column: 18
applied: false
- kind:
UselessObjectInheritance: B
@@ -198,13 +209,14 @@ expression: checks
row: 79
column: 15
fix:
content: ""
location:
row: 79
column: 9
end_location:
row: 79
column: 17
patch:
content: ""
location:
row: 79
column: 9
end_location:
row: 79
column: 17
applied: false
- kind:
UselessObjectInheritance: B
@@ -215,13 +227,14 @@ expression: checks
row: 84
column: 11
fix:
content: ""
location:
row: 84
column: 5
end_location:
row: 85
column: 5
patch:
content: ""
location:
row: 84
column: 5
end_location:
row: 85
column: 5
applied: false
- kind:
UselessObjectInheritance: B
@@ -232,13 +245,14 @@ expression: checks
row: 92
column: 11
fix:
content: ""
location:
row: 91
column: 6
end_location:
row: 92
column: 11
patch:
content: ""
location:
row: 91
column: 6
end_location:
row: 92
column: 11
applied: false
- kind:
UselessObjectInheritance: B
@@ -249,13 +263,14 @@ expression: checks
row: 98
column: 11
fix:
content: ""
location:
row: 98
column: 5
end_location:
row: 100
column: 5
patch:
content: ""
location:
row: 98
column: 5
end_location:
row: 100
column: 5
applied: false
- kind:
UselessObjectInheritance: B
@@ -266,13 +281,14 @@ expression: checks
row: 108
column: 11
fix:
content: ""
location:
row: 107
column: 6
end_location:
row: 108
column: 11
patch:
content: ""
location:
row: 107
column: 6
end_location:
row: 108
column: 11
applied: false
- kind:
UselessObjectInheritance: A
@@ -283,13 +299,14 @@ expression: checks
row: 114
column: 19
fix:
content: ""
location:
row: 114
column: 12
end_location:
row: 114
column: 20
patch:
content: ""
location:
row: 114
column: 12
end_location:
row: 114
column: 20
applied: false
- kind:
UselessObjectInheritance: A
@@ -300,13 +317,14 @@ expression: checks
row: 119
column: 11
fix:
content: ""
location:
row: 118
column: 8
end_location:
row: 120
column: 2
patch:
content: ""
location:
row: 118
column: 8
end_location:
row: 120
column: 2
applied: false
- kind:
UselessObjectInheritance: A
@@ -317,13 +335,14 @@ expression: checks
row: 125
column: 11
fix:
content: ""
location:
row: 124
column: 8
end_location:
row: 126
column: 2
patch:
content: ""
location:
row: 124
column: 8
end_location:
row: 126
column: 2
applied: false
- kind:
UselessObjectInheritance: A
@@ -334,12 +353,13 @@ expression: checks
row: 131
column: 11
fix:
content: ""
location:
row: 130
column: 8
end_location:
row: 133
column: 2
patch:
content: ""
location:
row: 130
column: 8
end_location:
row: 133
column: 2
applied: false

View File

@@ -13,13 +13,14 @@ expression: checks
row: 6
column: 26
fix:
content: self.assertEqual
location:
row: 6
column: 9
end_location:
row: 6
column: 26
patch:
content: self.assertEqual
location:
row: 6
column: 9
end_location:
row: 6
column: 26
applied: false
- kind:
DeprecatedUnittestAlias:
@@ -32,13 +33,14 @@ expression: checks
row: 7
column: 26
fix:
content: self.assertEqual
location:
row: 7
column: 9
end_location:
row: 7
column: 26
patch:
content: self.assertEqual
location:
row: 7
column: 9
end_location:
row: 7
column: 26
applied: false
- kind:
DeprecatedUnittestAlias:
@@ -51,13 +53,14 @@ expression: checks
row: 9
column: 35
fix:
content: self.assertAlmostEqual
location:
row: 9
column: 9
end_location:
row: 9
column: 35
patch:
content: self.assertAlmostEqual
location:
row: 9
column: 9
end_location:
row: 9
column: 35
applied: false
- kind:
DeprecatedUnittestAlias:
@@ -70,12 +73,13 @@ expression: checks
row: 10
column: 36
fix:
content: self.assertNotRegex
location:
row: 10
column: 9
end_location:
row: 10
column: 36
patch:
content: self.assertNotRegex
location:
row: 10
column: 9
end_location:
row: 10
column: 36
applied: false

View File

@@ -11,13 +11,14 @@ expression: checks
row: 4
column: 14
fix:
content: list
location:
row: 4
column: 10
end_location:
row: 4
column: 14
patch:
content: list
location:
row: 4
column: 10
end_location:
row: 4
column: 14
applied: false
- kind:
UsePEP585Annotation: List
@@ -28,12 +29,13 @@ expression: checks
row: 11
column: 21
fix:
content: list
location:
row: 11
column: 10
end_location:
row: 11
column: 21
patch:
content: list
location:
row: 11
column: 10
end_location:
row: 11
column: 21
applied: false

View File

@@ -10,13 +10,14 @@ expression: checks
row: 4
column: 23
fix:
content: str | None
location:
row: 4
column: 10
end_location:
row: 4
column: 23
patch:
content: str | None
location:
row: 4
column: 10
end_location:
row: 4
column: 23
applied: false
- kind: UsePEP604Annotation
location:
@@ -26,13 +27,14 @@ expression: checks
row: 11
column: 30
fix:
content: str | None
location:
row: 11
column: 10
end_location:
row: 11
column: 30
patch:
content: str | None
location:
row: 11
column: 10
end_location:
row: 11
column: 30
applied: false
- kind: UsePEP604Annotation
location:
@@ -42,13 +44,14 @@ expression: checks
row: 18
column: 46
fix:
content: "str | int | Union[float, bytes]"
location:
row: 18
column: 10
end_location:
row: 18
column: 46
patch:
content: "str | int | Union[float, bytes]"
location:
row: 18
column: 10
end_location:
row: 18
column: 46
applied: false
- kind: UsePEP604Annotation
location:
@@ -58,13 +61,14 @@ expression: checks
row: 18
column: 45
fix:
content: float | bytes
location:
row: 18
column: 26
end_location:
row: 18
column: 45
patch:
content: float | bytes
location:
row: 18
column: 26
end_location:
row: 18
column: 45
applied: false
- kind: UsePEP604Annotation
location:
@@ -74,13 +78,14 @@ expression: checks
row: 25
column: 32
fix:
content: str | int
location:
row: 25
column: 10
end_location:
row: 25
column: 32
patch:
content: str | int
location:
row: 25
column: 10
end_location:
row: 25
column: 32
applied: false
- kind: UsePEP604Annotation
location:
@@ -90,13 +95,14 @@ expression: checks
row: 32
column: 48
fix:
content: "str | int | Union[float, bytes]"
location:
row: 32
column: 10
end_location:
row: 32
column: 48
patch:
content: "str | int | Union[float, bytes]"
location:
row: 32
column: 10
end_location:
row: 32
column: 48
applied: false
- kind: UsePEP604Annotation
location:
@@ -106,13 +112,14 @@ expression: checks
row: 32
column: 48
fix:
content: float | bytes
location:
row: 32
column: 10
end_location:
row: 32
column: 48
patch:
content: float | bytes
location:
row: 32
column: 10
end_location:
row: 32
column: 48
applied: false
- kind: UsePEP604Annotation
location:
@@ -122,12 +129,13 @@ expression: checks
row: 39
column: 34
fix:
content: str | int
location:
row: 39
column: 10
end_location:
row: 39
column: 34
patch:
content: str | int
location:
row: 39
column: 10
end_location:
row: 39
column: 34
applied: false

View File

@@ -10,13 +10,14 @@ expression: checks
row: 17
column: 36
fix:
content: super()
location:
row: 17
column: 18
end_location:
row: 17
column: 36
patch:
content: super()
location:
row: 17
column: 18
end_location:
row: 17
column: 36
applied: false
- kind: SuperCallWithParameters
location:
@@ -26,13 +27,14 @@ expression: checks
row: 18
column: 27
fix:
content: super()
location:
row: 18
column: 9
end_location:
row: 18
column: 27
patch:
content: super()
location:
row: 18
column: 9
end_location:
row: 18
column: 27
applied: false
- kind: SuperCallWithParameters
location:
@@ -42,13 +44,14 @@ expression: checks
row: 22
column: 10
fix:
content: super()
location:
row: 19
column: 9
end_location:
row: 22
column: 10
patch:
content: super()
location:
row: 19
column: 9
end_location:
row: 22
column: 10
applied: false
- kind: SuperCallWithParameters
location:
@@ -58,13 +61,14 @@ expression: checks
row: 36
column: 29
fix:
content: super()
location:
row: 36
column: 9
end_location:
row: 36
column: 29
patch:
content: super()
location:
row: 36
column: 9
end_location:
row: 36
column: 29
applied: false
- kind: SuperCallWithParameters
location:
@@ -74,12 +78,13 @@ expression: checks
row: 50
column: 33
fix:
content: super()
location:
row: 50
column: 13
end_location:
row: 50
column: 33
patch:
content: super()
location:
row: 50
column: 13
end_location:
row: 50
column: 33
applied: false

View File

@@ -12,13 +12,14 @@ expression: checks
row: 8
column: 2
fix:
content: "from models import (\n Fruit,\n)"
location:
row: 5
column: 1
end_location:
row: 8
column: 2
patch:
content: "from models import (\n Fruit,\n)"
location:
row: 5
column: 1
end_location:
row: 8
column: 2
applied: false
- kind:
UndefinedName: Bar

View File

@@ -11,13 +11,14 @@ expression: checks
row: 9
column: 18
fix:
content: ""
location:
row: 9
column: 10
end_location:
row: 9
column: 18
patch:
content: ""
location:
row: 9
column: 10
end_location:
row: 9
column: 18
applied: false
- kind:
UnusedNOQA:
@@ -29,13 +30,14 @@ expression: checks
row: 13
column: 24
fix:
content: ""
location:
row: 13
column: 10
end_location:
row: 13
column: 24
patch:
content: ""
location:
row: 13
column: 10
end_location:
row: 13
column: 24
applied: false
- kind:
UnusedNOQA:
@@ -48,13 +50,14 @@ expression: checks
row: 16
column: 30
fix:
content: ""
location:
row: 16
column: 10
end_location:
row: 16
column: 30
patch:
content: ""
location:
row: 16
column: 10
end_location:
row: 16
column: 30
applied: false
- kind:
UnusedNOQA:
@@ -66,13 +69,14 @@ expression: checks
row: 19
column: 30
fix:
content: " # noqa: F841"
location:
row: 19
column: 10
end_location:
row: 19
column: 30
patch:
content: " # noqa: F841"
location:
row: 19
column: 10
end_location:
row: 19
column: 30
applied: false
- kind:
UnusedNOQA:
@@ -84,13 +88,14 @@ expression: checks
row: 44
column: 24
fix:
content: " # noqa: E501"
location:
row: 44
column: 4
end_location:
row: 44
column: 24
patch:
content: " # noqa: E501"
location:
row: 44
column: 4
end_location:
row: 44
column: 24
applied: false
- kind:
UnusedNOQA:
@@ -102,13 +107,14 @@ expression: checks
row: 52
column: 18
fix:
content: ""
location:
row: 52
column: 4
end_location:
row: 52
column: 18
patch:
content: ""
location:
row: 52
column: 4
end_location:
row: 52
column: 18
applied: false
- kind:
UnusedNOQA: ~
@@ -119,12 +125,13 @@ expression: checks
row: 60
column: 12
fix:
content: ""
location:
row: 60
column: 4
end_location:
row: 60
column: 12
patch:
content: ""
location:
row: 60
column: 4
end_location:
row: 60
column: 12
applied: false