Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/astroid/transforms.py: 84%
63 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:53 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:53 +0000
1# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
2# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE
3# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
5from __future__ import annotations
7from collections import defaultdict
8from collections.abc import Callable
9from typing import TYPE_CHECKING, List, Optional, Tuple, TypeVar, Union, cast, overload
11from astroid.context import _invalidate_cache
12from astroid.typing import SuccessfulInferenceResult, TransformFn
14if TYPE_CHECKING:
15 from astroid import nodes
17 _SuccessfulInferenceResultT = TypeVar(
18 "_SuccessfulInferenceResultT", bound=SuccessfulInferenceResult
19 )
20 _Predicate = Optional[Callable[[_SuccessfulInferenceResultT], bool]]
22_Vistables = Union[
23 "nodes.NodeNG", List["nodes.NodeNG"], Tuple["nodes.NodeNG", ...], str, None
24]
25_VisitReturns = Union[
26 SuccessfulInferenceResult,
27 List[SuccessfulInferenceResult],
28 Tuple[SuccessfulInferenceResult, ...],
29 str,
30 None,
31]
34class TransformVisitor:
35 """A visitor for handling transforms.
37 The standard approach of using it is to call
38 :meth:`~visit` with an *astroid* module and the class
39 will take care of the rest, walking the tree and running the
40 transforms for each encountered node.
42 Based on its usage in AstroidManager.brain, it should not be reinstantiated.
43 """
45 def __init__(self) -> None:
46 # The typing here is incorrect, but it's the best we can do
47 # Refer to register_transform and unregister_transform for the correct types
48 self.transforms: defaultdict[
49 type[SuccessfulInferenceResult],
50 list[
51 tuple[
52 TransformFn[SuccessfulInferenceResult],
53 _Predicate[SuccessfulInferenceResult],
54 ]
55 ],
56 ] = defaultdict(list)
58 def _transform(self, node: SuccessfulInferenceResult) -> SuccessfulInferenceResult:
59 """Call matching transforms for the given node if any and return the
60 transformed node.
61 """
62 cls = node.__class__
64 for transform_func, predicate in self.transforms[cls]:
65 if predicate is None or predicate(node):
66 ret = transform_func(node)
67 # if the transformation function returns something, it's
68 # expected to be a replacement for the node
69 if ret is not None:
70 _invalidate_cache()
71 node = ret
72 if ret.__class__ != cls:
73 # Can no longer apply the rest of the transforms.
74 break
75 return node
77 def _visit(self, node: nodes.NodeNG) -> SuccessfulInferenceResult:
78 for name in node._astroid_fields:
79 value = getattr(node, name)
80 value = cast(_Vistables, value)
81 visited = self._visit_generic(value)
82 if visited != value:
83 setattr(node, name, visited)
84 return self._transform(node)
86 @overload
87 def _visit_generic(self, node: None) -> None:
88 ...
90 @overload
91 def _visit_generic(self, node: str) -> str:
92 ...
94 @overload
95 def _visit_generic(
96 self, node: list[nodes.NodeNG]
97 ) -> list[SuccessfulInferenceResult]:
98 ...
100 @overload
101 def _visit_generic(
102 self, node: tuple[nodes.NodeNG, ...]
103 ) -> tuple[SuccessfulInferenceResult, ...]:
104 ...
106 @overload
107 def _visit_generic(self, node: nodes.NodeNG) -> SuccessfulInferenceResult:
108 ...
110 def _visit_generic(self, node: _Vistables) -> _VisitReturns:
111 if isinstance(node, list):
112 return [self._visit_generic(child) for child in node]
113 if isinstance(node, tuple):
114 return tuple(self._visit_generic(child) for child in node)
115 if not node or isinstance(node, str):
116 return node
118 return self._visit(node)
120 def register_transform(
121 self,
122 node_class: type[_SuccessfulInferenceResultT],
123 transform: TransformFn[_SuccessfulInferenceResultT],
124 predicate: _Predicate[_SuccessfulInferenceResultT] | None = None,
125 ) -> None:
126 """Register `transform(node)` function to be applied on the given node.
128 The transform will only be applied if `predicate` is None or returns true
129 when called with the node as argument.
131 The transform function may return a value which is then used to
132 substitute the original node in the tree.
133 """
134 self.transforms[node_class].append((transform, predicate)) # type: ignore[index, arg-type]
136 def unregister_transform(
137 self,
138 node_class: type[_SuccessfulInferenceResultT],
139 transform: TransformFn[_SuccessfulInferenceResultT],
140 predicate: _Predicate[_SuccessfulInferenceResultT] | None = None,
141 ) -> None:
142 """Unregister the given transform."""
143 self.transforms[node_class].remove((transform, predicate)) # type: ignore[index, arg-type]
145 def visit(self, node: nodes.NodeNG) -> SuccessfulInferenceResult:
146 """Walk the given astroid *tree* and transform each encountered node.
148 Only the nodes which have transforms registered will actually
149 be replaced or changed.
150 """
151 return self._visit(node)