ast_walker.py 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  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. import collections
  4. import traceback
  5. from astroid import nodes
  6. class ASTWalker:
  7. def __init__(self, linter):
  8. # callbacks per node types
  9. self.nbstatements = 0
  10. self.visit_events = collections.defaultdict(list)
  11. self.leave_events = collections.defaultdict(list)
  12. self.linter = linter
  13. self.exception_msg = False
  14. def _is_method_enabled(self, method):
  15. if not hasattr(method, "checks_msgs"):
  16. return True
  17. return any(self.linter.is_message_enabled(m) for m in method.checks_msgs)
  18. def add_checker(self, checker):
  19. """walk to the checker's dir and collect visit and leave methods"""
  20. vcids = set()
  21. lcids = set()
  22. visits = self.visit_events
  23. leaves = self.leave_events
  24. for member in dir(checker):
  25. cid = member[6:]
  26. if cid == "default":
  27. continue
  28. if member.startswith("visit_"):
  29. v_meth = getattr(checker, member)
  30. # don't use visit_methods with no activated message:
  31. if self._is_method_enabled(v_meth):
  32. visits[cid].append(v_meth)
  33. vcids.add(cid)
  34. elif member.startswith("leave_"):
  35. l_meth = getattr(checker, member)
  36. # don't use leave_methods with no activated message:
  37. if self._is_method_enabled(l_meth):
  38. leaves[cid].append(l_meth)
  39. lcids.add(cid)
  40. visit_default = getattr(checker, "visit_default", None)
  41. if visit_default:
  42. for cls in nodes.ALL_NODE_CLASSES:
  43. cid = cls.__name__.lower()
  44. if cid not in vcids:
  45. visits[cid].append(visit_default)
  46. # for now we have no "leave_default" method in Pylint
  47. def walk(self, astroid):
  48. """call visit events of astroid checkers for the given node, recurse on
  49. its children, then leave events.
  50. """
  51. cid = astroid.__class__.__name__.lower()
  52. # Detect if the node is a new name for a deprecated alias.
  53. # In this case, favour the methods for the deprecated
  54. # alias if any, in order to maintain backwards
  55. # compatibility.
  56. visit_events = self.visit_events.get(cid, ())
  57. leave_events = self.leave_events.get(cid, ())
  58. try:
  59. if astroid.is_statement:
  60. self.nbstatements += 1
  61. # generate events for this node on each checker
  62. for callback in visit_events or ():
  63. callback(astroid)
  64. # recurse on children
  65. for child in astroid.get_children():
  66. self.walk(child)
  67. for callback in leave_events or ():
  68. callback(astroid)
  69. except Exception:
  70. if self.exception_msg is False:
  71. file = getattr(astroid.root(), "file", None)
  72. print(f"Exception on node {repr(astroid)} in file '{file}'")
  73. traceback.print_exc()
  74. self.exception_msg = True
  75. raise