gvsig-scripting / org.gvsig.scripting / trunk / org.gvsig.scripting / org.gvsig.scripting.app / org.gvsig.scripting.app.mainplugin / src / main / resources-plugin / scripting / lib / pylint / pyreverse / inspector.py @ 745
History | View | Annotate | Download (12.6 KB)
1 |
# Copyright (c) 2003-2015 LOGILAB S.A. (Paris, FRANCE).
|
---|---|
2 |
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
|
3 |
# Copyright (c) 2009-2010 Arista Networks, Inc.
|
4 |
#
|
5 |
# This program is free software; you can redistribute it and/or modify it under
|
6 |
# the terms of the GNU General Public License as published by the Free Software
|
7 |
# Foundation; either version 2 of the License, or (at your option) any later
|
8 |
# version.
|
9 |
#
|
10 |
# This program is distributed in the hope that it will be useful, but WITHOUT
|
11 |
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
12 |
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
13 |
#
|
14 |
# You should have received a copy of the GNU General Public License along with
|
15 |
# this program; if not, write to the Free Software Foundation, Inc.,
|
16 |
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
17 |
"""
|
18 |
Visitor doing some postprocessing on the astroid tree.
|
19 |
Try to resolve definitions (namespace) dictionary, relationship...
|
20 |
"""
|
21 |
from __future__ import print_function |
22 |
|
23 |
import collections |
24 |
import os |
25 |
import traceback |
26 |
|
27 |
import astroid |
28 |
from astroid import bases |
29 |
from astroid import exceptions |
30 |
from astroid import manager |
31 |
from astroid import modutils |
32 |
from astroid import node_classes |
33 |
|
34 |
|
35 |
from pylint.pyreverse import utils |
36 |
|
37 |
|
38 |
def _iface_hdlr(_): |
39 |
"""Handler used by interfaces to handle suspicious interface nodes."""
|
40 |
return True |
41 |
|
42 |
|
43 |
def _astroid_wrapper(func, modname): |
44 |
print('parsing %s...' % modname)
|
45 |
try:
|
46 |
return func(modname)
|
47 |
except exceptions.AstroidBuildingException as exc: |
48 |
print(exc) |
49 |
except Exception as exc: # pylint: disable=broad-except |
50 |
traceback.print_exc() |
51 |
|
52 |
|
53 |
def interfaces(node, herited=True, handler_func=_iface_hdlr): |
54 |
"""Return an iterator on interfaces implemented by the given class node."""
|
55 |
# FIXME: what if __implements__ = (MyIFace, MyParent.__implements__)...
|
56 |
try:
|
57 |
implements = bases.Instance(node).getattr('__implements__')[0] |
58 |
except exceptions.NotFoundError:
|
59 |
return
|
60 |
if not herited and implements.frame() is not node: |
61 |
return
|
62 |
found = set()
|
63 |
missing = False
|
64 |
for iface in node_classes.unpack_infer(implements): |
65 |
if iface is astroid.YES: |
66 |
missing = True
|
67 |
continue
|
68 |
if iface not in found and handler_func(iface): |
69 |
found.add(iface) |
70 |
yield iface
|
71 |
if missing:
|
72 |
raise exceptions.InferenceError()
|
73 |
|
74 |
|
75 |
class IdGeneratorMixIn(object): |
76 |
"""Mixin adding the ability to generate integer uid."""
|
77 |
|
78 |
def __init__(self, start_value=0): |
79 |
self.id_count = start_value
|
80 |
|
81 |
def init_counter(self, start_value=0): |
82 |
"""init the id counter
|
83 |
"""
|
84 |
self.id_count = start_value
|
85 |
|
86 |
def generate_id(self): |
87 |
"""generate a new identifier
|
88 |
"""
|
89 |
self.id_count += 1 |
90 |
return self.id_count |
91 |
|
92 |
|
93 |
class Linker(IdGeneratorMixIn, utils.LocalsVisitor): |
94 |
"""Walk on the project tree and resolve relationships.
|
95 |
|
96 |
According to options the following attributes may be
|
97 |
added to visited nodes:
|
98 |
|
99 |
* uid,
|
100 |
a unique identifier for the node (on astroid.Project, astroid.Module,
|
101 |
astroid.Class and astroid.locals_type). Only if the linker
|
102 |
has been instantiated with tag=True parameter (False by default).
|
103 |
|
104 |
* Function
|
105 |
a mapping from locals names to their bounded value, which may be a
|
106 |
constant like a string or an integer, or an astroid node
|
107 |
(on astroid.Module, astroid.Class and astroid.Function).
|
108 |
|
109 |
* instance_attrs_type
|
110 |
as locals_type but for klass member attributes (only on astroid.Class)
|
111 |
|
112 |
* implements,
|
113 |
list of implemented interface _objects_ (only on astroid.Class nodes)
|
114 |
"""
|
115 |
|
116 |
def __init__(self, project, inherited_interfaces=0, tag=False): |
117 |
IdGeneratorMixIn.__init__(self)
|
118 |
utils.LocalsVisitor.__init__(self)
|
119 |
# take inherited interface in consideration or not
|
120 |
self.inherited_interfaces = inherited_interfaces
|
121 |
# tag nodes or not
|
122 |
self.tag = tag
|
123 |
# visited project
|
124 |
self.project = project
|
125 |
|
126 |
def visit_project(self, node): |
127 |
"""visit an pyreverse.utils.Project node
|
128 |
|
129 |
* optionally tag the node with a unique id
|
130 |
"""
|
131 |
if self.tag: |
132 |
node.uid = self.generate_id()
|
133 |
for module in node.modules: |
134 |
self.visit(module)
|
135 |
|
136 |
def visit_package(self, node): |
137 |
"""visit an astroid.Package node
|
138 |
|
139 |
* optionally tag the node with a unique id
|
140 |
"""
|
141 |
if self.tag: |
142 |
node.uid = self.generate_id()
|
143 |
for subelmt in node.values(): |
144 |
self.visit(subelmt)
|
145 |
|
146 |
def visit_module(self, node): |
147 |
"""visit an astroid.Module node
|
148 |
|
149 |
* set the locals_type mapping
|
150 |
* set the depends mapping
|
151 |
* optionally tag the node with a unique id
|
152 |
"""
|
153 |
if hasattr(node, 'locals_type'): |
154 |
return
|
155 |
node.locals_type = collections.defaultdict(list)
|
156 |
node.depends = [] |
157 |
if self.tag: |
158 |
node.uid = self.generate_id()
|
159 |
|
160 |
def visit_classdef(self, node): |
161 |
"""visit an astroid.Class node
|
162 |
|
163 |
* set the locals_type and instance_attrs_type mappings
|
164 |
* set the implements list and build it
|
165 |
* optionally tag the node with a unique id
|
166 |
"""
|
167 |
if hasattr(node, 'locals_type'): |
168 |
return
|
169 |
node.locals_type = collections.defaultdict(list)
|
170 |
if self.tag: |
171 |
node.uid = self.generate_id()
|
172 |
# resolve ancestors
|
173 |
for baseobj in node.ancestors(recurs=False): |
174 |
specializations = getattr(baseobj, 'specializations', []) |
175 |
specializations.append(node) |
176 |
baseobj.specializations = specializations |
177 |
# resolve instance attributes
|
178 |
node.instance_attrs_type = collections.defaultdict(list)
|
179 |
for assattrs in node.instance_attrs.values(): |
180 |
for assattr in assattrs: |
181 |
self.handle_assattr_type(assattr, node)
|
182 |
# resolve implemented interface
|
183 |
try:
|
184 |
node.implements = list(interfaces(node, self.inherited_interfaces)) |
185 |
except astroid.InferenceError:
|
186 |
node.implements = () |
187 |
|
188 |
def visit_functiondef(self, node): |
189 |
"""visit an astroid.Function node
|
190 |
|
191 |
* set the locals_type mapping
|
192 |
* optionally tag the node with a unique id
|
193 |
"""
|
194 |
if hasattr(node, 'locals_type'): |
195 |
return
|
196 |
node.locals_type = collections.defaultdict(list)
|
197 |
if self.tag: |
198 |
node.uid = self.generate_id()
|
199 |
|
200 |
link_project = visit_project |
201 |
link_module = visit_module |
202 |
link_class = visit_classdef |
203 |
link_function = visit_functiondef |
204 |
|
205 |
def visit_assignname(self, node): |
206 |
"""visit an astroid.AssName node
|
207 |
|
208 |
handle locals_type
|
209 |
"""
|
210 |
# avoid double parsing done by different Linkers.visit
|
211 |
# running over the same project:
|
212 |
if hasattr(node, '_handled'): |
213 |
return
|
214 |
node._handled = True
|
215 |
if node.name in node.frame(): |
216 |
frame = node.frame() |
217 |
else:
|
218 |
# the name has been defined as 'global' in the frame and belongs
|
219 |
# there.
|
220 |
frame = node.root() |
221 |
try:
|
222 |
if not hasattr(frame, 'locals_type'): |
223 |
# If the frame doesn't have a locals_type yet,
|
224 |
# it means it wasn't yet visited. Visit it now
|
225 |
# to add what's missing from it.
|
226 |
if isinstance(frame, astroid.ClassDef): |
227 |
self.visit_classdef(frame)
|
228 |
elif isinstance(frame, astroid.FunctionDef): |
229 |
self.visit_functiondef(frame)
|
230 |
else:
|
231 |
self.visit_module(frame)
|
232 |
|
233 |
current = frame.locals_type[node.name] |
234 |
values = set(node.infer())
|
235 |
frame.locals_type[node.name] = list(set(current) | values) |
236 |
except astroid.InferenceError:
|
237 |
pass
|
238 |
|
239 |
@staticmethod
|
240 |
def handle_assattr_type(node, parent): |
241 |
"""handle an astroid.AssAttr node
|
242 |
|
243 |
handle instance_attrs_type
|
244 |
"""
|
245 |
try:
|
246 |
values = set(node.infer())
|
247 |
current = set(parent.instance_attrs_type[node.attrname])
|
248 |
parent.instance_attrs_type[node.attrname] = list(current | values)
|
249 |
except astroid.InferenceError:
|
250 |
pass
|
251 |
|
252 |
def visit_import(self, node): |
253 |
"""visit an astroid.Import node
|
254 |
|
255 |
resolve module dependencies
|
256 |
"""
|
257 |
context_file = node.root().file |
258 |
for name in node.names: |
259 |
relative = modutils.is_relative(name[0], context_file)
|
260 |
self._imported_module(node, name[0], relative) |
261 |
|
262 |
def visit_importfrom(self, node): |
263 |
"""visit an astroid.From node
|
264 |
|
265 |
resolve module dependencies
|
266 |
"""
|
267 |
basename = node.modname |
268 |
context_file = node.root().file |
269 |
if context_file is not None: |
270 |
relative = modutils.is_relative(basename, context_file) |
271 |
else:
|
272 |
relative = False
|
273 |
for name in node.names: |
274 |
if name[0] == '*': |
275 |
continue
|
276 |
# analyze dependencies
|
277 |
fullname = '%s.%s' % (basename, name[0]) |
278 |
if fullname.find('.') > -1: |
279 |
try:
|
280 |
# TODO: don't use get_module_part,
|
281 |
# missing package precedence
|
282 |
fullname = modutils.get_module_part(fullname, |
283 |
context_file) |
284 |
except ImportError: |
285 |
continue
|
286 |
if fullname != basename:
|
287 |
self._imported_module(node, fullname, relative)
|
288 |
|
289 |
def compute_module(self, context_name, mod_path): |
290 |
"""return true if the module should be added to dependencies"""
|
291 |
package_dir = os.path.dirname(self.project.path)
|
292 |
if context_name == mod_path:
|
293 |
return 0 |
294 |
elif modutils.is_standard_module(mod_path, (package_dir,)):
|
295 |
return 1 |
296 |
return 0 |
297 |
|
298 |
def _imported_module(self, node, mod_path, relative): |
299 |
"""Notify an imported module, used to analyze dependencies"""
|
300 |
module = node.root() |
301 |
context_name = module.name |
302 |
if relative:
|
303 |
mod_path = '%s.%s' % ('.'.join(context_name.split('.')[:-1]), |
304 |
mod_path) |
305 |
if self.compute_module(context_name, mod_path): |
306 |
# handle dependencies
|
307 |
if not hasattr(module, 'depends'): |
308 |
module.depends = [] |
309 |
mod_paths = module.depends |
310 |
if mod_path not in mod_paths: |
311 |
mod_paths.append(mod_path) |
312 |
|
313 |
|
314 |
class Project(object): |
315 |
"""a project handle a set of modules / packages"""
|
316 |
def __init__(self, name=''): |
317 |
self.name = name
|
318 |
self.path = None |
319 |
self.modules = []
|
320 |
self.locals = {}
|
321 |
self.__getitem__ = self.locals.__getitem__ |
322 |
self.__iter__ = self.locals.__iter__ |
323 |
self.values = self.locals.values |
324 |
self.keys = self.locals.keys |
325 |
self.items = self.locals.items |
326 |
|
327 |
def add_module(self, node): |
328 |
self.locals[node.name] = node
|
329 |
self.modules.append(node)
|
330 |
|
331 |
def get_module(self, name): |
332 |
return self.locals[name] |
333 |
|
334 |
def get_children(self): |
335 |
return self.modules |
336 |
|
337 |
def __repr__(self): |
338 |
return '<Project %r at %s (%s modules)>' % (self.name, id(self), |
339 |
len(self.modules)) |
340 |
|
341 |
|
342 |
def project_from_files(files, func_wrapper=_astroid_wrapper, |
343 |
project_name="no name",
|
344 |
black_list=('CVS',)):
|
345 |
"""return a Project from a list of files or modules"""
|
346 |
# build the project representation
|
347 |
astroid_manager = manager.AstroidManager() |
348 |
project = Project(project_name) |
349 |
for something in files: |
350 |
if not os.path.exists(something): |
351 |
fpath = modutils.file_from_modpath(something.split('.'))
|
352 |
elif os.path.isdir(something):
|
353 |
fpath = os.path.join(something, '__init__.py')
|
354 |
else:
|
355 |
fpath = something |
356 |
ast = func_wrapper(astroid_manager.ast_from_file, fpath) |
357 |
if ast is None: |
358 |
continue
|
359 |
# XXX why is first file defining the project.path ?
|
360 |
project.path = project.path or ast.file
|
361 |
project.add_module(ast) |
362 |
base_name = ast.name |
363 |
# recurse in package except if __init__ was explicitly given
|
364 |
if ast.package and something.find('__init__') == -1: |
365 |
# recurse on others packages / modules if this is a package
|
366 |
for fpath in modutils.get_module_files(os.path.dirname(ast.file), |
367 |
black_list): |
368 |
ast = func_wrapper(astroid_manager.ast_from_file, fpath) |
369 |
if ast is None or ast.name == base_name: |
370 |
continue
|
371 |
project.add_module(ast) |
372 |
return project
|