summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaudiu Popa <cpopa@cloudbasesolutions.com>2015-06-17 22:23:38 +0300
committerClaudiu Popa <cpopa@cloudbasesolutions.com>2015-06-17 22:23:38 +0300
commit0208d0716e5413689b1592691f92b2c16d4b9a94 (patch)
treeee95bba59a0e89a18d38da8c367e0fe6b18a0ac9
parenta6863085d1ce484220a556b3fe8904ad59cf3367 (diff)
downloadpylint-0208d0716e5413689b1592691f92b2c16d4b9a94.tar.gz
Import astroid inspector to pyreverse.inspector.
-rw-r--r--ChangeLog3
-rw-r--r--pylint/pyreverse/diadefslib.py2
-rw-r--r--pylint/pyreverse/inspector.py268
-rw-r--r--pylint/pyreverse/main.py2
-rw-r--r--pylint/test/unittest_pyreverse_diadefs.py2
-rw-r--r--pylint/test/unittest_pyreverse_inspector.py79
-rw-r--r--pylint/test/unittest_pyreverse_writer.py2
7 files changed, 354 insertions, 4 deletions
diff --git a/ChangeLog b/ChangeLog
index 5a6610f..0c25dcd 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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