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

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 

4 

5from __future__ import annotations 

6 

7import traceback 

8from collections import defaultdict 

9from collections.abc import Sequence 

10from typing import TYPE_CHECKING, Callable 

11 

12from astroid import nodes 

13 

14if TYPE_CHECKING: 

15 from pylint.checkers.base_checker import BaseChecker 

16 from pylint.lint import PyLinter 

17 

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] 

25 

26 

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 

35 

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] 

40 

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 

70 

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() 

76 

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, ()) 

83 

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