mirror of https://github.com/astral-sh/ruff
Implement B007 (unused loop control variable) (#473)
This commit is contained in:
parent
4beea0484a
commit
b6c856bd07
|
|
@ -381,6 +381,7 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||||
| Code | Name | Message | Fix |
|
| Code | Name | Message | Fix |
|
||||||
| ---- | ---- | ------- | --- |
|
| ---- | ---- | ------- | --- |
|
||||||
| B002 | UnaryPrefixIncrement | Python does not support the unary prefix increment. | |
|
| 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()` | 🛠 |
|
| B011 | DoNotAssertFalse | Do not `assert False` (`python -O` removes these calls), raise `AssertionError()` | 🛠 |
|
||||||
| B014 | DuplicateHandlerException | Exception handler with duplicate exception: `ValueError` | 🛠 |
|
| B014 | DuplicateHandlerException | Exception handler with duplicate exception: `ValueError` | 🛠 |
|
||||||
| B017 | NoAssertRaisesException | `assertRaises(Exception):` should be considered evil. | |
|
| B017 | NoAssertRaisesException | `assertRaises(Exception):` should be considered evil. | |
|
||||||
|
|
@ -473,7 +474,7 @@ including:
|
||||||
- [`flake8-super`](https://pypi.org/project/flake8-super/)
|
- [`flake8-super`](https://pypi.org/project/flake8-super/)
|
||||||
- [`flake8-print`](https://pypi.org/project/flake8-print/)
|
- [`flake8-print`](https://pypi.org/project/flake8-print/)
|
||||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
||||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (8/32)
|
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (9/32)
|
||||||
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (8/34)
|
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (8/34)
|
||||||
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
|
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
|
||||||
|
|
||||||
|
|
@ -493,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-super`](https://pypi.org/project/flake8-super/)
|
||||||
- [`flake8-print`](https://pypi.org/project/flake8-print/)
|
- [`flake8-print`](https://pypi.org/project/flake8-print/)
|
||||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
||||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (8/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),
|
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).
|
and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (8/34).
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -687,6 +687,11 @@ where
|
||||||
flake8_bugbear::plugins::assert_raises_exception(self, stmt, items);
|
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, .. } => {
|
StmtKind::Try { handlers, .. } => {
|
||||||
if self.settings.enabled.contains(&CheckCode::F707) {
|
if self.settings.enabled.contains(&CheckCode::F707) {
|
||||||
if let Some(check) = pyflakes::checks::default_except_not_last(handlers) {
|
if let Some(check) = pyflakes::checks::default_except_not_last(handlers) {
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,7 @@ pub enum CheckCode {
|
||||||
A003,
|
A003,
|
||||||
// flake8-bugbear
|
// flake8-bugbear
|
||||||
B002,
|
B002,
|
||||||
|
B007,
|
||||||
B011,
|
B011,
|
||||||
B014,
|
B014,
|
||||||
B017,
|
B017,
|
||||||
|
|
@ -268,6 +269,7 @@ pub enum CheckKind {
|
||||||
BuiltinAttributeShadowing(String),
|
BuiltinAttributeShadowing(String),
|
||||||
// flake8-bugbear
|
// flake8-bugbear
|
||||||
UnaryPrefixIncrement,
|
UnaryPrefixIncrement,
|
||||||
|
UnusedLoopControlVariable(String),
|
||||||
DoNotAssertFalse,
|
DoNotAssertFalse,
|
||||||
DuplicateHandlerException(Vec<String>),
|
DuplicateHandlerException(Vec<String>),
|
||||||
NoAssertRaisesException,
|
NoAssertRaisesException,
|
||||||
|
|
@ -429,6 +431,7 @@ impl CheckCode {
|
||||||
CheckCode::A003 => CheckKind::BuiltinAttributeShadowing("...".to_string()),
|
CheckCode::A003 => CheckKind::BuiltinAttributeShadowing("...".to_string()),
|
||||||
// flake8-bugbear
|
// flake8-bugbear
|
||||||
CheckCode::B002 => CheckKind::UnaryPrefixIncrement,
|
CheckCode::B002 => CheckKind::UnaryPrefixIncrement,
|
||||||
|
CheckCode::B007 => CheckKind::UnusedLoopControlVariable("i".to_string()),
|
||||||
CheckCode::B011 => CheckKind::DoNotAssertFalse,
|
CheckCode::B011 => CheckKind::DoNotAssertFalse,
|
||||||
CheckCode::B014 => CheckKind::DuplicateHandlerException(vec!["ValueError".to_string()]),
|
CheckCode::B014 => CheckKind::DuplicateHandlerException(vec!["ValueError".to_string()]),
|
||||||
CheckCode::B017 => CheckKind::NoAssertRaisesException,
|
CheckCode::B017 => CheckKind::NoAssertRaisesException,
|
||||||
|
|
@ -605,6 +608,7 @@ impl CheckCode {
|
||||||
CheckCode::A002 => CheckCategory::Flake8Builtins,
|
CheckCode::A002 => CheckCategory::Flake8Builtins,
|
||||||
CheckCode::A003 => CheckCategory::Flake8Builtins,
|
CheckCode::A003 => CheckCategory::Flake8Builtins,
|
||||||
CheckCode::B002 => CheckCategory::Flake8Bugbear,
|
CheckCode::B002 => CheckCategory::Flake8Bugbear,
|
||||||
|
CheckCode::B007 => CheckCategory::Flake8Bugbear,
|
||||||
CheckCode::B011 => CheckCategory::Flake8Bugbear,
|
CheckCode::B011 => CheckCategory::Flake8Bugbear,
|
||||||
CheckCode::B014 => CheckCategory::Flake8Bugbear,
|
CheckCode::B014 => CheckCategory::Flake8Bugbear,
|
||||||
CheckCode::B017 => CheckCategory::Flake8Bugbear,
|
CheckCode::B017 => CheckCategory::Flake8Bugbear,
|
||||||
|
|
@ -750,6 +754,7 @@ impl CheckKind {
|
||||||
CheckKind::BuiltinAttributeShadowing(_) => &CheckCode::A003,
|
CheckKind::BuiltinAttributeShadowing(_) => &CheckCode::A003,
|
||||||
// flake8-bugbear
|
// flake8-bugbear
|
||||||
CheckKind::UnaryPrefixIncrement => &CheckCode::B002,
|
CheckKind::UnaryPrefixIncrement => &CheckCode::B002,
|
||||||
|
CheckKind::UnusedLoopControlVariable(_) => &CheckCode::B007,
|
||||||
CheckKind::DoNotAssertFalse => &CheckCode::B011,
|
CheckKind::DoNotAssertFalse => &CheckCode::B011,
|
||||||
CheckKind::DuplicateHandlerException(_) => &CheckCode::B014,
|
CheckKind::DuplicateHandlerException(_) => &CheckCode::B014,
|
||||||
CheckKind::NoAssertRaisesException => &CheckCode::B017,
|
CheckKind::NoAssertRaisesException => &CheckCode::B017,
|
||||||
|
|
@ -990,6 +995,7 @@ impl CheckKind {
|
||||||
}
|
}
|
||||||
// flake8-bugbear
|
// 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::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 => {
|
CheckKind::DoNotAssertFalse => {
|
||||||
"Do not `assert False` (`python -O` removes these calls), raise `AssertionError()`"
|
"Do not `assert False` (`python -O` removes these calls), raise `AssertionError()`"
|
||||||
.to_string()
|
.to_string()
|
||||||
|
|
@ -1278,6 +1284,9 @@ impl CheckKind {
|
||||||
CheckKind::UnaryPrefixIncrement => {
|
CheckKind::UnaryPrefixIncrement => {
|
||||||
"Python does not support the unary prefix increment.".to_string()
|
"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 => {
|
CheckKind::NoAssertRaisesException => {
|
||||||
"`assertRaises(Exception):` should be considered evil.".to_string()
|
"`assertRaises(Exception):` should be considered evil.".to_string()
|
||||||
}
|
}
|
||||||
|
|
@ -1320,6 +1329,7 @@ impl CheckKind {
|
||||||
| CheckKind::TypeOfPrimitive(_)
|
| CheckKind::TypeOfPrimitive(_)
|
||||||
| CheckKind::UnnecessaryAbspath
|
| CheckKind::UnnecessaryAbspath
|
||||||
| CheckKind::UnusedImport(_)
|
| CheckKind::UnusedImport(_)
|
||||||
|
| CheckKind::UnusedLoopControlVariable(_)
|
||||||
| CheckKind::UnusedNOQA(_)
|
| CheckKind::UnusedNOQA(_)
|
||||||
| CheckKind::UsePEP585Annotation(_)
|
| CheckKind::UsePEP585Annotation(_)
|
||||||
| CheckKind::UsePEP604Annotation
|
| CheckKind::UsePEP604Annotation
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,10 @@ pub use assert_raises_exception::assert_raises_exception;
|
||||||
pub use duplicate_exceptions::duplicate_exceptions;
|
pub use duplicate_exceptions::duplicate_exceptions;
|
||||||
pub use duplicate_exceptions::duplicate_handler_exceptions;
|
pub use duplicate_exceptions::duplicate_handler_exceptions;
|
||||||
pub use unary_prefix_increment::unary_prefix_increment;
|
pub use unary_prefix_increment::unary_prefix_increment;
|
||||||
|
pub use unused_loop_control_variable::unused_loop_control_variable;
|
||||||
|
|
||||||
mod assert_false;
|
mod assert_false;
|
||||||
mod assert_raises_exception;
|
mod assert_raises_exception;
|
||||||
mod duplicate_exceptions;
|
mod duplicate_exceptions;
|
||||||
mod unary_prefix_increment;
|
mod unary_prefix_increment;
|
||||||
|
mod unused_loop_control_variable;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -246,6 +246,7 @@ mod tests {
|
||||||
#[test_case(CheckCode::A002, Path::new("A002.py"); "A002")]
|
#[test_case(CheckCode::A002, Path::new("A002.py"); "A002")]
|
||||||
#[test_case(CheckCode::A003, Path::new("A003.py"); "A003")]
|
#[test_case(CheckCode::A003, Path::new("A003.py"); "A003")]
|
||||||
#[test_case(CheckCode::B002, Path::new("B002.py"); "B002")]
|
#[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::B011, Path::new("B011.py"); "B011")]
|
||||||
#[test_case(CheckCode::B014, Path::new("B014.py"); "B014")]
|
#[test_case(CheckCode::B014, Path::new("B014.py"); "B014")]
|
||||||
#[test_case(CheckCode::B017, Path::new("B017.py"); "B017")]
|
#[test_case(CheckCode::B017, Path::new("B017.py"); "B017")]
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
Loading…
Reference in New Issue