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

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# 

6 

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) 

23 

24from libcst._batched_visitor import BatchableCSTVisitor, visit_batched, VisitorMethod 

25from libcst._exceptions import MetadataException 

26from libcst.metadata.base_provider import BatchableMetadataProvider 

27 

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 ) 

36 

37 

38_T = TypeVar("_T") 

39 

40 

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) 

50 

51 # Make immutable metadata mapping 

52 # pyre-ignore[7] 

53 return {type(p): MappingProxyType(dict(p._computed)) for p in providers} 

54 

55 

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 

67 

68 

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 

78 

79 while len(remaining) > 0: 

80 batchable = set() 

81 

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) 

93 

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 

100 

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}") 

105 

106 remaining -= completed 

107 

108 

109class MetadataWrapper: 

110 """ 

111 A wrapper around a :class:`~libcst.Module` that stores associated metadata 

112 for that module. 

113 

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``. 

117 

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 """ 

122 

123 __slots__ = ["__module", "_metadata", "_cache"] 

124 

125 __module: "Module" 

126 _metadata: MutableMapping["ProviderT", Mapping["CSTNode", object]] 

127 _cache: Mapping["ProviderT", object] 

128 

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 

150 

151 def __repr__(self) -> str: 

152 return f"MetadataWrapper(\n{textwrap.indent(repr(self.module), ' ' * 4)},\n)" 

153 

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. 

159 

160 :: 

161 

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 

169 

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] 

180 

181 return cast(Mapping["CSTNode", _T], metadata) 

182 

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``. 

189 

190 The returned map does not contain any metadata from undeclared metadata 

191 dependencies that ``providers`` has. 

192 """ 

193 _resolve_impl(self, providers) 

194 

195 # Only return what what declared in providers 

196 return {k: self._metadata[k] for k in providers} 

197 

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) 

205 

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

220 

221 return visit_batched(self.module, visitors, before_visit, after_leave)