OLD | NEW |
| (Empty) |
1 # copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. | |
2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr | |
3 # | |
4 # This file is part of astroid. | |
5 # | |
6 # astroid is free software: you can redistribute it and/or modify it | |
7 # under the terms of the GNU Lesser General Public License as published by the | |
8 # Free Software Foundation, either version 2.1 of the License, or (at your | |
9 # option) any later version. | |
10 # | |
11 # astroid is distributed in the hope that it will be useful, but | |
12 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
13 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License | |
14 # for more details. | |
15 # | |
16 # You should have received a copy of the GNU Lesser General Public License along | |
17 # with astroid. If not, see <http://www.gnu.org/licenses/>. | |
18 """visitor doing some postprocessing on the astroid tree. | |
19 Try to resolve definitions (namespace) dictionary, relationship... | |
20 | |
21 This module has been imported from pyreverse | |
22 """ | |
23 | |
24 __docformat__ = "restructuredtext en" | |
25 | |
26 from os.path import dirname | |
27 | |
28 import astroid | |
29 from astroid.exceptions import InferenceError | |
30 from astroid.utils import LocalsVisitor | |
31 from astroid.modutils import get_module_part, is_relative, is_standard_module | |
32 | |
33 class IdGeneratorMixIn(object): | |
34 """ | |
35 Mixin adding the ability to generate integer uid | |
36 """ | |
37 def __init__(self, start_value=0): | |
38 self.id_count = start_value | |
39 | |
40 def init_counter(self, start_value=0): | |
41 """init the id counter | |
42 """ | |
43 self.id_count = start_value | |
44 | |
45 def generate_id(self): | |
46 """generate a new identifier | |
47 """ | |
48 self.id_count += 1 | |
49 return self.id_count | |
50 | |
51 | |
52 class Linker(IdGeneratorMixIn, LocalsVisitor): | |
53 """ | |
54 walk on the project tree and resolve relationships. | |
55 | |
56 According to options the following attributes may be added to visited nodes: | |
57 | |
58 * uid, | |
59 a unique identifier for the node (on astroid.Project, astroid.Module, | |
60 astroid.Class and astroid.locals_type). Only if the linker has been instan
tiated | |
61 with tag=True parameter (False by default). | |
62 | |
63 * Function | |
64 a mapping from locals names to their bounded value, which may be a | |
65 constant like a string or an integer, or an astroid node (on astroid.Modul
e, | |
66 astroid.Class and astroid.Function). | |
67 | |
68 * instance_attrs_type | |
69 as locals_type but for klass member attributes (only on astroid.Class) | |
70 | |
71 * implements, | |
72 list of implemented interface _objects_ (only on astroid.Class nodes) | |
73 """ | |
74 | |
75 def __init__(self, project, inherited_interfaces=0, tag=False): | |
76 IdGeneratorMixIn.__init__(self) | |
77 LocalsVisitor.__init__(self) | |
78 # take inherited interface in consideration or not | |
79 self.inherited_interfaces = inherited_interfaces | |
80 # tag nodes or not | |
81 self.tag = tag | |
82 # visited project | |
83 self.project = project | |
84 | |
85 | |
86 def visit_project(self, node): | |
87 """visit an astroid.Project node | |
88 | |
89 * optionally tag the node with a unique id | |
90 """ | |
91 if self.tag: | |
92 node.uid = self.generate_id() | |
93 for module in node.modules: | |
94 self.visit(module) | |
95 | |
96 def visit_package(self, node): | |
97 """visit an astroid.Package node | |
98 | |
99 * optionally tag the node with a unique id | |
100 """ | |
101 if self.tag: | |
102 node.uid = self.generate_id() | |
103 for subelmt in node.values(): | |
104 self.visit(subelmt) | |
105 | |
106 def visit_module(self, node): | |
107 """visit an astroid.Module node | |
108 | |
109 * set the locals_type mapping | |
110 * set the depends mapping | |
111 * optionally tag the node with a unique id | |
112 """ | |
113 if hasattr(node, 'locals_type'): | |
114 return | |
115 node.locals_type = {} | |
116 node.depends = [] | |
117 if self.tag: | |
118 node.uid = self.generate_id() | |
119 | |
120 def visit_class(self, node): | |
121 """visit an astroid.Class node | |
122 | |
123 * set the locals_type and instance_attrs_type mappings | |
124 * set the implements list and build it | |
125 * optionally tag the node with a unique id | |
126 """ | |
127 if hasattr(node, 'locals_type'): | |
128 return | |
129 node.locals_type = {} | |
130 if self.tag: | |
131 node.uid = self.generate_id() | |
132 # resolve ancestors | |
133 for baseobj in node.ancestors(recurs=False): | |
134 specializations = getattr(baseobj, 'specializations', []) | |
135 specializations.append(node) | |
136 baseobj.specializations = specializations | |
137 # resolve instance attributes | |
138 node.instance_attrs_type = {} | |
139 for assattrs in node.instance_attrs.values(): | |
140 for assattr in assattrs: | |
141 self.handle_assattr_type(assattr, node) | |
142 # resolve implemented interface | |
143 try: | |
144 node.implements = list(node.interfaces(self.inherited_interfaces)) | |
145 except InferenceError: | |
146 node.implements = () | |
147 | |
148 def visit_function(self, node): | |
149 """visit an astroid.Function node | |
150 | |
151 * set the locals_type mapping | |
152 * optionally tag the node with a unique id | |
153 """ | |
154 if hasattr(node, 'locals_type'): | |
155 return | |
156 node.locals_type = {} | |
157 if self.tag: | |
158 node.uid = self.generate_id() | |
159 | |
160 link_project = visit_project | |
161 link_module = visit_module | |
162 link_class = visit_class | |
163 link_function = visit_function | |
164 | |
165 def visit_assname(self, node): | |
166 """visit an astroid.AssName node | |
167 | |
168 handle locals_type | |
169 """ | |
170 # avoid double parsing done by different Linkers.visit | |
171 # running over the same project: | |
172 if hasattr(node, '_handled'): | |
173 return | |
174 node._handled = True | |
175 if node.name in node.frame(): | |
176 frame = node.frame() | |
177 else: | |
178 # the name has been defined as 'global' in the frame and belongs | |
179 # there. Btw the frame is not yet visited as the name is in the | |
180 # root locals; the frame hence has no locals_type attribute | |
181 frame = node.root() | |
182 try: | |
183 values = node.infered() | |
184 try: | |
185 already_infered = frame.locals_type[node.name] | |
186 for valnode in values: | |
187 if not valnode in already_infered: | |
188 already_infered.append(valnode) | |
189 except KeyError: | |
190 frame.locals_type[node.name] = values | |
191 except astroid.InferenceError: | |
192 pass | |
193 | |
194 def handle_assattr_type(self, node, parent): | |
195 """handle an astroid.AssAttr node | |
196 | |
197 handle instance_attrs_type | |
198 """ | |
199 try: | |
200 values = list(node.infer()) | |
201 try: | |
202 already_infered = parent.instance_attrs_type[node.attrname] | |
203 for valnode in values: | |
204 if not valnode in already_infered: | |
205 already_infered.append(valnode) | |
206 except KeyError: | |
207 parent.instance_attrs_type[node.attrname] = values | |
208 except astroid.InferenceError: | |
209 pass | |
210 | |
211 def visit_import(self, node): | |
212 """visit an astroid.Import node | |
213 | |
214 resolve module dependencies | |
215 """ | |
216 context_file = node.root().file | |
217 for name in node.names: | |
218 relative = is_relative(name[0], context_file) | |
219 self._imported_module(node, name[0], relative) | |
220 | |
221 | |
222 def visit_from(self, node): | |
223 """visit an astroid.From node | |
224 | |
225 resolve module dependencies | |
226 """ | |
227 basename = node.modname | |
228 context_file = node.root().file | |
229 if context_file is not None: | |
230 relative = is_relative(basename, context_file) | |
231 else: | |
232 relative = False | |
233 for name in node.names: | |
234 if name[0] == '*': | |
235 continue | |
236 # analyze dependencies | |
237 fullname = '%s.%s' % (basename, name[0]) | |
238 if fullname.find('.') > -1: | |
239 try: | |
240 # XXX: don't use get_module_part, missing package precedence | |
241 fullname = get_module_part(fullname, context_file) | |
242 except ImportError: | |
243 continue | |
244 if fullname != basename: | |
245 self._imported_module(node, fullname, relative) | |
246 | |
247 | |
248 def compute_module(self, context_name, mod_path): | |
249 """return true if the module should be added to dependencies""" | |
250 package_dir = dirname(self.project.path) | |
251 if context_name == mod_path: | |
252 return 0 | |
253 elif is_standard_module(mod_path, (package_dir,)): | |
254 return 1 | |
255 return 0 | |
256 | |
257 # protected methods ######################################################## | |
258 | |
259 def _imported_module(self, node, mod_path, relative): | |
260 """notify an imported module, used to analyze dependencies | |
261 """ | |
262 module = node.root() | |
263 context_name = module.name | |
264 if relative: | |
265 mod_path = '%s.%s' % ('.'.join(context_name.split('.')[:-1]), | |
266 mod_path) | |
267 if self.compute_module(context_name, mod_path): | |
268 # handle dependencies | |
269 if not hasattr(module, 'depends'): | |
270 module.depends = [] | |
271 mod_paths = module.depends | |
272 if not mod_path in mod_paths: | |
273 mod_paths.append(mod_path) | |
OLD | NEW |