diff options
author | Claudiu Popa <cpopa@cloudbasesolutions.com> | 2015-06-17 22:23:38 +0300 |
---|---|---|
committer | Claudiu Popa <cpopa@cloudbasesolutions.com> | 2015-06-17 22:23:38 +0300 |
commit | 0208d0716e5413689b1592691f92b2c16d4b9a94 (patch) | |
tree | ee95bba59a0e89a18d38da8c367e0fe6b18a0ac9 | |
parent | a6863085d1ce484220a556b3fe8904ad59cf3367 (diff) | |
download | pylint-0208d0716e5413689b1592691f92b2c16d4b9a94.tar.gz |
Import astroid inspector to pyreverse.inspector.
-rw-r--r-- | ChangeLog | 3 | ||||
-rw-r--r-- | pylint/pyreverse/diadefslib.py | 2 | ||||
-rw-r--r-- | pylint/pyreverse/inspector.py | 268 | ||||
-rw-r--r-- | pylint/pyreverse/main.py | 2 | ||||
-rw-r--r-- | pylint/test/unittest_pyreverse_diadefs.py | 2 | ||||
-rw-r--r-- | pylint/test/unittest_pyreverse_inspector.py | 79 | ||||
-rw-r--r-- | pylint/test/unittest_pyreverse_writer.py | 2 |
7 files changed, 354 insertions, 4 deletions
@@ -163,6 +163,9 @@ ChangeLog for Pylint * Take in consideration differences between arguments of various type of functions (classmethods, staticmethods, properties) when checking for `arguments-differ`. Closes issue #548. + + * astroid.inspector was moved to pylint.pyreverse, since it belongs + there and it doesn't need to be in astroid. diff --git a/pylint/pyreverse/diadefslib.py b/pylint/pyreverse/diadefslib.py index e0dc8cf..89c77f3 100644 --- a/pylint/pyreverse/diadefslib.py +++ b/pylint/pyreverse/diadefslib.py @@ -217,7 +217,7 @@ class DiadefsHandler(object): def get_diadefs(self, project, linker): """get the diagrams configuration data - :param linker: astroid.inspector.Linker(IdGeneratorMixIn, LocalsVisitor) + :param linker: pyreverse.inspector.Linker(IdGeneratorMixIn, LocalsVisitor) :param project: astroid.manager.Project """ diff --git a/pylint/pyreverse/inspector.py b/pylint/pyreverse/inspector.py new file mode 100644 index 0000000..06c1c93 --- /dev/null +++ b/pylint/pyreverse/inspector.py @@ -0,0 +1,268 @@ +# copyright 2003-2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This file is part of astroid. +# +# astroid is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 2.1 of the License, or (at your +# option) any later version. +# +# astroid is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License along +# with astroid. If not, see <http://www.gnu.org/licenses/>. + +""" +Visitor doing some postprocessing on the astroid tree. +Try to resolve definitions (namespace) dictionary, relationship... +""" + +import os + +import astroid +from astroid import utils +from astroid import modutils + + +class IdGeneratorMixIn(object): + """Mixin adding the ability to generate integer uid.""" + + def __init__(self, start_value=0): + self.id_count = start_value + + def init_counter(self, start_value=0): + """init the id counter + """ + self.id_count = start_value + + def generate_id(self): + """generate a new identifier + """ + self.id_count += 1 + return self.id_count + + +class Linker(IdGeneratorMixIn, utils.LocalsVisitor): + """Walk on the project tree and resolve relationships. + + According to options the following attributes may be + added to visited nodes: + + * uid, + a unique identifier for the node (on astroid.Project, astroid.Module, + astroid.Class and astroid.locals_type). Only if the linker + has been instantiated with tag=True parameter (False by default). + + * Function + a mapping from locals names to their bounded value, which may be a + constant like a string or an integer, or an astroid node + (on astroid.Module, astroid.Class and astroid.Function). + + * instance_attrs_type + as locals_type but for klass member attributes (only on astroid.Class) + + * implements, + list of implemented interface _objects_ (only on astroid.Class nodes) + """ + + def __init__(self, project, inherited_interfaces=0, tag=False): + IdGeneratorMixIn.__init__(self) + astroid.utils.LocalsVisitor.__init__(self) + # take inherited interface in consideration or not + self.inherited_interfaces = inherited_interfaces + # tag nodes or not + self.tag = tag + # visited project + self.project = project + + def visit_project(self, node): + """visit an astroid.Project node + + * optionally tag the node with a unique id + """ + if self.tag: + node.uid = self.generate_id() + for module in node.modules: + self.visit(module) + + def visit_package(self, node): + """visit an astroid.Package node + + * optionally tag the node with a unique id + """ + if self.tag: + node.uid = self.generate_id() + for subelmt in node.values(): + self.visit(subelmt) + + def visit_module(self, node): + """visit an astroid.Module node + + * set the locals_type mapping + * set the depends mapping + * optionally tag the node with a unique id + """ + if hasattr(node, 'locals_type'): + return + node.locals_type = {} + node.depends = [] + if self.tag: + node.uid = self.generate_id() + + def visit_class(self, node): + """visit an astroid.Class node + + * set the locals_type and instance_attrs_type mappings + * set the implements list and build it + * optionally tag the node with a unique id + """ + if hasattr(node, 'locals_type'): + return + node.locals_type = {} + if self.tag: + node.uid = self.generate_id() + # resolve ancestors + for baseobj in node.ancestors(recurs=False): + specializations = getattr(baseobj, 'specializations', []) + specializations.append(node) + baseobj.specializations = specializations + # resolve instance attributes + node.instance_attrs_type = {} + for assattrs in node.instance_attrs.values(): + for assattr in assattrs: + self.handle_assattr_type(assattr, node) + # resolve implemented interface + try: + node.implements = list(node.interfaces(self.inherited_interfaces)) + except astroid.InferenceError: + node.implements = () + + def visit_function(self, node): + """visit an astroid.Function node + + * set the locals_type mapping + * optionally tag the node with a unique id + """ + if hasattr(node, 'locals_type'): + return + node.locals_type = {} + if self.tag: + node.uid = self.generate_id() + + link_project = visit_project + link_module = visit_module + link_class = visit_class + link_function = visit_function + + @staticmethod + def visit_assname(node): + """visit an astroid.AssName node + + handle locals_type + """ + # avoid double parsing done by different Linkers.visit + # running over the same project: + if hasattr(node, '_handled'): + return + node._handled = True + if node.name in node.frame(): + frame = node.frame() + else: + # the name has been defined as 'global' in the frame and belongs + # there. Btw the frame is not yet visited as the name is in the + # root locals; the frame hence has no locals_type attribute + frame = node.root() + try: + values = node.infered() + try: + already_infered = frame.locals_type[node.name] + for valnode in values: + if valnode not in already_infered: + already_infered.append(valnode) + except KeyError: + frame.locals_type[node.name] = values + except astroid.InferenceError: + pass + + @staticmethod + def handle_assattr_type(node, parent): + """handle an astroid.AssAttr node + + handle instance_attrs_type + """ + try: + values = list(node.infer()) + try: + already_infered = parent.instance_attrs_type[node.attrname] + for valnode in values: + if valnode not in already_infered: + already_infered.append(valnode) + except KeyError: + parent.instance_attrs_type[node.attrname] = values + except astroid.InferenceError: + pass + + def visit_import(self, node): + """visit an astroid.Import node + + resolve module dependencies + """ + context_file = node.root().file + for name in node.names: + relative = modutils.is_relative(name[0], context_file) + self._imported_module(node, name[0], relative) + + def visit_from(self, node): + """visit an astroid.From node + + resolve module dependencies + """ + basename = node.modname + context_file = node.root().file + if context_file is not None: + relative = modutils.is_relative(basename, context_file) + else: + relative = False + for name in node.names: + if name[0] == '*': + continue + # analyze dependencies + fullname = '%s.%s' % (basename, name[0]) + if fullname.find('.') > -1: + try: + # TODO: don't use get_module_part, + # missing package precedence + fullname = modutils.get_module_part(fullname, + context_file) + except ImportError: + continue + if fullname != basename: + self._imported_module(node, fullname, relative) + + def compute_module(self, context_name, mod_path): + """return true if the module should be added to dependencies""" + package_dir = os.path.dirname(self.project.path) + if context_name == mod_path: + return 0 + elif modutils.is_standard_module(mod_path, (package_dir,)): + return 1 + return 0 + + def _imported_module(self, node, mod_path, relative): + """Notify an imported module, used to analyze dependencies""" + module = node.root() + context_name = module.name + if relative: + mod_path = '%s.%s' % ('.'.join(context_name.split('.')[:-1]), + mod_path) + if self.compute_module(context_name, mod_path): + # handle dependencies + if not hasattr(module, 'depends'): + module.depends = [] + mod_paths = module.depends + if mod_path not in mod_paths: + mod_paths.append(mod_path) diff --git a/pylint/pyreverse/main.py b/pylint/pyreverse/main.py index 408c172..f517d23 100644 --- a/pylint/pyreverse/main.py +++ b/pylint/pyreverse/main.py @@ -23,8 +23,8 @@ from __future__ import print_function import sys, os from logilab.common.configuration import ConfigurationMixIn from astroid.manager import AstroidManager -from astroid.inspector import Linker +from pylint.pyreverse.inspector import Linker from pylint.pyreverse.diadefslib import DiadefsHandler from pylint.pyreverse import writer from pylint.pyreverse.utils import insert_default_options diff --git a/pylint/test/unittest_pyreverse_diadefs.py b/pylint/test/unittest_pyreverse_diadefs.py index 5f775aa..511ccea 100644 --- a/pylint/test/unittest_pyreverse_diadefs.py +++ b/pylint/test/unittest_pyreverse_diadefs.py @@ -24,8 +24,8 @@ import six import astroid from astroid import MANAGER -from astroid.inspector import Linker +from pylint.pyreverse.inspector import Linker from pylint.pyreverse.diadefslib import * from unittest_pyreverse_writer import Config, get_project diff --git a/pylint/test/unittest_pyreverse_inspector.py b/pylint/test/unittest_pyreverse_inspector.py new file mode 100644 index 0000000..3b2ecc4 --- /dev/null +++ b/pylint/test/unittest_pyreverse_inspector.py @@ -0,0 +1,79 @@ +# Copyright (c) 2003-2015 LOGILAB S.A. (Paris, FRANCE). +# http://www.logilab.fr/ -- mailto:contact@logilab.fr +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +""" + for the visitors.diadefs module +""" + +import unittest + +from astroid import nodes +from astroid import bases +from astroid import manager + +from pylint.pyreverse import inspector +from unittest_pyreverse_writer import get_project + +MANAGER = manager.AstroidManager() + +def astroid_wrapper(func, modname): + return func(modname) + + +class LinkerTest(unittest.TestCase): + + def setUp(self): + super(LinkerTest, self).setUp() + self.project = get_project('data') + self.linker = inspector.Linker(self.project) + self.linker.visit(self.project) + + def test_class_implements(self): + klass = self.project.get_module('data.clientmodule_test')['Ancestor'] + self.assertTrue(hasattr(klass, 'implements')) + self.assertEqual(len(klass.implements), 1) + self.assertTrue(isinstance(klass.implements[0], nodes.Class)) + self.assertEqual(klass.implements[0].name, "Interface") + klass = self.project.get_module('data.clientmodule_test')['Specialization'] + self.assertTrue(hasattr(klass, 'implements')) + self.assertEqual(len(klass.implements), 0) + + def test_locals_assignment_resolution(self): + klass = self.project.get_module('data.clientmodule_test')['Specialization'] + self.assertTrue(hasattr(klass, 'locals_type')) + type_dict = klass.locals_type + self.assertEqual(len(type_dict), 2) + keys = sorted(type_dict.keys()) + self.assertEqual(keys, ['TYPE', 'top']) + self.assertEqual(len(type_dict['TYPE']), 1) + self.assertEqual(type_dict['TYPE'][0].value, 'final class') + self.assertEqual(len(type_dict['top']), 1) + self.assertEqual(type_dict['top'][0].value, 'class') + + def test_instance_attrs_resolution(self): + klass = self.project.get_module('data.clientmodule_test')['Specialization'] + self.assertTrue(hasattr(klass, 'instance_attrs_type')) + type_dict = klass.instance_attrs_type + self.assertEqual(len(type_dict), 2) + keys = sorted(type_dict.keys()) + self.assertEqual(keys, ['_id', 'relation']) + self.assertTrue(isinstance(type_dict['relation'][0], bases.Instance), + type_dict['relation']) + self.assertEqual(type_dict['relation'][0].name, 'DoNothing') + self.assertIs(type_dict['_id'][0], bases.YES) + + +if __name__ == '__main__': + unittest.main() diff --git a/pylint/test/unittest_pyreverse_writer.py b/pylint/test/unittest_pyreverse_writer.py index b4da835..f57b5a2 100644 --- a/pylint/test/unittest_pyreverse_writer.py +++ b/pylint/test/unittest_pyreverse_writer.py @@ -26,8 +26,8 @@ from difflib import unified_diff import unittest from astroid import MANAGER -from astroid.inspector import Linker +from pylint.pyreverse.inspector import Linker from pylint.pyreverse.diadefslib import DefaultDiadefGenerator, DiadefsHandler from pylint.pyreverse.writer import DotWriter from pylint.pyreverse.utils import get_visibility |