Compare commits

..

23 Commits

Author SHA1 Message Date
Charlie Marsh
a9bcc15797 Bump version to 0.0.84 2022-10-26 12:01:55 -04:00
Charlie Marsh
b6c856bd07 Implement B007 (unused loop control variable) (#473) 2022-10-26 12:01:27 -04:00
Charlie Marsh
4beea0484a Use lazy initialization for SourceCodeLocator (#472) 2022-10-26 11:27:48 -04:00
Suguru Yamamoto
2679db1d10 Correct EOL offset for lines ending with multi-byte char (#471) 2022-10-26 11:00:27 -04:00
Charlie Marsh
3e73462e04 Optimize imports 2022-10-25 22:06:51 -04:00
Charlie Marsh
f63a87737a DRY up usages of matches with fixer Mode (#470) 2022-10-25 22:02:58 -04:00
Charlie Marsh
e7472eac1c Increment flake8-bugbear to 8/32 2022-10-25 21:54:00 -04:00
Charlie Marsh
db3c847771 Bump version to 0.0.83 2022-10-25 21:24:13 -04:00
Charlie Marsh
4adbfc24a5 Increment flake8-bugbear to 6/32 2022-10-25 21:23:13 -04:00
Charlie Marsh
8f734a6562 Implement B002 (unary prefix increment) (#468) 2022-10-25 21:10:51 -04:00
Charlie Marsh
bcf7519eb3 Implement B017 (no assertRaises(Exception)) (#467) 2022-10-25 20:55:00 -04:00
Heyward Fann
66089052ee chore: typo on #283 link (#464) 2022-10-25 08:02:11 -04:00
Jeong YunWon
d50cc8ff65 Restyle flake8_comprehensions::check to reduce indent (#462) 2022-10-24 15:16:56 -04:00
Harutaka Kawamura
b75ea94f58 Fix uppercase and lowercase check (#461) 2022-10-22 12:49:13 -04:00
Harutaka Kawamura
2c24e2fd28 Enable N811, 812, 813, 814, 817 for Import (#460) 2022-10-22 08:31:02 -04:00
Charlie Marsh
f8dc208665 Tweak messages for pep8-naming rules 2022-10-21 17:06:16 -04:00
Charlie Marsh
c72b8e8d1e Bump version to 0.0.82 2022-10-21 12:15:19 -04:00
Harutaka Kawamura
b108c693fa Implement N811, 812, 813, 814, and 817 (#457) 2022-10-20 12:20:44 -04:00
Harutaka Kawamura
aac1912ea7 Implement N807 (#456) 2022-10-20 11:12:02 -04:00
Charlie Marsh
3dcd26aac3 Add SectionUnderlineAfterName to autofix list 2022-10-17 22:08:49 -04:00
Charlie Marsh
e53b9807f6 Bump version to 0.0.81 2022-10-17 21:43:49 -04:00
Charlie Marsh
36fe8b76d4 Enable autofix for over- and under-indented docstrings (#451) 2022-10-17 21:43:38 -04:00
Charlie Marsh
f832f88c75 Implement autofix support for D214, D405, D406, and D416 (#450) 2022-10-17 17:37:20 -04:00
54 changed files with 1746 additions and 600 deletions

2
Cargo.lock generated
View File

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

View File

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

View File

@@ -77,7 +77,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.80
rev: v0.0.84
hooks:
- id: lint
```
@@ -296,24 +296,24 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
| D204 | OneBlankLineAfterClass | 1 blank line required after class docstring | 🛠 |
| D205 | BlankLineAfterSummary | 1 blank line required between summary line and description | 🛠 |
| D206 | IndentWithSpaces | Docstring should be indented with spaces, not tabs | |
| D207 | NoUnderIndentation | Docstring is under-indented | |
| D208 | NoOverIndentation | Docstring is over-indented | |
| D207 | NoUnderIndentation | Docstring is under-indented | 🛠 |
| D208 | NoOverIndentation | Docstring is over-indented | 🛠 |
| D209 | NewLineAfterLastParagraph | Multi-line docstring closing quotes should be on a separate line | 🛠 |
| D210 | NoSurroundingWhitespace | No whitespaces allowed surrounding docstring text | 🛠 |
| D211 | NoBlankLineBeforeClass | No blank lines allowed before class docstring | 🛠 |
| D212 | MultiLineSummaryFirstLine | Multi-line docstring summary should start at the first line | |
| D213 | MultiLineSummarySecondLine | Multi-line docstring summary should start at the second line | |
| D214 | SectionNotOverIndented | Section is over-indented ("Returns") | |
| D214 | SectionNotOverIndented | Section is over-indented ("Returns") | 🛠 |
| D215 | SectionUnderlineNotOverIndented | Section underline is over-indented ("Returns") | 🛠 |
| D300 | UsesTripleQuotes | Use """triple double quotes""" | |
| D400 | EndsInPeriod | First line should end with a period | |
| D402 | NoSignature | First line should not be the function's 'signature' | |
| D402 | NoSignature | First line should not be the function's signature | |
| D403 | FirstLineCapitalized | First word of the first line should be properly capitalized | |
| D404 | NoThisPrefix | First word of the docstring should not be `This` | |
| D405 | CapitalizeSectionName | Section name should be properly capitalized ("returns") | |
| D406 | NewLineAfterSectionName | Section name should end with a newline ("Returns") | |
| D405 | CapitalizeSectionName | Section name should be properly capitalized ("returns") | 🛠 |
| D406 | NewLineAfterSectionName | Section name should end with a newline ("Returns") | 🛠 |
| D407 | DashedUnderlineAfterSection | Missing dashed underline after section ("Returns") | 🛠 |
| D408 | SectionUnderlineAfterName | Section underline should be in the line following the section's name ("Returns") | |
| D408 | SectionUnderlineAfterName | Section underline should be in the line following the section's name ("Returns") | 🛠 |
| D409 | SectionUnderlineMatchesSectionLength | Section underline should match the length of its name ("Returns") | 🛠 |
| D410 | BlankLineAfterSection | Missing blank line after section ("Returns") | 🛠 |
| D411 | BlankLineBeforeSection | Missing blank line before section ("Returns") | 🛠 |
@@ -321,7 +321,7 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
| D413 | BlankLineAfterLastSection | Missing blank line after last section ("Returns") | 🛠 |
| D414 | NonEmptySection | Section has no content ("Returns") | |
| D415 | EndsInPunctuation | First line should end with a period, question mark, or exclamation point | |
| D416 | SectionNameEndsInColon | Section name should end with a colon ("Returns") | |
| D416 | SectionNameEndsInColon | Section name should end with a colon ("Returns") | 🛠 |
| D417 | DocumentAllArguments | Missing argument descriptions in the docstring: `x`, `y` | |
| D418 | SkipDocstring | Function decorated with @overload shouldn't contain a docstring | |
| D419 | NonEmpty | Docstring is empty | |
@@ -348,6 +348,12 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
| N803 | InvalidArgumentName | Argument name `...` should be lowercase | |
| N804 | InvalidFirstArgumentNameForClassMethod | First argument of a class method should be named `cls` | |
| N805 | InvalidFirstArgumentNameForMethod | First argument of a method should be named `self` | |
| N807 | DunderFunctionName | Function name should not start and end with `__` | |
| N811 | ConstantImportedAsNonConstant | Constant `...` imported as non-constant `...` | |
| N812 | LowercaseImportedAsNonLowercase | Lowercase `...` imported as non-lowercase `...` | |
| N813 | CamelcaseImportedAsLowercase | Camelcase `...` imported as lowercase `...` | |
| N814 | CamelcaseImportedAsConstant | Camelcase `...` imported as constant `...` | |
| N817 | CamelcaseImportedAsAcronym | Camelcase `...` imported as acronym `...` | |
### flake8-comprehensions
@@ -374,8 +380,11 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| B002 | UnaryPrefixIncrement | Python does not support the unary prefix increment. | |
| B007 | UnusedLoopControlVariable | Loop control variable `i` not used within the loop body. | 🛠 |
| B011 | DoNotAssertFalse | Do not `assert False` (`python -O` removes these calls), raise `AssertionError()` | 🛠 |
| B014 | DuplicateHandlerException | Exception handler with duplicate exception: `ValueError` | 🛠 |
| B017 | NoAssertRaisesException | `assertRaises(Exception):` should be considered evil. | |
| B025 | DuplicateTryBlockException | try-except block with duplicate exception `Exception` | |
### flake8-builtins
@@ -465,7 +474,7 @@ including:
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (3/32)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (9/32)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (8/34)
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
@@ -485,7 +494,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (3/32)
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (9/32)
Ruff also implements the functionality that you get from [`yesqa`](https://github.com/asottile/yesqa),
and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (8/34).
@@ -506,7 +515,7 @@ on Rust at all.
### Can I write my own plugins for Ruff?
Ruff does not yet support third-party plugins, though a plugin system is within-scope for the
project. See [#283](https://github.com/charliermarsh/ruff/issues/2830) for more.
project. See [#283](https://github.com/charliermarsh/ruff/issues/283) for more.
### Does Ruff support NumPy- or Google-style docstrings?

View File

@@ -19,7 +19,7 @@ fn main() {
"| {} | {} | {} | {} |",
check_kind.code().as_ref(),
check_kind.as_ref(),
check_kind.body().replace("|", r"\|"),
check_kind.summary().replace("|", r"\|"),
fix_token
);
}

20
resources/test/fixtures/B002.py vendored Normal file
View File

@@ -0,0 +1,20 @@
"""
Should emit:
B002 - on lines 15 and 20
"""
def this_is_all_fine(n):
x = n + 1
y = 1 + n
z = +x + y
return +z
def this_is_buggy(n):
x = ++n
return x
def this_is_buggy_too(n):
return ++n

31
resources/test/fixtures/B007.py vendored Normal file
View File

@@ -0,0 +1,31 @@
for i in range(10):
print(i)
print(i) # name no longer defined on Python 3; no warning yet
for i in range(10): # name not used within the loop; B007
print(10)
print(i) # name no longer defined on Python 3; no warning yet
for _ in range(10): # _ is okay for a throw-away variable
print(10)
for i in range(10):
for j in range(10):
for k in range(10): # k not used, i and j used transitively
print(i + j)
def strange_generator():
for i in range(10):
for j in range(10):
for k in range(10):
for l in range(10):
yield i, (j, (k, l))
for i, (j, (k, l)) in strange_generator(): # i, k not used
print(j, l)

36
resources/test/fixtures/B017.py vendored Normal file
View File

@@ -0,0 +1,36 @@
"""
Should emit:
B017 - on lines 20
"""
import asyncio
import unittest
CONSTANT = True
def something_else() -> None:
for i in (1, 2, 3):
print(i)
class Foo:
pass
class Foobar(unittest.TestCase):
def evil_raises(self) -> None:
with self.assertRaises(Exception):
raise Exception("Evil I say!")
def context_manager_raises(self) -> None:
with self.assertRaises(Exception) as ex:
raise Exception("Context manager is good")
self.assertEqual("Context manager is good", str(ex.exception))
def regex_raises(self) -> None:
with self.assertRaisesRegex(Exception, "Regex is good"):
raise Exception("Regex is good")
def raises_with_absolute_reference(self):
with self.assertRaises(asyncio.CancelledError):
Foo()

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

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

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

@@ -0,0 +1,3 @@
import mod.CONST as const
from mod import CONSTANT as constant
from mod import ANOTHER_CONSTANT as another_constant

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

@@ -0,0 +1,3 @@
import modl.lowercase as Lower
from mod import lowercase as Lowercase
from mod import another_lowercase as AnotherLowercase

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

@@ -0,0 +1,3 @@
import mod.Camel as camel
from mod import CamelCase as camelcase
from mod import AnotherCamelCase as another_camelcase

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

@@ -0,0 +1,3 @@
import mod.Camel as CAMEL
from mod import CamelCase as CAMELCASE
from mod import AnotherCamelCase as ANOTHER_CAMELCASE

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

@@ -0,0 +1,2 @@
import mod.CaMel as CM
from mod import CamelCase as CC

View File

@@ -122,56 +122,50 @@ pub fn is_unpacking_assignment(stmt: &Stmt) -> bool {
pub struct SourceCodeLocator<'a> {
content: &'a str,
offsets: Vec<Vec<usize>>,
initialized: bool,
}
impl<'a> SourceCodeLocator<'a> {
pub fn new(content: &'a str) -> Self {
SourceCodeLocator {
content,
offsets: vec![],
initialized: false,
offsets: Self::compute_offsets(content),
}
}
fn init(&mut self) {
if !self.initialized {
let mut offset = 0;
for line in self.content.lines() {
let mut newline = 0;
let mut line_offsets: Vec<usize> = vec![];
for (i, _char) in line.char_indices() {
line_offsets.push(offset + i);
newline = i + 1;
}
line_offsets.push(offset + newline);
self.offsets.push(line_offsets);
offset += newline + 1;
fn compute_offsets(content: &str) -> Vec<Vec<usize>> {
let mut offsets = vec![];
let mut offset = 0;
for line in content.lines() {
let mut newline = 0;
let mut line_offsets: Vec<usize> = vec![];
for (i, char) in line.char_indices() {
line_offsets.push(offset + i);
newline = i + char.len_utf8();
}
self.offsets.push(vec![offset]);
self.initialized = true;
line_offsets.push(offset + newline);
offsets.push(line_offsets);
offset += newline + 1;
}
offsets.push(vec![offset]);
offsets
}
pub fn slice_source_code_at(&mut self, location: &Location) -> &'a str {
self.init();
pub fn slice_source_code_at(&self, location: &Location) -> &'a str {
let offset = self.offsets[location.row() - 1][location.column() - 1];
&self.content[offset..]
}
pub fn slice_source_code_range(&mut self, range: &Range) -> &'a str {
self.init();
pub fn slice_source_code_range(&self, range: &Range) -> &'a str {
let start = self.offsets[range.location.row() - 1][range.location.column() - 1];
let end = self.offsets[range.end_location.row() - 1][range.end_location.column() - 1];
&self.content[start..end]
}
pub fn partition_source_code_at(
&mut self,
&self,
outer: &Range,
inner: &Range,
) -> (&'a str, &'a str, &'a str) {
self.init();
let outer_start = self.offsets[outer.location.row() - 1][outer.location.column() - 1];
let outer_end = self.offsets[outer.end_location.row() - 1][outer.end_location.column() - 1];
let inner_start = self.offsets[inner.location.row() - 1][inner.location.column() - 1];
@@ -183,3 +177,22 @@ impl<'a> SourceCodeLocator<'a> {
)
}
}
#[cfg(test)]
mod tests {
use super::SourceCodeLocator;
#[test]
fn source_code_locator_init() {
let content = "# \u{4e9c}\nclass Foo:\n \"\"\".\"\"\"";
let locator = SourceCodeLocator::new(content);
assert_eq!(locator.offsets.len(), 4);
assert_eq!(locator.offsets[0], [0, 1, 2, 5]);
assert_eq!(locator.offsets[1], [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
assert_eq!(
locator.offsets[2],
[17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28]
);
assert_eq!(locator.offsets[3], [29]);
}
}

View File

@@ -1,9 +1,10 @@
use crate::autofix::Fix;
use crate::autofix::Patch;
use itertools::Itertools;
use rustpython_parser::ast::Location;
use std::collections::BTreeSet;
use itertools::Itertools;
use rustpython_parser::ast::Location;
use crate::autofix::Fix;
use crate::autofix::Patch;
use crate::checks::Check;
#[derive(Hash)]
@@ -13,6 +14,17 @@ pub enum Mode {
None,
}
impl Mode {
/// Return `true` if a patch should be generated under the given `Mode`.
pub fn patch(&self) -> bool {
match &self {
Mode::Generate => true,
Mode::Apply => true,
Mode::None => false,
}
}
}
impl From<bool> for Mode {
fn from(value: bool) -> Self {
match value {

View File

@@ -3,6 +3,7 @@ use std::ops::Deref;
use std::path::Path;
use log::error;
use once_cell::unsync::OnceCell;
use rustpython_parser::ast::{
Arg, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind,
KeywordData, Operator, Stmt, StmtKind, Suite,
@@ -34,12 +35,14 @@ pub const GLOBAL_SCOPE_INDEX: usize = 0;
pub struct Checker<'a> {
// Input data.
pub(crate) path: &'a Path,
pub(crate) locator: SourceCodeLocator<'a>,
path: &'a Path,
content: &'a str,
autofix: &'a fixer::Mode,
pub(crate) settings: &'a Settings,
pub(crate) autofix: &'a fixer::Mode,
// Computed checks.
checks: Vec<Check>,
// Efficient source-code slicing.
locator: OnceCell<SourceCodeLocator<'a>>,
// Docstring tracking.
docstrings: Vec<(Definition<'a>, Visibility)>,
// Edit tracking.
@@ -79,7 +82,8 @@ impl<'a> Checker<'a> {
settings,
autofix,
path,
locator: SourceCodeLocator::new(content),
content,
locator: OnceCell::new(),
checks: Default::default(),
docstrings: Default::default(),
deletions: Default::default(),
@@ -106,6 +110,17 @@ impl<'a> Checker<'a> {
except_handlers: Default::default(),
}
}
/// Get access to a lazily-initialized `SourceCodeLocator` for the file contents.
pub fn get_locator(&self) -> &SourceCodeLocator {
self.locator
.get_or_init(|| SourceCodeLocator::new(self.content))
}
/// Return `true` if a patch should be generated under the given autofix `Mode`.
pub fn patch(&self) -> bool {
self.autofix.patch()
}
}
impl<'a, 'b> Visitor<'b> for Checker<'a>
@@ -245,6 +260,14 @@ where
}
}
if self.settings.enabled.contains(&CheckCode::N807) {
if let Some(check) =
pep8_naming::checks::dunder_function_name(stmt, self.current_scope(), name)
{
self.checks.push(check);
}
}
self.check_builtin_shadowing(name, Range::from_located(stmt), true);
// Visit the decorators and arguments, but avoid the body, which will be deferred.
@@ -405,6 +428,55 @@ where
},
)
}
if let Some(asname) = &alias.node.asname {
let name = alias.node.name.split('.').last().unwrap();
if self.settings.enabled.contains(&CheckCode::N811) {
if let Some(check) =
pep8_naming::checks::constant_imported_as_non_constant(
stmt, name, asname,
)
{
self.checks.push(check);
}
}
if self.settings.enabled.contains(&CheckCode::N812) {
if let Some(check) =
pep8_naming::checks::lowercase_imported_as_non_lowercase(
stmt, name, asname,
)
{
self.checks.push(check);
}
}
if self.settings.enabled.contains(&CheckCode::N813) {
if let Some(check) =
pep8_naming::checks::camelcase_imported_as_lowercase(
stmt, name, asname,
)
{
self.checks.push(check);
}
}
if self.settings.enabled.contains(&CheckCode::N814) {
if let Some(check) = pep8_naming::checks::camelcase_imported_as_constant(
stmt, name, asname,
) {
self.checks.push(check);
}
}
if self.settings.enabled.contains(&CheckCode::N817) {
if let Some(check) = pep8_naming::checks::camelcase_imported_as_acronym(
stmt, name, asname,
) {
self.checks.push(check);
}
}
}
}
}
StmtKind::ImportFrom {
@@ -525,6 +597,64 @@ where
},
)
}
if let Some(asname) = &alias.node.asname {
if self.settings.enabled.contains(&CheckCode::N811) {
if let Some(check) =
pep8_naming::checks::constant_imported_as_non_constant(
stmt,
&alias.node.name,
asname,
)
{
self.checks.push(check);
}
}
if self.settings.enabled.contains(&CheckCode::N812) {
if let Some(check) =
pep8_naming::checks::lowercase_imported_as_non_lowercase(
stmt,
&alias.node.name,
asname,
)
{
self.checks.push(check);
}
}
if self.settings.enabled.contains(&CheckCode::N813) {
if let Some(check) =
pep8_naming::checks::camelcase_imported_as_lowercase(
stmt,
&alias.node.name,
asname,
)
{
self.checks.push(check);
}
}
if self.settings.enabled.contains(&CheckCode::N814) {
if let Some(check) = pep8_naming::checks::camelcase_imported_as_constant(
stmt,
&alias.node.name,
asname,
) {
self.checks.push(check);
}
}
if self.settings.enabled.contains(&CheckCode::N817) {
if let Some(check) = pep8_naming::checks::camelcase_imported_as_acronym(
stmt,
&alias.node.name,
asname,
) {
self.checks.push(check);
}
}
}
}
}
StmtKind::Raise { exc, .. } => {
@@ -552,6 +682,16 @@ where
flake8_bugbear::plugins::assert_false(self, stmt, test, msg);
}
}
StmtKind::With { items, .. } | StmtKind::AsyncWith { items, .. } => {
if self.settings.enabled.contains(&CheckCode::B017) {
flake8_bugbear::plugins::assert_raises_exception(self, stmt, items);
}
}
StmtKind::For { target, body, .. } => {
if self.settings.enabled.contains(&CheckCode::B007) {
flake8_bugbear::plugins::unused_loop_control_variable(self, target, body);
}
}
StmtKind::Try { handlers, .. } => {
if self.settings.enabled.contains(&CheckCode::F707) {
if let Some(check) = pyflakes::checks::default_except_not_last(handlers) {
@@ -1011,6 +1151,10 @@ where
self,
));
}
if self.settings.enabled.contains(&CheckCode::B002) {
flake8_bugbear::plugins::unary_prefix_increment(self, expr, op, operand);
}
}
ExprKind::Compare {
left,
@@ -1946,8 +2090,7 @@ impl<'a> Checker<'a> {
let child = self.parents[defined_by];
let parent = defined_in.map(|defined_in| self.parents[defined_in]);
let fix = if matches!(self.autofix, fixer::Mode::Generate | fixer::Mode::Apply)
{
let fix = if self.patch() {
let deleted: Vec<&Stmt> = self
.deletions
.iter()
@@ -1959,7 +2102,7 @@ impl<'a> Checker<'a> {
ImportKind::ImportFrom => pyflakes::fixes::remove_unused_import_froms,
};
match removal_fn(&mut self.locator, &full_names, child, parent, &deleted) {
match removal_fn(self.get_locator(), &full_names, child, parent, &deleted) {
Ok(fix) => Some(fix),
Err(e) => {
error!("Failed to fix unused imports: {}", e);

View File

@@ -166,7 +166,7 @@ pub fn check_lines(
end_location: Location::new(row + 1, end + 1),
},
);
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if autofix.patch() {
check.amend(Fix::deletion(
Location::new(row + 1, start + 1),
Location::new(row + 1, lines[row].chars().count() + 1),
@@ -194,7 +194,7 @@ pub fn check_lines(
end_location: Location::new(row + 1, end + 1),
},
);
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if autofix.patch() {
if valid_codes.is_empty() {
check.amend(Fix::deletion(
Location::new(row + 1, start + 1),

View File

@@ -75,8 +75,11 @@ pub enum CheckCode {
A002,
A003,
// flake8-bugbear
B002,
B007,
B011,
B014,
B017,
B025,
// flake8-comprehensions
C400,
@@ -158,6 +161,12 @@ pub enum CheckCode {
N803,
N804,
N805,
N807,
N811,
N812,
N813,
N814,
N817,
// Meta
M001,
}
@@ -259,8 +268,11 @@ pub enum CheckKind {
BuiltinArgumentShadowing(String),
BuiltinAttributeShadowing(String),
// flake8-bugbear
UnaryPrefixIncrement,
UnusedLoopControlVariable(String),
DoNotAssertFalse,
DuplicateHandlerException(Vec<String>),
NoAssertRaisesException,
DuplicateTryBlockException(String),
// flake8-comprehensions
UnnecessaryGeneratorList,
@@ -342,6 +354,12 @@ pub enum CheckKind {
InvalidArgumentName(String),
InvalidFirstArgumentNameForClassMethod,
InvalidFirstArgumentNameForMethod,
DunderFunctionName,
ConstantImportedAsNonConstant(String, String),
LowercaseImportedAsNonLowercase(String, String),
CamelcaseImportedAsLowercase(String, String),
CamelcaseImportedAsConstant(String, String),
CamelcaseImportedAsAcronym(String, String),
// Meta
UnusedNOQA(Option<Vec<String>>),
}
@@ -412,8 +430,11 @@ impl CheckCode {
CheckCode::A002 => CheckKind::BuiltinArgumentShadowing("...".to_string()),
CheckCode::A003 => CheckKind::BuiltinAttributeShadowing("...".to_string()),
// flake8-bugbear
CheckCode::B002 => CheckKind::UnaryPrefixIncrement,
CheckCode::B007 => CheckKind::UnusedLoopControlVariable("i".to_string()),
CheckCode::B011 => CheckKind::DoNotAssertFalse,
CheckCode::B014 => CheckKind::DuplicateHandlerException(vec!["ValueError".to_string()]),
CheckCode::B017 => CheckKind::NoAssertRaisesException,
CheckCode::B025 => CheckKind::DuplicateTryBlockException("Exception".to_string()),
// flake8-comprehensions
CheckCode::C400 => CheckKind::UnnecessaryGeneratorList,
@@ -517,6 +538,22 @@ impl CheckCode {
CheckCode::N803 => CheckKind::InvalidArgumentName("...".to_string()),
CheckCode::N804 => CheckKind::InvalidFirstArgumentNameForClassMethod,
CheckCode::N805 => CheckKind::InvalidFirstArgumentNameForMethod,
CheckCode::N807 => CheckKind::DunderFunctionName,
CheckCode::N811 => {
CheckKind::ConstantImportedAsNonConstant("...".to_string(), "...".to_string())
}
CheckCode::N812 => {
CheckKind::LowercaseImportedAsNonLowercase("...".to_string(), "...".to_string())
}
CheckCode::N813 => {
CheckKind::CamelcaseImportedAsLowercase("...".to_string(), "...".to_string())
}
CheckCode::N814 => {
CheckKind::CamelcaseImportedAsConstant("...".to_string(), "...".to_string())
}
CheckCode::N817 => {
CheckKind::CamelcaseImportedAsAcronym("...".to_string(), "...".to_string())
}
// Meta
CheckCode::M001 => CheckKind::UnusedNOQA(None),
}
@@ -570,8 +607,11 @@ impl CheckCode {
CheckCode::A001 => CheckCategory::Flake8Builtins,
CheckCode::A002 => CheckCategory::Flake8Builtins,
CheckCode::A003 => CheckCategory::Flake8Builtins,
CheckCode::B002 => CheckCategory::Flake8Bugbear,
CheckCode::B007 => CheckCategory::Flake8Bugbear,
CheckCode::B011 => CheckCategory::Flake8Bugbear,
CheckCode::B014 => CheckCategory::Flake8Bugbear,
CheckCode::B017 => CheckCategory::Flake8Bugbear,
CheckCode::B025 => CheckCategory::Flake8Bugbear,
CheckCode::C400 => CheckCategory::Flake8Comprehensions,
CheckCode::C401 => CheckCategory::Flake8Comprehensions,
@@ -648,6 +688,12 @@ impl CheckCode {
CheckCode::N803 => CheckCategory::PEP8Naming,
CheckCode::N804 => CheckCategory::PEP8Naming,
CheckCode::N805 => CheckCategory::PEP8Naming,
CheckCode::N807 => CheckCategory::PEP8Naming,
CheckCode::N811 => CheckCategory::PEP8Naming,
CheckCode::N812 => CheckCategory::PEP8Naming,
CheckCode::N813 => CheckCategory::PEP8Naming,
CheckCode::N814 => CheckCategory::PEP8Naming,
CheckCode::N817 => CheckCategory::PEP8Naming,
CheckCode::M001 => CheckCategory::Meta,
}
}
@@ -707,8 +753,11 @@ impl CheckKind {
CheckKind::BuiltinArgumentShadowing(_) => &CheckCode::A002,
CheckKind::BuiltinAttributeShadowing(_) => &CheckCode::A003,
// flake8-bugbear
CheckKind::UnaryPrefixIncrement => &CheckCode::B002,
CheckKind::UnusedLoopControlVariable(_) => &CheckCode::B007,
CheckKind::DoNotAssertFalse => &CheckCode::B011,
CheckKind::DuplicateHandlerException(_) => &CheckCode::B014,
CheckKind::NoAssertRaisesException => &CheckCode::B017,
CheckKind::DuplicateTryBlockException(_) => &CheckCode::B025,
// flake8-comprehensions
CheckKind::UnnecessaryGeneratorList => &CheckCode::C400,
@@ -790,6 +839,12 @@ impl CheckKind {
CheckKind::InvalidArgumentName(_) => &CheckCode::N803,
CheckKind::InvalidFirstArgumentNameForClassMethod => &CheckCode::N804,
CheckKind::InvalidFirstArgumentNameForMethod => &CheckCode::N805,
CheckKind::DunderFunctionName => &CheckCode::N807,
CheckKind::ConstantImportedAsNonConstant(..) => &CheckCode::N811,
CheckKind::LowercaseImportedAsNonLowercase(..) => &CheckCode::N812,
CheckKind::CamelcaseImportedAsLowercase(..) => &CheckCode::N813,
CheckKind::CamelcaseImportedAsConstant(..) => &CheckCode::N814,
CheckKind::CamelcaseImportedAsAcronym(..) => &CheckCode::N817,
// Meta
CheckKind::UnusedNOQA(_) => &CheckCode::M001,
}
@@ -939,6 +994,8 @@ impl CheckKind {
format!("Class attribute `{name}` is shadowing a python builtin")
}
// flake8-bugbear
CheckKind::UnaryPrefixIncrement => "Python does not support the unary prefix increment. Writing `++n` is equivalent to `+(+(n))`, which equals `n`. You meant `n += 1`.".to_string(),
CheckKind::UnusedLoopControlVariable(name) => format!("Loop control variable `{name}` not used within the loop body. If this is intended, start the name with an underscore."),
CheckKind::DoNotAssertFalse => {
"Do not `assert False` (`python -O` removes these calls), raise `AssertionError()`"
.to_string()
@@ -952,6 +1009,9 @@ impl CheckKind {
format!("Exception handler with duplicate exceptions: {names}")
}
}
CheckKind::NoAssertRaisesException => {
"`assertRaises(Exception):` should be considered evil. It can lead to your test passing even if the code being tested is never executed due to a typo. Either assert for a more specific exception (builtin or custom), use `assertRaisesRegex`, or use the context manager form of `assertRaises`.".to_string()
}
CheckKind::DuplicateTryBlockException(name) => {
format!("try-except block with duplicate exception `{name}`")
}
@@ -1080,7 +1140,7 @@ impl CheckKind {
"Multi-line docstring summary should start at the second line".to_string()
}
CheckKind::NoSignature => {
"First line should not be the function's 'signature'".to_string()
"First line should not be the function's signature".to_string()
}
CheckKind::NoBlankLineBeforeFunction(num_lines) => {
format!("No blank lines allowed before function docstring (found {num_lines})")
@@ -1180,6 +1240,24 @@ impl CheckKind {
CheckKind::InvalidFirstArgumentNameForMethod => {
"First argument of a method should be named `self`".to_string()
}
CheckKind::DunderFunctionName => {
"Function name should not start and end with `__`".to_string()
}
CheckKind::ConstantImportedAsNonConstant(name, asname) => {
format!("Constant `{name}` imported as non-constant `{asname}`")
}
CheckKind::LowercaseImportedAsNonLowercase(name, asname) => {
format!("Lowercase `{name}` imported as non-lowercase `{asname}`")
}
CheckKind::CamelcaseImportedAsLowercase(name, asname) => {
format!("Camelcase `{name}` imported as lowercase `{asname}`")
}
CheckKind::CamelcaseImportedAsConstant(name, asname) => {
format!("Camelcase `{name}` imported as constant `{asname}`")
}
CheckKind::CamelcaseImportedAsAcronym(name, asname) => {
format!("Camelcase `{name}` imported as acronym `{asname}`")
}
// Meta
CheckKind::UnusedNOQA(codes) => match codes {
None => "Unused `noqa` directive".to_string(),
@@ -1200,6 +1278,22 @@ impl CheckKind {
}
}
/// The summary text for the check. Typically a truncated form of the body text.
pub fn summary(&self) -> String {
match self {
CheckKind::UnaryPrefixIncrement => {
"Python does not support the unary prefix increment.".to_string()
}
CheckKind::UnusedLoopControlVariable(name) => {
format!("Loop control variable `{name}` not used within the loop body.")
}
CheckKind::NoAssertRaisesException => {
"`assertRaises(Exception):` should be considered evil.".to_string()
}
_ => self.body(),
}
}
/// Whether the check kind is (potentially) fixable.
pub fn fixable(&self) -> bool {
matches!(
@@ -1208,26 +1302,34 @@ impl CheckKind {
| CheckKind::BlankLineAfterSection(_)
| CheckKind::BlankLineAfterSummary
| CheckKind::BlankLineBeforeSection(_)
| CheckKind::CapitalizeSectionName(_)
| CheckKind::DashedUnderlineAfterSection(_)
| CheckKind::DeprecatedUnittestAlias(_, _)
| CheckKind::DoNotAssertFalse
| CheckKind::DuplicateHandlerException(_)
| CheckKind::NewLineAfterLastParagraph
| CheckKind::NewLineAfterSectionName(_)
| CheckKind::NoBlankLineAfterFunction(_)
| CheckKind::NoBlankLineBeforeClass(_)
| CheckKind::NoBlankLineBeforeFunction(_)
| CheckKind::NoBlankLinesBetweenHeaderAndContent(_)
| CheckKind::NoOverIndentation
| CheckKind::NoSurroundingWhitespace
| CheckKind::NoUnderIndentation
| CheckKind::OneBlankLineAfterClass(_)
| CheckKind::OneBlankLineBeforeClass(_)
| CheckKind::PPrintFound
| CheckKind::PrintFound
| CheckKind::SectionNameEndsInColon(_)
| CheckKind::SectionNotOverIndented(_)
| CheckKind::SectionUnderlineAfterName(_)
| CheckKind::SectionUnderlineMatchesSectionLength(_)
| CheckKind::SectionUnderlineNotOverIndented(_)
| CheckKind::SuperCallWithParameters
| CheckKind::TypeOfPrimitive(_)
| CheckKind::UnnecessaryAbspath
| CheckKind::UnusedImport(_)
| CheckKind::UnusedLoopControlVariable(_)
| CheckKind::UnusedNOQA(_)
| CheckKind::UsePEP585Annotation(_)
| CheckKind::UsePEP604Annotation

View File

@@ -25,10 +25,18 @@ pub fn leading_space(line: &str) -> String {
}
/// Extract the leading indentation from a docstring.
pub fn indentation<'a>(checker: &'a mut Checker, docstring: &Expr) -> &'a str {
pub fn indentation<'a>(checker: &'a Checker, docstring: &Expr) -> &'a str {
let range = Range::from_located(docstring);
checker.locator.slice_source_code_range(&Range {
checker.get_locator().slice_source_code_range(&Range {
location: Location::new(range.location.row(), 1),
end_location: Location::new(range.location.row(), range.location.column()),
})
}
/// Replace any non-whitespace characters from an indentation string.
pub fn clean(indentation: &str) -> String {
indentation
.chars()
.map(|char| if char.is_whitespace() { char } else { ' ' })
.collect()
}

View File

@@ -1,7 +1,6 @@
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};
@@ -44,7 +43,7 @@ pub fn assert_false(checker: &mut Checker, stmt: &Stmt, test: &Expr, msg: &Optio
} = &test.node
{
let mut check = Check::new(CheckKind::DoNotAssertFalse, Range::from_located(test));
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if checker.patch() {
let mut generator = SourceGenerator::new();
if let Ok(()) = generator.unparse_stmt(&assertion_error(msg)) {
if let Ok(content) = generator.generate() {

View File

@@ -0,0 +1,25 @@
use rustpython_ast::{ExprKind, Stmt, Withitem};
use crate::ast::helpers::match_name_or_attr;
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
/// B017
pub fn assert_raises_exception(checker: &mut Checker, stmt: &Stmt, items: &[Withitem]) {
if let Some(item) = items.first() {
let item_context = &item.context_expr;
if let ExprKind::Call { func, args, .. } = &item_context.node {
if match_name_or_attr(func, "assertRaises")
&& args.len() == 1
&& match_name_or_attr(args.first().unwrap(), "Exception")
&& item.optional_vars.is_none()
{
checker.add_check(Check::new(
CheckKind::NoAssertRaisesException,
Range::from_located(stmt),
));
}
}
}
}

View File

@@ -5,7 +5,6 @@ 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};
@@ -50,7 +49,7 @@ pub fn duplicate_handler_exceptions(
),
checker.locate_check(Range::from_located(expr)),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if checker.patch() {
// TODO(charlie): If we have a single element, remove the tuple.
let mut generator = SourceGenerator::new();
if let Ok(()) = generator.unparse_expr(&type_pattern(unique_elts), 0) {

View File

@@ -1,6 +1,12 @@
pub use assert_false::assert_false;
pub use assert_raises_exception::assert_raises_exception;
pub use duplicate_exceptions::duplicate_exceptions;
pub use duplicate_exceptions::duplicate_handler_exceptions;
pub use unary_prefix_increment::unary_prefix_increment;
pub use unused_loop_control_variable::unused_loop_control_variable;
mod assert_false;
mod assert_raises_exception;
mod duplicate_exceptions;
mod unary_prefix_increment;
mod unused_loop_control_variable;

View File

@@ -0,0 +1,19 @@
use rustpython_ast::{Expr, ExprKind, Unaryop};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
/// B002
pub fn unary_prefix_increment(checker: &mut Checker, expr: &Expr, op: &Unaryop, operand: &Expr) {
if matches!(op, Unaryop::UAdd) {
if let ExprKind::UnaryOp { op, .. } = &operand.node {
if matches!(op, Unaryop::UAdd) {
checker.add_check(Check::new(
CheckKind::UnaryPrefixIncrement,
Range::from_located(expr),
))
}
}
}
}

View File

@@ -0,0 +1,79 @@
use std::collections::BTreeMap;
use rustpython_ast::{Expr, ExprKind, Stmt};
use crate::ast::types::Range;
use crate::ast::visitor;
use crate::ast::visitor::Visitor;
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
/// Identify all `ExprKind::Name` nodes in an AST.
struct NameFinder<'a> {
/// A map from identifier to defining expression.
names: BTreeMap<&'a str, &'a Expr>,
}
impl NameFinder<'_> {
fn new() -> Self {
NameFinder {
names: Default::default(),
}
}
}
impl<'a, 'b> Visitor<'b> for NameFinder<'a>
where
'b: 'a,
{
fn visit_expr(&mut self, expr: &'a Expr) {
if let ExprKind::Name { id, .. } = &expr.node {
self.names.insert(id, expr);
}
visitor::walk_expr(self, expr);
}
}
/// B007
pub fn unused_loop_control_variable(checker: &mut Checker, target: &Expr, body: &[Stmt]) {
let control_names = {
let mut finder = NameFinder::new();
finder.visit_expr(target);
finder.names
};
let used_names = {
let mut finder = NameFinder::new();
for stmt in body {
finder.visit_stmt(stmt);
}
finder.names
};
for (name, expr) in control_names {
// Ignore names that are already underscore-prefixed.
if name.starts_with('_') {
continue;
}
// Ignore any names that are actually used in the loop body.
if used_names.contains_key(name) {
continue;
}
let mut check = Check::new(
CheckKind::UnusedLoopControlVariable(name.to_string()),
Range::from_located(expr),
);
if checker.patch() {
// Prefix the variable name with an underscore.
check.amend(Fix::replacement(
format!("_{name}"),
expr.location,
expr.end_location.unwrap(),
))
}
checker.add_check(check);
}
}

View File

@@ -4,57 +4,75 @@ use rustpython_ast::{Comprehension, Constant, Expr, ExprKind, KeywordData, Locat
use crate::ast::types::Range;
use crate::checks::{Check, CheckKind};
fn function_name(func: &Expr) -> Option<&str> {
if let ExprKind::Name { id, .. } = &func.node {
Some(id)
} else {
None
}
}
fn exactly_one_argument_with_matching_function<'a>(
name: &str,
func: &Expr,
args: &'a [Expr],
) -> Option<&'a ExprKind> {
if args.len() != 1 {
return None;
}
if function_name(func)? != name {
return None;
}
Some(&args[0].node)
}
fn first_argument_with_matching_function<'a>(
name: &str,
func: &Expr,
args: &'a [Expr],
) -> Option<&'a ExprKind> {
if function_name(func)? != name {
return None;
}
Some(&args.first()?.node)
}
/// 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),
));
}
}
}
let argument = exactly_one_argument_with_matching_function("list", func, args)?;
if let ExprKind::GeneratorExp { .. } = argument {
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),
));
}
}
}
let argument = exactly_one_argument_with_matching_function("set", func, args)?;
if let ExprKind::GeneratorExp { .. } = argument {
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),
));
}
_ => {}
}
}
let argument = exactly_one_argument_with_matching_function("dict", func, args)?;
if let ExprKind::GeneratorExp { elt, .. } = argument {
match &elt.node {
ExprKind::Tuple { elts, .. } if elts.len() == 2 => {
return Some(Check::new(
CheckKind::UnnecessaryGeneratorDict,
Range::from_located(expr),
));
}
_ => {}
}
}
None
@@ -66,17 +84,12 @@ pub fn unnecessary_list_comprehension_set(
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),
));
}
}
}
let argument = exactly_one_argument_with_matching_function("set", func, args)?;
if let ExprKind::ListComp { .. } = &argument {
return Some(Check::new(
CheckKind::UnnecessaryListComprehensionSet,
Range::from_located(expr),
));
}
None
}
@@ -87,21 +100,16 @@ pub fn unnecessary_list_comprehension_dict(
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),
));
}
_ => {}
}
}
let argument = exactly_one_argument_with_matching_function("dict", func, args)?;
if let ExprKind::ListComp { elt, .. } = &argument {
match &elt.node {
ExprKind::Tuple { elts, .. } if elts.len() == 2 => {
return Some(Check::new(
CheckKind::UnnecessaryListComprehensionDict,
Range::from_located(expr),
));
}
_ => {}
}
}
None
@@ -109,82 +117,38 @@ pub fn unnecessary_list_comprehension_dict(
/// 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
let argument = exactly_one_argument_with_matching_function("set", func, args)?;
let kind = match argument {
ExprKind::List { .. } => "list",
ExprKind::Tuple { .. } => "tuple",
_ => return None,
};
Some(Check::new(
CheckKind::UnnecessaryLiteralSet(kind.to_string()),
Range::from_located(expr),
))
}
/// 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),
));
}
}
_ => {}
}
}
let argument = exactly_one_argument_with_matching_function("dict", func, args)?;
let (kind, elts) = match argument {
ExprKind::Tuple { elts, .. } => ("tuple", elts),
ExprKind::List { elts, .. } => ("list", elts),
_ => return None,
};
if let Some(elt) = elts.first() {
// dict((1, 2), ...)) or dict([(1, 2), ...])
if !matches!(&elt.node, ExprKind::Tuple { elts, .. } if elts.len() == 2) {
return None;
}
}
None
Some(Check::new(
CheckKind::UnnecessaryLiteralDict(kind.to_string()),
Range::from_located(expr),
))
}
pub fn unnecessary_collection_call(
@@ -193,26 +157,21 @@ pub fn unnecessary_collection_call(
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),
));
}
}
}
if !args.is_empty() {
return None;
}
None
let id = function_name(func)?;
match id {
"list" | "tuple" => {
// list() or tuple()
}
"dict" if keywords.is_empty() || keywords.iter().all(|kw| kw.node.arg.is_some()) => (),
_ => return None,
};
Some(Check::new(
CheckKind::UnnecessaryCollectionCall(id.to_string()),
Range::from_located(expr),
))
}
pub fn unnecessary_literal_within_tuple_call(
@@ -220,29 +179,16 @@ pub fn unnecessary_literal_within_tuple_call(
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
let argument = first_argument_with_matching_function("tuple", func, args)?;
let argument_kind = match argument {
ExprKind::Tuple { .. } => "tuple",
ExprKind::List { .. } => "list",
_ => return None,
};
Some(Check::new(
CheckKind::UnnecessaryLiteralWithinTupleCall(argument_kind.to_string()),
Range::from_located(expr),
))
}
pub fn unnecessary_literal_within_list_call(
@@ -250,62 +196,40 @@ pub fn unnecessary_literal_within_list_call(
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
let argument = first_argument_with_matching_function("list", func, args)?;
let argument_kind = match argument {
ExprKind::Tuple { .. } => "tuple",
ExprKind::List { .. } => "list",
_ => return None,
};
Some(Check::new(
CheckKind::UnnecessaryLiteralWithinListCall(argument_kind.to_string()),
Range::from_located(expr),
))
}
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),
));
}
}
}
let argument = first_argument_with_matching_function("list", func, args)?;
if let ExprKind::ListComp { .. } = argument {
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),
));
}
}
}
}
let outer = function_name(func)?;
if !(outer == "list" || outer == "reversed") {
return None;
}
if let ExprKind::Call { func, .. } = &args.first()?.node {
if function_name(func)? == "sorted" {
return Some(Check::new(
CheckKind::UnnecessaryCallAroundSorted(outer.to_string()),
Range::from_located(expr),
));
}
}
None
@@ -316,91 +240,65 @@ pub fn unnecessary_double_cast_or_process(
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"
let outer = function_name(func)?;
if !["list", "tuple", "set", "reversed", "sorted"].contains(&outer) {
return None;
}
fn new_check(inner: &str, outer: &str, expr: &Expr) -> Check {
Check::new(
CheckKind::UnnecessaryDoubleCastOrProcess(inner.to_string(), outer.to_string()),
Range::from_located(expr),
)
}
if let ExprKind::Call { func, .. } = &args.first()?.node {
let inner = function_name(func)?;
// Ex) set(tuple(...))
if (outer == "set" || outer == "sorted")
&& (inner == "list" || inner == "tuple" || inner == "reversed" || inner == "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),
));
}
return Some(new_check(inner, outer, 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) list(tuple(...))
if (outer == "list" || outer == "tuple") && (inner == "list" || inner == "tuple") {
return Some(new_check(inner, outer, 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),
));
}
}
}
}
// Ex) set(set(...))
if outer == "set" && inner == "set" {
return Some(new_check(inner, outer, 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),
));
}
}
}
let first_arg = args.first()?;
let id = function_name(func)?;
if !["set", "sorted", "reversed"].contains(&id) {
return None;
}
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),
));
}
}
}
@@ -416,90 +314,65 @@ pub fn unnecessary_comprehension(
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),
))
}
_ => {}
};
}
}
}
}
if generators.len() != 1 {
return None;
}
None
let generator = &generators[0];
if !(generator.ifs.is_empty() && generator.is_async == 0) {
return None;
}
let elt_id = function_name(elt)?;
let target_id = function_name(&generator.target)?;
if elt_id != target_id {
return None;
}
let expr_kind = match &expr.node {
ExprKind::ListComp { .. } => "list",
ExprKind::SetComp { .. } => "set",
_ => return None,
};
Some(Check::new(
CheckKind::UnnecessaryComprehension(expr_kind.to_string()),
Range::from_located(expr),
))
}
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),
));
fn new_check(kind: &str, expr: &Expr) -> Check {
Check::new(
CheckKind::UnnecessaryMap(kind.to_string()),
Range::from_located(expr),
)
}
let id = function_name(func)?;
match id {
"map" => {
if args.len() == 2 && matches!(&args[0].node, ExprKind::Lambda { .. }) {
return Some(new_check("generator", expr));
}
}
"list" | "set" => {
if let ExprKind::Call { func, args, .. } = &args.first()?.node {
let argument = first_argument_with_matching_function("map", func, args)?;
if let ExprKind::Lambda { .. } = argument {
return Some(new_check(id, 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" {
}
"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),
))
}
_ => {}
}
}
}
let argument = first_argument_with_matching_function("map", func, args)?;
if let ExprKind::Lambda { body, .. } = &argument {
if matches!(&body.node, ExprKind::Tuple { elts, .. } | ExprKind::List { elts, .. } if elts.len() == 2)
{
return Some(new_check(id, expr));
}
}
}
}
}
_ => (),
}
None
}

View File

@@ -1,7 +1,7 @@
use log::error;
use rustpython_ast::{Expr, Stmt, StmtKind};
use crate::autofix::{fixer, helpers};
use crate::autofix::helpers;
use crate::check_ast::Checker;
use crate::checks::CheckCode;
use crate::flake8_print::checks;
@@ -13,7 +13,7 @@ pub fn print_call(checker: &mut Checker, expr: &Expr, func: &Expr) {
checker.settings.enabled.contains(&CheckCode::T201),
checker.settings.enabled.contains(&CheckCode::T203),
) {
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if checker.patch() {
let context = checker.binding_context();
if matches!(
checker.parents[context.defined_by].node,

View File

@@ -245,8 +245,11 @@ mod tests {
#[test_case(CheckCode::A001, Path::new("A001.py"); "A001")]
#[test_case(CheckCode::A002, Path::new("A002.py"); "A002")]
#[test_case(CheckCode::A003, Path::new("A003.py"); "A003")]
#[test_case(CheckCode::B002, Path::new("B002.py"); "B002")]
#[test_case(CheckCode::B007, Path::new("B007.py"); "B007")]
#[test_case(CheckCode::B011, Path::new("B011.py"); "B011")]
#[test_case(CheckCode::B014, Path::new("B014.py"); "B014")]
#[test_case(CheckCode::B017, Path::new("B017.py"); "B017")]
#[test_case(CheckCode::B025, Path::new("B025.py"); "B025")]
#[test_case(CheckCode::C400, Path::new("C400.py"); "C400")]
#[test_case(CheckCode::C401, Path::new("C401.py"); "C401")]
@@ -358,6 +361,12 @@ mod tests {
#[test_case(CheckCode::N803, Path::new("N803.py"); "N803")]
#[test_case(CheckCode::N804, Path::new("N804.py"); "N804")]
#[test_case(CheckCode::N805, Path::new("N805.py"); "N805")]
#[test_case(CheckCode::N807, Path::new("N807.py"); "N807")]
#[test_case(CheckCode::N811, Path::new("N811.py"); "N811")]
#[test_case(CheckCode::N812, Path::new("N812.py"); "N812")]
#[test_case(CheckCode::N813, Path::new("N813.py"); "N813")]
#[test_case(CheckCode::N814, Path::new("N814.py"); "N814")]
#[test_case(CheckCode::N817, Path::new("N817.py"); "N817")]
#[test_case(CheckCode::T201, Path::new("T201.py"); "T201")]
#[test_case(CheckCode::T203, Path::new("T203.py"); "T203")]
#[test_case(CheckCode::U001, Path::new("U001.py"); "U001")]

View File

@@ -1,3 +1,4 @@
use itertools::Itertools;
use rustpython_ast::{Arguments, Expr, ExprKind, Stmt};
use crate::ast::types::{Range, Scope, ScopeKind};
@@ -97,3 +98,164 @@ pub fn invalid_first_argument_name_for_method(
}
None
}
pub fn dunder_function_name(func_def: &Stmt, scope: &Scope, name: &str) -> Option<Check> {
if matches!(scope.kind, ScopeKind::Class) {
return None;
}
if name.starts_with("__") && name.ends_with("__") {
return Some(Check::new(
CheckKind::DunderFunctionName,
Range::from_located(func_def),
));
}
None
}
fn is_lower(s: &str) -> bool {
let mut cased = false;
for c in s.chars() {
if c.is_uppercase() {
return false;
} else if !cased && c.is_lowercase() {
cased = true;
}
}
cased
}
fn is_upper(s: &str) -> bool {
let mut cased = false;
for c in s.chars() {
if c.is_lowercase() {
return false;
} else if (!cased) && c.is_uppercase() {
cased = true;
}
}
cased
}
pub fn constant_imported_as_non_constant(
import_from: &Stmt,
name: &str,
asname: &str,
) -> Option<Check> {
if is_upper(name) && !is_upper(asname) {
return Some(Check::new(
CheckKind::ConstantImportedAsNonConstant(name.to_string(), asname.to_string()),
Range::from_located(import_from),
));
}
None
}
pub fn lowercase_imported_as_non_lowercase(
import_from: &Stmt,
name: &str,
asname: &str,
) -> Option<Check> {
if is_lower(name) && asname.to_lowercase() != asname {
return Some(Check::new(
CheckKind::LowercaseImportedAsNonLowercase(name.to_string(), asname.to_string()),
Range::from_located(import_from),
));
}
None
}
fn is_camelcase(name: &str) -> bool {
!is_lower(name) && !is_upper(name) && !name.contains('_')
}
fn is_acronym(name: &str, asname: &str) -> bool {
name.chars().filter(|c| c.is_uppercase()).join("") == asname
}
pub fn camelcase_imported_as_lowercase(
import_from: &Stmt,
name: &str,
asname: &str,
) -> Option<Check> {
if is_camelcase(name) && is_lower(asname) {
return Some(Check::new(
CheckKind::CamelcaseImportedAsLowercase(name.to_string(), asname.to_string()),
Range::from_located(import_from),
));
}
None
}
pub fn camelcase_imported_as_constant(
import_from: &Stmt,
name: &str,
asname: &str,
) -> Option<Check> {
if is_camelcase(name) && is_upper(asname) && !is_acronym(name, asname) {
return Some(Check::new(
CheckKind::CamelcaseImportedAsConstant(name.to_string(), asname.to_string()),
Range::from_located(import_from),
));
}
None
}
pub fn camelcase_imported_as_acronym(
import_from: &Stmt,
name: &str,
asname: &str,
) -> Option<Check> {
if is_camelcase(name) && is_upper(asname) && is_acronym(name, asname) {
return Some(Check::new(
CheckKind::CamelcaseImportedAsAcronym(name.to_string(), asname.to_string()),
Range::from_located(import_from),
));
}
None
}
#[cfg(test)]
mod tests {
use super::{is_acronym, is_camelcase, is_lower, is_upper};
#[test]
fn test_is_lower() -> () {
assert!(is_lower("abc"));
assert!(is_lower("a_b_c"));
assert!(is_lower("a2c"));
assert!(!is_lower("aBc"));
assert!(!is_lower("ABC"));
assert!(!is_lower(""));
assert!(!is_lower("_"));
}
#[test]
fn test_is_upper() -> () {
assert!(is_upper("ABC"));
assert!(is_upper("A_B_C"));
assert!(is_upper("A2C"));
assert!(!is_upper("aBc"));
assert!(!is_upper("abc"));
assert!(!is_upper(""));
assert!(!is_upper("_"));
}
#[test]
fn test_is_camelcase() -> () {
assert!(is_camelcase("Camel"));
assert!(is_camelcase("CamelCase"));
assert!(!is_camelcase("camel"));
assert!(!is_camelcase("camel_case"));
assert!(!is_camelcase("CAMEL"));
assert!(!is_camelcase("CAMEL_CASE"));
}
#[test]
fn test_is_acronym() -> () {
assert!(is_acronym("AB", "AB"));
assert!(is_acronym("AbcDef", "AD"));
assert!(!is_acronym("AbcDef", "Ad"));
assert!(!is_acronym("AbcDef", "AB"));
}
}

View File

@@ -4,10 +4,8 @@ use itertools::Itertools;
use once_cell::sync::Lazy;
use regex::Regex;
use rustpython_ast::{Arg, Constant, ExprKind, Location, StmtKind};
use titlecase::titlecase;
use crate::ast::types::Range;
use crate::autofix::fixer;
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckCode, CheckKind};
@@ -163,12 +161,12 @@ pub fn blank_before_after_function(checker: &mut Checker, definition: &Definitio
..
} = &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 (before, _, _) = checker.get_locator().partition_source_code_at(
&Range::from_located(parent),
&Range::from_located(docstring),
);
let blank_lines_before = before
.lines()
.rev()
@@ -180,7 +178,7 @@ pub fn blank_before_after_function(checker: &mut Checker, definition: &Definitio
CheckKind::NoBlankLineBeforeFunction(blank_lines_before),
Range::from_located(docstring),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if checker.patch() {
// Delete the blank line before the docstring.
check.amend(Fix::deletion(
Location::new(docstring.location.row() - blank_lines_before, 1),
@@ -192,6 +190,11 @@ pub fn blank_before_after_function(checker: &mut Checker, definition: &Definitio
}
if checker.settings.enabled.contains(&CheckCode::D202) {
let (_, _, after) = checker.get_locator().partition_source_code_at(
&Range::from_located(parent),
&Range::from_located(docstring),
);
let all_blank_after = after
.lines()
.skip(1)
@@ -218,7 +221,7 @@ pub fn blank_before_after_function(checker: &mut Checker, definition: &Definitio
CheckKind::NoBlankLineAfterFunction(blank_lines_after),
Range::from_located(docstring),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if checker.patch() {
// Delete the blank line after the docstring.
check.amend(Fix::deletion(
Location::new(
@@ -247,14 +250,14 @@ pub fn blank_before_after_class(checker: &mut Checker, definition: &Definition)
..
} = &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 (before, _, _) = checker.get_locator().partition_source_code_at(
&Range::from_located(parent),
&Range::from_located(docstring),
);
let blank_lines_before = before
.lines()
.rev()
@@ -267,8 +270,7 @@ pub fn blank_before_after_class(checker: &mut Checker, definition: &Definition)
CheckKind::NoBlankLineBeforeClass(blank_lines_before),
Range::from_located(docstring),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply)
{
if checker.patch() {
// Delete the blank line before the class.
check.amend(Fix::deletion(
Location::new(docstring.location.row() - blank_lines_before, 1),
@@ -284,8 +286,7 @@ pub fn blank_before_after_class(checker: &mut Checker, definition: &Definition)
CheckKind::OneBlankLineBeforeClass(blank_lines_before),
Range::from_located(docstring),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply)
{
if checker.patch() {
// Insert one blank line before the class.
check.amend(Fix::replacement(
"\n".to_string(),
@@ -299,6 +300,11 @@ pub fn blank_before_after_class(checker: &mut Checker, definition: &Definition)
}
if checker.settings.enabled.contains(&CheckCode::D204) {
let (_, _, after) = checker.get_locator().partition_source_code_at(
&Range::from_located(parent),
&Range::from_located(docstring),
);
let all_blank_after = after
.lines()
.skip(1)
@@ -317,7 +323,7 @@ pub fn blank_before_after_class(checker: &mut Checker, definition: &Definition)
CheckKind::OneBlankLineAfterClass(blank_lines_after),
Range::from_located(docstring),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if checker.patch() {
// Insert a blank line before the class (replacing any existing lines).
check.amend(Fix::replacement(
"\n".to_string(),
@@ -359,7 +365,7 @@ pub fn blank_after_summary(checker: &mut Checker, definition: &Definition) {
CheckKind::BlankLineAfterSummary,
Range::from_located(docstring),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if checker.patch() {
// Insert one blank line after the summary (replacing any existing lines).
check.amend(Fix::replacement(
"\n".to_string(),
@@ -386,23 +392,10 @@ pub fn indent(checker: &mut Checker, definition: &Definition) {
return;
}
let mut has_seen_tab = false;
let mut has_seen_over_indent = false;
let mut has_seen_under_indent = false;
let docstring_indent = helpers::indentation(checker, docstring).to_string();
if !has_seen_tab {
if docstring_indent.contains('\t') {
if checker.settings.enabled.contains(&CheckCode::D206) {
checker.add_check(Check::new(
CheckKind::IndentWithSpaces,
Range::from_located(docstring),
));
}
has_seen_tab = true;
}
}
let mut has_seen_tab = docstring_indent.contains('\t');
let mut is_over_indented = true;
let mut over_indented_lines = vec![];
for i in 0..lines.len() {
// First lines and continuations doesn't need any indentation.
if i == 0 || lines[i - 1].ends_with('\\') {
@@ -416,39 +409,105 @@ pub fn indent(checker: &mut Checker, definition: &Definition) {
}
let line_indent = helpers::leading_space(lines[i]);
if !has_seen_tab {
if line_indent.contains('\t') {
if checker.settings.enabled.contains(&CheckCode::D206) {
checker.add_check(Check::new(
CheckKind::IndentWithSpaces,
Range::from_located(docstring),
));
}
has_seen_tab = true;
}
}
if !has_seen_over_indent {
if line_indent.len() > docstring_indent.len() {
if checker.settings.enabled.contains(&CheckCode::D208) {
checker.add_check(Check::new(
CheckKind::NoOverIndentation,
Range::from_located(docstring),
));
}
has_seen_over_indent = true;
}
}
// We only report tab indentation once, so only check if we haven't seen a tab yet.
has_seen_tab = has_seen_tab || line_indent.contains('\t');
if !has_seen_under_indent {
if checker.settings.enabled.contains(&CheckCode::D207) {
// We report under-indentation on every line. This isn't great, but enables
// autofix.
if line_indent.len() < docstring_indent.len() {
if checker.settings.enabled.contains(&CheckCode::D207) {
checker.add_check(Check::new(
CheckKind::NoUnderIndentation,
Range::from_located(docstring),
let mut check = Check::new(
CheckKind::NoUnderIndentation,
Range {
location: Location::new(docstring.location.row() + i, 1),
end_location: Location::new(docstring.location.row() + i, 1),
},
);
if checker.patch() {
check.amend(Fix::replacement(
helpers::clean(&docstring_indent),
Location::new(docstring.location.row() + i, 1),
Location::new(docstring.location.row() + i, 1 + line_indent.len()),
));
}
has_seen_under_indent = true;
checker.add_check(check);
}
}
// Like pydocstyle, we only report over-indentation if either: (1) every line
// (except, optionally, the last line) is over-indented, or (2) the last line (which
// contains the closing quotation marks) is over-indented. We can't know if we've
// achieved that condition until we've viewed all the lines, so for now, just track
// the over-indentation status of every line.
if i < lines.len() - 1 {
if line_indent.len() > docstring_indent.len() {
over_indented_lines.push(i);
} else {
is_over_indented = false;
}
}
}
if checker.settings.enabled.contains(&CheckCode::D206) {
if has_seen_tab {
checker.add_check(Check::new(
CheckKind::IndentWithSpaces,
Range::from_located(docstring),
));
}
}
if checker.settings.enabled.contains(&CheckCode::D208) {
// If every line (except the last) is over-indented...
if is_over_indented {
for i in over_indented_lines {
let line_indent = helpers::leading_space(lines[i]);
if line_indent.len() > docstring_indent.len() {
// We report over-indentation on every line. This isn't great, but
// enables autofix.
let mut check = Check::new(
CheckKind::NoOverIndentation,
Range {
location: Location::new(docstring.location.row() + i, 1),
end_location: Location::new(docstring.location.row() + i, 1),
},
);
if checker.patch() {
check.amend(Fix::replacement(
helpers::clean(&docstring_indent),
Location::new(docstring.location.row() + i, 1),
Location::new(
docstring.location.row() + i,
1 + line_indent.len(),
),
));
}
checker.add_check(check);
}
}
}
// If the last line is over-indented...
if !lines.is_empty() {
let i = lines.len() - 1;
let line_indent = helpers::leading_space(lines[i]);
if line_indent.len() > docstring_indent.len() {
let mut check = Check::new(
CheckKind::NoOverIndentation,
Range {
location: Location::new(docstring.location.row() + i, 1),
end_location: Location::new(docstring.location.row() + i, 1),
},
);
if checker.patch() {
check.amend(Fix::replacement(
helpers::clean(&docstring_indent),
Location::new(docstring.location.row() + i, 1),
Location::new(docstring.location.row() + i, 1 + line_indent.len()),
));
}
checker.add_check(check);
}
}
}
@@ -471,7 +530,7 @@ pub fn newline_after_last_paragraph(checker: &mut Checker, definition: &Definiti
}
if line_count > 1 {
let content = checker
.locator
.get_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 != "'''" {
@@ -479,11 +538,12 @@ pub fn newline_after_last_paragraph(checker: &mut Checker, definition: &Definiti
CheckKind::NewLineAfterLastParagraph,
Range::from_located(docstring),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply)
{
if checker.patch() {
// Insert a newline just before the end-quote(s).
let mut content = "\n".to_string();
content.push_str(helpers::indentation(checker, docstring));
let content = format!(
"\n{}",
helpers::clean(helpers::indentation(checker, docstring))
);
check.amend(Fix::insertion(
content,
Location::new(
@@ -521,9 +581,9 @@ pub fn no_surrounding_whitespace(checker: &mut Checker, definition: &Definition)
CheckKind::NoSurroundingWhitespace,
Range::from_located(docstring),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if checker.patch() {
if let Some(first_line) = checker
.locator
.get_locator()
.slice_source_code_range(&Range::from_located(docstring))
.lines()
.next()
@@ -569,7 +629,7 @@ pub fn multi_line_summary_start(checker: &mut Checker, definition: &Definition)
{
if string.lines().nth(1).is_some() {
if let Some(first_line) = checker
.locator
.get_locator()
.slice_source_code_range(&Range::from_located(docstring))
.lines()
.next()
@@ -605,7 +665,7 @@ pub fn triple_quotes(checker: &mut Checker, definition: &Definition) {
} = &docstring.node
{
if let Some(first_line) = checker
.locator
.get_locator()
.slice_source_code_range(&Range::from_located(docstring))
.lines()
.next()
@@ -856,12 +916,13 @@ fn blanks_and_section_underline(
CheckKind::DashedUnderlineAfterSection(context.section_name.to_string()),
Range::from_located(docstring),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if checker.patch() {
// Add a dashed line (of the appropriate length) under the section header.
let mut content = "".to_string();
content.push_str(helpers::indentation(checker, docstring));
content.push_str(&"-".repeat(context.section_name.len()));
content.push('\n');
let content = format!(
"{}{}\n",
helpers::clean(helpers::indentation(checker, docstring)),
"-".repeat(context.section_name.len())
);
check.amend(Fix::insertion(
content,
Location::new(docstring.location.row() + context.original_index + 1, 1),
@@ -889,12 +950,13 @@ fn blanks_and_section_underline(
CheckKind::DashedUnderlineAfterSection(context.section_name.to_string()),
Range::from_located(docstring),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if checker.patch() {
// Add a dashed line (of the appropriate length) under the section header.
let mut content = "".to_string();
content.push_str(helpers::indentation(checker, docstring));
content.push_str(&"-".repeat(context.section_name.len()));
content.push('\n');
let content = format!(
"{}{}\n",
helpers::clean(helpers::indentation(checker, docstring)),
"-".repeat(context.section_name.len())
);
check.amend(Fix::insertion(
content,
Location::new(docstring.location.row() + context.original_index + 1, 1),
@@ -910,7 +972,7 @@ fn blanks_and_section_underline(
),
Range::from_located(docstring),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if checker.patch() {
// Delete any blank lines between the header and content.
check.amend(Fix::deletion(
Location::new(docstring.location.row() + context.original_index + 1, 1),
@@ -933,7 +995,7 @@ fn blanks_and_section_underline(
CheckKind::SectionUnderlineAfterName(context.section_name.to_string()),
Range::from_located(docstring),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if checker.patch() {
// Delete any blank lines between the header and the underline.
check.amend(Fix::deletion(
Location::new(docstring.location.row() + context.original_index + 1, 1),
@@ -964,12 +1026,13 @@ fn blanks_and_section_underline(
),
Range::from_located(docstring),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if checker.patch() {
// Replace the existing underline with a line of the appropriate length.
let mut content = "".to_string();
content.push_str(helpers::indentation(checker, docstring));
content.push_str(&"-".repeat(context.section_name.len()));
content.push('\n');
let content = format!(
"{}{}\n",
helpers::clean(helpers::indentation(checker, docstring)),
"-".repeat(context.section_name.len())
);
check.amend(Fix::replacement(
content,
Location::new(
@@ -1001,10 +1064,10 @@ fn blanks_and_section_underline(
CheckKind::SectionUnderlineNotOverIndented(context.section_name.to_string()),
Range::from_located(docstring),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if checker.patch() {
// Replace the existing indentation with whitespace of the appropriate length.
check.amend(Fix::replacement(
indentation,
helpers::clean(&indentation),
Location::new(
docstring.location.row()
+ context.original_index
@@ -1050,7 +1113,7 @@ fn blanks_and_section_underline(
),
Range::from_located(docstring),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if checker.patch() {
// Delete any blank lines between the header and content.
check.amend(Fix::deletion(
Location::new(
@@ -1099,25 +1162,61 @@ fn common_section(
if !style
.section_names()
.contains(&context.section_name.as_str())
&& style
.section_names()
.contains(titlecase(&context.section_name).as_str())
{
checker.add_check(Check::new(
CheckKind::CapitalizeSectionName(context.section_name.to_string()),
Range::from_located(docstring),
))
let capitalized_section_name = titlecase::titlecase(&context.section_name);
if style
.section_names()
.contains(capitalized_section_name.as_str())
{
let mut check = Check::new(
CheckKind::CapitalizeSectionName(context.section_name.to_string()),
Range::from_located(docstring),
);
if checker.patch() {
// Replace the section title with the capitalized variant. This requires
// locating the start and end of the section name.
if let Some(index) = context.line.find(&context.section_name) {
// Map from bytes to characters.
let section_name_start = &context.line[..index].chars().count();
let section_name_length = &context.section_name.chars().count();
check.amend(Fix::replacement(
capitalized_section_name,
Location::new(
docstring.location.row() + context.original_index,
1 + section_name_start,
),
Location::new(
docstring.location.row() + context.original_index,
1 + section_name_start + section_name_length,
),
))
}
}
checker.add_check(check);
}
}
}
if checker.settings.enabled.contains(&CheckCode::D214) {
if helpers::leading_space(context.line).len()
> helpers::indentation(checker, docstring).len()
{
checker.add_check(Check::new(
let leading_space = helpers::leading_space(context.line);
let indentation = helpers::indentation(checker, docstring).to_string();
if leading_space.len() > indentation.len() {
let mut check = Check::new(
CheckKind::SectionNotOverIndented(context.section_name.to_string()),
Range::from_located(docstring),
))
);
if checker.patch() {
// Replace the existing indentation with whitespace of the appropriate length.
check.amend(Fix::replacement(
helpers::clean(&indentation),
Location::new(docstring.location.row() + context.original_index, 1),
Location::new(
docstring.location.row() + context.original_index,
1 + leading_space.len(),
),
));
};
checker.add_check(check);
}
}
@@ -1133,7 +1232,7 @@ fn common_section(
CheckKind::BlankLineAfterLastSection(context.section_name.to_string()),
Range::from_located(docstring),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if checker.patch() {
// Add a newline after the section.
check.amend(Fix::insertion(
"\n".to_string(),
@@ -1144,7 +1243,7 @@ fn common_section(
+ context.following_lines.len(),
1,
),
))
));
}
checker.add_check(check);
}
@@ -1154,7 +1253,7 @@ fn common_section(
CheckKind::BlankLineAfterSection(context.section_name.to_string()),
Range::from_located(docstring),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if checker.patch() {
// Add a newline after the section.
check.amend(Fix::insertion(
"\n".to_string(),
@@ -1178,7 +1277,7 @@ fn common_section(
CheckKind::BlankLineBeforeSection(context.section_name.to_string()),
Range::from_located(docstring),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if checker.patch() {
// Add a blank line before the section.
check.amend(Fix::insertion(
"\n".to_string(),
@@ -1334,10 +1433,31 @@ fn numpy_section(checker: &mut Checker, definition: &Definition, context: &Secti
let docstring = definition
.docstring
.expect("Sections are only available for docstrings.");
checker.add_check(Check::new(
let mut check = Check::new(
CheckKind::NewLineAfterSectionName(context.section_name.to_string()),
Range::from_located(docstring),
))
);
if checker.patch() {
// Delete the suffix. This requires locating the end of the section name.
if let Some(index) = context.line.find(&context.section_name) {
// Map from bytes to characters.
let suffix_start = &context.line[..index + context.section_name.len()]
.chars()
.count();
let suffix_length = suffix.chars().count();
check.amend(Fix::deletion(
Location::new(
docstring.location.row() + context.original_index,
1 + suffix_start,
),
Location::new(
docstring.location.row() + context.original_index,
1 + suffix_start + suffix_length,
),
));
}
}
checker.add_check(check)
}
}
@@ -1362,10 +1482,32 @@ fn google_section(checker: &mut Checker, definition: &Definition, context: &Sect
let docstring = definition
.docstring
.expect("Sections are only available for docstrings.");
checker.add_check(Check::new(
let mut check = Check::new(
CheckKind::SectionNameEndsInColon(context.section_name.to_string()),
Range::from_located(docstring),
))
);
if checker.patch() {
// Replace the suffix. This requires locating the end of the section name.
if let Some(index) = context.line.find(&context.section_name) {
// Map from bytes to characters.
let suffix_start = &context.line[..index + context.section_name.len()]
.chars()
.count();
let suffix_length = suffix.chars().count();
check.amend(Fix::replacement(
":".to_string(),
Location::new(
docstring.location.row() + context.original_index,
1 + suffix_start,
),
Location::new(
docstring.location.row() + context.original_index,
1 + suffix_start + suffix_length,
),
));
}
}
checker.add_check(check);
}
}

View File

@@ -9,7 +9,7 @@ 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,
locator: &SourceCodeLocator,
full_names: &[&str],
stmt: &Stmt,
parent: Option<&Stmt>,
@@ -74,7 +74,7 @@ pub fn remove_unused_imports(
/// Generate a Fix to remove any unused imports from an `import from` statement.
pub fn remove_unused_import_froms(
locator: &mut SourceCodeLocator,
locator: &SourceCodeLocator,
full_names: &[&str],
stmt: &Stmt,
parent: Option<&Stmt>,

View File

@@ -10,7 +10,7 @@ use crate::autofix::Fix;
/// Generate a fix to remove a base from a ClassDef statement.
pub fn remove_class_def_base(
locator: &mut SourceCodeLocator,
locator: &SourceCodeLocator,
stmt_at: &Location,
expr_at: Location,
bases: &[Expr],
@@ -101,7 +101,7 @@ pub fn remove_class_def_base(
}
}
pub fn remove_super_arguments(locator: &mut SourceCodeLocator, expr: &Expr) -> Option<Fix> {
pub fn remove_super_arguments(locator: &SourceCodeLocator, expr: &Expr) -> Option<Fix> {
let range = Range::from_located(expr);
let contents = locator.slice_source_code_range(&range);

View File

@@ -4,7 +4,6 @@ use once_cell::sync::Lazy;
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};
@@ -38,7 +37,7 @@ pub fn deprecated_unittest_alias(checker: &mut Checker, expr: &Expr) {
CheckKind::DeprecatedUnittestAlias(attr.to_string(), target.to_string()),
Range::from_located(expr),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if checker.patch() {
check.amend(Fix::replacement(
format!("self.{}", target),
expr.location,

View File

@@ -1,7 +1,6 @@
use rustpython_ast::{Expr, Stmt};
use crate::ast::helpers;
use crate::autofix::fixer;
use crate::check_ast::Checker;
use crate::pyupgrade;
use crate::pyupgrade::checks;
@@ -17,9 +16,9 @@ pub fn super_call_with_parameters(checker: &mut Checker, expr: &Expr, func: &Exp
.map(|index| checker.parents[*index])
.collect();
if let Some(mut check) = checks::super_args(scope, &parents, expr, func, args) {
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if checker.patch() {
if let Some(fix) =
pyupgrade::fixes::remove_super_arguments(&mut checker.locator, expr)
pyupgrade::fixes::remove_super_arguments(checker.get_locator(), expr)
{
check.amend(fix);
}

View File

@@ -1,7 +1,6 @@
use rustpython_ast::Expr;
use crate::ast::types::{CheckLocator, Range};
use crate::autofix::fixer;
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checks::CheckKind;
@@ -11,7 +10,7 @@ pub fn type_of_primitive(checker: &mut Checker, expr: &Expr, func: &Expr, args:
if let Some(mut check) =
checks::type_of_primitive(func, args, checker.locate_check(Range::from_located(expr)))
{
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if checker.patch() {
if let CheckKind::TypeOfPrimitive(primitive) = &check.kind {
check.amend(Fix::replacement(
primitive.builtin(),

View File

@@ -1,7 +1,6 @@
use rustpython_ast::Expr;
use crate::ast::types::{CheckLocator, Range};
use crate::autofix::fixer;
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::pyupgrade::checks;
@@ -10,7 +9,7 @@ pub fn unnecessary_abspath(checker: &mut Checker, expr: &Expr, func: &Expr, args
if let Some(mut check) =
checks::unnecessary_abspath(func, args, checker.locate_check(Range::from_located(expr)))
{
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if checker.patch() {
check.amend(Fix::replacement(
"__file__".to_string(),
expr.location,

View File

@@ -1,7 +1,6 @@
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};
@@ -14,7 +13,7 @@ pub fn use_pep585_annotation(checker: &mut Checker, expr: &Expr, id: &str) {
CheckKind::UsePEP585Annotation(id.to_string()),
Range::from_located(expr),
);
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if checker.patch() {
check.amend(Fix::replacement(
id.to_lowercase(),
expr.location,

View File

@@ -2,7 +2,6 @@ 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};
@@ -46,7 +45,7 @@ fn union(elts: &[Expr]) -> Expr {
pub fn use_pep604_annotation(checker: &mut Checker, expr: &Expr, value: &Expr, slice: &Expr) {
if match_name_or_attr(value, "Optional") {
let mut check = Check::new(CheckKind::UsePEP604Annotation, Range::from_located(expr));
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if checker.patch() {
let mut generator = SourceGenerator::new();
if let Ok(()) = generator.unparse_expr(&optional(slice), 0) {
if let Ok(content) = generator.generate() {
@@ -61,7 +60,7 @@ pub fn use_pep604_annotation(checker: &mut Checker, expr: &Expr, value: &Expr, s
checker.add_check(check);
} else if match_name_or_attr(value, "Union") {
let mut check = Check::new(CheckKind::UsePEP604Annotation, Range::from_located(expr));
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if checker.patch() {
match &slice.node {
ExprKind::Slice { .. } => {
// Invalid type annotation.

View File

@@ -2,7 +2,7 @@ use log::error;
use rustpython_ast::{Expr, Stmt};
use crate::ast::types::{CheckLocator, Range};
use crate::autofix::{fixer, helpers};
use crate::autofix::helpers;
use crate::check_ast::Checker;
use crate::pyupgrade::checks;
@@ -12,7 +12,7 @@ pub fn useless_metaclass_type(checker: &mut Checker, stmt: &Stmt, value: &Expr,
value,
checker.locate_check(Range::from_located(stmt)),
) {
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if checker.patch() {
let context = checker.binding_context();
let deleted: Vec<&Stmt> = checker
.deletions

View File

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

View File

@@ -0,0 +1,21 @@
---
source: src/linter.rs
expression: checks
---
- kind: UnaryPrefixIncrement
location:
row: 15
column: 9
end_location:
row: 15
column: 12
fix: ~
- kind: UnaryPrefixIncrement
location:
row: 20
column: 12
end_location:
row: 20
column: 15
fix: ~

View File

@@ -0,0 +1,77 @@
---
source: src/linter.rs
expression: checks
---
- kind:
UnusedLoopControlVariable: i
location:
row: 6
column: 5
end_location:
row: 6
column: 6
fix:
patch:
content: _i
location:
row: 6
column: 5
end_location:
row: 6
column: 6
applied: false
- kind:
UnusedLoopControlVariable: k
location:
row: 18
column: 13
end_location:
row: 18
column: 14
fix:
patch:
content: _k
location:
row: 18
column: 13
end_location:
row: 18
column: 14
applied: false
- kind:
UnusedLoopControlVariable: i
location:
row: 30
column: 5
end_location:
row: 30
column: 6
fix:
patch:
content: _i
location:
row: 30
column: 5
end_location:
row: 30
column: 6
applied: false
- kind:
UnusedLoopControlVariable: k
location:
row: 30
column: 13
end_location:
row: 30
column: 14
fix:
patch:
content: _k
location:
row: 30
column: 13
end_location:
row: 30
column: 14
applied: false

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,38 @@
---
source: src/linter.rs
expression: checks
---
- kind:
ConstantImportedAsNonConstant:
- CONST
- const
location:
row: 1
column: 1
end_location:
row: 1
column: 26
fix: ~
- kind:
ConstantImportedAsNonConstant:
- CONSTANT
- constant
location:
row: 2
column: 1
end_location:
row: 2
column: 37
fix: ~
- kind:
ConstantImportedAsNonConstant:
- ANOTHER_CONSTANT
- another_constant
location:
row: 3
column: 1
end_location:
row: 3
column: 53
fix: ~

View File

@@ -0,0 +1,38 @@
---
source: src/linter.rs
expression: checks
---
- kind:
LowercaseImportedAsNonLowercase:
- lowercase
- Lower
location:
row: 1
column: 1
end_location:
row: 1
column: 31
fix: ~
- kind:
LowercaseImportedAsNonLowercase:
- lowercase
- Lowercase
location:
row: 2
column: 1
end_location:
row: 2
column: 39
fix: ~
- kind:
LowercaseImportedAsNonLowercase:
- another_lowercase
- AnotherLowercase
location:
row: 3
column: 1
end_location:
row: 3
column: 54
fix: ~

View File

@@ -0,0 +1,38 @@
---
source: src/linter.rs
expression: checks
---
- kind:
CamelcaseImportedAsLowercase:
- Camel
- camel
location:
row: 1
column: 1
end_location:
row: 1
column: 26
fix: ~
- kind:
CamelcaseImportedAsLowercase:
- CamelCase
- camelcase
location:
row: 2
column: 1
end_location:
row: 2
column: 39
fix: ~
- kind:
CamelcaseImportedAsLowercase:
- AnotherCamelCase
- another_camelcase
location:
row: 3
column: 1
end_location:
row: 3
column: 54
fix: ~

View File

@@ -0,0 +1,38 @@
---
source: src/linter.rs
expression: checks
---
- kind:
CamelcaseImportedAsConstant:
- Camel
- CAMEL
location:
row: 1
column: 1
end_location:
row: 1
column: 26
fix: ~
- kind:
CamelcaseImportedAsConstant:
- CamelCase
- CAMELCASE
location:
row: 2
column: 1
end_location:
row: 2
column: 39
fix: ~
- kind:
CamelcaseImportedAsConstant:
- AnotherCamelCase
- ANOTHER_CAMELCASE
location:
row: 3
column: 1
end_location:
row: 3
column: 54
fix: ~

View File

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