Skip to content

Commit adca610

Browse files
committed
now prints except* in error messages
1 parent 5314dd8 commit adca610

File tree

3 files changed

+134
-58
lines changed

3 files changed

+134
-58
lines changed

bugbear.py

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ def _flatten_excepthandler(node: ast.expr | None) -> Iterator[ast.expr | None]:
243243
yield expr
244244

245245

246-
def _check_redundant_excepthandlers(names: Sequence[str], node):
246+
def _check_redundant_excepthandlers(names: Sequence[str], node, in_trystar):
247247
# See if any of the given exception names could be removed, e.g. from:
248248
# (MyError, MyError) # duplicate names
249249
# (MyError, BaseException) # everything derives from the Base
@@ -275,7 +275,7 @@ def _check_redundant_excepthandlers(names: Sequence[str], node):
275275
return B014(
276276
node.lineno,
277277
node.col_offset,
278-
vars=(", ".join(names), as_, desc),
278+
vars=(", ".join(names), as_, desc, in_trystar),
279279
)
280280
return None
281281

@@ -388,6 +388,9 @@ class BugBearVisitor(ast.NodeVisitor):
388388
_b023_seen: set[error] = attr.ib(factory=set, init=False)
389389
_b005_imports: set[str] = attr.ib(factory=set, init=False)
390390

391+
# set to "*" when inside a try/except*, for correctly printing errors
392+
in_trystar: str = attr.ib(default="")
393+
391394
if False:
392395
# Useful for tracing what the hell is going on.
393396

@@ -604,7 +607,11 @@ def visit_Try(self, node) -> None:
604607
self.check_for_b025(node)
605608
self.generic_visit(node)
606609

607-
visit_TryStar = visit_Try
610+
def visit_TryStar(self, node) -> None:
611+
outer_trystar = self.in_trystar
612+
self.in_trystar = "*"
613+
self.visit_Try(node)
614+
self.in_trystar = outer_trystar
608615

609616
def visit_Compare(self, node) -> None:
610617
self.check_for_b015(node)
@@ -764,7 +771,9 @@ def _loop(node, bad_node_types) -> None:
764771
bad_node_types = (ast.Return,)
765772

766773
elif isinstance(node, bad_node_types):
767-
self.errors.append(B012(node.lineno, node.col_offset))
774+
self.errors.append(
775+
B012(node.lineno, node.col_offset, vars=(self.in_trystar,))
776+
)
768777

769778
for child in ast.iter_child_nodes(node):
770779
_loop(child, bad_node_types)
@@ -792,16 +801,27 @@ def check_for_b013_b014_b029_b030(self, node: ast.ExceptHandler) -> list[str]:
792801
if bad_handlers:
793802
self.errors.append(B030(node.lineno, node.col_offset))
794803
if len(names) == 0 and not bad_handlers and not ignored_handlers:
795-
self.errors.append(B029(node.lineno, node.col_offset))
804+
self.errors.append(
805+
B029(node.lineno, node.col_offset, vars=(self.in_trystar,))
806+
)
796807
elif (
797808
len(names) == 1
798809
and not bad_handlers
799810
and not ignored_handlers
800811
and isinstance(node.type, ast.Tuple)
801812
):
802-
self.errors.append(B013(node.lineno, node.col_offset, vars=names))
813+
self.errors.append(
814+
B013(
815+
node.lineno,
816+
node.col_offset,
817+
vars=(
818+
*names,
819+
self.in_trystar,
820+
),
821+
)
822+
)
803823
else:
804-
maybe_error = _check_redundant_excepthandlers(names, node)
824+
maybe_error = _check_redundant_excepthandlers(names, node, self.in_trystar)
805825
if maybe_error is not None:
806826
self.errors.append(maybe_error)
807827
return names
@@ -1217,7 +1237,9 @@ def check_for_b904(self, node) -> None:
12171237
and not (isinstance(node.exc, ast.Name) and node.exc.id.islower())
12181238
and any(isinstance(n, ast.ExceptHandler) for n in self.node_stack)
12191239
):
1220-
self.errors.append(B904(node.lineno, node.col_offset))
1240+
self.errors.append(
1241+
B904(node.lineno, node.col_offset, vars=(self.in_trystar,))
1242+
)
12211243

12221244
def walk_function_body(self, node):
12231245
def _loop(parent, node):
@@ -1481,7 +1503,9 @@ def check_for_b025(self, node) -> None:
14811503
# sort to have a deterministic output
14821504
duplicates = sorted({x for x in seen if seen.count(x) > 1})
14831505
for duplicate in duplicates:
1484-
self.errors.append(B025(node.lineno, node.col_offset, vars=(duplicate,)))
1506+
self.errors.append(
1507+
B025(node.lineno, node.col_offset, vars=(duplicate, self.in_trystar))
1508+
)
14851509

14861510
@staticmethod
14871511
def _is_infinite_iterator(node: ast.expr) -> bool:
@@ -2060,7 +2084,7 @@ def visit_Lambda(self, node) -> None:
20602084
error = namedtuple("error", "lineno col message type vars")
20612085
Error = partial(partial, error, type=BugBearChecker, vars=())
20622086

2063-
# note: bare except* is a syntax error
2087+
# note: bare except* is a syntax error, so B001 does not need to handle it
20642088
B001 = Error(
20652089
message=(
20662090
"B001 Do not use bare `except:`, it also catches unexpected "
@@ -2182,20 +2206,20 @@ def visit_Lambda(self, node) -> None:
21822206
B012 = Error(
21832207
message=(
21842208
"B012 return/continue/break inside finally blocks cause exceptions "
2185-
"to be silenced. Exceptions should be silenced in except blocks. Control "
2209+
"to be silenced. Exceptions should be silenced in except{0} blocks. Control "
21862210
"statements can be moved outside the finally block."
21872211
)
21882212
)
21892213
B013 = Error(
21902214
message=(
21912215
"B013 A length-one tuple literal is redundant. "
2192-
"Write `except {0}:` instead of `except ({0},):`."
2216+
"Write `except{1} {0}:` instead of `except{1} ({0},):`."
21932217
)
21942218
)
21952219
B014 = Error(
21962220
message=(
2197-
"B014 Redundant exception types in `except ({0}){1}:`. "
2198-
"Write `except {2}{1}:`, which catches exactly the same exceptions."
2221+
"B014 Redundant exception types in `except{3} ({0}){1}:`. "
2222+
"Write `except{3} {2}{1}:`, which catches exactly the same exceptions."
21992223
)
22002224
)
22012225
B014_REDUNDANT_EXCEPTIONS = {
@@ -2284,8 +2308,8 @@ def visit_Lambda(self, node) -> None:
22842308
)
22852309
B025 = Error(
22862310
message=(
2287-
"B025 Exception `{0}` has been caught multiple times. Only the first except"
2288-
" will be considered and all other except catches can be safely removed."
2311+
"B025 Exception `{0}` has been caught multiple times. Only the first except{0}"
2312+
" will be considered and all other except{0} catches can be safely removed."
22892313
)
22902314
)
22912315
B026 = Error(
@@ -2313,7 +2337,7 @@ def visit_Lambda(self, node) -> None:
23132337
)
23142338
B029 = Error(
23152339
message=(
2316-
"B029 Using `except ():` with an empty tuple does not handle/catch "
2340+
"B029 Using `except{0} ():` with an empty tuple does not handle/catch "
23172341
"anything. Add exceptions to handle."
23182342
)
23192343
)
@@ -2402,7 +2426,7 @@ def visit_Lambda(self, node) -> None:
24022426

24032427
B904 = Error(
24042428
message=(
2405-
"B904 Within an `except` clause, raise exceptions with `raise ... from err` or"
2429+
"B904 Within an `except{0}` clause, raise exceptions with `raise ... from err` or"
24062430
" `raise ... from None` to distinguish them from errors in exception handling. "
24072431
" See https://docs.python.org/3/tutorial/errors.html#exception-chaining for"
24082432
" details."

tests/b013_py311.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""
2+
Should emit:
3+
B013 - on lines 10 and 28
4+
"""
5+
6+
import re
7+
8+
try:
9+
pass
10+
except* (ValueError,):
11+
# pointless use of tuple
12+
pass
13+
14+
# fmt: off
15+
# Turn off black to keep brackets around
16+
# single except*ion for testing purposes.
17+
try:
18+
pass
19+
except* (ValueError):
20+
# not using a tuple means it's OK (if odd)
21+
pass
22+
# fmt: on
23+
24+
try:
25+
pass
26+
except* ValueError:
27+
# no warning here, all good
28+
pass
29+
30+
try:
31+
pass
32+
except* (re.error,):
33+
# pointless use of tuple with dotted attribute
34+
pass
35+
36+
try:
37+
pass
38+
except* (a.b.c.d, b.c.d):
39+
# attribute of attribute, etc.
40+
pass

tests/test_bugbear.py

Lines changed: 52 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -207,16 +207,16 @@ def test_b012(self):
207207
bbc = BugBearChecker(filename=str(filename))
208208
errors = list(bbc.run())
209209
all_errors = [
210-
B012(5, 8),
211-
B012(13, 12),
212-
B012(21, 12),
213-
B012(31, 12),
214-
B012(44, 20),
215-
B012(66, 12),
216-
B012(78, 12),
217-
B012(94, 12),
218-
B012(101, 8),
219-
B012(107, 8),
210+
B012(5, 8, vars=("",)),
211+
B012(13, 12, vars=("",)),
212+
B012(21, 12, vars=("",)),
213+
B012(31, 12, vars=("",)),
214+
B012(44, 20, vars=("",)),
215+
B012(66, 12, vars=("",)),
216+
B012(78, 12, vars=("",)),
217+
B012(94, 12, vars=("",)),
218+
B012(101, 8, vars=("",)),
219+
B012(107, 8, vars=("",)),
220220
]
221221
self.assertEqual(errors, self.errors(*all_errors))
222222

@@ -226,9 +226,9 @@ def test_b012_py311(self):
226226
bbc = BugBearChecker(filename=str(filename))
227227
errors = list(bbc.run())
228228
all_errors = [
229-
B012(7, 8),
230-
B012(17, 12),
231-
B012(27, 12),
229+
B012(7, 8, vars=("*",)),
230+
B012(17, 12, vars=("*",)),
231+
B012(27, 12, vars=("*",)),
232232
]
233233
self.assertEqual(errors, self.errors(*all_errors))
234234

@@ -237,7 +237,19 @@ def test_b013(self):
237237
bbc = BugBearChecker(filename=str(filename))
238238
errors = list(bbc.run())
239239
expected = self.errors(
240-
B013(10, 0, vars=("ValueError",)), B013(32, 0, vars=("re.error",))
240+
B013(10, 0, vars=("ValueError", "")),
241+
B013(32, 0, vars=("re.error", "")),
242+
)
243+
self.assertEqual(errors, expected)
244+
245+
@unittest.skipIf(sys.version_info < (3, 11), "requires 3.11+")
246+
def test_b013_py311(self):
247+
filename = Path(__file__).absolute().parent / "b013_py311.py"
248+
bbc = BugBearChecker(filename=str(filename))
249+
errors = list(bbc.run())
250+
expected = self.errors(
251+
B013(10, 0, vars=("ValueError", "*")),
252+
B013(32, 0, vars=("re.error", "*")),
241253
)
242254
self.assertEqual(errors, expected)
243255

@@ -246,17 +258,17 @@ def test_b014(self):
246258
bbc = BugBearChecker(filename=str(filename))
247259
errors = list(bbc.run())
248260
expected = self.errors(
249-
B014(11, 0, vars=("Exception, TypeError", "", "Exception")),
250-
B014(17, 0, vars=("OSError, OSError", " as err", "OSError")),
251-
B014(28, 0, vars=("MyError, MyError", "", "MyError")),
252-
B014(42, 0, vars=("MyError, BaseException", " as e", "BaseException")),
253-
B014(49, 0, vars=("re.error, re.error", "", "re.error")),
261+
B014(11, 0, vars=("Exception, TypeError", "", "Exception", "")),
262+
B014(17, 0, vars=("OSError, OSError", " as err", "OSError", "")),
263+
B014(28, 0, vars=("MyError, MyError", "", "MyError", "")),
264+
B014(42, 0, vars=("MyError, BaseException", " as e", "BaseException", "")),
265+
B014(49, 0, vars=("re.error, re.error", "", "re.error", "")),
254266
B014(
255267
56,
256268
0,
257-
vars=("IOError, EnvironmentError, OSError", "", "OSError"),
269+
vars=("IOError, EnvironmentError, OSError", "", "OSError", ""),
258270
),
259-
B014(74, 0, vars=("ValueError, binascii.Error", "", "ValueError")),
271+
B014(74, 0, vars=("ValueError, binascii.Error", "", "ValueError", "")),
260272
)
261273
self.assertEqual(errors, expected)
262274

@@ -266,17 +278,17 @@ def test_b014_py311(self):
266278
bbc = BugBearChecker(filename=str(filename))
267279
errors = list(bbc.run())
268280
expected = self.errors(
269-
B014(11, 0, vars=("Exception, TypeError", "", "Exception")),
270-
B014(17, 0, vars=("OSError, OSError", " as err", "OSError")),
271-
B014(28, 0, vars=("MyError, MyError", "", "MyError")),
272-
B014(42, 0, vars=("MyError, BaseException", " as e", "BaseException")),
273-
B014(49, 0, vars=("re.error, re.error", "", "re.error")),
281+
B014(11, 0, vars=("Exception, TypeError", "", "Exception", "*")),
282+
B014(17, 0, vars=("OSError, OSError", " as err", "OSError", "*")),
283+
B014(28, 0, vars=("MyError, MyError", "", "MyError", "*")),
284+
B014(42, 0, vars=("MyError, BaseException", " as e", "BaseException", "*")),
285+
B014(49, 0, vars=("re.error, re.error", "", "re.error", "*")),
274286
B014(
275287
56,
276288
0,
277-
vars=("IOError, EnvironmentError, OSError", "", "OSError"),
289+
vars=("IOError, EnvironmentError, OSError", "", "OSError", "*"),
278290
),
279-
B014(74, 0, vars=("ValueError, binascii.Error", "", "ValueError")),
291+
B014(74, 0, vars=("ValueError, binascii.Error", "", "ValueError", "*")),
280292
)
281293
self.assertEqual(errors, expected)
282294

@@ -571,8 +583,8 @@ def test_b029(self):
571583
bbc = BugBearChecker(filename=str(filename))
572584
errors = list(bbc.run())
573585
expected = self.errors(
574-
B029(8, 0),
575-
B029(13, 0),
586+
B029(8, 0, vars=("",)),
587+
B029(13, 0, vars=("",)),
576588
)
577589
self.assertEqual(errors, expected)
578590

@@ -582,8 +594,8 @@ def test_b029_py311(self):
582594
bbc = BugBearChecker(filename=str(filename))
583595
errors = list(bbc.run())
584596
expected = self.errors(
585-
B029(8, 0),
586-
B029(13, 0),
597+
B029(8, 0, vars=("*",)),
598+
B029(13, 0, vars=("*",)),
587599
)
588600
self.assertEqual(errors, expected)
589601

@@ -987,10 +999,10 @@ def test_b904(self):
987999
bbc = BugBearChecker(filename=str(filename))
9881000
errors = list(bbc.run())
9891001
expected = [
990-
B904(10, 8),
991-
B904(11, 4),
992-
B904(16, 4),
993-
B904(55, 16),
1002+
B904(10, 8, vars=("",)),
1003+
B904(11, 4, vars=("",)),
1004+
B904(16, 4, vars=("",)),
1005+
B904(55, 16, vars=("",)),
9941006
]
9951007
self.assertEqual(errors, self.errors(*expected))
9961008

@@ -1000,10 +1012,10 @@ def test_b904_py311(self):
10001012
bbc = BugBearChecker(filename=str(filename))
10011013
errors = list(bbc.run())
10021014
expected = [
1003-
B904(10, 8),
1004-
B904(11, 4),
1005-
B904(16, 4),
1006-
B904(55, 16),
1015+
B904(10, 8, vars=("*",)),
1016+
B904(11, 4, vars=("*",)),
1017+
B904(16, 4, vars=("*",)),
1018+
B904(55, 16, vars=("*",)),
10071019
]
10081020
self.assertEqual(errors, self.errors(*expected))
10091021

0 commit comments

Comments
 (0)