| Index: third_party/pylint/checkers/imports.py
|
| ===================================================================
|
| --- third_party/pylint/checkers/imports.py (revision 292881)
|
| +++ third_party/pylint/checkers/imports.py (working copy)
|
| @@ -1,4 +1,4 @@
|
| -# Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE).
|
| +# Copyright (c) 2003-2013 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
|
| @@ -12,55 +12,51 @@
|
| #
|
| # You should have received a copy of the GNU 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.
|
| +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
| """imports checkers for Python code"""
|
|
|
| +import sys
|
| +
|
| from logilab.common.graph import get_cycles, DotBackend
|
| -from logilab.common.modutils import is_standard_module
|
| from logilab.common.ureports import VerbatimText, Paragraph
|
|
|
| -from logilab import astng
|
| -from logilab.astng import are_exclusive
|
| +import astroid
|
| +from astroid import are_exclusive
|
| +from astroid.modutils import get_module_part, is_standard_module
|
|
|
| -from pylint.interfaces import IASTNGChecker
|
| -from pylint.checkers import BaseChecker, EmptyReport
|
| +from pylint.interfaces import IAstroidChecker
|
| +from pylint.utils import EmptyReport
|
| +from pylint.checkers import BaseChecker
|
| +from pylint.checkers.utils import check_messages
|
|
|
|
|
| def get_first_import(node, context, name, base, level):
|
| """return the node where [base.]<name> is imported or None if not found
|
| """
|
| + fullname = '%s.%s' % (base, name) if base else name
|
| +
|
| first = None
|
| found = False
|
| - for first in context.values():
|
| - if isinstance(first, astng.Import):
|
| - if name in [iname[0] for iname in first.names]:
|
| + for first in context.body:
|
| + if first is node:
|
| + continue
|
| + if first.scope() is node.scope() and first.fromlineno > node.fromlineno:
|
| + continue
|
| + if isinstance(first, astroid.Import):
|
| + if any(fullname == iname[0] for iname in first.names):
|
| found = True
|
| break
|
| - elif isinstance(first, astng.From):
|
| - if base == first.modname and level == first.level and \
|
| - name in [iname[0] for iname in first.names]:
|
| + elif isinstance(first, astroid.From):
|
| + if level == first.level and any(
|
| + fullname == '%s.%s' % (first.modname, iname[0])
|
| + for iname in first.names):
|
| found = True
|
| break
|
| - if found and first is not node and not are_exclusive(first, node):
|
| + if found and not are_exclusive(first, node):
|
| return first
|
|
|
| # utilities to represents import dependencies as tree and dot graph ###########
|
|
|
| -def filter_dependencies_info(dep_info, package_dir, mode='external'):
|
| - """filter external or internal dependencies from dep_info (return a
|
| - new dictionary containing the filtered modules only)
|
| - """
|
| - if mode == 'external':
|
| - filter_func = lambda x: not is_standard_module(x, (package_dir,))
|
| - else:
|
| - assert mode == 'internal'
|
| - filter_func = lambda x: is_standard_module(x, (package_dir,))
|
| - result = {}
|
| - for importee, importers in dep_info.items():
|
| - if filter_func(importee):
|
| - result[importee] = importers
|
| - return result
|
| -
|
| def make_tree_defs(mod_files_list):
|
| """get a list of 2-uple (module, list_of_files_which_import_this_module),
|
| it will return a dictionary to represent this as a tree
|
| @@ -86,7 +82,7 @@
|
| lines.append('%s %s' % (mod, files))
|
| sub_indent_str = ' '
|
| else:
|
| - lines.append('%s\-%s %s' % (indent_str, mod, files))
|
| + lines.append(r'%s\-%s %s' % (indent_str, mod, files))
|
| if i == len(nodes)-1:
|
| sub_indent_str = '%s ' % indent_str
|
| else:
|
| @@ -100,9 +96,9 @@
|
| """write dependencies as a dot (graphviz) file
|
| """
|
| done = {}
|
| - printer = DotBackend(filename[:-4], rankdir = "LR")
|
| + printer = DotBackend(filename[:-4], rankdir='LR')
|
| printer.emit('URL="." node[shape="box"]')
|
| - for modname, dependencies in dep_info.items():
|
| + for modname, dependencies in sorted(dep_info.iteritems()):
|
| done[modname] = 1
|
| printer.emit_node(modname)
|
| for modname in dependencies:
|
| @@ -109,7 +105,7 @@
|
| if modname not in done:
|
| done[modname] = 1
|
| printer.emit_node(modname)
|
| - for depmodname, dependencies in dep_info.items():
|
| + for depmodname, dependencies in sorted(dep_info.iteritems()):
|
| for modname in dependencies:
|
| printer.emit_edge(modname, depmodname)
|
| printer.generate(filename)
|
| @@ -128,26 +124,36 @@
|
|
|
| MSGS = {
|
| 'F0401': ('Unable to import %s',
|
| + 'import-error',
|
| 'Used when pylint has been unable to import a module.'),
|
| 'R0401': ('Cyclic import (%s)',
|
| + 'cyclic-import',
|
| 'Used when a cyclic import between two or more modules is \
|
| detected.'),
|
|
|
| 'W0401': ('Wildcard import %s',
|
| + 'wildcard-import',
|
| 'Used when `from module import *` is detected.'),
|
| 'W0402': ('Uses of a deprecated module %r',
|
| + 'deprecated-module',
|
| 'Used a module marked as deprecated is imported.'),
|
| 'W0403': ('Relative import %r, should be %r',
|
| - 'Used when an import relative to the package directory is \
|
| - detected.'),
|
| + 'relative-import',
|
| + 'Used when an import relative to the package directory is '
|
| + 'detected.',
|
| + {'maxversion': (3, 0)}),
|
| 'W0404': ('Reimport %r (imported line %s)',
|
| + 'reimported',
|
| 'Used when a module is reimported multiple times.'),
|
| 'W0406': ('Module import itself',
|
| + 'import-self',
|
| 'Used when a module is importing itself.'),
|
|
|
| 'W0410': ('__future__ import is not the first non docstring statement',
|
| + 'misplaced-future',
|
| 'Python 2.5 and greater require __future__ import to be the \
|
| - first non docstring statement in the module.'),
|
| + first non docstring statement in the module.',
|
| + {'maxversion': (3, 0)}),
|
| }
|
|
|
| class ImportsChecker(BaseChecker):
|
| @@ -158,20 +164,23 @@
|
| * uses of deprecated modules
|
| """
|
|
|
| - __implements__ = IASTNGChecker
|
| + __implements__ = IAstroidChecker
|
|
|
| name = 'imports'
|
| msgs = MSGS
|
| priority = -2
|
|
|
| + if sys.version_info < (3,):
|
| + deprecated_modules = ('regsub', 'TERMIOS', 'Bastion', 'rexec')
|
| + else:
|
| + deprecated_modules = ('stringprep', 'optparse')
|
| options = (('deprecated-modules',
|
| - {'default' : ('regsub', 'string', 'TERMIOS',
|
| - 'Bastion', 'rexec'),
|
| + {'default' : deprecated_modules,
|
| 'type' : 'csv',
|
| 'metavar' : '<modules>',
|
| 'help' : 'Deprecated modules which should not be used, \
|
| separated by a comma'}
|
| - ),
|
| + ),
|
| ('import-graph',
|
| {'default' : '',
|
| 'type' : 'string',
|
| @@ -178,7 +187,7 @@
|
| 'metavar' : '<file.dot>',
|
| 'help' : 'Create a graph of every (i.e. internal and \
|
| external) dependencies in the given file (report RP0402 must not be disabled)'}
|
| - ),
|
| + ),
|
| ('ext-import-graph',
|
| {'default' : '',
|
| 'type' : 'string',
|
| @@ -185,7 +194,7 @@
|
| 'metavar' : '<file.dot>',
|
| 'help' : 'Create a graph of external dependencies in the \
|
| given file (report RP0402 must not be disabled)'}
|
| - ),
|
| + ),
|
| ('int-import-graph',
|
| {'default' : '',
|
| 'type' : 'string',
|
| @@ -192,10 +201,9 @@
|
| 'metavar' : '<file.dot>',
|
| 'help' : 'Create a graph of internal dependencies in the \
|
| given file (report RP0402 must not be disabled)'}
|
| - ),
|
| + ),
|
| + )
|
|
|
| - )
|
| -
|
| def __init__(self, linter=None):
|
| BaseChecker.__init__(self, linter)
|
| self.stats = None
|
| @@ -205,7 +213,7 @@
|
| self.report_external_dependencies),
|
| ('RP0402', 'Modules dependencies graph',
|
| self.report_dependencies_graph),
|
| - )
|
| + )
|
|
|
| def open(self):
|
| """called before visiting project (i.e set of modules)"""
|
| @@ -217,9 +225,9 @@
|
| def close(self):
|
| """called before visiting project (i.e set of modules)"""
|
| # don't try to compute cycles if the associated message is disabled
|
| - if self.linter.is_message_enabled('R0401'):
|
| + if self.linter.is_message_enabled('cyclic-import'):
|
| for cycle in get_cycles(self.import_graph):
|
| - self.add_message('R0401', args=' -> '.join(cycle))
|
| + self.add_message('cyclic-import', args=' -> '.join(cycle))
|
|
|
| def visit_import(self, node):
|
| """triggered when an import statement is seen"""
|
| @@ -233,7 +241,9 @@
|
| self._check_deprecated_module(node, name)
|
| self._check_reimport(node, name)
|
|
|
| -
|
| + # TODO This appears to be the list of all messages of the checker...
|
| + # @check_messages('W0410', 'W0401', 'W0403', 'W0402', 'W0404', 'W0406', 'F0401')
|
| + @check_messages(*(MSGS.keys()))
|
| def visit_from(self, node):
|
| """triggered when a from statement is seen"""
|
| basename = node.modname
|
| @@ -242,10 +252,13 @@
|
| prev = node.previous_sibling()
|
| if prev:
|
| # consecutive future statements are possible
|
| - if not (isinstance(prev, astng.From)
|
| - and prev.modname == '__future__'):
|
| - self.add_message('W0410', node=node)
|
| + if not (isinstance(prev, astroid.From)
|
| + and prev.modname == '__future__'):
|
| + self.add_message('misplaced-future', node=node)
|
| return
|
| + for name, _ in node.names:
|
| + if name == '*':
|
| + self.add_message('wildcard-import', args=basename, node=node)
|
| modnode = node.root()
|
| importedmodnode = self.get_imported_module(modnode, node, basename)
|
| if importedmodnode is None:
|
| @@ -253,21 +266,19 @@
|
| self._check_relative_import(modnode, node, importedmodnode, basename)
|
| self._check_deprecated_module(node, basename)
|
| for name, _ in node.names:
|
| - if name == '*':
|
| - self.add_message('W0401', args=basename, node=node)
|
| - continue
|
| - self._add_imported_module(node, '%s.%s' % (importedmodnode.name, name))
|
| - self._check_reimport(node, name, basename, node.level)
|
| + if name != '*':
|
| + self._add_imported_module(node, '%s.%s' % (importedmodnode.name, name))
|
| + self._check_reimport(node, name, basename, node.level)
|
|
|
| def get_imported_module(self, modnode, importnode, modname):
|
| try:
|
| return importnode.do_import_module(modname)
|
| - except astng.InferenceError, ex:
|
| + except astroid.InferenceError, ex:
|
| if str(ex) != modname:
|
| args = '%r (%s)' % (modname, ex)
|
| else:
|
| args = repr(modname)
|
| - self.add_message("F0401", args=args, node=importnode)
|
| + self.add_message("import-error", args=args, node=importnode)
|
|
|
| def _check_relative_import(self, modnode, importnode, importedmodnode,
|
| importedasname):
|
| @@ -274,7 +285,7 @@
|
| """check relative import. node is either an Import or From node, modname
|
| the imported module name.
|
| """
|
| - if 'W0403' not in self.active_msgs:
|
| + if not self.linter.is_message_enabled('relative-import'):
|
| return
|
| if importedmodnode.file is None:
|
| return False # built-in module
|
| @@ -284,15 +295,19 @@
|
| return False
|
| if importedmodnode.name != importedasname:
|
| # this must be a relative import...
|
| - self.add_message('W0403', args=(importedasname, importedmodnode.name),
|
| + self.add_message('relative-import', args=(importedasname, importedmodnode.name),
|
| node=importnode)
|
|
|
| def _add_imported_module(self, node, importedmodname):
|
| """notify an imported module, used to analyze dependencies"""
|
| + try:
|
| + importedmodname = get_module_part(importedmodname)
|
| + except ImportError:
|
| + pass
|
| context_name = node.root().name
|
| if context_name == importedmodname:
|
| # module importing itself !
|
| - self.add_message('W0406', node=node)
|
| + self.add_message('import-self', node=node)
|
| elif not is_standard_module(importedmodname):
|
| # handle dependencies
|
| importedmodnames = self.stats['dependencies'].setdefault(
|
| @@ -299,37 +314,36 @@
|
| importedmodname, set())
|
| if not context_name in importedmodnames:
|
| importedmodnames.add(context_name)
|
| - if is_standard_module( importedmodname, (self.package_dir(),) ):
|
| - # update import graph
|
| - mgraph = self.import_graph.setdefault(context_name, set())
|
| - if not importedmodname in mgraph:
|
| - mgraph.add(importedmodname)
|
| + # update import graph
|
| + mgraph = self.import_graph.setdefault(context_name, set())
|
| + if not importedmodname in mgraph:
|
| + mgraph.add(importedmodname)
|
|
|
| def _check_deprecated_module(self, node, mod_path):
|
| """check if the module is deprecated"""
|
| for mod_name in self.config.deprecated_modules:
|
| if mod_path == mod_name or mod_path.startswith(mod_name + '.'):
|
| - self.add_message('W0402', node=node, args=mod_path)
|
| + self.add_message('deprecated-module', node=node, args=mod_path)
|
|
|
| - def _check_reimport(self, node, name, basename=None, level=0):
|
| + def _check_reimport(self, node, name, basename=None, level=None):
|
| """check if the import is necessary (i.e. not already done)"""
|
| - if 'W0404' not in self.active_msgs:
|
| + if not self.linter.is_message_enabled('reimported'):
|
| return
|
| frame = node.frame()
|
| root = node.root()
|
| contexts = [(frame, level)]
|
| if root is not frame:
|
| - contexts.append((root, 0))
|
| + contexts.append((root, None))
|
| for context, level in contexts:
|
| first = get_first_import(node, context, name, basename, level)
|
| if first is not None:
|
| - self.add_message('W0404', node=node,
|
| + self.add_message('reimported', node=node,
|
| args=(name, first.fromlineno))
|
|
|
|
|
| def report_external_dependencies(self, sect, _, dummy):
|
| """return a verbatim layout for displaying dependencies"""
|
| - dep_info = make_tree_defs(self._external_dependencies_info().items())
|
| + dep_info = make_tree_defs(self._external_dependencies_info().iteritems())
|
| if not dep_info:
|
| raise EmptyReport()
|
| tree_str = repr_tree_defs(dep_info)
|
| @@ -359,8 +373,11 @@
|
| cache them
|
| """
|
| if self.__ext_dep_info is None:
|
| - self.__ext_dep_info = filter_dependencies_info(
|
| - self.stats['dependencies'], self.package_dir(), 'external')
|
| + package = self.linter.current_name
|
| + self.__ext_dep_info = result = {}
|
| + for importee, importers in self.stats['dependencies'].iteritems():
|
| + if not importee.startswith(package):
|
| + result[importee] = importers
|
| return self.__ext_dep_info
|
|
|
| def _internal_dependencies_info(self):
|
| @@ -368,8 +385,11 @@
|
| cache them
|
| """
|
| if self.__int_dep_info is None:
|
| - self.__int_dep_info = filter_dependencies_info(
|
| - self.stats['dependencies'], self.package_dir(), 'internal')
|
| + package = self.linter.current_name
|
| + self.__int_dep_info = result = {}
|
| + for importee, importers in self.stats['dependencies'].iteritems():
|
| + if importee.startswith(package):
|
| + result[importee] = importers
|
| return self.__int_dep_info
|
|
|
|
|
|
|