overlapping_exceptions.py 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  2. # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
  3. """Looks for overlapping exceptions."""
  4. from typing import Any, List, Tuple
  5. import astroid
  6. from astroid import nodes
  7. from pylint import checkers, interfaces
  8. from pylint.checkers import utils
  9. from pylint.checkers.exceptions import _annotated_unpack_infer
  10. class OverlappingExceptionsChecker(checkers.BaseChecker):
  11. """Checks for two or more exceptions in the same exception handler
  12. clause that are identical or parts of the same inheritance hierarchy
  13. (i.e. overlapping)."""
  14. __implements__ = interfaces.IAstroidChecker
  15. name = "overlap-except"
  16. msgs = {
  17. "W0714": (
  18. "Overlapping exceptions (%s)",
  19. "overlapping-except",
  20. "Used when exceptions in handler overlap or are identical",
  21. )
  22. }
  23. priority = -2
  24. options = ()
  25. @utils.check_messages("overlapping-except")
  26. def visit_tryexcept(self, node: nodes.TryExcept) -> None:
  27. """check for empty except"""
  28. for handler in node.handlers:
  29. if handler.type is None:
  30. continue
  31. if isinstance(handler.type, astroid.BoolOp):
  32. continue
  33. try:
  34. excs = list(_annotated_unpack_infer(handler.type))
  35. except astroid.InferenceError:
  36. continue
  37. handled_in_clause: List[Tuple[Any, Any]] = []
  38. for part, exc in excs:
  39. if exc is astroid.Uninferable:
  40. continue
  41. if isinstance(exc, astroid.Instance) and utils.inherit_from_std_ex(exc):
  42. exc = exc._proxied
  43. if not isinstance(exc, astroid.ClassDef):
  44. continue
  45. exc_ancestors = [
  46. anc for anc in exc.ancestors() if isinstance(anc, astroid.ClassDef)
  47. ]
  48. for prev_part, prev_exc in handled_in_clause:
  49. prev_exc_ancestors = [
  50. anc
  51. for anc in prev_exc.ancestors()
  52. if isinstance(anc, astroid.ClassDef)
  53. ]
  54. if exc == prev_exc:
  55. self.add_message(
  56. "overlapping-except",
  57. node=handler.type,
  58. args=f"{prev_part.as_string()} and {part.as_string()} are the same",
  59. )
  60. elif prev_exc in exc_ancestors or exc in prev_exc_ancestors:
  61. ancestor = part if exc in prev_exc_ancestors else prev_part
  62. descendant = part if prev_exc in exc_ancestors else prev_part
  63. self.add_message(
  64. "overlapping-except",
  65. node=handler.type,
  66. args=f"{ancestor.as_string()} is an ancestor class of {descendant.as_string()}",
  67. )
  68. handled_in_clause += [(part, exc)]
  69. def register(linter):
  70. """Required method to auto register this checker."""
  71. linter.register_checker(OverlappingExceptionsChecker(linter))