From e4de78e28f30010ceeaf60a711069de6f90c8581 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Tue, 6 Dec 2022 13:42:15 +0100
Subject: [PATCH] add b905: require strict= argument to zip()
---
README.rst | 5 ++++-
bugbear.py | 13 ++++++++++++-
tests/b905_py310.py | 10 ++++++++++
tests/test_bugbear.py | 19 ++++++++++++++++++-
4 files changed, 44 insertions(+), 3 deletions(-)
create mode 100644 tests/b905_py310.py
diff --git a/README.rst b/README.rst
index 15ced9a..8c54715 100644
--- a/README.rst
+++ b/README.rst
@@ -200,6 +200,8 @@ or ``raise ... from None`` to distinguish them from errors in exception handling
See `the exception chaining tutorial `_
for details.
+**B905**: `zip()` without an explicit `strict=` parameter. Added with python3.10, so don't enable this flag for code that should work on previous versions. https://peps.python.org/pep-0618/
+
**B950**: Line too long. This is a pragmatic equivalent of
``pycodestyle``'s ``E501``: it considers "max-line-length" but only triggers
when the value has been exceeded by **more than 10%**. You will no
@@ -299,7 +301,8 @@ Change Log
Future
~~~~~~~~~
-* B027: ignore @overload when typing is import with other names
+* B027: ignore @overload when typing is imported with other names
+* Add B905: `zip()` without an explicit `strict=` parameter.
22.10.27
~~~~~~~~~
diff --git a/bugbear.py b/bugbear.py
index db4e0c0..3344204 100644
--- a/bugbear.py
+++ b/bugbear.py
@@ -356,6 +356,7 @@ def visit_Call(self, node):
self.check_for_b026(node)
+ self.check_for_b905(node)
self.generic_visit(node)
def visit_Assign(self, node):
@@ -960,6 +961,14 @@ def check_for_b025(self, node):
for duplicate in duplicates:
self.errors.append(B025(node.lineno, node.col_offset, vars=(duplicate,)))
+ def check_for_b905(self, node):
+ if (
+ isinstance(node.func, ast.Name)
+ and node.func.id == "zip"
+ and not any(kw.arg == "strict" for kw in node.keywords)
+ ):
+ self.errors.append(B905(node.lineno, node.col_offset))
+
def compose_call_path(node):
if isinstance(node, ast.Attribute):
@@ -1360,6 +1369,8 @@ def visit_Lambda(self, node):
)
)
+B905 = Error(message="B905 `zip()` without an explicit `strict=` parameter.")
+
B950 = Error(message="B950 line too long ({} > {} characters)")
-disabled_by_default = ["B901", "B902", "B903", "B904", "B950"]
+disabled_by_default = ["B901", "B902", "B903", "B904", "B905", "B950"]
diff --git a/tests/b905_py310.py b/tests/b905_py310.py
new file mode 100644
index 0000000..1a01b5e
--- /dev/null
+++ b/tests/b905_py310.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/tests/test_bugbear.py b/tests/test_bugbear.py
index 7773221..bfd536c 100644
--- a/tests/test_bugbear.py
+++ b/tests/test_bugbear.py
@@ -42,6 +42,7 @@
B902,
B903,
B904,
+ B905,
B950,
BugBearChecker,
BugBearVisitor,
@@ -297,7 +298,7 @@ def test_b019(self):
def test_b020(self):
filename = Path(__file__).absolute().parent / "b020.py"
bbc = BugBearChecker(filename=str(filename))
- errors = list(bbc.run())
+ errors = list(e for e in bbc.run() if e[2][:4] == "B020")
self.assertEqual(
errors,
self.errors(
@@ -484,6 +485,22 @@ def test_b904(self):
]
self.assertEqual(errors, self.errors(*expected))
+ @unittest.skipIf(sys.version_info < (3, 10), "requires 3.10+")
+ def test_b905(self):
+ filename = Path(__file__).absolute().parent / "b905_py310.py"
+ bbc = BugBearChecker(filename=str(filename))
+ errors = list(bbc.run())
+ expected = [
+ B905(1, 0),
+ B905(2, 0),
+ B905(3, 0),
+ B905(4, 0),
+ B905(4, 15),
+ B905(5, 4),
+ B905(6, 0),
+ ]
+ self.assertEqual(errors, self.errors(*expected))
+
def test_b950(self):
filename = Path(__file__).absolute().parent / "b950.py"
bbc = BugBearChecker(filename=str(filename))