Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/libcst/metadata/wrapper.py: 52%
73 statements
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-25 06:43 +0000
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-25 06:43 +0000
1# Copyright (c) Meta Platforms, Inc. and affiliates.
2#
3# This source code is licensed under the MIT license found in the
4# LICENSE file in the root directory of this source tree.
5#
7import textwrap
8from contextlib import ExitStack
9from types import MappingProxyType
10from typing import (
11 Any,
12 cast,
13 Collection,
14 Iterable,
15 Mapping,
16 MutableMapping,
17 MutableSet,
18 Optional,
19 Type,
20 TYPE_CHECKING,
21 TypeVar,
22)
24from libcst._batched_visitor import BatchableCSTVisitor, visit_batched, VisitorMethod
25from libcst._exceptions import MetadataException
26from libcst.metadata.base_provider import BatchableMetadataProvider
28if TYPE_CHECKING:
29 from libcst._nodes.base import CSTNode # noqa: F401
30 from libcst._nodes.module import Module # noqa: F401
31 from libcst._visitors import CSTVisitorT # noqa: F401
32 from libcst.metadata.base_provider import ( # noqa: F401
33 BaseMetadataProvider,
34 ProviderT,
35 )
38_T = TypeVar("_T")
41def _gen_batchable(
42 wrapper: "MetadataWrapper",
43 # pyre-fixme[2]: Parameter `providers` must have a type that does not contain `Any`
44 providers: Iterable[BatchableMetadataProvider[Any]],
45) -> Mapping["ProviderT", Mapping["CSTNode", object]]:
46 """
47 Returns map of metadata mappings from resolving ``providers`` on ``wrapper``.
48 """
49 wrapper.visit_batched(providers)
51 # Make immutable metadata mapping
52 # pyre-ignore[7]
53 return {type(p): MappingProxyType(dict(p._computed)) for p in providers}
56def _gather_providers(
57 providers: Collection["ProviderT"], gathered: MutableSet["ProviderT"]
58) -> MutableSet["ProviderT"]:
59 """
60 Recursively gathers all the given providers and their dependencies.
61 """
62 for P in providers:
63 if P not in gathered:
64 gathered.add(P)
65 _gather_providers(P.METADATA_DEPENDENCIES, gathered)
66 return gathered
69def _resolve_impl(
70 wrapper: "MetadataWrapper", providers: Collection["ProviderT"]
71) -> None:
72 """
73 Updates the _metadata map on wrapper with metadata from the given providers
74 as well as their dependencies.
75 """
76 completed = set(wrapper._metadata.keys())
77 remaining = _gather_providers(set(providers), set()) - completed
79 while len(remaining) > 0:
80 batchable = set()
82 for P in remaining:
83 if set(P.METADATA_DEPENDENCIES).issubset(completed):
84 if issubclass(P, BatchableMetadataProvider):
85 batchable.add(P)
86 else:
87 wrapper._metadata[P] = (
88 P(wrapper._cache.get(P))._gen(wrapper)
89 if P.gen_cache
90 else P()._gen(wrapper)
91 )
92 completed.add(P)
94 initialized_batchable = [
95 p(wrapper._cache.get(p)) if p.gen_cache else p() for p in batchable
96 ]
97 metadata_batch = _gen_batchable(wrapper, initialized_batchable)
98 wrapper._metadata.update(metadata_batch)
99 completed |= batchable
101 if len(completed) == 0 and len(batchable) == 0:
102 # remaining must be non-empty at this point
103 names = ", ".join([P.__name__ for P in remaining])
104 raise MetadataException(f"Detected circular dependencies in {names}")
106 remaining -= completed
109class MetadataWrapper:
110 """
111 A wrapper around a :class:`~libcst.Module` that stores associated metadata
112 for that module.
114 When a :class:`MetadataWrapper` is constructed over a module, the wrapper will
115 store a deep copy of the original module. This means
116 ``MetadataWrapper(module).module == module`` is ``False``.
118 This copying operation ensures that a node will never appear twice (by identity) in
119 the same tree. This allows us to uniquely look up metadata for a node based on a
120 node's identity.
121 """
123 __slots__ = ["__module", "_metadata", "_cache"]
125 __module: "Module"
126 _metadata: MutableMapping["ProviderT", Mapping["CSTNode", object]]
127 _cache: Mapping["ProviderT", object]
129 def __init__(
130 self,
131 module: "Module",
132 unsafe_skip_copy: bool = False,
133 cache: Mapping["ProviderT", object] = {},
134 ) -> None:
135 """
136 :param module: The module to wrap. This is deeply copied by default.
137 :param unsafe_skip_copy: When true, this skips the deep cloning of the module.
138 This can provide a small performance benefit, but you should only use this
139 if you know that there are no duplicate nodes in your tree (e.g. this
140 module came from the parser).
141 :param cache: Pass the needed cache to wrapper to be used when resolving metadata.
142 """
143 # Ensure that module is safe to use by copying the module to remove
144 # any duplicate nodes.
145 if not unsafe_skip_copy:
146 module = module.deep_clone()
147 self.__module = module
148 self._metadata = {}
149 self._cache = cache
151 def __repr__(self) -> str:
152 return f"MetadataWrapper(\n{textwrap.indent(repr(self.module), ' ' * 4)},\n)"
154 @property
155 def module(self) -> "Module":
156 """
157 The module that's wrapped by this MetadataWrapper. By default, this is a deep
158 copy of the passed in module.
160 ::
162 mw = ModuleWrapper(module)
163 # Because `mw.module is not module`, you probably want to do visit and do
164 # your analysis on `mw.module`, not `module`.
165 mw.module.visit(DoSomeAnalysisVisitor)
166 """
167 # use a property getter to enforce that this is a read-only variable
168 return self.__module
170 def resolve(
171 self, provider: Type["BaseMetadataProvider[_T]"]
172 ) -> Mapping["CSTNode", _T]:
173 """
174 Returns a copy of the metadata mapping computed by ``provider``.
175 """
176 if provider in self._metadata:
177 metadata = self._metadata[provider]
178 else:
179 metadata = self.resolve_many([provider])[provider]
181 return cast(Mapping["CSTNode", _T], metadata)
183 def resolve_many(
184 self, providers: Collection["ProviderT"]
185 ) -> Mapping["ProviderT", Mapping["CSTNode", object]]:
186 """
187 Returns a copy of the map of metadata mapping computed by each provider
188 in ``providers``.
190 The returned map does not contain any metadata from undeclared metadata
191 dependencies that ``providers`` has.
192 """
193 _resolve_impl(self, providers)
195 # Only return what what declared in providers
196 return {k: self._metadata[k] for k in providers}
198 def visit(self, visitor: "CSTVisitorT") -> "Module":
199 """
200 Convenience method to resolve metadata before performing a traversal over
201 ``self.module`` with ``visitor``. See :func:`~libcst.Module.visit`.
202 """
203 with visitor.resolve(self):
204 return self.module.visit(visitor)
206 def visit_batched(
207 self,
208 visitors: Iterable[BatchableCSTVisitor],
209 before_visit: Optional[VisitorMethod] = None,
210 after_leave: Optional[VisitorMethod] = None,
211 ) -> "CSTNode":
212 """
213 Convenience method to resolve metadata before performing a traversal over
214 ``self.module`` with ``visitors``. See :func:`~libcst.visit_batched`.
215 """
216 with ExitStack() as stack:
217 # Resolve dependencies of visitors
218 for v in visitors:
219 stack.enter_context(v.resolve(self))
221 return visit_batched(self.module, visitors, before_visit, after_leave)