diff --git a/bugbear.py b/bugbear.py index c7d67cc..f85879b 100644 --- a/bugbear.py +++ b/bugbear.py @@ -26,6 +26,7 @@ import attr # type: ignore import pycodestyle # type: ignore +from flake8.exceptions import PluginExecutionFailed __version__ = "25.11.29" @@ -106,7 +107,10 @@ def run(self) -> Iterable[tuple[int, int, str, type]]: b008_b039_extend_immutable_calls=b008_b039_extend_immutable_calls, b902_classmethod_decorators=b902_classmethod_decorators, ) - visitor.visit(self.tree) + try: + visitor.visit(self.tree) + except RecursionError as exc: + raise PluginExecutionFailed(self.filename, self.name, exc) from exc for e in itertools.chain(visitor.errors, self.gen_line_based_checks()): if self.should_warn(e.message[:4]): yield self.adapt_error(e) diff --git a/tests/test_bugbear.py b/tests/test_bugbear.py index eb8e35a..ca8f666 100644 --- a/tests/test_bugbear.py +++ b/tests/test_bugbear.py @@ -13,6 +13,7 @@ from pathlib import Path import pytest +from flake8.exceptions import PluginExecutionFailed from bugbear import ( BugBearChecker, @@ -235,6 +236,18 @@ def test_b9_flake8_next_default_options(self): errors = list(bbc.run()) self.assertEqual(errors, []) + def test_recursion_error_is_wrapped(self): + from unittest.mock import patch + + bbc = BugBearChecker(filename=str(EVAL_FILES_DIR / "b001.py")) + with patch.object(BugBearVisitor, "visit", side_effect=RecursionError("boom")): + with self.assertRaises(PluginExecutionFailed) as excinfo: + list(bbc.run()) + + self.assertEqual(excinfo.exception.filename, str(EVAL_FILES_DIR / "b001.py")) + self.assertEqual(excinfo.exception.plugin_name, "flake8-bugbear") + self.assertIsInstance(excinfo.exception.original_exception, RecursionError) + def test_selfclean_bugbear(self): filename = Path(__file__).absolute().parent.parent / "bugbear.py" proc = subprocess.run(