Skip to content

Commit 651ed80

Browse files
authored
Add new check which finds duplicate except clauses (#284)
* Add new check which finds duplicate except clauses Closes #254 * Add sorting
1 parent 4c34177 commit 651ed80

File tree

4 files changed

+82
-0
lines changed

4 files changed

+82
-0
lines changed

README.rst

+4
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,10 @@ the loop, because `late-binding closures are a classic gotcha
156156

157157
**B024**: Abstract base class with no abstract method. Remember to use @abstractmethod, @abstractclassmethod, and/or @abstractproperty decorators.
158158

159+
**B025**: ``try-except`` block with duplicate exceptions found.
160+
This check identifies exception types that are specified in multiple ``except``
161+
clauses. The first specification is the only one ever considered, so all others can be removed.
162+
159163
Opinionated warnings
160164
~~~~~~~~~~~~~~~~~~~~
161165

bugbear.py

+25
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,7 @@ def visit_ClassDef(self, node):
421421

422422
def visit_Try(self, node):
423423
self.check_for_b012(node)
424+
self.check_for_b025(node)
424425
self.generic_visit(node)
425426

426427
def visit_Compare(self, node):
@@ -837,6 +838,24 @@ def check_for_b022(self, node):
837838
):
838839
self.errors.append(B022(node.lineno, node.col_offset))
839840

841+
def check_for_b025(self, node):
842+
seen = []
843+
for handler in node.handlers:
844+
if isinstance(handler.type, (ast.Name, ast.Attribute)):
845+
name = ".".join(compose_call_path(handler.type))
846+
seen.append(name)
847+
elif isinstance(handler.type, ast.Tuple):
848+
# to avoid checking the same as B014, remove duplicates per except
849+
uniques = set()
850+
for entry in handler.type.elts:
851+
name = ".".join(compose_call_path(entry))
852+
uniques.add(name)
853+
seen.extend(uniques)
854+
# sort to have a deterministic output
855+
duplicates = sorted(set(x for x in seen if seen.count(x) > 1))
856+
for duplicate in duplicates:
857+
self.errors.append(B025(node.lineno, node.col_offset, vars=(duplicate,)))
858+
840859

841860
def compose_call_path(node):
842861
if isinstance(node, ast.Attribute):
@@ -1178,6 +1197,12 @@ def visit_Lambda(self, node):
11781197
" decorators."
11791198
)
11801199
)
1200+
B025 = Error(
1201+
message=(
1202+
"B025 Exception `{0}` has been caught multiple times. Only the first except"
1203+
" will be considered and all other except catches can be safely removed."
1204+
)
1205+
)
11811206

11821207
# Warnings disabled by default.
11831208
B901 = Error(

tests/b025.py

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""
2+
Should emit:
3+
B025 - on lines 15, 22, 31
4+
"""
5+
6+
import pickle
7+
8+
try:
9+
a = 1
10+
except ValueError:
11+
a = 2
12+
finally:
13+
a = 3
14+
15+
try:
16+
a = 1
17+
except ValueError:
18+
a = 2
19+
except ValueError:
20+
a = 2
21+
22+
try:
23+
a = 1
24+
except pickle.PickleError:
25+
a = 2
26+
except ValueError:
27+
a = 2
28+
except pickle.PickleError:
29+
a = 2
30+
31+
try:
32+
a = 1
33+
except (ValueError, TypeError):
34+
a = 2
35+
except ValueError:
36+
a = 2
37+
except (OSError, TypeError):
38+
a = 2

tests/test_bugbear.py

+15
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
B022,
3636
B023,
3737
B024,
38+
B025,
3839
B901,
3940
B902,
4041
B903,
@@ -366,6 +367,20 @@ def test_b024(self):
366367
)
367368
self.assertEqual(errors, expected)
368369

370+
def test_b025(self):
371+
filename = Path(__file__).absolute().parent / "b025.py"
372+
bbc = BugBearChecker(filename=str(filename))
373+
errors = list(bbc.run())
374+
self.assertEqual(
375+
errors,
376+
self.errors(
377+
B025(15, 0, vars=("ValueError",)),
378+
B025(22, 0, vars=("pickle.PickleError",)),
379+
B025(31, 0, vars=("TypeError",)),
380+
B025(31, 0, vars=("ValueError",)),
381+
),
382+
)
383+
369384
def test_b901(self):
370385
filename = Path(__file__).absolute().parent / "b901.py"
371386
bbc = BugBearChecker(filename=str(filename))

0 commit comments

Comments
 (0)