mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-01 22:31:47 +00:00
Allow unused assignments in for loops and unpacking (#163)
This commit is contained in:
parent
97388cefda
commit
43e1f20b28
6 changed files with 86 additions and 19 deletions
|
@ -124,7 +124,7 @@ ruff's goal is to achieve feature-parity with Flake8 when used (1) without any p
|
||||||
stylistic checks; limiting to Python 3 obviates the need for certain compatibility checks.)
|
stylistic checks; limiting to Python 3 obviates the need for certain compatibility checks.)
|
||||||
|
|
||||||
Under those conditions, Flake8 implements about 58 rules, give or take. At time of writing, ruff
|
Under those conditions, Flake8 implements about 58 rules, give or take. At time of writing, ruff
|
||||||
implements 31 rules. (Note that these 31 rules likely cover a disproportionate share of errors:
|
implements 33 rules. (Note that these 33 rules likely cover a disproportionate share of errors:
|
||||||
unused imports, undefined variables, etc.)
|
unused imports, undefined variables, etc.)
|
||||||
|
|
||||||
Of the unimplemented rules, ruff is missing:
|
Of the unimplemented rules, ruff is missing:
|
||||||
|
|
8
resources/test/fixtures/F841.py
vendored
8
resources/test/fixtures/F841.py
vendored
|
@ -14,3 +14,11 @@ def f():
|
||||||
x = 1
|
x = 1
|
||||||
y = 2
|
y = 2
|
||||||
z = x + y
|
z = x + y
|
||||||
|
|
||||||
|
|
||||||
|
def g():
|
||||||
|
foo = (1, 2)
|
||||||
|
(a, b) = (1, 2)
|
||||||
|
|
||||||
|
bar = (1, 2)
|
||||||
|
(c, d) = bar
|
||||||
|
|
|
@ -100,6 +100,24 @@ pub fn in_nested_block(parent_stack: &[usize], parents: &[&Stmt]) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if a node represents an unpacking assignment.
|
||||||
|
pub fn is_unpacking_assignment(stmt: &Stmt) -> bool {
|
||||||
|
if let StmtKind::Assign { targets, value, .. } = &stmt.node {
|
||||||
|
for child in targets {
|
||||||
|
match &child.node {
|
||||||
|
ExprKind::Set { .. } | ExprKind::List { .. } | ExprKind::Tuple { .. } => {}
|
||||||
|
_ => return false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match &value.node {
|
||||||
|
ExprKind::Set { .. } | ExprKind::List { .. } | ExprKind::Tuple { .. } => return false,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
/// Struct used to efficiently slice source code at (row, column) Locations.
|
/// Struct used to efficiently slice source code at (row, column) Locations.
|
||||||
pub struct SourceCodeLocator<'a> {
|
pub struct SourceCodeLocator<'a> {
|
||||||
content: &'a str,
|
content: &'a str,
|
||||||
|
|
|
@ -35,8 +35,10 @@ impl Scope {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum BindingKind {
|
pub enum BindingKind {
|
||||||
|
Annotation,
|
||||||
Argument,
|
Argument,
|
||||||
Assignment,
|
Assignment,
|
||||||
|
Binding,
|
||||||
Builtin,
|
Builtin,
|
||||||
ClassDefinition,
|
ClassDefinition,
|
||||||
Definition,
|
Definition,
|
||||||
|
|
|
@ -553,7 +553,7 @@ where
|
||||||
self.in_literal = true;
|
self.in_literal = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ExprKind::Tuple { elts, ctx } => {
|
ExprKind::Tuple { elts, ctx } | ExprKind::List { elts, ctx } => {
|
||||||
if matches!(ctx, ExprContext::Store) {
|
if matches!(ctx, ExprContext::Store) {
|
||||||
let check_too_many_expressions =
|
let check_too_many_expressions =
|
||||||
self.settings.select.contains(&CheckCode::F621);
|
self.settings.select.contains(&CheckCode::F621);
|
||||||
|
@ -581,7 +581,7 @@ where
|
||||||
}
|
}
|
||||||
let parent =
|
let parent =
|
||||||
self.parents[*(self.parent_stack.last().expect("No parent found."))];
|
self.parents[*(self.parent_stack.last().expect("No parent found."))];
|
||||||
self.handle_node_store(expr, Some(parent));
|
self.handle_node_store(expr, parent);
|
||||||
}
|
}
|
||||||
ExprContext::Del => self.handle_node_delete(expr),
|
ExprContext::Del => self.handle_node_delete(expr),
|
||||||
},
|
},
|
||||||
|
@ -835,7 +835,7 @@ where
|
||||||
ctx: ExprContext::Store,
|
ctx: ExprContext::Store,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Some(parent),
|
parent,
|
||||||
);
|
);
|
||||||
self.parents.push(parent);
|
self.parents.push(parent);
|
||||||
}
|
}
|
||||||
|
@ -853,7 +853,7 @@ where
|
||||||
ctx: ExprContext::Store,
|
ctx: ExprContext::Store,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Some(parent),
|
parent,
|
||||||
);
|
);
|
||||||
self.parents.push(parent);
|
self.parents.push(parent);
|
||||||
|
|
||||||
|
@ -1023,7 +1023,7 @@ impl<'a> Checker<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_node_store(&mut self, expr: &Expr, parent: Option<&Stmt>) {
|
fn handle_node_store(&mut self, expr: &Expr, parent: &Stmt) {
|
||||||
if let ExprKind::Name { id, .. } = &expr.node {
|
if let ExprKind::Name { id, .. } = &expr.node {
|
||||||
let current =
|
let current =
|
||||||
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
|
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
|
||||||
|
@ -1053,35 +1053,59 @@ impl<'a> Checker<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(charlie): Handle alternate binding types (like `Annotation`).
|
if matches!(parent.node, StmtKind::AnnAssign { value: None, .. }) {
|
||||||
if id == "__all__"
|
self.add_binding(
|
||||||
&& matches!(current.kind, ScopeKind::Module)
|
id.to_string(),
|
||||||
&& parent
|
Binding {
|
||||||
.map(|stmt| {
|
kind: BindingKind::Annotation,
|
||||||
matches!(stmt.node, StmtKind::Assign { .. })
|
used: None,
|
||||||
|| matches!(stmt.node, StmtKind::AugAssign { .. })
|
location: expr.location,
|
||||||
|| matches!(stmt.node, StmtKind::AnnAssign { .. })
|
},
|
||||||
})
|
);
|
||||||
.unwrap_or_default()
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(charlie): Include comprehensions here.
|
||||||
|
if matches!(parent.node, StmtKind::For { .. })
|
||||||
|
|| matches!(parent.node, StmtKind::AsyncFor { .. })
|
||||||
|
|| operations::is_unpacking_assignment(parent)
|
||||||
{
|
{
|
||||||
self.add_binding(
|
self.add_binding(
|
||||||
id.to_string(),
|
id.to_string(),
|
||||||
Binding {
|
Binding {
|
||||||
kind: BindingKind::Export(extract_all_names(parent.unwrap(), current)),
|
kind: BindingKind::Binding,
|
||||||
used: None,
|
used: None,
|
||||||
location: expr.location,
|
location: expr.location,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if id == "__all__"
|
||||||
|
&& matches!(current.kind, ScopeKind::Module)
|
||||||
|
&& (matches!(parent.node, StmtKind::Assign { .. })
|
||||||
|
|| matches!(parent.node, StmtKind::AugAssign { .. })
|
||||||
|
|| matches!(parent.node, StmtKind::AnnAssign { .. }))
|
||||||
|
{
|
||||||
self.add_binding(
|
self.add_binding(
|
||||||
id.to_string(),
|
id.to_string(),
|
||||||
Binding {
|
Binding {
|
||||||
kind: BindingKind::Assignment,
|
kind: BindingKind::Export(extract_all_names(parent, current)),
|
||||||
used: None,
|
used: None,
|
||||||
location: expr.location,
|
location: expr.location,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.add_binding(
|
||||||
|
id.to_string(),
|
||||||
|
Binding {
|
||||||
|
kind: BindingKind::Assignment,
|
||||||
|
used: None,
|
||||||
|
location: expr.location,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1084,6 +1084,21 @@ mod tests {
|
||||||
location: Location::new(16, 5),
|
location: Location::new(16, 5),
|
||||||
fix: None,
|
fix: None,
|
||||||
},
|
},
|
||||||
|
Check {
|
||||||
|
kind: CheckKind::UnusedVariable("foo".to_string()),
|
||||||
|
location: Location::new(20, 5),
|
||||||
|
fix: None,
|
||||||
|
},
|
||||||
|
Check {
|
||||||
|
kind: CheckKind::UnusedVariable("a".to_string()),
|
||||||
|
location: Location::new(21, 6),
|
||||||
|
fix: None,
|
||||||
|
},
|
||||||
|
Check {
|
||||||
|
kind: CheckKind::UnusedVariable("b".to_string()),
|
||||||
|
location: Location::new(21, 9),
|
||||||
|
fix: None,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
assert_eq!(actual.len(), expected.len());
|
assert_eq!(actual.len(), expected.len());
|
||||||
for i in 0..actual.len() {
|
for i in 0..actual.len() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue