From 97338e4cd6b41c96b19a4483c543533137779f33 Mon Sep 17 00:00:00 2001 From: Matthew Lloyd Date: Tue, 21 Feb 2023 22:23:47 -0500 Subject: [PATCH] [pylint] redefined-loop-name (W2901) (#3022) Slightly broadens W2901 to cover `with` statements too. Closes #2972. --- .../fixtures/pylint/redefined_loop_name.py | 155 ++++++++ crates/ruff/src/ast/helpers.rs | 6 +- crates/ruff/src/ast/types.rs | 2 +- crates/ruff/src/checkers/ast.rs | 10 +- crates/ruff/src/codes.rs | 1 + crates/ruff/src/registry.rs | 1 + crates/ruff/src/rules/pylint/mod.rs | 1 + crates/ruff/src/rules/pylint/rules/mod.rs | 2 + .../rules/pylint/rules/redefined_loop_name.rs | 302 ++++++++++++++++ ...tests__PLW2901_redefined_loop_name.py.snap | 330 ++++++++++++++++++ ruff.schema.json | 4 + 11 files changed, 809 insertions(+), 5 deletions(-) create mode 100644 crates/ruff/resources/test/fixtures/pylint/redefined_loop_name.py create mode 100644 crates/ruff/src/rules/pylint/rules/redefined_loop_name.rs create mode 100644 crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLW2901_redefined_loop_name.py.snap diff --git a/crates/ruff/resources/test/fixtures/pylint/redefined_loop_name.py b/crates/ruff/resources/test/fixtures/pylint/redefined_loop_name.py new file mode 100644 index 0000000000..13a230d504 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/pylint/redefined_loop_name.py @@ -0,0 +1,155 @@ +# For -> for, variable reused +for i in []: + for i in []: # error + pass + +# With -> for, variable reused +with None as i: + for i in []: # error + pass + +# For -> with, variable reused +for i in []: + with None as i: # error + pass + +# With -> with, variable reused +with None as i: + with None as i: # error + pass + +# For -> for, different variable +for i in []: + for j in []: # ok + pass + +# With -> with, different variable +with None as i: + with None as j: # ok + pass + +# For -> for -> for, doubly nested variable reuse +for i in []: + for j in []: + for i in []: # error + pass + +# For -> for -> for -> for, doubly nested variable reuse x2 +for i in []: + for j in []: + for i in []: # error + for j in []: # error + pass + +# For -> assignment +for i in []: + i = 5 # error + +# For -> augmented assignment +for i in []: + i += 5 # error + +# For -> annotated assignment +for i in []: + i: int = 5 # error + +# Async for -> for, variable reused +async for i in []: + for i in []: # error + pass + +# For -> async for, variable reused +for i in []: + async for i in []: # error + pass + +# For -> for, outer loop unpacks tuple +for i, j in enumerate([]): + for i in []: # error + pass + +# For -> for, inner loop unpacks tuple +for i in []: + for i, j in enumerate([]): # error + pass + +# For -> for, both loops unpack tuple +for (i, (j, k)) in []: + for i, j in enumerate([]): # two errors + pass + +# For else -> for, variable reused in else +for i in []: + pass +else: + for i in []: # no error + pass + +# For -> for, ignore dummy variables +for _ in []: + for _ in []: # no error + pass + +# For -> for, outer loop unpacks with asterisk +for i, *j in []: + for j in []: # error + pass + +# For -> function definition +for i in []: + def f(): + i = 2 # no error + +# For -> class definition +for i in []: + class A: + i = 2 # no error + +# For -> function definition -> for -> assignment +for i in []: + def f(): + for i in []: # no error + i = 2 # error + +# For -> class definition -> for -> for +for i in []: + class A: + for i in []: # no error + for i in []: # error + pass + +# For -> use in assignment target without actual assignment; subscript +for i in []: + a[i] = 2 # no error + i[a] = 2 # no error + +# For -> use in assignment target without actual assignment; attribute +for i in []: + a.i = 2 # no error + i.a = 2 # no error + +# For target with subscript -> assignment +for a[0] in []: + a[0] = 2 # error + a[1] = 2 # no error + +# For target with subscript -> assignment +for a['i'] in []: + a['i'] = 2 # error + a['j'] = 2 # no error + +# For target with attribute -> assignment +for a.i in []: + a.i = 2 # error + a.j = 2 # no error + +# For target with double nested attribute -> assignment +for a.i.j in []: + a.i.j = 2 # error + a.j.i = 2 # no error + +# For target with attribute -> assignment with different spacing +for a.i in []: + a. i = 2 # error +for a. i in []: + a.i = 2 # error diff --git a/crates/ruff/src/ast/helpers.rs b/crates/ruff/src/ast/helpers.rs index 05aa478360..5631451623 100644 --- a/crates/ruff/src/ast/helpers.rs +++ b/crates/ruff/src/ast/helpers.rs @@ -928,7 +928,10 @@ pub fn binding_range(binding: &Binding, locator: &Locator) -> Range { } // Return the ranges of `Name` tokens within a specified node. -pub fn find_names(located: &Located, locator: &Locator) -> Vec { +pub fn find_names<'a, T, U>( + located: &'a Located, + locator: &'a Locator, +) -> impl Iterator + 'a { let contents = locator.slice(&Range::from_located(located)); lexer::make_tokenizer_located(contents, located.location) .flatten() @@ -937,7 +940,6 @@ pub fn find_names(located: &Located, locator: &Locator) -> Vec { location: start, end_location: end, }) - .collect() } /// Return the `Range` of `name` in `Excepthandler`. diff --git a/crates/ruff/src/ast/types.rs b/crates/ruff/src/ast/types.rs index d4904bce62..c3e11be0f3 100644 --- a/crates/ruff/src/ast/types.rs +++ b/crates/ruff/src/ast/types.rs @@ -29,7 +29,7 @@ impl Range { } } - pub fn from_located(located: &Located) -> Self { + pub fn from_located(located: &Located) -> Self { Range::new(located.location, located.end_location.unwrap()) } diff --git a/crates/ruff/src/checkers/ast.rs b/crates/ruff/src/checkers/ast.rs index 0ad020c53b..50ef194572 100644 --- a/crates/ruff/src/checkers/ast.rs +++ b/crates/ruff/src/checkers/ast.rs @@ -342,7 +342,7 @@ where match &stmt.node { StmtKind::Global { names } => { let scope_index = *self.scope_stack.last().expect("No current scope found"); - let ranges = helpers::find_names(stmt, self.locator); + let ranges: Vec = helpers::find_names(stmt, self.locator).collect(); if scope_index != GLOBAL_SCOPE_INDEX { // Add the binding to the current scope. let context = self.execution_context(); @@ -372,7 +372,7 @@ where } StmtKind::Nonlocal { names } => { let scope_index = *self.scope_stack.last().expect("No current scope found"); - let ranges = helpers::find_names(stmt, self.locator); + let ranges: Vec = helpers::find_names(stmt, self.locator).collect(); if scope_index != GLOBAL_SCOPE_INDEX { let context = self.execution_context(); let scope = &mut self.scopes[scope_index]; @@ -1651,6 +1651,9 @@ where self.current_stmt_parent().map(Into::into), ); } + if self.settings.rules.enabled(&Rule::RedefinedLoopName) { + pylint::rules::redefined_loop_name(self, &Node::Stmt(stmt)); + } } StmtKind::While { body, orelse, .. } => { if self.settings.rules.enabled(&Rule::FunctionUsesLoopVariable) { @@ -1695,6 +1698,9 @@ where if self.settings.rules.enabled(&Rule::UselessElseOnLoop) { pylint::rules::useless_else_on_loop(self, stmt, body, orelse); } + if self.settings.rules.enabled(&Rule::RedefinedLoopName) { + pylint::rules::redefined_loop_name(self, &Node::Stmt(stmt)); + } if matches!(stmt.node, StmtKind::For { .. }) { if self.settings.rules.enabled(&Rule::ReimplementedBuiltin) { flake8_simplify::rules::convert_for_loop_to_any_all( diff --git a/crates/ruff/src/codes.rs b/crates/ruff/src/codes.rs index 2fe096ed22..e923bbafe7 100644 --- a/crates/ruff/src/codes.rs +++ b/crates/ruff/src/codes.rs @@ -146,6 +146,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option { (Pylint, "R0913") => Rule::TooManyArguments, (Pylint, "R0912") => Rule::TooManyBranches, (Pylint, "R0915") => Rule::TooManyStatements, + (Pylint, "W2901") => Rule::RedefinedLoopName, // flake8-builtins (Flake8Builtins, "001") => Rule::BuiltinVariableShadowing, diff --git a/crates/ruff/src/registry.rs b/crates/ruff/src/registry.rs index 419994f23b..b615e4d873 100644 --- a/crates/ruff/src/registry.rs +++ b/crates/ruff/src/registry.rs @@ -149,6 +149,7 @@ ruff_macros::register_rules!( rules::pylint::rules::TooManyArguments, rules::pylint::rules::TooManyBranches, rules::pylint::rules::TooManyStatements, + rules::pylint::rules::RedefinedLoopName, // flake8-builtins rules::flake8_builtins::rules::BuiltinVariableShadowing, rules::flake8_builtins::rules::BuiltinArgumentShadowing, diff --git a/crates/ruff/src/rules/pylint/mod.rs b/crates/ruff/src/rules/pylint/mod.rs index 72e673019d..eed3b0d67c 100644 --- a/crates/ruff/src/rules/pylint/mod.rs +++ b/crates/ruff/src/rules/pylint/mod.rs @@ -47,6 +47,7 @@ mod tests { #[test_case(Rule::BidirectionalUnicode, Path::new("bidirectional_unicode.py"); "PLE2502")] #[test_case(Rule::BadStrStripCall, Path::new("bad_str_strip_call.py"); "PLE01310")] #[test_case(Rule::YieldInInit, Path::new("yield_in_init.py"); "PLE0100")] + #[test_case(Rule::RedefinedLoopName, Path::new("redefined_loop_name.py"); "PLW2901")] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let diagnostics = test_path( diff --git a/crates/ruff/src/rules/pylint/rules/mod.rs b/crates/ruff/src/rules/pylint/rules/mod.rs index e16247fae2..d4db6fabeb 100644 --- a/crates/ruff/src/rules/pylint/rules/mod.rs +++ b/crates/ruff/src/rules/pylint/rules/mod.rs @@ -11,6 +11,7 @@ pub use magic_value_comparison::{magic_value_comparison, MagicValueComparison}; pub use merge_isinstance::{merge_isinstance, ConsiderMergingIsinstance}; pub use nonlocal_without_binding::NonlocalWithoutBinding; pub use property_with_parameters::{property_with_parameters, PropertyWithParameters}; +pub use redefined_loop_name::{redefined_loop_name, RedefinedLoopName}; pub use return_in_init::{return_in_init, ReturnInInit}; pub use too_many_arguments::{too_many_arguments, TooManyArguments}; pub use too_many_branches::{too_many_branches, TooManyBranches}; @@ -40,6 +41,7 @@ mod magic_value_comparison; mod merge_isinstance; mod nonlocal_without_binding; mod property_with_parameters; +mod redefined_loop_name; mod return_in_init; mod too_many_arguments; mod too_many_branches; diff --git a/crates/ruff/src/rules/pylint/rules/redefined_loop_name.rs b/crates/ruff/src/rules/pylint/rules/redefined_loop_name.rs new file mode 100644 index 0000000000..66d114586e --- /dev/null +++ b/crates/ruff/src/rules/pylint/rules/redefined_loop_name.rs @@ -0,0 +1,302 @@ +use crate::ast::comparable::ComparableExpr; +use crate::ast::helpers::unparse_expr; +use crate::ast::types::{Node, Range}; +use crate::ast::visitor; +use crate::ast::visitor::Visitor; +use crate::checkers::ast::Checker; +use crate::registry::Diagnostic; +use crate::settings::hashable::HashableRegex; +use crate::violation::Violation; +use ruff_macros::{define_violation, derive_message_formats}; +use rustpython_parser::ast::{Expr, ExprContext, ExprKind, Stmt, StmtKind, Withitem}; +use serde::{Deserialize, Serialize}; +use std::{fmt, iter}; + +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)] +pub enum BindingKind { + For, + With, + Assignment, +} + +impl fmt::Display for BindingKind { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + match self { + BindingKind::For => fmt.write_str("for loop"), + BindingKind::With => fmt.write_str("with statement"), + BindingKind::Assignment => fmt.write_str("assignment"), + } + } +} + +define_violation!( + /// ## What it does + /// Checks for variables defined in `for` loops and `with` statements that + /// get overwritten within the body, for example by another `for` loop or + /// `with` statement or by direct assignment. + /// + /// ## Why is this bad? + /// Redefinition of a loop variable inside the loop's body causes its value + /// to differ from the original loop iteration for the remainder of the + /// block, in a way that will likely cause bugs. + /// + /// In Python, unlike many other languages, `for` loops and `with` + /// statements don't define their own scopes. Therefore, a nested loop that + /// uses the same target variable name as an outer loop will reuse the same + /// actual variable, and the value from the last iteration will "leak out" + /// into the remainder of the enclosing loop. + /// + /// While this mistake is easy to spot in small examples, it can be hidden + /// in larger blocks of code where the definition and redefinition of the + /// variable may not be visible at the same time. + /// + /// ## Example + /// ```python + /// for i in range(10): + /// i = 9 + /// print(i) # prints 9 every iteration + /// + /// for i in range(10): + /// for i in range(10): # original value overwritten + /// pass + /// print(i) # also prints 9 every iteration + /// + /// with path1.open() as f: + /// with path2.open() as f: + /// f = path2.open() + /// print(f.readline()) # prints a line from path2 + /// ``` + pub struct RedefinedLoopName { + pub name: String, + pub outer_kind: BindingKind, + pub inner_kind: BindingKind, + } +); +impl Violation for RedefinedLoopName { + #[derive_message_formats] + fn message(&self) -> String { + let RedefinedLoopName { + name, + outer_kind, + inner_kind, + } = self; + format!("Outer {outer_kind} variable `{name}` overwritten by inner {inner_kind} target") + } +} + +struct ExprWithBindingKind<'a> { + expr: &'a Expr, + binding_kind: BindingKind, +} + +struct InnerForWithAssignTargetsVisitor<'a> { + dummy_variable_rgx: &'a HashableRegex, + assignment_targets: Vec>, +} + +impl<'a, 'b> Visitor<'b> for InnerForWithAssignTargetsVisitor<'a> +where + 'b: 'a, +{ + fn visit_stmt(&mut self, stmt: &'b Stmt) { + // Collect target expressions. + match &stmt.node { + // For and async for. + StmtKind::For { target, .. } | StmtKind::AsyncFor { target, .. } => { + self.assignment_targets.extend( + assignment_targets_from_expr(target, self.dummy_variable_rgx).map(|expr| { + ExprWithBindingKind { + expr, + binding_kind: BindingKind::For, + } + }), + ); + } + // With. + StmtKind::With { items, .. } => { + self.assignment_targets.extend( + assignment_targets_from_with_items(items, self.dummy_variable_rgx).map( + |expr| ExprWithBindingKind { + expr, + binding_kind: BindingKind::With, + }, + ), + ); + } + // Assignment, augmented assignment, and annotated assignment. + StmtKind::Assign { targets, .. } => { + self.assignment_targets.extend( + assignment_targets_from_assign_targets(targets, self.dummy_variable_rgx).map( + |expr| ExprWithBindingKind { + expr, + binding_kind: BindingKind::Assignment, + }, + ), + ); + } + StmtKind::AugAssign { target, .. } | StmtKind::AnnAssign { target, .. } => { + self.assignment_targets.extend( + assignment_targets_from_expr(target, self.dummy_variable_rgx).map(|expr| { + ExprWithBindingKind { + expr, + binding_kind: BindingKind::Assignment, + } + }), + ); + } + _ => {} + } + // Decide whether to recurse. + match &stmt.node { + // Don't recurse into blocks that create a new scope. + StmtKind::ClassDef { .. } => {} + StmtKind::FunctionDef { .. } => {} + // Otherwise, do recurse. + _ => { + visitor::walk_stmt(self, stmt); + } + } + } +} + +fn assignment_targets_from_expr<'a, U>( + expr: &'a Expr, + dummy_variable_rgx: &'a HashableRegex, +) -> Box> + 'a> { + // The Box is necessary to ensure the match arms have the same return type - we can't use + // a cast to "impl Iterator", since at the time of writing that is only allowed for + // return types and argument types. + match &expr.node { + ExprKind::Attribute { + ctx: ExprContext::Store, + .. + } => Box::new(iter::once(expr)), + ExprKind::Subscript { + ctx: ExprContext::Store, + .. + } => Box::new(iter::once(expr)), + ExprKind::Starred { + ctx: ExprContext::Store, + value, + .. + } => Box::new(iter::once(&**value)), + ExprKind::Name { + ctx: ExprContext::Store, + id, + .. + } => { + // Ignore dummy variables. + if dummy_variable_rgx.is_match(id) { + Box::new(iter::empty()) + } else { + Box::new(iter::once(expr)) + } + } + ExprKind::List { + ctx: ExprContext::Store, + elts, + .. + } => Box::new( + elts.iter() + .flat_map(|elt| assignment_targets_from_expr(elt, dummy_variable_rgx)), + ), + ExprKind::Tuple { + ctx: ExprContext::Store, + elts, + .. + } => Box::new( + elts.iter() + .flat_map(|elt| assignment_targets_from_expr(elt, dummy_variable_rgx)), + ), + _ => Box::new(iter::empty()), + } +} + +fn assignment_targets_from_with_items<'a, U>( + items: &'a [Withitem], + dummy_variable_rgx: &'a HashableRegex, +) -> impl Iterator> + 'a { + items + .iter() + .filter_map(|item| { + item.optional_vars + .as_ref() + .map(|expr| assignment_targets_from_expr(&**expr, dummy_variable_rgx)) + }) + .flatten() +} + +fn assignment_targets_from_assign_targets<'a, U>( + targets: &'a [Expr], + dummy_variable_rgx: &'a HashableRegex, +) -> impl Iterator> + 'a { + targets + .iter() + .flat_map(|target| assignment_targets_from_expr(target, dummy_variable_rgx)) +} + +/// PLW2901 +pub fn redefined_loop_name<'a, 'b>(checker: &'a mut Checker<'b>, node: &Node<'b>) { + let (outer_assignment_targets, inner_assignment_targets) = match node { + Node::Stmt(stmt) => match &stmt.node { + // With. + StmtKind::With { items, body, .. } => { + let outer_assignment_targets: Vec> = + assignment_targets_from_with_items(items, &checker.settings.dummy_variable_rgx) + .map(|expr| ExprWithBindingKind { + expr, + binding_kind: BindingKind::With, + }) + .collect(); + let mut visitor = InnerForWithAssignTargetsVisitor { + dummy_variable_rgx: &checker.settings.dummy_variable_rgx, + assignment_targets: vec![], + }; + for stmt in body { + visitor.visit_stmt(stmt); + } + (outer_assignment_targets, visitor.assignment_targets) + } + // For and async for. + StmtKind::For { target, body, .. } | StmtKind::AsyncFor { target, body, .. } => { + let outer_assignment_targets: Vec> = + assignment_targets_from_expr(target, &checker.settings.dummy_variable_rgx) + .map(|expr| ExprWithBindingKind { + expr, + binding_kind: BindingKind::For, + }) + .collect(); + let mut visitor = InnerForWithAssignTargetsVisitor { + dummy_variable_rgx: &checker.settings.dummy_variable_rgx, + assignment_targets: vec![], + }; + for stmt in body { + visitor.visit_stmt(stmt); + } + (outer_assignment_targets, visitor.assignment_targets) + } + _ => panic!( + "redefined_loop_name called on Statement that is not a With, For, or AsyncFor" + ), + }, + Node::Expr(_) => panic!("redefined_loop_name called on Node that is not a Statement"), + }; + + for outer_assignment_target in &outer_assignment_targets { + for inner_assignment_target in &inner_assignment_targets { + // Compare the targets structurally. + if ComparableExpr::from(outer_assignment_target.expr) + .eq(&(ComparableExpr::from(inner_assignment_target.expr))) + { + checker.diagnostics.push(Diagnostic::new( + RedefinedLoopName { + name: unparse_expr(outer_assignment_target.expr, checker.stylist), + outer_kind: outer_assignment_target.binding_kind, + inner_kind: inner_assignment_target.binding_kind, + }, + Range::from_located(inner_assignment_target.expr), + )); + } + } + } +} diff --git a/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLW2901_redefined_loop_name.py.snap b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLW2901_redefined_loop_name.py.snap new file mode 100644 index 0000000000..b3423f8c79 --- /dev/null +++ b/crates/ruff/src/rules/pylint/snapshots/ruff__rules__pylint__tests__PLW2901_redefined_loop_name.py.snap @@ -0,0 +1,330 @@ +--- +source: crates/ruff/src/rules/pylint/mod.rs +expression: diagnostics +--- +- kind: + RedefinedLoopName: + name: i + outer_kind: For + inner_kind: For + location: + row: 3 + column: 8 + end_location: + row: 3 + column: 9 + fix: ~ + parent: ~ +- kind: + RedefinedLoopName: + name: i + outer_kind: With + inner_kind: For + location: + row: 8 + column: 8 + end_location: + row: 8 + column: 9 + fix: ~ + parent: ~ +- kind: + RedefinedLoopName: + name: i + outer_kind: For + inner_kind: With + location: + row: 13 + column: 17 + end_location: + row: 13 + column: 18 + fix: ~ + parent: ~ +- kind: + RedefinedLoopName: + name: i + outer_kind: With + inner_kind: With + location: + row: 18 + column: 17 + end_location: + row: 18 + column: 18 + fix: ~ + parent: ~ +- kind: + RedefinedLoopName: + name: i + outer_kind: For + inner_kind: For + location: + row: 34 + column: 12 + end_location: + row: 34 + column: 13 + fix: ~ + parent: ~ +- kind: + RedefinedLoopName: + name: i + outer_kind: For + inner_kind: For + location: + row: 40 + column: 12 + end_location: + row: 40 + column: 13 + fix: ~ + parent: ~ +- kind: + RedefinedLoopName: + name: j + outer_kind: For + inner_kind: For + location: + row: 41 + column: 16 + end_location: + row: 41 + column: 17 + fix: ~ + parent: ~ +- kind: + RedefinedLoopName: + name: i + outer_kind: For + inner_kind: Assignment + location: + row: 46 + column: 4 + end_location: + row: 46 + column: 5 + fix: ~ + parent: ~ +- kind: + RedefinedLoopName: + name: i + outer_kind: For + inner_kind: Assignment + location: + row: 50 + column: 4 + end_location: + row: 50 + column: 5 + fix: ~ + parent: ~ +- kind: + RedefinedLoopName: + name: i + outer_kind: For + inner_kind: Assignment + location: + row: 54 + column: 4 + end_location: + row: 54 + column: 5 + fix: ~ + parent: ~ +- kind: + RedefinedLoopName: + name: i + outer_kind: For + inner_kind: For + location: + row: 58 + column: 8 + end_location: + row: 58 + column: 9 + fix: ~ + parent: ~ +- kind: + RedefinedLoopName: + name: i + outer_kind: For + inner_kind: For + location: + row: 63 + column: 14 + end_location: + row: 63 + column: 15 + fix: ~ + parent: ~ +- kind: + RedefinedLoopName: + name: i + outer_kind: For + inner_kind: For + location: + row: 68 + column: 8 + end_location: + row: 68 + column: 9 + fix: ~ + parent: ~ +- kind: + RedefinedLoopName: + name: i + outer_kind: For + inner_kind: For + location: + row: 73 + column: 8 + end_location: + row: 73 + column: 9 + fix: ~ + parent: ~ +- kind: + RedefinedLoopName: + name: i + outer_kind: For + inner_kind: For + location: + row: 78 + column: 8 + end_location: + row: 78 + column: 9 + fix: ~ + parent: ~ +- kind: + RedefinedLoopName: + name: j + outer_kind: For + inner_kind: For + location: + row: 78 + column: 11 + end_location: + row: 78 + column: 12 + fix: ~ + parent: ~ +- kind: + RedefinedLoopName: + name: j + outer_kind: For + inner_kind: For + location: + row: 95 + column: 8 + end_location: + row: 95 + column: 9 + fix: ~ + parent: ~ +- kind: + RedefinedLoopName: + name: i + outer_kind: For + inner_kind: Assignment + location: + row: 112 + column: 12 + end_location: + row: 112 + column: 13 + fix: ~ + parent: ~ +- kind: + RedefinedLoopName: + name: i + outer_kind: For + inner_kind: For + location: + row: 118 + column: 16 + end_location: + row: 118 + column: 17 + fix: ~ + parent: ~ +- kind: + RedefinedLoopName: + name: "a[0]" + outer_kind: For + inner_kind: Assignment + location: + row: 133 + column: 4 + end_location: + row: 133 + column: 8 + fix: ~ + parent: ~ +- kind: + RedefinedLoopName: + name: "a['i']" + outer_kind: For + inner_kind: Assignment + location: + row: 138 + column: 4 + end_location: + row: 138 + column: 10 + fix: ~ + parent: ~ +- kind: + RedefinedLoopName: + name: a.i + outer_kind: For + inner_kind: Assignment + location: + row: 143 + column: 4 + end_location: + row: 143 + column: 7 + fix: ~ + parent: ~ +- kind: + RedefinedLoopName: + name: a.i.j + outer_kind: For + inner_kind: Assignment + location: + row: 148 + column: 4 + end_location: + row: 148 + column: 9 + fix: ~ + parent: ~ +- kind: + RedefinedLoopName: + name: a.i + outer_kind: For + inner_kind: Assignment + location: + row: 153 + column: 4 + end_location: + row: 153 + column: 8 + fix: ~ + parent: ~ +- kind: + RedefinedLoopName: + name: a.i + outer_kind: For + inner_kind: Assignment + location: + row: 155 + column: 4 + end_location: + row: 155 + column: 7 + fix: ~ + parent: ~ + diff --git a/ruff.schema.json b/ruff.schema.json index 569f1c16f9..a4d07f6240 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -1837,6 +1837,10 @@ "PLW06", "PLW060", "PLW0602", + "PLW2", + "PLW29", + "PLW290", + "PLW2901", "PT", "PT0", "PT00",