diff --git a/README.md b/README.md index 5cef6aaa2a..b2cd2e2826 100644 --- a/README.md +++ b/README.md @@ -677,6 +677,7 @@ For more, see [flake8-bugbear](https://pypi.org/project/flake8-bugbear/22.10.27/ | B026 | StarArgUnpackingAfterKeywordArg | Star-arg unpacking after a keyword argument is strongly discouraged | | | B027 | EmptyMethodWithoutAbstractDecorator | `...` is an empty method in an abstract base class, but has no abstract decorator | | | B904 | RaiseWithoutFromInsideExcept | Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling | | +| B905 | ZipWithoutExplicitStrict | `zip()` without an explicit `strict=` parameter | | ### flake8-builtins (A) diff --git a/resources/test/fixtures/flake8_bugbear/B905.py b/resources/test/fixtures/flake8_bugbear/B905.py new file mode 100644 index 0000000000..1a01b5ebac --- /dev/null +++ b/resources/test/fixtures/flake8_bugbear/B905.py @@ -0,0 +1,10 @@ +zip() +zip(range(3)) +zip("a", "b") +zip("a", "b", *zip("c")) +zip(zip("a"), strict=False) +zip(zip("a", strict=True)) + +zip(range(3), strict=True) +zip("a", "b", strict=False) +zip("a", "b", "c", strict=True) diff --git a/src/check_ast.rs b/src/check_ast.rs index 551c2cf1df..6c7d28b42e 100644 --- a/src/check_ast.rs +++ b/src/check_ast.rs @@ -1458,16 +1458,15 @@ where if self.settings.enabled.contains(&CheckCode::UP005) { pyupgrade::plugins::deprecated_unittest_alias(self, func); } + if self.settings.enabled.contains(&CheckCode::UP012) { + pyupgrade::plugins::unnecessary_encode_utf8(self, expr, func, args, keywords); + } // flake8-super if self.settings.enabled.contains(&CheckCode::UP008) { pyupgrade::plugins::super_call_with_parameters(self, expr, func, args); } - if self.settings.enabled.contains(&CheckCode::UP012) { - pyupgrade::plugins::unnecessary_encode_utf8(self, expr, func, args, keywords); - } - // flake8-print if self.settings.enabled.contains(&CheckCode::T201) || self.settings.enabled.contains(&CheckCode::T203) @@ -1475,6 +1474,7 @@ where flake8_print::plugins::print_call(self, expr, func); } + // flake8-bugbear if self.settings.enabled.contains(&CheckCode::B004) { flake8_bugbear::plugins::unreliable_callable_check(self, expr, func, args); } @@ -1502,6 +1502,15 @@ where self, args, keywords, ); } + if self.settings.enabled.contains(&CheckCode::B905) + && self.settings.target_version >= PythonVersion::Py310 + { + flake8_bugbear::plugins::zip_without_explicit_strict( + self, expr, func, keywords, + ); + } + + // flake8-bandit if self.settings.enabled.contains(&CheckCode::S102) { if let Some(check) = flake8_bandit::plugins::exec_used(expr, func) { self.add_check(check); diff --git a/src/checks.rs b/src/checks.rs index a8cb5f45fd..ea75aff676 100644 --- a/src/checks.rs +++ b/src/checks.rs @@ -135,6 +135,7 @@ pub enum CheckCode { B026, B027, B904, + B905, // flake8-blind-except BLE001, // flake8-comprehensions @@ -659,6 +660,7 @@ pub enum CheckKind { UselessComparison, UselessContextlibSuppress, UselessExpression, + ZipWithoutExplicitStrict, // flake8-comprehensions UnnecessaryGeneratorList, UnnecessaryGeneratorSet, @@ -967,6 +969,7 @@ impl CheckCode { CheckCode::B026 => CheckKind::StarArgUnpackingAfterKeywordArg, CheckCode::B027 => CheckKind::EmptyMethodWithoutAbstractDecorator("...".to_string()), CheckCode::B904 => CheckKind::RaiseWithoutFromInsideExcept, + CheckCode::B905 => CheckKind::ZipWithoutExplicitStrict, // flake8-comprehensions CheckCode::C400 => CheckKind::UnnecessaryGeneratorList, CheckCode::C401 => CheckKind::UnnecessaryGeneratorSet, @@ -1212,6 +1215,7 @@ impl CheckCode { CheckCode::B026 => CheckCategory::Flake8Bugbear, CheckCode::B027 => CheckCategory::Flake8Bugbear, CheckCode::B904 => CheckCategory::Flake8Bugbear, + CheckCode::B905 => CheckCategory::Flake8Bugbear, CheckCode::BLE001 => CheckCategory::Flake8BlindExcept, CheckCode::C400 => CheckCategory::Flake8Comprehensions, CheckCode::C401 => CheckCategory::Flake8Comprehensions, @@ -1513,6 +1517,7 @@ impl CheckKind { CheckKind::MutableArgumentDefault => &CheckCode::B006, CheckKind::NoAssertRaisesException => &CheckCode::B017, CheckKind::RaiseWithoutFromInsideExcept => &CheckCode::B904, + CheckKind::ZipWithoutExplicitStrict => &CheckCode::B905, CheckKind::RedundantTupleInExceptionHandler(_) => &CheckCode::B013, CheckKind::SetAttrWithConstant => &CheckCode::B010, CheckKind::StarArgUnpackingAfterKeywordArg => &CheckCode::B026, @@ -2026,6 +2031,9 @@ impl CheckKind { from None to distinguish them from errors in exception handling" .to_string() } + CheckKind::ZipWithoutExplicitStrict => { + "`zip()` without an explicit `strict=` parameter".to_string() + } // flake8-comprehensions CheckKind::UnnecessaryGeneratorList => { "Unnecessary generator (rewrite as a `list` comprehension)".to_string() diff --git a/src/checks_gen.rs b/src/checks_gen.rs index 0bddf295c3..0f5b96e35c 100644 --- a/src/checks_gen.rs +++ b/src/checks_gen.rs @@ -70,6 +70,7 @@ pub enum CheckCodePrefix { B9, B90, B904, + B905, BLE, BLE0, BLE00, @@ -519,6 +520,7 @@ impl CheckCodePrefix { CheckCode::B026, CheckCode::B027, CheckCode::B904, + CheckCode::B905, ], CheckCodePrefix::B0 => vec![ CheckCode::B002, @@ -606,9 +608,10 @@ impl CheckCodePrefix { CheckCodePrefix::B025 => vec![CheckCode::B025], CheckCodePrefix::B026 => vec![CheckCode::B026], CheckCodePrefix::B027 => vec![CheckCode::B027], - CheckCodePrefix::B9 => vec![CheckCode::B904], - CheckCodePrefix::B90 => vec![CheckCode::B904], + CheckCodePrefix::B9 => vec![CheckCode::B904, CheckCode::B905], + CheckCodePrefix::B90 => vec![CheckCode::B904, CheckCode::B905], CheckCodePrefix::B904 => vec![CheckCode::B904], + CheckCodePrefix::B905 => vec![CheckCode::B905], CheckCodePrefix::BLE => vec![CheckCode::BLE001], CheckCodePrefix::BLE0 => vec![CheckCode::BLE001], CheckCodePrefix::BLE00 => vec![CheckCode::BLE001], @@ -1729,6 +1732,7 @@ impl CheckCodePrefix { CheckCodePrefix::B9 => SuffixLength::One, CheckCodePrefix::B90 => SuffixLength::Two, CheckCodePrefix::B904 => SuffixLength::Three, + CheckCodePrefix::B905 => SuffixLength::Three, CheckCodePrefix::BLE => SuffixLength::Zero, CheckCodePrefix::BLE0 => SuffixLength::One, CheckCodePrefix::BLE00 => SuffixLength::Two, diff --git a/src/flake8_bugbear/mod.rs b/src/flake8_bugbear/mod.rs index 8504110d32..ff68626046 100644 --- a/src/flake8_bugbear/mod.rs +++ b/src/flake8_bugbear/mod.rs @@ -39,6 +39,7 @@ mod tests { #[test_case(CheckCode::B026, Path::new("B026.py"); "B026")] #[test_case(CheckCode::B027, Path::new("B027.py"); "B027")] #[test_case(CheckCode::B904, Path::new("B904.py"); "B904")] + #[test_case(CheckCode::B905, Path::new("B905.py"); "B905")] fn checks(check_code: CheckCode, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy()); let mut checks = test_path( diff --git a/src/flake8_bugbear/plugins/mod.rs b/src/flake8_bugbear/plugins/mod.rs index 5ca3142e5a..49459143f5 100644 --- a/src/flake8_bugbear/plugins/mod.rs +++ b/src/flake8_bugbear/plugins/mod.rs @@ -23,6 +23,7 @@ pub use unused_loop_control_variable::unused_loop_control_variable; pub use useless_comparison::useless_comparison; pub use useless_contextlib_suppress::useless_contextlib_suppress; pub use useless_expression::useless_expression; +pub use zip_without_explicit_strict::zip_without_explicit_strict; mod abstract_base_class; mod assert_false; @@ -49,3 +50,4 @@ mod unused_loop_control_variable; mod useless_comparison; mod useless_contextlib_suppress; mod useless_expression; +mod zip_without_explicit_strict; diff --git a/src/flake8_bugbear/plugins/zip_without_explicit_strict.rs b/src/flake8_bugbear/plugins/zip_without_explicit_strict.rs new file mode 100644 index 0000000000..00bd683612 --- /dev/null +++ b/src/flake8_bugbear/plugins/zip_without_explicit_strict.rs @@ -0,0 +1,31 @@ +use rustpython_ast::{Expr, ExprKind, Keyword}; + +use crate::ast::types::Range; +use crate::check_ast::Checker; +use crate::checks::{Check, CheckKind}; + +/// B905 +pub fn zip_without_explicit_strict( + checker: &mut Checker, + expr: &Expr, + func: &Expr, + kwargs: &[Keyword], +) { + if let ExprKind::Name { id, .. } = &func.node { + if id == "zip" + && checker.is_builtin("zip") + && !kwargs.iter().any(|keyword| { + keyword + .node + .arg + .as_ref() + .map_or(false, |name| name == "strict") + }) + { + checker.add_check(Check::new( + CheckKind::ZipWithoutExplicitStrict, + Range::from_located(expr), + )); + } + } +} diff --git a/src/flake8_bugbear/snapshots/ruff__flake8_bugbear__tests__B905_B905.py.snap b/src/flake8_bugbear/snapshots/ruff__flake8_bugbear__tests__B905_B905.py.snap new file mode 100644 index 0000000000..9f37d1aadf --- /dev/null +++ b/src/flake8_bugbear/snapshots/ruff__flake8_bugbear__tests__B905_B905.py.snap @@ -0,0 +1,61 @@ +--- +source: src/flake8_bugbear/mod.rs +expression: checks +--- +- kind: ZipWithoutExplicitStrict + location: + row: 1 + column: 0 + end_location: + row: 1 + column: 5 + fix: ~ +- kind: ZipWithoutExplicitStrict + location: + row: 2 + column: 0 + end_location: + row: 2 + column: 13 + fix: ~ +- kind: ZipWithoutExplicitStrict + location: + row: 3 + column: 0 + end_location: + row: 3 + column: 13 + fix: ~ +- kind: ZipWithoutExplicitStrict + location: + row: 4 + column: 0 + end_location: + row: 4 + column: 24 + fix: ~ +- kind: ZipWithoutExplicitStrict + location: + row: 4 + column: 15 + end_location: + row: 4 + column: 23 + fix: ~ +- kind: ZipWithoutExplicitStrict + location: + row: 5 + column: 4 + end_location: + row: 5 + column: 12 + fix: ~ +- kind: ZipWithoutExplicitStrict + location: + row: 6 + column: 0 + end_location: + row: 6 + column: 26 + fix: ~ +