| Viewing file:  ast_walker.py (4.14 KB)      -rw-r--r-- Select action/file-type:
 
  (+) |  (+) |  (+) | Code (+) | Session (+) |  (+) | SDB (+) |  (+) |  (+) |  (+) |  (+) |  (+) | 
 
# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html# For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
 # Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt
 
 from __future__ import annotations
 
 import sys
 import traceback
 from collections import defaultdict
 from collections.abc import Sequence
 from typing import TYPE_CHECKING, Callable
 
 from astroid import nodes
 
 if TYPE_CHECKING:
 from pylint.checkers.base_checker import BaseChecker
 from pylint.lint import PyLinter
 
 # Callable parameter type NodeNG not completely correct.
 # Due to contravariance of Callable parameter types,
 # it should be a Union of all NodeNG subclasses.
 # However, since the methods are only retrieved with
 # getattr(checker, member) and thus are inferred as Any,
 # NodeNG will work too.
 AstCallback = Callable[[nodes.NodeNG], None]
 
 
 class ASTWalker:
 def __init__(self, linter: PyLinter) -> None:
 # callbacks per node types
 self.nbstatements = 0
 self.visit_events: defaultdict[str, list[AstCallback]] = defaultdict(list)
 self.leave_events: defaultdict[str, list[AstCallback]] = defaultdict(list)
 self.linter = linter
 self.exception_msg = False
 
 def _is_method_enabled(self, method: AstCallback) -> bool:
 if not hasattr(method, "checks_msgs"):
 return True
 return any(self.linter.is_message_enabled(m) for m in method.checks_msgs)
 
 def add_checker(self, checker: BaseChecker) -> None:
 """Walk to the checker's dir and collect visit and leave methods."""
 vcids: set[str] = set()
 lcids: set[str] = set()
 visits = self.visit_events
 leaves = self.leave_events
 for member in dir(checker):
 cid = member[6:]
 if cid == "default":
 continue
 if member.startswith("visit_"):
 v_meth = getattr(checker, member)
 # don't use visit_methods with no activated message:
 if self._is_method_enabled(v_meth):
 visits[cid].append(v_meth)
 vcids.add(cid)
 elif member.startswith("leave_"):
 l_meth = getattr(checker, member)
 # don't use leave_methods with no activated message:
 if self._is_method_enabled(l_meth):
 leaves[cid].append(l_meth)
 lcids.add(cid)
 visit_default = getattr(checker, "visit_default", None)
 if visit_default:
 for cls in nodes.ALL_NODE_CLASSES:
 cid = cls.__name__.lower()
 if cid not in vcids:
 visits[cid].append(visit_default)
 # For now, we have no "leave_default" method in Pylint
 
 def walk(self, astroid: nodes.NodeNG) -> None:
 """Call visit events of astroid checkers for the given node, recurse on
 its children, then leave events.
 """
 cid = astroid.__class__.__name__.lower()
 
 # Detect if the node is a new name for a deprecated alias.
 # In this case, favour the methods for the deprecated
 # alias if any,  in order to maintain backwards
 # compatibility.
 visit_events: Sequence[AstCallback] = self.visit_events.get(cid, ())
 leave_events: Sequence[AstCallback] = self.leave_events.get(cid, ())
 
 # pylint: disable = too-many-try-statements
 try:
 if astroid.is_statement:
 self.nbstatements += 1
 # generate events for this node on each checker
 for callback in visit_events:
 callback(astroid)
 # recurse on children
 for child in astroid.get_children():
 self.walk(child)
 for callback in leave_events:
 callback(astroid)
 except Exception:
 if self.exception_msg is False:
 file = getattr(astroid.root(), "file", None)
 print(
 f"Exception on node {repr(astroid)} in file '{file}'",
 file=sys.stderr,
 )
 traceback.print_exc()
 self.exception_msg = True
 raise
 
 |