| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106 |
- # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
- # For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE
- # Copyright (c) https://github.com/pylint-dev/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
|