diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/zip_without_explicit_strict.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/zip_without_explicit_strict.rs index b92168b3487b0..61b3fe246da45 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/zip_without_explicit_strict.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/zip_without_explicit_strict.rs @@ -1,4 +1,4 @@ -use ruff_diagnostics::{Diagnostic, Violation}; +use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Fix}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, Arguments, Expr}; @@ -6,6 +6,7 @@ use ruff_python_semantic::SemanticModel; use ruff_text_size::Ranged; use crate::checkers::ast::Checker; +use crate::fix::edits::add_argument; /// ## What it does /// Checks for `zip` calls without an explicit `strict` parameter. @@ -28,16 +29,25 @@ use crate::checkers::ast::Checker; /// zip(a, b, strict=True) /// ``` /// +/// ## Fix safety +/// This rule's fix is marked as unsafe for `zip` calls that contain +/// `**kwargs`, as adding a `check` keyword argument to such a call may lead +/// to a duplicate keyword argument error. +/// /// ## References /// - [Python documentation: `zip`](https://docs.python.org/3/library/functions.html#zip) #[violation] pub struct ZipWithoutExplicitStrict; -impl Violation for ZipWithoutExplicitStrict { +impl AlwaysFixableViolation for ZipWithoutExplicitStrict { #[derive_message_formats] fn message(&self) -> String { format!("`zip()` without an explicit `strict=` parameter") } + + fn fix_title(&self) -> String { + "Add explicit `strict=False`".to_string() + } } /// B905 @@ -52,9 +62,27 @@ pub(crate) fn zip_without_explicit_strict(checker: &mut Checker, call: &ast::Exp .iter() .any(|arg| is_infinite_iterator(arg, checker.semantic())) { - checker - .diagnostics - .push(Diagnostic::new(ZipWithoutExplicitStrict, call.range())); + let mut diagnostic = Diagnostic::new(ZipWithoutExplicitStrict, call.range()); + diagnostic.set_fix(Fix::applicable_edit( + add_argument( + "strict=False", + &call.arguments, + checker.indexer().comment_ranges(), + checker.locator().contents(), + ), + // If the function call contains `**kwargs`, mark the fix as unsafe. + if call + .arguments + .keywords + .iter() + .any(|keyword| keyword.arg.is_none()) + { + Applicability::Unsafe + } else { + Applicability::Safe + }, + )); + checker.diagnostics.push(diagnostic); } } } diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B905.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B905.py.snap index f4b41f223980f..6e51121edc9f4 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B905.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B905.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs --- -B905.py:4:1: B905 `zip()` without an explicit `strict=` parameter +B905.py:4:1: B905 [*] `zip()` without an explicit `strict=` parameter | 3 | # Errors 4 | zip() @@ -9,8 +9,19 @@ B905.py:4:1: B905 `zip()` without an explicit `strict=` parameter 5 | zip(range(3)) 6 | zip("a", "b") | + = help: Add explicit `strict=False` -B905.py:5:1: B905 `zip()` without an explicit `strict=` parameter +ℹ Safe fix +1 1 | from itertools import count, cycle, repeat +2 2 | +3 3 | # Errors +4 |-zip() + 4 |+zip(strict=False) +5 5 | zip(range(3)) +6 6 | zip("a", "b") +7 7 | zip("a", "b", *zip("c")) + +B905.py:5:1: B905 [*] `zip()` without an explicit `strict=` parameter | 3 | # Errors 4 | zip() @@ -19,8 +30,19 @@ B905.py:5:1: B905 `zip()` without an explicit `strict=` parameter 6 | zip("a", "b") 7 | zip("a", "b", *zip("c")) | + = help: Add explicit `strict=False` + +ℹ Safe fix +2 2 | +3 3 | # Errors +4 4 | zip() +5 |-zip(range(3)) + 5 |+zip(range(3), strict=False) +6 6 | zip("a", "b") +7 7 | zip("a", "b", *zip("c")) +8 8 | zip(zip("a"), strict=False) -B905.py:6:1: B905 `zip()` without an explicit `strict=` parameter +B905.py:6:1: B905 [*] `zip()` without an explicit `strict=` parameter | 4 | zip() 5 | zip(range(3)) @@ -29,8 +51,19 @@ B905.py:6:1: B905 `zip()` without an explicit `strict=` parameter 7 | zip("a", "b", *zip("c")) 8 | zip(zip("a"), strict=False) | + = help: Add explicit `strict=False` + +ℹ Safe fix +3 3 | # Errors +4 4 | zip() +5 5 | zip(range(3)) +6 |-zip("a", "b") + 6 |+zip("a", "b", strict=False) +7 7 | zip("a", "b", *zip("c")) +8 8 | zip(zip("a"), strict=False) +9 9 | zip(zip("a", strict=True)) -B905.py:7:1: B905 `zip()` without an explicit `strict=` parameter +B905.py:7:1: B905 [*] `zip()` without an explicit `strict=` parameter | 5 | zip(range(3)) 6 | zip("a", "b") @@ -39,8 +72,19 @@ B905.py:7:1: B905 `zip()` without an explicit `strict=` parameter 8 | zip(zip("a"), strict=False) 9 | zip(zip("a", strict=True)) | + = help: Add explicit `strict=False` -B905.py:7:16: B905 `zip()` without an explicit `strict=` parameter +ℹ Safe fix +4 4 | zip() +5 5 | zip(range(3)) +6 6 | zip("a", "b") +7 |-zip("a", "b", *zip("c")) + 7 |+zip("a", "b", *zip("c"), strict=False) +8 8 | zip(zip("a"), strict=False) +9 9 | zip(zip("a", strict=True)) +10 10 | + +B905.py:7:16: B905 [*] `zip()` without an explicit `strict=` parameter | 5 | zip(range(3)) 6 | zip("a", "b") @@ -49,8 +93,19 @@ B905.py:7:16: B905 `zip()` without an explicit `strict=` parameter 8 | zip(zip("a"), strict=False) 9 | zip(zip("a", strict=True)) | + = help: Add explicit `strict=False` + +ℹ Safe fix +4 4 | zip() +5 5 | zip(range(3)) +6 6 | zip("a", "b") +7 |-zip("a", "b", *zip("c")) + 7 |+zip("a", "b", *zip("c", strict=False)) +8 8 | zip(zip("a"), strict=False) +9 9 | zip(zip("a", strict=True)) +10 10 | -B905.py:8:5: B905 `zip()` without an explicit `strict=` parameter +B905.py:8:5: B905 [*] `zip()` without an explicit `strict=` parameter | 6 | zip("a", "b") 7 | zip("a", "b", *zip("c")) @@ -58,8 +113,19 @@ B905.py:8:5: B905 `zip()` without an explicit `strict=` parameter | ^^^^^^^^ B905 9 | zip(zip("a", strict=True)) | + = help: Add explicit `strict=False` -B905.py:9:1: B905 `zip()` without an explicit `strict=` parameter +ℹ Safe fix +5 5 | zip(range(3)) +6 6 | zip("a", "b") +7 7 | zip("a", "b", *zip("c")) +8 |-zip(zip("a"), strict=False) + 8 |+zip(zip("a", strict=False), strict=False) +9 9 | zip(zip("a", strict=True)) +10 10 | +11 11 | # OK + +B905.py:9:1: B905 [*] `zip()` without an explicit `strict=` parameter | 7 | zip("a", "b", *zip("c")) 8 | zip(zip("a"), strict=False) @@ -68,21 +134,49 @@ B905.py:9:1: B905 `zip()` without an explicit `strict=` parameter 10 | 11 | # OK | + = help: Add explicit `strict=False` + +ℹ Safe fix +6 6 | zip("a", "b") +7 7 | zip("a", "b", *zip("c")) +8 8 | zip(zip("a"), strict=False) +9 |-zip(zip("a", strict=True)) + 9 |+zip(zip("a", strict=True), strict=False) +10 10 | +11 11 | # OK +12 12 | zip(range(3), strict=True) -B905.py:24:1: B905 `zip()` without an explicit `strict=` parameter +B905.py:24:1: B905 [*] `zip()` without an explicit `strict=` parameter | 23 | # Errors (limited iterators). 24 | zip([1, 2, 3], repeat(1, 1)) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B905 25 | zip([1, 2, 3], repeat(1, times=4)) | + = help: Add explicit `strict=False` -B905.py:25:1: B905 `zip()` without an explicit `strict=` parameter +ℹ Safe fix +21 21 | zip([1, 2, 3], repeat(1, times=None)) +22 22 | +23 23 | # Errors (limited iterators). +24 |-zip([1, 2, 3], repeat(1, 1)) + 24 |+zip([1, 2, 3], repeat(1, 1), strict=False) +25 25 | zip([1, 2, 3], repeat(1, times=4)) + +B905.py:25:1: B905 [*] `zip()` without an explicit `strict=` parameter | 23 | # Errors (limited iterators). 24 | zip([1, 2, 3], repeat(1, 1)) 25 | zip([1, 2, 3], repeat(1, times=4)) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ B905 | + = help: Add explicit `strict=False` + +ℹ Safe fix +22 22 | +23 23 | # Errors (limited iterators). +24 24 | zip([1, 2, 3], repeat(1, 1)) +25 |-zip([1, 2, 3], repeat(1, times=4)) + 25 |+zip([1, 2, 3], repeat(1, times=4), strict=False)