summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrett Cannon <brett@python.org>2014-08-29 15:48:24 -0400
committerBrett Cannon <brett@python.org>2014-08-29 15:48:24 -0400
commit8e23aa2b179274c9c1b128a8b3daf8d2e24cfd75 (patch)
treec234e08aa390be9bca941e66e508071e5e44a801
parent001469c8272a235b4f62b9b5c3ee248635168ad4 (diff)
parentc2cc31874880d7f0d5bd444c6a3b17fa817e2509 (diff)
downloadpylint-python_6.tar.gz
Merge with defaultpython_6
-rw-r--r--ChangeLog3
-rw-r--r--__pkginfo__.py6
-rw-r--r--checkers/base.py24
-rw-r--r--checkers/classes.py13
-rw-r--r--checkers/exceptions.py6
-rw-r--r--checkers/format.py12
-rw-r--r--checkers/imports.py14
-rw-r--r--checkers/logging.py7
-rw-r--r--checkers/misc.py5
-rw-r--r--checkers/newstyle.py2
-rw-r--r--checkers/similar.py29
-rw-r--r--checkers/strings.py17
-rw-r--r--checkers/utils.py6
-rw-r--r--checkers/variables.py19
-rw-r--r--config.py7
-rwxr-xr-xepylint.py7
-rw-r--r--gui.py5
-rw-r--r--lint.py19
-rw-r--r--pyreverse/diadefslib.py6
-rw-r--r--pyreverse/diagrams.py8
-rw-r--r--pyreverse/main.py3
-rw-r--r--pyreverse/utils.py5
-rw-r--r--reporters/__init__.py3
-rw-r--r--reporters/text.py6
-rw-r--r--setup.py12
-rw-r--r--testutils.py19
-rw-r--r--tox.ini1
-rw-r--r--utils.py37
28 files changed, 166 insertions, 135 deletions
diff --git a/ChangeLog b/ChangeLog
index d9276d8..5ae6341 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,6 +2,9 @@ ChangeLog for Pylint
====================
--
+ * Port source code to be Python 2/3 compatible. This drops the
+ need for 2to3, but does drop support for Python 2.5.
+
* Each message now comes with a confidence level attached, and
can be filtered base on this level. This allows to filter out
all messages that were emitted even though an inference failure
diff --git a/__pkginfo__.py b/__pkginfo__.py
index 757bfe9..255c6c1 100644
--- a/__pkginfo__.py
+++ b/__pkginfo__.py
@@ -22,11 +22,9 @@ modname = distname = 'pylint'
numversion = (1, 3, 0)
version = '.'.join([str(num) for num in numversion])
+install_requires = ['logilab-common >= 0.53.0', 'astroid >= 1.2', 'six']
if sys.version_info < (2, 6):
- install_requires = ['logilab-common >= 0.53.0', 'astroid >= 1.2',
- 'StringFormat']
-else:
- install_requires = ['logilab-common >= 0.53.0', 'astroid >= 1.2']
+ install_requires.append('StringFormat')
license = 'GPL'
description = "python code static checker"
diff --git a/checkers/base.py b/checkers/base.py
index 298ac5f..501d930 100644
--- a/checkers/base.py
+++ b/checkers/base.py
@@ -43,6 +43,8 @@ from pylint.checkers.utils import (
import re
+import six
+from six.moves import zip
# regex for class/function/variable/constant name
CLASS_NAME_RGX = re.compile('[A-Z_][a-zA-Z0-9]+$')
@@ -175,7 +177,7 @@ def decorated_with_abc(func):
if func.decorators:
for node in func.decorators.nodes:
try:
- infered = node.infer().next()
+ infered = next(node.infer())
except InferenceError:
continue
if infered and infered.qname() in ABC_METHODS:
@@ -380,7 +382,7 @@ class BasicErrorChecker(_BasicChecker):
abc.ABCMeta as metaclass.
"""
try:
- infered = node.func.infer().next()
+ infered = next(node.func.infer())
except astroid.InferenceError:
return
if not isinstance(infered, astroid.Class):
@@ -576,7 +578,7 @@ functions, methods
"""check for various kind of statements without effect"""
expr = node.value
if isinstance(expr, astroid.Const) and isinstance(expr.value,
- basestring):
+ six.string_types):
# treat string statement in a separated message
# Handle PEP-257 attribute docstrings.
# An attribute docstring is defined as being a string right after
@@ -653,7 +655,7 @@ functions, methods
# ordinary_args[i].name == call.args[i].name.
if len(ordinary_args) != len(call.args):
return
- for i in xrange(len(ordinary_args)):
+ for i in range(len(ordinary_args)):
if not isinstance(call.args[i], astroid.Name):
return
if node.args.args[i].name != call.args[i].name:
@@ -674,7 +676,7 @@ functions, methods
# check for dangerous default values as arguments
for default in node.args.defaults:
try:
- value = default.infer().next()
+ value = next(default.infer())
except astroid.InferenceError:
continue
@@ -840,7 +842,7 @@ functions, methods
# Try to see if we have iter().
if isinstance(node.args[0], astroid.CallFunc):
try:
- func = node.args[0].func.infer().next()
+ func = next(node.args[0].func.infer())
except InferenceError:
return
if (getattr(func, 'name', None) == 'iter' and
@@ -893,7 +895,7 @@ _NAME_TYPES = {
def _create_naming_options():
name_options = []
- for name_type, (rgx, human_readable_name) in _NAME_TYPES.iteritems():
+ for name_type, (rgx, human_readable_name) in six.iteritems(_NAME_TYPES):
name_type = name_type.replace('_', '-')
name_options.append((
'%s-rgx' % (name_type,),
@@ -968,12 +970,12 @@ class NameChecker(_BasicChecker):
self._bad_names = {}
def leave_module(self, node):
- for category, all_groups in self._bad_names.iteritems():
+ for category, all_groups in six.iteritems(self._bad_names):
if len(all_groups) < 2:
continue
groups = collections.defaultdict(list)
- min_warnings = sys.maxint
- for group in all_groups.itervalues():
+ min_warnings = sys.maxsize
+ for group in six.itervalues(all_groups):
groups[len(group)].append(group)
min_warnings = min(len(group), min_warnings)
if len(groups[min_warnings]) > 1:
@@ -988,7 +990,7 @@ class NameChecker(_BasicChecker):
@check_messages('blacklisted-name', 'invalid-name')
def visit_class(self, node):
self._check_name('class', node.name, node)
- for attr, anodes in node.instance_attrs.iteritems():
+ for attr, anodes in six.iteritems(node.instance_attrs):
if not list(node.instance_attr_ancestors(attr)):
self._check_name('attr', attr, anodes[0])
diff --git a/checkers/classes.py b/checkers/classes.py
index aa05558..d9b7581 100644
--- a/checkers/classes.py
+++ b/checkers/classes.py
@@ -28,6 +28,7 @@ from pylint.checkers import BaseChecker
from pylint.checkers.utils import (
PYMETHODS, overrides_a_method, check_messages, is_attr_private,
is_attr_protected, node_frame_class, safe_infer, is_builtin_object)
+import six
if sys.version_info >= (3, 0):
NEXT_METHOD = '__next__'
@@ -298,7 +299,7 @@ a metaclass class method.'}
return
defining_methods = self.config.defining_attr_methods
current_module = cnode.root()
- for attr, nodes in cnode.instance_attrs.iteritems():
+ for attr, nodes in six.iteritems(cnode.instance_attrs):
# skip nodes which are not in the current module and it may screw up
# the output, while it's not worth it
nodes = [n for n in nodes if not
@@ -612,7 +613,7 @@ a metaclass class method.'}
def _check_accessed_members(self, node, accessed):
"""check that accessed members are defined"""
# XXX refactor, probably much simpler now that E0201 is in type checker
- for attr, nodes in accessed.iteritems():
+ for attr, nodes in six.iteritems(accessed):
# deactivate "except doesn't do anything", that's expected
# pylint: disable=W0704
try:
@@ -624,7 +625,7 @@ a metaclass class method.'}
pass
# is it an instance attribute of a parent class ?
try:
- node.instance_attr_ancestors(attr).next()
+ next(node.instance_attr_ancestors(attr))
# yes, stop here
continue
except StopIteration:
@@ -816,7 +817,7 @@ a metaclass class method.'}
expr.expr.func.name == 'super':
return
try:
- klass = expr.expr.infer().next()
+ klass = next(expr.expr.infer())
if klass is YES:
continue
# The infered klass can be super(), which was
@@ -838,7 +839,7 @@ a metaclass class method.'}
node=expr, args=klass.name)
except astroid.InferenceError:
continue
- for klass, method in not_called_yet.iteritems():
+ for klass, method in six.iteritems(not_called_yet):
if klass.name == 'object' or method.parent.name == 'object':
continue
self.add_message('super-init-not-called', args=klass.name, node=node)
@@ -880,7 +881,7 @@ def _ancestors_to_call(klass_node, method='__init__'):
to_call = {}
for base_node in klass_node.ancestors(recurs=False):
try:
- to_call[base_node] = base_node.igetattr(method).next()
+ to_call[base_node] = next(base_node.igetattr(method))
except astroid.InferenceError:
continue
return to_call
diff --git a/checkers/exceptions.py b/checkers/exceptions.py
index 9b1a071..f9b78bb 100644
--- a/checkers/exceptions.py
+++ b/checkers/exceptions.py
@@ -67,7 +67,7 @@ def infer_bases(klass):
"""
for base in klass.bases:
try:
- inferit = base.infer().next()
+ inferit = next(base.infer())
except astroid.InferenceError:
continue
if inferit is YES:
@@ -178,7 +178,7 @@ class ExceptionsChecker(BaseChecker):
return
if PY3K and node.cause:
try:
- cause = node.cause.infer().next()
+ cause = next(node.cause.infer())
except astroid.InferenceError:
pass
else:
@@ -197,7 +197,7 @@ class ExceptionsChecker(BaseChecker):
return
else:
try:
- value = unpack_infer(expr).next()
+ value = next(unpack_infer(expr))
except astroid.InferenceError:
return
self._check_raise_value(node, value)
diff --git a/checkers/format.py b/checkers/format.py
index 34da653..134999e 100644
--- a/checkers/format.py
+++ b/checkers/format.py
@@ -24,6 +24,9 @@ Some parts of the process_token method is based from The Tab Nanny std module.
import keyword
import sys
import tokenize
+from functools import reduce
+import six
+from six.moves import zip
if not hasattr(tokenize, 'NL'):
raise ValueError("tokenize.NL doesn't exist -- tokenize module too old")
@@ -342,7 +345,8 @@ class ContinuedLineState(object):
# current indent level
paren_align = self._cont_stack[-1].valid_outdent_offsets
next_align = self._cont_stack[-1].valid_continuation_offsets.copy()
- next_align[next_align.keys()[0] + self._continuation_size] = True
+ next_align_keys = list(next_align.keys())
+ next_align[next_align_keys[0] + self._continuation_size] = True
# Note that the continuation of
# d = {
# 'a': 'b'
@@ -506,7 +510,7 @@ class FormatChecker(BaseTokenChecker):
keyword_token = tokens[start][1]
line_num = tokens[start][2][0]
- for i in xrange(start, len(tokens) - 1):
+ for i in range(start, len(tokens) - 1):
token = tokens[i]
# If we hit a newline, then assume any parens were for continuation.
@@ -812,7 +816,7 @@ class FormatChecker(BaseTokenChecker):
for indent_pos, state, offsets in self._current_line.retained_warnings:
block_type = offsets[tokens.start_col(indent_pos)]
- hints = dict((k, v) for k, v in offsets.iteritems()
+ hints = dict((k, v) for k, v in six.iteritems(offsets)
if v != block_type)
if single_line_block_stmt and block_type == WITH_BODY:
self._add_continuation_message(state, hints, tokens, indent_pos)
@@ -886,7 +890,7 @@ class FormatChecker(BaseTokenChecker):
tolineno = node.tolineno
assert tolineno, node
lines = []
- for line in xrange(line, tolineno + 1):
+ for line in range(line, tolineno + 1):
self._visited_lines[line] = 1
try:
lines.append(self._lines[line].rstrip())
diff --git a/checkers/imports.py b/checkers/imports.py
index a861ce6..4369b02 100644
--- a/checkers/imports.py
+++ b/checkers/imports.py
@@ -28,6 +28,8 @@ from pylint.interfaces import IAstroidChecker
from pylint.utils import EmptyReport
from pylint.checkers import BaseChecker
from pylint.checkers.utils import check_messages, is_import_error
+import six
+from six.moves import map
def _except_import_error(node):
"""
@@ -106,14 +108,14 @@ def dependencies_graph(filename, dep_info):
done = {}
printer = DotBackend(filename[:-4], rankdir='LR')
printer.emit('URL="." node[shape="box"]')
- for modname, dependencies in sorted(dep_info.iteritems()):
+ for modname, dependencies in sorted(six.iteritems(dep_info)):
done[modname] = 1
printer.emit_node(modname)
for modname in dependencies:
if modname not in done:
done[modname] = 1
printer.emit_node(modname)
- for depmodname, dependencies in sorted(dep_info.iteritems()):
+ for depmodname, dependencies in sorted(six.iteritems(dep_info)):
for modname in dependencies:
printer.emit_edge(modname, depmodname)
printer.generate(filename)
@@ -281,7 +283,7 @@ given file (report RP0402 must not be disabled)'}
def get_imported_module(self, modnode, importnode, modname):
try:
return importnode.do_import_module(modname)
- except astroid.InferenceError, ex:
+ except astroid.InferenceError as ex:
if str(ex) != modname:
args = '%r (%s)' % (modname, ex)
else:
@@ -353,7 +355,7 @@ given file (report RP0402 must not be disabled)'}
def report_external_dependencies(self, sect, _, dummy):
"""return a verbatim layout for displaying dependencies"""
- dep_info = make_tree_defs(self._external_dependencies_info().iteritems())
+ dep_info = make_tree_defs(six.iteritems(self._external_dependencies_info()))
if not dep_info:
raise EmptyReport()
tree_str = repr_tree_defs(dep_info)
@@ -385,7 +387,7 @@ given file (report RP0402 must not be disabled)'}
if self.__ext_dep_info is None:
package = self.linter.current_name
self.__ext_dep_info = result = {}
- for importee, importers in self.stats['dependencies'].iteritems():
+ for importee, importers in six.iteritems(self.stats['dependencies']):
if not importee.startswith(package):
result[importee] = importers
return self.__ext_dep_info
@@ -397,7 +399,7 @@ given file (report RP0402 must not be disabled)'}
if self.__int_dep_info is None:
package = self.linter.current_name
self.__int_dep_info = result = {}
- for importee, importers in self.stats['dependencies'].iteritems():
+ for importee, importers in six.iteritems(self.stats['dependencies']):
if importee.startswith(package):
result[importee] = importers
return self.__int_dep_info
diff --git a/checkers/logging.py b/checkers/logging.py
index d82d74b..39c4d9c 100644
--- a/checkers/logging.py
+++ b/checkers/logging.py
@@ -20,6 +20,9 @@ from pylint import interfaces
from pylint.checkers import utils
from pylint.checkers.utils import check_messages
+import six
+
+
MSGS = {
'W1201': ('Specify string format arguments as logging function parameters',
'logging-not-lazy',
@@ -166,7 +169,7 @@ class LoggingChecker(checkers.BaseChecker):
# don't check any further.
return
format_string = node.args[format_arg].value
- if not isinstance(format_string, basestring):
+ if not isinstance(format_string, six.string_types):
# If the log format is constant non-string (e.g. logging.debug(5)),
# ensure there are no arguments.
required_num_args = 0
@@ -178,7 +181,7 @@ class LoggingChecker(checkers.BaseChecker):
# Keyword checking on logging strings is complicated by
# special keywords - out of scope.
return
- except utils.UnsupportedFormatCharacter, ex:
+ except utils.UnsupportedFormatCharacter as ex:
char = format_string[ex.index]
self.add_message('logging-unsupported-format', node=node,
args=(char, ord(char), ex.index))
diff --git a/checkers/misc.py b/checkers/misc.py
index b27b86a..d4adfd7 100644
--- a/checkers/misc.py
+++ b/checkers/misc.py
@@ -21,6 +21,7 @@ import re
from pylint.interfaces import IRawChecker
from pylint.checkers import BaseChecker
+import six
MSGS = {
@@ -72,8 +73,8 @@ class EncodingChecker(BaseChecker):
def _check_encoding(self, lineno, line, file_encoding):
try:
- return unicode(line, file_encoding)
- except UnicodeDecodeError, ex:
+ return six.text_type(line, file_encoding)
+ except UnicodeDecodeError as ex:
self.add_message('invalid-encoded-data', line=lineno,
args=(file_encoding, ex.args[2]))
diff --git a/checkers/newstyle.py b/checkers/newstyle.py
index 1946f94..335b052 100644
--- a/checkers/newstyle.py
+++ b/checkers/newstyle.py
@@ -133,7 +133,7 @@ class NewStyleConflictChecker(BaseChecker):
continue
try:
- supcls = (call.args and call.args[0].infer().next()
+ supcls = (call.args and next(call.args[0].infer())
or None)
except astroid.InferenceError:
continue
diff --git a/checkers/similar.py b/checkers/similar.py
index e5c9300..2cc2e00 100644
--- a/checkers/similar.py
+++ b/checkers/similar.py
@@ -16,14 +16,17 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""a similarities / code duplication command line tool and pylint checker
"""
+from __future__ import print_function
import sys
-from itertools import izip
from logilab.common.ureports import Table
from pylint.interfaces import IRawChecker
from pylint.checkers import BaseChecker, table_lines_from_stats
+import six
+from six.moves import zip
+
class Similar(object):
"""finds copy-pasted lines of code in a project"""
@@ -69,7 +72,7 @@ class Similar(object):
else:
duplicate.append(set([(lineset1, idx1), (lineset2, idx2)]))
sims = []
- for num, ensembles in no_duplicates.iteritems():
+ for num, ensembles in six.iteritems(no_duplicates):
for couples in ensembles:
sims.append((num, couples))
sims.sort()
@@ -80,19 +83,19 @@ class Similar(object):
"""display computed similarities on stdout"""
nb_lignes_dupliquees = 0
for num, couples in sims:
- print
- print num, "similar lines in", len(couples), "files"
+ print()
+ print(num, "similar lines in", len(couples), "files")
couples = sorted(couples)
for lineset, idx in couples:
- print "==%s:%s" % (lineset.name, idx)
+ print("==%s:%s" % (lineset.name, idx))
# pylint: disable=W0631
for line in lineset._real_lines[idx:idx+num]:
- print " ", line.rstrip()
+ print(" ", line.rstrip())
nb_lignes_dupliquees += num * (len(couples)-1)
nb_total_lignes = sum([len(lineset) for lineset in self.linesets])
- print "TOTAL lines=%s duplicates=%s percent=%.2f" \
+ print("TOTAL lines=%s duplicates=%s percent=%.2f" \
% (nb_total_lignes, nb_lignes_dupliquees,
- nb_lignes_dupliquees*100. / nb_total_lignes)
+ nb_lignes_dupliquees*100. / nb_total_lignes))
def _find_common(self, lineset1, lineset2):
"""find similarities in the two given linesets"""
@@ -107,7 +110,7 @@ class Similar(object):
for index2 in find(lineset1[index1]):
non_blank = 0
for num, ((_, line1), (_, line2)) in enumerate(
- izip(lines1(index1), lines2(index2))):
+ zip(lines1(index1), lines2(index2))):
if line1 != line2:
if non_blank > min_lines:
yield num, lineset1, index1, lineset2, index2
@@ -323,10 +326,10 @@ def register(linter):
def usage(status=0):
"""display command line usage information"""
- print "finds copy pasted blocks in a set of files"
- print
- print 'Usage: symilar [-d|--duplicates min_duplicated_lines] \
-[-i|--ignore-comments] [--ignore-docstrings] [--ignore-imports] file1...'
+ print("finds copy pasted blocks in a set of files")
+ print()
+ print('Usage: symilar [-d|--duplicates min_duplicated_lines] \
+[-i|--ignore-comments] [--ignore-docstrings] [--ignore-imports] file1...')
sys.exit(status)
def Run(argv=None):
diff --git a/checkers/strings.py b/checkers/strings.py
index 40995f6..03c557a 100644
--- a/checkers/strings.py
+++ b/checkers/strings.py
@@ -30,6 +30,9 @@ from pylint.checkers import BaseChecker, BaseTokenChecker
from pylint.checkers import utils
from pylint.checkers.utils import check_messages
+import six
+
+
_PY3K = sys.version_info[:2] >= (3, 0)
_PY27 = sys.version_info[:2] == (2, 7)
@@ -231,13 +234,13 @@ class StringFormatChecker(BaseChecker):
args = node.right
if not (isinstance(left, astroid.Const)
- and isinstance(left.value, basestring)):
+ and isinstance(left.value, six.string_types)):
return
format_string = left.value
try:
required_keys, required_num_args = \
utils.parse_format_string(format_string)
- except utils.UnsupportedFormatCharacter, e:
+ except utils.UnsupportedFormatCharacter as e:
c = format_string[e.index]
self.add_message('bad-format-character',
node=node, args=(c, ord(c), e.index))
@@ -260,7 +263,7 @@ class StringFormatChecker(BaseChecker):
for k, _ in args.items:
if isinstance(k, astroid.Const):
key = k.value
- if isinstance(key, basestring):
+ if isinstance(key, six.string_types):
keys.add(key)
else:
self.add_message('bad-format-string-key',
@@ -346,7 +349,7 @@ class StringMethodsChecker(BaseChecker):
if not isinstance(node.func.expr, astroid.Const):
return
try:
- strnode = func.bound.infer().next()
+ strnode = next(func.bound.infer())
except astroid.InferenceError:
return
if not isinstance(strnode, astroid.Const):
@@ -365,7 +368,7 @@ class StringMethodsChecker(BaseChecker):
return
named_fields = set(field[0] for field in fields
- if isinstance(field[0], basestring))
+ if isinstance(field[0], six.string_types))
if num_args and manual_pos:
self.add_message('format-combined-specification',
node=node)
@@ -435,7 +438,7 @@ class StringMethodsChecker(BaseChecker):
if argname in (astroid.YES, None):
continue
try:
- argument = argname.infer().next()
+ argument = next(argname.infer())
except astroid.InferenceError:
continue
if not specifiers or argument is astroid.YES:
@@ -492,7 +495,7 @@ class StringMethodsChecker(BaseChecker):
break
try:
- previous = previous.infer().next()
+ previous = next(previous.infer())
except astroid.InferenceError:
# can't check further if we can't infer it
break
diff --git a/checkers/utils.py b/checkers/utils.py
index af1440d..55bcdd4 100644
--- a/checkers/utils.py
+++ b/checkers/utils.py
@@ -87,11 +87,11 @@ def safe_infer(node):
"""
try:
inferit = node.infer()
- value = inferit.next()
+ value = next(inferit)
except astroid.InferenceError:
return
try:
- inferit.next()
+ next(inferit)
return # None if there is ambiguity on the inferred node
except astroid.InferenceError:
return # there is some kind of ambiguity
@@ -471,7 +471,7 @@ def has_known_bases(klass):
pass
try:
for base in klass.bases:
- result = base.infer().next()
+ result = next(base.infer())
# TODO: check for A->B->A->B pattern in class structure too?
if not isinstance(result, astroid.Class) or result is klass or not has_known_bases(result):
klass._all_bases_known = False
diff --git a/checkers/variables.py b/checkers/variables.py
index 71b4be4..6a26f5a 100644
--- a/checkers/variables.py
+++ b/checkers/variables.py
@@ -33,6 +33,7 @@ from pylint.checkers.utils import (
is_defined_before, is_error, is_func_default, is_func_decorator,
assign_parent, check_messages, is_inside_except, clobber_in_except,
get_all_elements, has_known_bases)
+import six
SPECIAL_OBJ = re.compile("^_{2}[a-z]+_{2}$")
@@ -45,7 +46,7 @@ def in_for_else_branch(parent, stmt):
def overridden_method(klass, name):
"""get overridden method if any"""
try:
- parent = klass.local_attr_ancestors(name).next()
+ parent = next(klass.local_attr_ancestors(name))
except (StopIteration, KeyError):
return None
try:
@@ -145,7 +146,7 @@ def _fix_dot_imports(not_consumed):
"""
# TODO: this should be improved in issue astroid #46
names = {}
- for name, stmts in not_consumed.iteritems():
+ for name, stmts in six.iteritems(not_consumed):
if any(isinstance(stmt, astroid.AssName)
and isinstance(stmt.ass_type(), astroid.AugAssign)
for stmt in stmts):
@@ -296,7 +297,7 @@ builtins. Remember that you should avoid to define new builtins when possible.'
checks globals doesn't overrides builtins
"""
self._to_consume = [(copy(node.locals), {}, 'module')]
- for name, stmts in node.locals.iteritems():
+ for name, stmts in six.iteritems(node.locals):
if is_builtin(name) and not is_inside_except(stmts[0]):
# do not print Redefining builtin for additional builtins
self.add_message('redefined-builtin', args=name, node=stmts[0])
@@ -311,16 +312,16 @@ builtins. Remember that you should avoid to define new builtins when possible.'
not_consumed = self._to_consume.pop()[0]
# attempt to check for __all__ if defined
if '__all__' in node.locals:
- assigned = node.igetattr('__all__').next()
+ assigned = next(node.igetattr('__all__'))
if assigned is not astroid.YES:
for elt in getattr(assigned, 'elts', ()):
try:
- elt_name = elt.infer().next()
+ elt_name = next(elt.infer())
except astroid.InferenceError:
continue
if not isinstance(elt_name, astroid.Const) \
- or not isinstance(elt_name.value, basestring):
+ or not isinstance(elt_name.value, six.string_types):
self.add_message('invalid-all-object',
args=elt.as_string(), node=elt)
continue
@@ -503,7 +504,7 @@ builtins. Remember that you should avoid to define new builtins when possible.'
for nonlocal_stmt in node.nodes_of_class(astroid.Nonlocal):
nonlocal_names.update(set(nonlocal_stmt.names))
- for name, stmts in not_consumed.iteritems():
+ for name, stmts in six.iteritems(not_consumed):
# ignore some special names specified by user configuration
if authorized_rgx.match(name):
continue
@@ -829,7 +830,7 @@ builtins. Remember that you should avoid to define new builtins when possible.'
for name, _ in node.names:
parts = name.split('.')
try:
- module = node.infer_name_module(parts[0]).next()
+ module = next(node.infer_name_module(parts[0]))
except astroid.ResolveError:
continue
self._check_module_attrs(node, module, parts[1:])
@@ -916,7 +917,7 @@ builtins. Remember that you should avoid to define new builtins when possible.'
module = None
break
try:
- module = module.getattr(name)[0].infer().next()
+ module = next(module.getattr(name)[0].infer())
if module is astroid.YES:
return None
except astroid.NotFoundError:
diff --git a/config.py b/config.py
index c346a0e..942dc21 100644
--- a/config.py
+++ b/config.py
@@ -17,6 +17,7 @@
* pylint.d (PYLINTHOME)
"""
from __future__ import with_statement
+from __future__ import print_function
import pickle
import os
@@ -66,13 +67,13 @@ def save_results(results, base):
try:
os.mkdir(PYLINT_HOME)
except OSError:
- print >> sys.stderr, 'Unable to create directory %s' % PYLINT_HOME
+ print('Unable to create directory %s' % PYLINT_HOME, file=sys.stderr)
data_file = get_pdata_path(base, 1)
try:
with open(data_file, _PICK_DUMP) as stream:
pickle.dump(results, stream)
- except (IOError, OSError), ex:
- print >> sys.stderr, 'Unable to create file %s: %s' % (data_file, ex)
+ except (IOError, OSError) as ex:
+ print('Unable to create file %s: %s' % (data_file, ex), file=sys.stderr)
# location of the configuration file ##########################################
diff --git a/epylint.py b/epylint.py
index beae481..b34ef63 100755
--- a/epylint.py
+++ b/epylint.py
@@ -45,6 +45,7 @@ For example:
You may also use py_run to run pylint with desired options and get back (or not)
its output.
"""
+from __future__ import print_function
import sys, os
import os.path as osp
@@ -102,7 +103,7 @@ def lint(filename, options=None):
parts = line.split(":")
if parts and parts[0] == child_path:
line = ":".join([filename] + parts[1:])
- print line,
+ print(line, end=' ')
process.wait()
return process.returncode
@@ -162,10 +163,10 @@ def py_run(command_options='', return_std=False, stdout=None, stderr=None,
def Run():
if len(sys.argv) == 1:
- print "Usage: %s <filename> [options]" % sys.argv[0]
+ print("Usage: %s <filename> [options]" % sys.argv[0])
sys.exit(1)
elif not osp.exists(sys.argv[1]):
- print "%s does not exist" % sys.argv[1]
+ print("%s does not exist" % sys.argv[1])
sys.exit(1)
else:
sys.exit(lint(sys.argv[1], sys.argv[2:]))
diff --git a/gui.py b/gui.py
index fcc84e0..bf624b4 100644
--- a/gui.py
+++ b/gui.py
@@ -14,6 +14,7 @@
# this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""Tkinker gui for pylint"""
+from __future__ import print_function
import os
import sys
@@ -86,7 +87,7 @@ class BasicStream(object):
"""finalize what the contents of the dict should look like before output"""
for item in self.outdict:
num_empty = self.outdict[item].count('')
- for _ in xrange(num_empty):
+ for _ in range(num_empty):
self.outdict[item].remove('')
if self.outdict[item]:
self.outdict[item].pop(0)
@@ -503,7 +504,7 @@ def lint_thread(module, reporter, gui):
def Run(args):
"""launch pylint gui from args"""
if args:
- print 'USAGE: pylint-gui\n launch a simple pylint gui using Tk'
+ print('USAGE: pylint-gui\n launch a simple pylint gui using Tk')
sys.exit(1)
gui = LintGui()
gui.mainloop()
diff --git a/lint.py b/lint.py
index b258c29..18e37cd 100644
--- a/lint.py
+++ b/lint.py
@@ -60,6 +60,7 @@ from pylint.reporters import initialize as reporters_initialize
from pylint import config
from pylint.__pkginfo__ import version
+import six
@@ -449,7 +450,7 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn,
checker.load_defaults()
def disable_noerror_messages(self):
- for msgcat, msgids in self.msgs_store._msgs_by_category.iteritems():
+ for msgcat, msgids in six.iteritems(self.msgs_store._msgs_by_category):
if msgcat == 'E':
for msgid in msgids:
self.enable(msgid)
@@ -459,7 +460,7 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn,
def disable_reporters(self):
"""disable all reporters"""
- for reporters in self._reports.itervalues():
+ for reporters in six.itervalues(self._reports):
for report_id, _title, _cb in reporters:
self.disable_report(report_id)
@@ -524,7 +525,7 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn,
def get_checkers(self):
"""return all available checkers as a list"""
- return [self] + [c for checkers in self._checkers.itervalues()
+ return [self] + [c for checkers in six.itervalues(self._checkers)
for c in checkers if c is not self]
def prepare_checkers(self):
@@ -639,7 +640,7 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn,
self.current_file = filepath or modname
self.stats['by_module'][modname] = {}
self.stats['by_module'][modname]['statement'] = 0
- for msg_cat in MSG_TYPES.itervalues():
+ for msg_cat in six.itervalues(MSG_TYPES):
self.stats['by_module'][modname][msg_cat] = 0
def get_ast(self, filepath, modname):
@@ -691,7 +692,7 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn,
self.stats = {'by_module' : {},
'by_msg' : {},
}
- for msg_cat in MSG_TYPES.itervalues():
+ for msg_cat in six.itervalues(MSG_TYPES):
self.stats[msg_cat] = 0
def close(self):
@@ -764,7 +765,7 @@ def report_messages_stats(sect, stats, _):
# don't print this report when we didn't detected any errors
raise EmptyReport()
in_order = sorted([(value, msg_id)
- for msg_id, value in stats['by_msg'].iteritems()
+ for msg_id, value in six.iteritems(stats['by_msg'])
if not msg_id.startswith('I')])
in_order.reverse()
lines = ('message id', 'occurrences')
@@ -780,7 +781,7 @@ def report_messages_by_module_stats(sect, stats, _):
by_mod = {}
for m_type in ('fatal', 'error', 'warning', 'refactor', 'convention'):
total = stats[m_type]
- for module in stats['by_module'].iterkeys():
+ for module in six.iterkeys(stats['by_module']):
mod_total = stats['by_module'][module][m_type]
if total == 0:
percent = 0
@@ -788,7 +789,7 @@ def report_messages_by_module_stats(sect, stats, _):
percent = float((mod_total)*100) / total
by_mod.setdefault(module, {})[m_type] = percent
sorted_result = []
- for module, mod_info in by_mod.iteritems():
+ for module, mod_info in six.iteritems(by_mod):
sorted_result.append((mod_info['error'],
mod_info['warning'],
mod_info['refactor'],
@@ -1077,7 +1078,7 @@ group are mutually exclusive.'),
def cb_init_hook(optname, value):
"""exec arbitrary code to set sys.path for instance"""
- exec value
+ exec(value)
if __name__ == '__main__':
diff --git a/pyreverse/diadefslib.py b/pyreverse/diadefslib.py
index 46d0f19..0b42065 100644
--- a/pyreverse/diadefslib.py
+++ b/pyreverse/diadefslib.py
@@ -99,8 +99,8 @@ class DiaDefGenerator(object):
"""return associated nodes of a class node"""
if level == 0:
return
- for ass_nodes in klass_node.instance_attrs_type.values() + \
- klass_node.locals_type.values():
+ for ass_nodes in list(klass_node.instance_attrs_type.values()) + \
+ list(klass_node.locals_type.values()):
for ass_node in ass_nodes:
if isinstance(ass_node, astroid.Instance):
ass_node = ass_node._proxied
@@ -198,7 +198,7 @@ class ClassDiadefGenerator(DiaDefGenerator):
else:
module = project.modules[0]
klass = klass.split('.')[-1]
- klass = module.ilookup(klass).next()
+ klass = next(module.ilookup(klass))
anc_level, ass_level = self._get_levels()
self.extract_classes(klass, anc_level, ass_level)
diff --git a/pyreverse/diagrams.py b/pyreverse/diagrams.py
index 28cc500..f0d7a92 100644
--- a/pyreverse/diagrams.py
+++ b/pyreverse/diagrams.py
@@ -77,8 +77,8 @@ class ClassDiagram(Figure, FilterMixIn):
def get_attrs(self, node):
"""return visible attributes, possibly with class name"""
attrs = []
- for node_name, ass_nodes in node.instance_attrs_type.items() + \
- node.locals_type.items():
+ for node_name, ass_nodes in list(node.instance_attrs_type.items()) + \
+ list(node.locals_type.items()):
if not self.show_attr(node_name):
continue
names = self.class_names(ass_nodes)
@@ -170,8 +170,8 @@ class ClassDiagram(Figure, FilterMixIn):
except KeyError:
continue
# associations link
- for name, values in node.instance_attrs_type.items() + \
- node.locals_type.items():
+ for name, values in list(node.instance_attrs_type.items()) + \
+ list(node.locals_type.items()):
for value in values:
if value is astroid.YES:
continue
diff --git a/pyreverse/main.py b/pyreverse/main.py
index d4991b5..408c172 100644
--- a/pyreverse/main.py
+++ b/pyreverse/main.py
@@ -18,6 +18,7 @@
create UML diagrams for classes and modules in <packages>
"""
+from __future__ import print_function
import sys, os
from logilab.common.configuration import ConfigurationMixIn
@@ -99,7 +100,7 @@ class Run(ConfigurationMixIn):
def run(self, args):
"""checking arguments and run project"""
if not args:
- print self.help()
+ print(self.help())
return 1
# insert current working directory to the python path to recognize
# dependencies to local modules even if cwd is not in the PYTHONPATH
diff --git a/pyreverse/utils.py b/pyreverse/utils.py
index 3d12d41..58559d7 100644
--- a/pyreverse/utils.py
+++ b/pyreverse/utils.py
@@ -16,6 +16,7 @@
"""
generic classes/functions for pyreverse core/extensions
"""
+from __future__ import print_function
import sys
import re
@@ -118,8 +119,8 @@ class FilterMixIn(object):
for nummod in mode.split('+'):
try:
__mode += MODES[nummod]
- except KeyError, ex:
- print >> sys.stderr, 'Unknown filter mode %s' % ex
+ except KeyError as ex:
+ print('Unknown filter mode %s' % ex, file=sys.stderr)
self.__mode = __mode
diff --git a/reporters/__init__.py b/reporters/__init__.py
index 429155a..8be5d3a 100644
--- a/reporters/__init__.py
+++ b/reporters/__init__.py
@@ -12,6 +12,7 @@
# this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""utilities methods and classes for reporters"""
+from __future__ import print_function
import sys
import locale
@@ -90,7 +91,7 @@ class BaseReporter(object):
def writeln(self, string=''):
"""write a line in the output buffer"""
- print >> self.out, self.encode(string)
+ print(self.encode(string), file=self.out)
def display_results(self, layout):
"""display results encapsulated in the layout tree"""
diff --git a/reporters/text.py b/reporters/text.py
index 48b5155..acb22b5 100644
--- a/reporters/text.py
+++ b/reporters/text.py
@@ -16,6 +16,7 @@
:text: the default one grouping messages by module
:colorized: an ANSI colorized text reporter
"""
+from __future__ import print_function
import warnings
@@ -24,6 +25,7 @@ from logilab.common.textutils import colorize_ansi
from pylint.interfaces import IReporter
from pylint.reporters import BaseReporter
+import six
TITLE_UNDERLINES = ['', '=', '-', '.']
@@ -42,7 +44,7 @@ class TextReporter(BaseReporter):
self._template = None
def on_set_current_module(self, module, filepath):
- self._template = unicode(self.linter.config.msg_template or self.line_format)
+ self._template = six.text_type(self.linter.config.msg_template or self.line_format)
def write_message(self, msg):
"""Convenience method to write a formated message with class default template"""
@@ -60,7 +62,7 @@ class TextReporter(BaseReporter):
def _display(self, layout):
"""launch layouts display"""
- print >> self.out
+ print(file=self.out)
TextWriter().format(layout, self.out)
diff --git a/setup.py b/setup.py
index 45b76be..28b7d3e 100644
--- a/setup.py
+++ b/setup.py
@@ -20,6 +20,7 @@
# with pylint. If not, see <http://www.gnu.org/licenses/>.
"""Generic Setup script, takes package info from __pkginfo__.py file.
"""
+from __future__ import absolute_import, print_function
__docformat__ = "restructuredtext en"
import os
@@ -38,14 +39,7 @@ except ImportError:
from distutils.command import install_lib
USE_SETUPTOOLS = 0
-try:
- # pylint: disable=no-name-in-module
- # python3
- from distutils.command.build_py import build_py_2to3 as build_py
-except ImportError:
- # pylint: disable=no-name-in-module
- # python2.x
- from distutils.command.build_py import build_py
+from distutils.command.build_py import build_py
sys.modules.pop('__pkginfo__', None)
# import optional features
@@ -144,7 +138,7 @@ class MyInstallLib(install_lib.install_lib):
# process manually python file in include_dirs (test data)
# pylint: disable=no-name-in-module
from distutils.util import run_2to3
- print('running 2to3 on', dest)
+ print(('running 2to3 on', dest))
run_2to3([dest])
# override this since pip/easy_install attempt to byte compile test data
diff --git a/testutils.py b/testutils.py
index 86539ac..a059fe4 100644
--- a/testutils.py
+++ b/testutils.py
@@ -14,11 +14,11 @@
# this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""functional/non regression tests for pylint"""
+from __future__ import print_function
import collections
import contextlib
import functools
-import cStringIO
import sys
import re
import unittest
@@ -35,6 +35,9 @@ from pylint.reporters import BaseReporter
from pylint.interfaces import IReporter
from pylint.lint import PyLinter
+import six
+from six.moves import StringIO
+
# Utils
@@ -94,7 +97,7 @@ class TestReporter(BaseReporter):
self.reset()
def reset(self):
- self.out = cStringIO.StringIO()
+ self.out = StringIO()
self.messages = []
def add_message(self, msg_id, location, msg):
@@ -113,7 +116,7 @@ class TestReporter(BaseReporter):
def finalize(self):
self.messages.sort()
for msg in self.messages:
- print >> self.out, msg
+ print(msg, file=self.out)
result = self.out.getvalue()
self.reset()
return result
@@ -148,7 +151,7 @@ class UnittestLinter(object):
return True
def add_stats(self, **kwargs):
- for name, value in kwargs.iteritems():
+ for name, value in six.iteritems(kwargs):
self.stats[name] = value
return self.stats
@@ -161,7 +164,7 @@ def set_config(**kwargs):
def _Wrapper(fun):
@functools.wraps(fun)
def _Forward(self):
- for key, value in kwargs.iteritems():
+ for key, value in six.iteritems(kwargs):
setattr(self.checker.config, key, value)
if isinstance(self, CheckerTestCase):
# reopen checker in case, it may be interested in configuration change
@@ -180,7 +183,7 @@ class CheckerTestCase(unittest.TestCase):
def setUp(self):
self.linter = UnittestLinter()
self.checker = self.CHECKER_CLASS(self.linter) # pylint: disable=not-callable
- for key, value in self.CONFIG.iteritems():
+ for key, value in six.iteritems(self.CONFIG):
setattr(self.checker.config, key, value)
self.checker.open()
@@ -277,11 +280,11 @@ class LintTestUsingModule(unittest.TestCase):
self.linter.disable('I')
try:
self.linter.check(tocheck)
- except Exception, ex:
+ except Exception as ex:
# need finalization to restore a correct state
self.linter.reporter.finalize()
ex.file = tocheck
- print ex
+ print(ex)
ex.__str__ = exception_str
raise
self._check_result(self.linter.reporter.finalize())
diff --git a/tox.ini b/tox.ini
index 480ae76..5b3ec53 100644
--- a/tox.ini
+++ b/tox.ini
@@ -6,5 +6,6 @@ envlist = py27, py33
deps =
logilab-common
hg+https://bitbucket.org/logilab/astroid/
+ six
commands = python -Wi -m unittest discover -s {envsitepackagesdir}/pylint/test/ -p {posargs:*test_*}.py
changedir = {toxworkdir}
diff --git a/utils.py b/utils.py
index ef4bf44..068afce 100644
--- a/utils.py
+++ b/utils.py
@@ -37,6 +37,8 @@ from astroid.modutils import modpath_from_file, get_module_files, \
file_from_modpath, load_module_from_file
from pylint.interfaces import IRawChecker, ITokenChecker, UNDEFINED
+import six
+from six.moves import zip
class UnknownMessage(Exception):
@@ -54,7 +56,7 @@ MSG_TYPES = {
'E' : 'error',
'F' : 'fatal'
}
-MSG_TYPES_LONG = {v: k for k, v in MSG_TYPES.iteritems()}
+MSG_TYPES_LONG = {v: k for k, v in six.iteritems(MSG_TYPES)}
MSG_TYPES_STATUS = {
'I' : 0,
@@ -265,7 +267,7 @@ class MessagesHandlerMixIn(object):
msgs = self._msgs_state
msgs[msg.msgid] = False
# sync configuration object
- self.config.disable_msg = [mid for mid, val in msgs.iteritems()
+ self.config.disable_msg = [mid for mid, val in six.iteritems(msgs)
if not val]
def enable(self, msgid, scope='package', line=None, ignore_unknown=False):
@@ -303,7 +305,7 @@ class MessagesHandlerMixIn(object):
msgs = self._msgs_state
msgs[msg.msgid] = True
# sync configuration object
- self.config.enable = [mid for mid, val in msgs.iteritems() if val]
+ self.config.enable = [mid for mid, val in six.iteritems(msgs) if val]
def get_message_state_scope(self, msgid, line=None, confidence=UNDEFINED):
"""Returns the scope at which a message was enabled/disabled."""
@@ -425,7 +427,7 @@ class MessagesHandlerMixIn(object):
by_checker[checker.name] = [list(checker.options_and_values()),
dict(checker.msgs),
list(checker.reports)]
- for checker, (options, msgs, reports) in by_checker.iteritems():
+ for checker, (options, msgs, reports) in six.iteritems(by_checker):
prefix = ''
title = '%s checker' % checker
print(title)
@@ -441,7 +443,7 @@ class MessagesHandlerMixIn(object):
title = ('%smessages' % prefix).capitalize()
print(title)
print('~' * len(title))
- for msgid, msg in sorted(msgs.iteritems(),
+ for msgid, msg in sorted(six.iteritems(msgs),
key=lambda kv: (_MSG_ORDER.index(kv[0][0]), kv[1])):
msg = build_message_def(checker, msgid, msg)
print(msg.format_help(checkerref=False))
@@ -468,7 +470,7 @@ class FileState(object):
def collect_block_lines(self, msgs_store, module_node):
"""Walk the AST to collect block level options line numbers."""
- for msg, lines in self._module_msgs_state.iteritems():
+ for msg, lines in six.iteritems(self._module_msgs_state):
self._raw_module_msgs_state[msg] = lines.copy()
orig_state = self._module_msgs_state.copy()
self._module_msgs_state = {}
@@ -502,8 +504,8 @@ class FileState(object):
firstchildlineno = node.body[0].fromlineno
else:
firstchildlineno = last
- for msgid, lines in msg_state.iteritems():
- for lineno, state in lines.items():
+ for msgid, lines in six.iteritems(msg_state):
+ for lineno, state in list(lines.items()):
original_lineno = lineno
if first <= lineno <= last:
# Set state for all lines for this block, if the
@@ -515,7 +517,7 @@ class FileState(object):
else:
first_ = lineno
last_ = last
- for line in xrange(first_, last_+1):
+ for line in range(first_, last_+1):
# do not override existing entries
if not line in self._module_msgs_state.get(msgid, ()):
if line in lines: # state change in the same block
@@ -552,13 +554,13 @@ class FileState(object):
pass
def iter_spurious_suppression_messages(self, msgs_store):
- for warning, lines in self._raw_module_msgs_state.iteritems():
- for line, enable in lines.iteritems():
+ for warning, lines in six.iteritems(self._raw_module_msgs_state):
+ for line, enable in six.iteritems(lines):
if not enable and (warning, line) not in self._ignored_msgs:
yield 'useless-suppression', line, \
(msgs_store.get_msg_display_string(warning),)
# don't use iteritems here, _ignored_msgs may be modified by add_message
- for (warning, from_), lines in self._ignored_msgs.items():
+ for (warning, from_), lines in list(self._ignored_msgs.items()):
for line in lines:
yield 'suppressed-message', line, \
(msgs_store.get_msg_display_string(warning), from_)
@@ -584,7 +586,7 @@ class MessagesStore(object):
@property
def messages(self):
"""The list of all active messages."""
- return self._messages.itervalues()
+ return six.itervalues(self._messages)
def add_renamed_message(self, old_id, old_symbol, new_symbol):
"""Register the old ID and symbol for a warning that was renamed.
@@ -606,7 +608,7 @@ class MessagesStore(object):
are the checker id and the two last the message id in this checker
"""
chkid = None
- for msgid, msg_tuple in checker.msgs.iteritems():
+ for msgid, msg_tuple in six.iteritems(checker.msgs):
msg = build_message_def(checker, msgid, msg_tuple)
assert msg.symbol not in self._messages, \
'Message symbol %r is already defined' % msg.symbol
@@ -659,7 +661,7 @@ class MessagesStore(object):
def list_messages(self):
"""output full messages list documentation in ReST format"""
- msgs = sorted(self._messages.itervalues(), key=lambda msg: msg.msgid)
+ msgs = sorted(six.itervalues(self._messages), key=lambda msg: msg.msgid)
for msg in msgs:
if not msg.may_be_emitted():
continue
@@ -729,7 +731,7 @@ class ReportsHandlerMixIn(object):
"""add some stats entries to the statistic dictionary
raise an AssertionError if there is a key conflict
"""
- for key, value in kwargs.iteritems():
+ for key, value in six.iteritems(kwargs):
if key[-1] == '_':
key = key[:-1]
assert key not in self.stats
@@ -866,7 +868,8 @@ def register_plugins(linter, directory):
# empty module name (usually emacs auto-save files)
continue
except ImportError as exc:
- print >> sys.stderr, "Problem importing module %s: %s" % (filename, exc)
+ print("Problem importing module %s: %s" % (filename, exc),
+ file=sys.stderr)
else:
if hasattr(module, 'register'):
module.register(linter)