diff --git a/crates/ruff/resources/test/fixtures/ruff/RUF007.py b/crates/ruff/resources/test/fixtures/ruff/RUF007.py index 45f506b72903f..5b1fb845522e0 100644 --- a/crates/ruff/resources/test/fixtures/ruff/RUF007.py +++ b/crates/ruff/resources/test/fixtures/ruff/RUF007.py @@ -1,5 +1,6 @@ input = [1, 2, 3] otherInput = [2, 3, 4] +foo = [1, 2, 3, 4] # OK zip(input, otherInput) # different inputs @@ -8,6 +9,10 @@ zip(input[:-1], input[2:]) # not successive list(zip(input, otherInput)) # nested call zip(input, input[1::2]) # not successive +zip(foo[:-1], foo[1:], foo, strict=False) # more than 2 inputs +zip(foo[:-1], foo[1:], foo, strict=True) # more than 2 inputs +zip(foo[:-1], foo[1:], strict=True) # use strict +zip(foo[:-1], foo[1:], strict=bool(foo)) # use strict # Errors zip(input, input[1:]) @@ -17,3 +22,4 @@ zip(input[1:-1], input[2:]) list(zip(input, input[1:])) list(zip(input[:-1], input[1:])) +zip(foo[:-1], foo[1:], strict=False) diff --git a/crates/ruff/src/checkers/ast/mod.rs b/crates/ruff/src/checkers/ast/mod.rs index 7e2bea1009398..69ae403192411 100644 --- a/crates/ruff/src/checkers/ast/mod.rs +++ b/crates/ruff/src/checkers/ast/mod.rs @@ -2864,7 +2864,7 @@ where if self.settings.rules.enabled(Rule::PairwiseOverZipped) { if self.settings.target_version >= PythonVersion::Py310 { - ruff::rules::pairwise_over_zipped(self, func, args); + ruff::rules::pairwise_over_zipped(self, func, args, keywords); } } diff --git a/crates/ruff/src/rules/ruff/rules/pairwise_over_zipped.rs b/crates/ruff/src/rules/ruff/rules/pairwise_over_zipped.rs index 651fdf693dd17..bd034b606c781 100644 --- a/crates/ruff/src/rules/ruff/rules/pairwise_over_zipped.rs +++ b/crates/ruff/src/rules/ruff/rules/pairwise_over_zipped.rs @@ -1,5 +1,5 @@ use num_traits::ToPrimitive; -use rustpython_parser::ast::{Constant, Expr, ExprKind, Unaryop}; +use rustpython_parser::ast::{Constant, Expr, ExprKind, Keyword, KeywordData, Unaryop}; use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; @@ -88,14 +88,53 @@ fn to_bound(expr: &Expr) -> Option { } /// RUF007 -pub fn pairwise_over_zipped(checker: &mut Checker, func: &Expr, args: &[Expr]) { +pub fn pairwise_over_zipped( + checker: &mut Checker, + func: &Expr, + args: &[Expr], + keywords: &[Keyword], +) { let ExprKind::Name { id, .. } = &func.node else { return; }; - if !(args.len() > 1 && id == "zip" && checker.ctx.is_builtin(id)) { + // Require exactly two positional arguments. + if args.len() != 2 { return; - }; + } + + // Allow `strict=False`, but no other keyword arguments. + if keywords.iter().any(|keyword| { + let KeywordData { + arg: Some(arg), + value, + } = &keyword.node else { + return true; + }; + if arg != "strict" { + return true; + } + if matches!( + value.node, + ExprKind::Constant { + value: Constant::Bool(false), + .. + } + ) { + return false; + } + true + }) { + return; + } + + // Require the function to be the builtin `zip`. + if id != "zip" { + return; + } + if !checker.ctx.is_builtin(id) { + return; + } // Allow the first argument to be a `Name` or `Subscript`. let Some(first_arg_info) = ({ diff --git a/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__ruff_pairwise_over_zipped.snap b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__ruff_pairwise_over_zipped.snap index 944d3bb391585..39972631afd32 100644 --- a/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__ruff_pairwise_over_zipped.snap +++ b/crates/ruff/src/rules/ruff/snapshots/ruff__rules__ruff__tests__ruff_pairwise_over_zipped.snap @@ -8,10 +8,10 @@ expression: diagnostics suggestion: ~ fixable: false location: - row: 13 + row: 18 column: 0 end_location: - row: 13 + row: 18 column: 3 fix: ~ parent: ~ @@ -21,10 +21,10 @@ expression: diagnostics suggestion: ~ fixable: false location: - row: 14 + row: 19 column: 0 end_location: - row: 14 + row: 19 column: 3 fix: ~ parent: ~ @@ -34,10 +34,10 @@ expression: diagnostics suggestion: ~ fixable: false location: - row: 15 + row: 20 column: 0 end_location: - row: 15 + row: 20 column: 3 fix: ~ parent: ~ @@ -47,10 +47,10 @@ expression: diagnostics suggestion: ~ fixable: false location: - row: 16 + row: 21 column: 0 end_location: - row: 16 + row: 21 column: 3 fix: ~ parent: ~ @@ -60,10 +60,10 @@ expression: diagnostics suggestion: ~ fixable: false location: - row: 17 + row: 22 column: 0 end_location: - row: 17 + row: 22 column: 3 fix: ~ parent: ~ @@ -73,10 +73,10 @@ expression: diagnostics suggestion: ~ fixable: false location: - row: 18 + row: 23 column: 5 end_location: - row: 18 + row: 23 column: 8 fix: ~ parent: ~ @@ -86,11 +86,24 @@ expression: diagnostics suggestion: ~ fixable: false location: - row: 19 + row: 24 column: 5 end_location: - row: 19 + row: 24 column: 8 fix: ~ parent: ~ +- kind: + name: PairwiseOverZipped + body: "Prefer `itertools.pairwise()` over `zip()` when iterating over successive pairs" + suggestion: ~ + fixable: false + location: + row: 25 + column: 0 + end_location: + row: 25 + column: 3 + fix: ~ + parent: ~