Coverage for C:\Repos\ekr-pylint\pylint\utils\ast_walker.py: 20%
66 statements
« prev ^ index » next coverage.py v6.4, created at 2022-05-24 10:21 -0500
« prev ^ index » next coverage.py v6.4, created at 2022-05-24 10:21 -0500
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# Copyright (c) https://github.com/PyCQA/pylint/blob/main/CONTRIBUTORS.txt
5from __future__ import annotations
7import traceback
8from collections import defaultdict
9from collections.abc import Sequence
10from typing import TYPE_CHECKING, Callable
12from astroid import nodes
14if TYPE_CHECKING:
15 from pylint.checkers.base_checker import BaseChecker
16 from pylint.lint import PyLinter
18# Callable parameter type NodeNG not completely correct.
19# Due to contravariance of Callable parameter types,
20# it should be a Union of all NodeNG subclasses.
21# However, since the methods are only retrieved with
22# getattr(checker, member) and thus are inferred as Any,
23# NodeNG will work too.
24AstCallback = Callable[[nodes.NodeNG], None]
27class ASTWalker:
28 def __init__(self, linter: PyLinter) -> None:
29 # callbacks per node types
30 self.nbstatements = 0
31 self.visit_events: defaultdict[str, list[AstCallback]] = defaultdict(list)
32 self.leave_events: defaultdict[str, list[AstCallback]] = defaultdict(list)
33 self.linter = linter
34 self.exception_msg = False
36 def _is_method_enabled(self, method: AstCallback) -> bool:
37 if not hasattr(method, "checks_msgs"):
38 return True
39 return any(self.linter.is_message_enabled(m) for m in method.checks_msgs) # type: ignore[attr-defined]
41 def add_checker(self, checker: BaseChecker) -> None:
42 """Walk to the checker's dir and collect visit and leave methods."""
43 vcids: set[str] = set()
44 lcids: set[str] = set()
45 visits = self.visit_events
46 leaves = self.leave_events
47 for member in dir(checker):
48 cid = member[6:]
49 if cid == "default":
50 continue
51 if member.startswith("visit_"):
52 v_meth = getattr(checker, member)
53 # don't use visit_methods with no activated message:
54 if self._is_method_enabled(v_meth):
55 visits[cid].append(v_meth)
56 vcids.add(cid)
57 elif member.startswith("leave_"):
58 l_meth = getattr(checker, member)
59 # don't use leave_methods with no activated message:
60 if self._is_method_enabled(l_meth):
61 leaves[cid].append(l_meth)
62 lcids.add(cid)
63 visit_default = getattr(checker, "visit_default", None)
64 if visit_default:
65 for cls in nodes.ALL_NODE_CLASSES:
66 cid = cls.__name__.lower()
67 if cid not in vcids:
68 visits[cid].append(visit_default)
69 # For now, we have no "leave_default" method in Pylint
71 def walk(self, astroid: nodes.NodeNG) -> None:
72 """Call visit events of astroid checkers for the given node, recurse on
73 its children, then leave events.
74 """
75 cid = astroid.__class__.__name__.lower()
77 # Detect if the node is a new name for a deprecated alias.
78 # In this case, favour the methods for the deprecated
79 # alias if any, in order to maintain backwards
80 # compatibility.
81 visit_events: Sequence[AstCallback] = self.visit_events.get(cid, ())
82 leave_events: Sequence[AstCallback] = self.leave_events.get(cid, ())
84 try:
85 if astroid.is_statement:
86 self.nbstatements += 1
87 # generate events for this node on each checker
88 for callback in visit_events:
89 callback(astroid)
90 # recurse on children
91 for child in astroid.get_children():
92 self.walk(child)
93 for callback in leave_events:
94 callback(astroid)
95 except Exception:
96 if self.exception_msg is False:
97 file = getattr(astroid.root(), "file", None)
98 print(f"Exception on node {repr(astroid)} in file '{file}'")
99 traceback.print_exc()
100 self.exception_msg = True
101 raise