mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-26 20:10:09 +00:00
fix: avoid flagging unused loop variable (B007) with globals(), vars() or eval() (#2166)
This commit is contained in:
parent
a978706dce
commit
43a8ce6c89
6 changed files with 183 additions and 23 deletions
|
@ -853,7 +853,7 @@ For more, see [flake8-bugbear](https://pypi.org/project/flake8-bugbear/) on PyPI
|
||||||
| B004 | unreliable-callable-check | Using `hasattr(x, '__call__')` to test if x is callable is unreliable. Use `callable(x)` for consistent results. | |
|
| B004 | unreliable-callable-check | Using `hasattr(x, '__call__')` to test if x is callable is unreliable. Use `callable(x)` for consistent results. | |
|
||||||
| B005 | strip-with-multi-characters | Using `.strip()` with multi-character strings is misleading the reader | |
|
| B005 | strip-with-multi-characters | Using `.strip()` with multi-character strings is misleading the reader | |
|
||||||
| B006 | mutable-argument-default | Do not use mutable data structures for argument defaults | |
|
| B006 | mutable-argument-default | Do not use mutable data structures for argument defaults | |
|
||||||
| B007 | unused-loop-control-variable | Loop control variable `{name}` not used within the loop body | 🛠 |
|
| B007 | unused-loop-control-variable | Loop control variable `{name}` not used within loop body | |
|
||||||
| B008 | function-call-argument-default | Do not perform function call `{name}` in argument defaults | |
|
| B008 | function-call-argument-default | Do not perform function call `{name}` in argument defaults | |
|
||||||
| B009 | get-attr-with-constant | Do not call `getattr` with a constant attribute value. It is not any safer than normal property access. | 🛠 |
|
| B009 | get-attr-with-constant | Do not call `getattr` with a constant attribute value. It is not any safer than normal property access. | 🛠 |
|
||||||
| B010 | set-attr-with-constant | Do not call `setattr` with a constant attribute value. It is not any safer than normal property access. | 🛠 |
|
| B010 | set-attr-with-constant | Do not call `setattr` with a constant attribute value. It is not any safer than normal property access. | 🛠 |
|
||||||
|
|
11
resources/test/fixtures/flake8_bugbear/B007.py
vendored
11
resources/test/fixtures/flake8_bugbear/B007.py
vendored
|
@ -34,3 +34,14 @@ FMT = "{foo} {bar}"
|
||||||
for foo, bar in [(1, 2)]:
|
for foo, bar in [(1, 2)]:
|
||||||
if foo:
|
if foo:
|
||||||
print(FMT.format(**locals()))
|
print(FMT.format(**locals()))
|
||||||
|
|
||||||
|
for foo, bar in [(1, 2)]:
|
||||||
|
if foo:
|
||||||
|
print(FMT.format(**globals()))
|
||||||
|
|
||||||
|
for foo, bar in [(1, 2)]:
|
||||||
|
if foo:
|
||||||
|
print(FMT.format(**vars()))
|
||||||
|
|
||||||
|
for foo, bar in [(1, 2)]:
|
||||||
|
print(FMT.format(foo=foo, bar=eval('bar')))
|
||||||
|
|
|
@ -4,8 +4,8 @@ use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
use rustpython_ast::{
|
use rustpython_ast::{
|
||||||
Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind, Keyword,
|
Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Keyword, KeywordData,
|
||||||
KeywordData, Located, Location, Stmt, StmtKind,
|
Located, Location, Stmt, StmtKind,
|
||||||
};
|
};
|
||||||
use rustpython_parser::lexer;
|
use rustpython_parser::lexer;
|
||||||
use rustpython_parser::lexer::Tok;
|
use rustpython_parser::lexer::Tok;
|
||||||
|
@ -562,15 +562,17 @@ pub fn is_super_call_with_arguments(func: &Expr, args: &[Expr]) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `true` if the body uses `locals()`.
|
/// Return `true` if the body uses `locals()`, `globals()`, `vars()`, `eval()`.
|
||||||
pub fn uses_locals(body: &[Stmt]) -> bool {
|
pub fn uses_magic_variable_access(checker: &Checker, body: &[Stmt]) -> bool {
|
||||||
any_over_body(body, &|expr| {
|
any_over_body(body, &|expr| {
|
||||||
if let ExprKind::Call { func, .. } = &expr.node {
|
if let ExprKind::Call { func, .. } = &expr.node {
|
||||||
if let ExprKind::Name { id, ctx } = &func.node {
|
checker.resolve_call_path(func).map_or(false, |call_path| {
|
||||||
id == "locals" && matches!(ctx, ExprContext::Load)
|
call_path.as_slice() == ["", "locals"]
|
||||||
} else {
|
|| call_path.as_slice() == ["", "globals"]
|
||||||
false
|
|| call_path.as_slice() == ["", "vars"]
|
||||||
}
|
|| call_path.as_slice() == ["", "eval"]
|
||||||
|
|| call_path.as_slice() == ["", "exec"]
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,10 +57,6 @@ where
|
||||||
|
|
||||||
/// B007
|
/// B007
|
||||||
pub fn unused_loop_control_variable(checker: &mut Checker, target: &Expr, body: &[Stmt]) {
|
pub fn unused_loop_control_variable(checker: &mut Checker, target: &Expr, body: &[Stmt]) {
|
||||||
if helpers::uses_locals(body) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let control_names = {
|
let control_names = {
|
||||||
let mut finder = NameFinder::new();
|
let mut finder = NameFinder::new();
|
||||||
finder.visit_expr(target);
|
finder.visit_expr(target);
|
||||||
|
@ -86,11 +82,15 @@ pub fn unused_loop_control_variable(checker: &mut Checker, target: &Expr, body:
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let safe = !helpers::uses_magic_variable_access(checker, body);
|
||||||
let mut diagnostic = Diagnostic::new(
|
let mut diagnostic = Diagnostic::new(
|
||||||
violations::UnusedLoopControlVariable(name.to_string()),
|
violations::UnusedLoopControlVariable {
|
||||||
|
name: name.to_string(),
|
||||||
|
safe,
|
||||||
|
},
|
||||||
Range::from_located(expr),
|
Range::from_located(expr),
|
||||||
);
|
);
|
||||||
if checker.patch(diagnostic.kind.rule()) {
|
if safe && checker.patch(diagnostic.kind.rule()) {
|
||||||
// Prefix the variable name with an underscore.
|
// Prefix the variable name with an underscore.
|
||||||
diagnostic.amend(Fix::replacement(
|
diagnostic.amend(Fix::replacement(
|
||||||
format!("_{name}"),
|
format!("_{name}"),
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
---
|
||||||
|
source: src/rules/flake8_bugbear/mod.rs
|
||||||
|
assertion_line: 52
|
||||||
|
expression: diagnostics
|
||||||
|
---
|
||||||
|
- kind:
|
||||||
|
UnusedLoopControlVariable:
|
||||||
|
name: i
|
||||||
|
safe: true
|
||||||
|
location:
|
||||||
|
row: 6
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 6
|
||||||
|
column: 5
|
||||||
|
fix:
|
||||||
|
content: _i
|
||||||
|
location:
|
||||||
|
row: 6
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 6
|
||||||
|
column: 5
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
UnusedLoopControlVariable:
|
||||||
|
name: k
|
||||||
|
safe: true
|
||||||
|
location:
|
||||||
|
row: 18
|
||||||
|
column: 12
|
||||||
|
end_location:
|
||||||
|
row: 18
|
||||||
|
column: 13
|
||||||
|
fix:
|
||||||
|
content: _k
|
||||||
|
location:
|
||||||
|
row: 18
|
||||||
|
column: 12
|
||||||
|
end_location:
|
||||||
|
row: 18
|
||||||
|
column: 13
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
UnusedLoopControlVariable:
|
||||||
|
name: i
|
||||||
|
safe: true
|
||||||
|
location:
|
||||||
|
row: 30
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 30
|
||||||
|
column: 5
|
||||||
|
fix:
|
||||||
|
content: _i
|
||||||
|
location:
|
||||||
|
row: 30
|
||||||
|
column: 4
|
||||||
|
end_location:
|
||||||
|
row: 30
|
||||||
|
column: 5
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
UnusedLoopControlVariable:
|
||||||
|
name: k
|
||||||
|
safe: true
|
||||||
|
location:
|
||||||
|
row: 30
|
||||||
|
column: 12
|
||||||
|
end_location:
|
||||||
|
row: 30
|
||||||
|
column: 13
|
||||||
|
fix:
|
||||||
|
content: _k
|
||||||
|
location:
|
||||||
|
row: 30
|
||||||
|
column: 12
|
||||||
|
end_location:
|
||||||
|
row: 30
|
||||||
|
column: 13
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
UnusedLoopControlVariable:
|
||||||
|
name: bar
|
||||||
|
safe: false
|
||||||
|
location:
|
||||||
|
row: 34
|
||||||
|
column: 9
|
||||||
|
end_location:
|
||||||
|
row: 34
|
||||||
|
column: 12
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
UnusedLoopControlVariable:
|
||||||
|
name: bar
|
||||||
|
safe: false
|
||||||
|
location:
|
||||||
|
row: 38
|
||||||
|
column: 9
|
||||||
|
end_location:
|
||||||
|
row: 38
|
||||||
|
column: 12
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
UnusedLoopControlVariable:
|
||||||
|
name: bar
|
||||||
|
safe: false
|
||||||
|
location:
|
||||||
|
row: 42
|
||||||
|
column: 9
|
||||||
|
end_location:
|
||||||
|
row: 42
|
||||||
|
column: 12
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
- kind:
|
||||||
|
UnusedLoopControlVariable:
|
||||||
|
name: bar
|
||||||
|
safe: false
|
||||||
|
location:
|
||||||
|
row: 46
|
||||||
|
column: 9
|
||||||
|
end_location:
|
||||||
|
row: 46
|
||||||
|
column: 12
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
|
|
@ -1181,18 +1181,35 @@ impl Violation for MutableArgumentDefault {
|
||||||
}
|
}
|
||||||
|
|
||||||
define_violation!(
|
define_violation!(
|
||||||
pub struct UnusedLoopControlVariable(pub String);
|
pub struct UnusedLoopControlVariable {
|
||||||
|
/// The name of the loop control variable.
|
||||||
|
pub name: String,
|
||||||
|
/// Whether the variable is safe to rename. A variable is unsafe to
|
||||||
|
/// rename if it _might_ be used by magic control flow (e.g.,
|
||||||
|
/// `locals()`).
|
||||||
|
pub safe: bool,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
impl AlwaysAutofixableViolation for UnusedLoopControlVariable {
|
impl Violation for UnusedLoopControlVariable {
|
||||||
#[derive_message_formats]
|
#[derive_message_formats]
|
||||||
fn message(&self) -> String {
|
fn message(&self) -> String {
|
||||||
let UnusedLoopControlVariable(name) = self;
|
let UnusedLoopControlVariable { name, safe } = self;
|
||||||
format!("Loop control variable `{name}` not used within the loop body")
|
if *safe {
|
||||||
|
format!("Loop control variable `{name}` not used within loop body")
|
||||||
|
} else {
|
||||||
|
format!("Loop control variable `{name}` may not be used within loop body")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn autofix_title(&self) -> String {
|
fn autofix_title_formatter(&self) -> Option<fn(&Self) -> String> {
|
||||||
let UnusedLoopControlVariable(name) = self;
|
let UnusedLoopControlVariable { safe, .. } = self;
|
||||||
format!("Rename unused `{name}` to `_{name}`")
|
if *safe {
|
||||||
|
Some(|UnusedLoopControlVariable { name, .. }| {
|
||||||
|
format!("Rename unused `{name}` to `_{name}`")
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue