Statistics
| Revision:

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