summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcpopa <devnull@localhost>2014-01-09 14:50:10 +0200
committercpopa <devnull@localhost>2014-01-09 14:50:10 +0200
commit79ded0d5f158f19569b53f02e53f9eb8b5be1e3d (patch)
tree5f3a3e058493641d6e1edf9df334acef64209086
parent95ec900f33bae5dcf71a00cdf4ca4ab7392d604f (diff)
parent9e0dfe50de779a4fdf4d2abe80587f541c7cb71c (diff)
downloadpylint-79ded0d5f158f19569b53f02e53f9eb8b5be1e3d.tar.gz
Merge with default.
-rw-r--r--.hgignore1
-rw-r--r--ChangeLog63
-rw-r--r--MANIFEST.in4
-rw-r--r--README4
-rw-r--r--__pkginfo__.py26
-rw-r--r--checkers/__init__.py4
-rw-r--r--checkers/base.py22
-rw-r--r--checkers/classes.py8
-rw-r--r--checkers/design_analysis.py49
-rw-r--r--checkers/exceptions.py42
-rw-r--r--checkers/format.py418
-rw-r--r--checkers/imports.py11
-rw-r--r--checkers/logging.py34
-rw-r--r--checkers/newstyle.py5
-rw-r--r--checkers/raw_metrics.py4
-rw-r--r--checkers/similar.py18
-rw-r--r--checkers/stdlib.py2
-rw-r--r--checkers/strings.py6
-rw-r--r--checkers/typecheck.py4
-rw-r--r--checkers/utils.py68
-rw-r--r--checkers/variables.py64
-rw-r--r--config.py28
-rwxr-xr-xdebian.intrepid/rules79
-rwxr-xr-xdebian.jaunty/rules79
-rwxr-xr-xdebian.lenny/rules79
-rw-r--r--debian/changelog6
-rw-r--r--doc/logo.pngbin0 -> 9362 bytes
-rw-r--r--doc/run.rst35
-rwxr-xr-xepylint.py32
-rw-r--r--gui.py150
-rw-r--r--lint.py130
-rw-r--r--man/pyreverse.116
-rw-r--r--pyreverse/diadefslib.py2
-rw-r--r--pyreverse/diagrams.py21
-rw-r--r--pyreverse/main.py6
-rw-r--r--pyreverse/utils.py6
-rw-r--r--pyreverse/writer.py49
-rw-r--r--reporters/text.py4
-rw-r--r--setup.py34
-rw-r--r--test/data/classes_No_Name.dot16
-rw-r--r--test/data/packages_No_Name.dot8
-rw-r--r--test/input/func_assert_2uple.py2
-rw-r--r--test/input/func_block_disable_msg.py2
-rw-r--r--test/input/func_catching_non_exception.py15
-rw-r--r--test/input/func_dangerous_default.py14
-rw-r--r--test/input/func_format.py21
-rw-r--r--test/input/func_i0022.py22
-rw-r--r--test/input/func_method_could_be_function.py2
-rw-r--r--test/input/func_missing_super_argument_py_30.py (renamed from test/input/func_missing_super_argument_py20.py)0
-rw-r--r--test/input/func_namedtuple.py10
-rw-r--r--test/input/func_noerror_classes_meth_signature.py2
-rw-r--r--test/input/func_noerror_defined_and_used_on_same_line.py8
-rw-r--r--test/input/func_noerror_inner_classes.py2
-rw-r--r--test/input/func_noerror_new_style_class_py_30.py2
-rw-r--r--test/input/func_noerror_nonregr.py2
-rw-r--r--test/input/func_noerror_static_method.py4
-rw-r--r--test/input/func_noerror_used_before_assignment.py5
-rw-r--r--test/input/func_set_literal_as_default_py27.py3
-rw-r--r--test/input/func_special_methods.py55
-rw-r--r--test/input/func_superfluous_parens.py19
-rw-r--r--test/input/func_unpacking_non_sequence.py2
-rw-r--r--test/input/func_use_for_or_listcomp_var.py2
-rw-r--r--test/input/func_useless_else_on_loop.py14
-rw-r--r--test/input/func_w0108.py2
-rw-r--r--test/input/func_w0623_py_30.py3
-rw-r--r--test/input/func_w0711.py2
-rw-r--r--test/input/func_with_used_before_assignment.py12
-rw-r--r--test/input/syntax_error.py2
-rw-r--r--test/messages/func_bad_context_manager.txt6
-rw-r--r--test/messages/func_catching_non_exception.txt6
-rw-r--r--test/messages/func_format.txt43
-rw-r--r--test/messages/func_i0022.txt13
-rw-r--r--test/messages/func_missing_super_argument_py_30.txt (renamed from test/messages/func_missing_super_argument_py20.txt)0
-rw-r--r--test/messages/func_namedtuple.txt1
-rw-r--r--test/messages/func_special_methods.txt6
-rw-r--r--test/messages/func_superfluous_parens.txt5
-rw-r--r--test/messages/func_toolonglines_py30.txt4
-rw-r--r--test/messages/func_unbalanced_tuple_unpacking.txt11
-rw-r--r--test/messages/func_unpacking_non_sequence.txt15
-rw-r--r--test/messages/func_w0623_py_30.txt7
-rw-r--r--test/messages/func_with_used_before_assignment.txt2
-rw-r--r--test/regrtest_data/pygtk_import.py2
-rw-r--r--test/test_format.py365
-rw-r--r--test/test_func.py13
-rw-r--r--test/test_import_graph.py6
-rw-r--r--test/test_misc.py10
-rw-r--r--test/unittest_lint.py64
-rw-r--r--test/unittest_pyreverse_diadefs.py25
-rw-r--r--test/unittest_pyreverse_writer.py110
-rw-r--r--test/utils.py81
-rw-r--r--testutils.py21
-rw-r--r--tox.ini10
-rw-r--r--utils.py93
93 files changed, 1556 insertions, 1229 deletions
diff --git a/.hgignore b/.hgignore
index 44d78aa..e5660d0 100644
--- a/.hgignore
+++ b/.hgignore
@@ -8,3 +8,4 @@
^doc/_build
^dist/
^pylint.egg-info/
+.tox
diff --git a/ChangeLog b/ChangeLog
index 15cc4b8..a27bc5f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,21 +2,68 @@ ChangeLog for Pylint
====================
--
+ * bitbucket #128: pylint doesn't crash when looking
+ for used-before-assignment in context manager
+ assignments.
+
+2013-12-22 -- 1.1.0
+ * Add new check for use of deprecated pragma directives "pylint:disable-msg"
+ or "pylint:enable-msg" (I0022, deprecated-pragma) which was previously
+ emmited as a regular warn().
+
+ * Avoid false used-before-assignment for except handler defined
+ identifier used on the same line (#111).
+
+ * Combine 'no-space-after-operator', 'no-space-after-comma' and
+ 'no-space-before-operator' into a new warning 'bad-whitespace'.
+
+ * Add a new warning 'superfluous-parens' for unnecessary
+ parentheses after certain keywords.
+
+ * Fix a potential crash in the redefine-in-handler warning
+ if the redefined name is a nested getattr node.
+
+ * Add a new option for the multi-statement warning to
+ allow single-line if statements.
+
* Add 'bad-context-manager' error, checking that '__exit__'
special method accepts the right number of arguments.
-
- * Run pylint as a python module 'python -m pylint' (anatoly techtonik)
- * Check for non-exception classes inside an except clause
+ * Run pylint as a python module 'python -m pylint' (anatoly techtonik).
+
+ * Check for non-exception classes inside an except clause.
* epylint support options to give to pylint after the file to analyze and
have basic input validation (bitbucket #53 and #54), patches provided by
- felipeochoa and Brian Lane
+ felipeochoa and Brian Lane.
+
+ * Added a new warning, 'non-iterator-returned', for non-iterators
+ returned by '__iter__'.
+
+ * Add new checks for unpacking non-sequences in assignments
+ (unpacking-non-sequence) as well as unbalanced tuple unpacking
+ (unbalanced-tuple-unpacking).
- * Added a new warning, 'non-iterator-returned', for non-iterators
- returned by '__iter__'
+ * useless-else-on-loop not emited if there is a break in the
+ else clause of inner loop (#117).
+
+ * don't mark `input` as a bad function when using python3 (#110).
+
+ * badly-implemented-container caused several problems in its
+ current implementation. Deactivate it until we have something
+ better. See #112 for instance.
+
+ * Use attribute regexp for properties in python3, as in python2
+
+ * Create the PYLINTHOME directory when needed, it might fail and lead to
+ spurious warnings on import of pylint.config.
+
+ * Fix setup.py so that pylint properly install on Windows when using python3
+
+ * Various documentation fixes and enhancements
+
+ * Fix issue #55 (false-positive trailing-whitespace on Windows)
- * Add new warning for unpacking non-sequences in assignments
* Add new warning, 'bad-reversed-sequence', for checking that the
reversed() builtin receive a sequence (implements __getitem__ and __len__,
@@ -25,7 +72,6 @@ ChangeLog for Pylint
2013-08-06 -- 1.0.0
-
* Add check for the use of 'exec' function
* New --msg-template option to control output, deprecating "msvc" and
@@ -132,6 +178,7 @@ ChangeLog for Pylint
modules
+
2013-04-25 -- 0.28.0
* bitbucket #1: fix "dictionary changed size during iteration" crash
diff --git a/MANIFEST.in b/MANIFEST.in
index 32ec38e..37c6e67 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -7,6 +7,6 @@ include examples/*.py examples/pylintrc examples/pylintrc_camelcase
include elisp/startup elisp/*.el
include man/*.1
recursive-include doc *.rst *.jpeg Makefile *.html *.py
-recursive-include test *.py *.txt *.txt2 *.dot *.sh
-include test/input/similar* noext
+recursive-include test *.py *.txt *.dot *.sh
+include test/input/similar*
include test/input/noext
diff --git a/README b/README
index 25a82ea..ec6bfde 100644
--- a/README
+++ b/README
@@ -15,8 +15,8 @@ It's a free software distributed under the GNU Public Licence.
Development is hosted on bitbucket: https://bitbucket.org/logilab/pylint/
You can use the code-quality@python.org mailing list to discuss about
-Pylint. Subscribe at http://lists.python.org/mailman/listinfo/code-quality
-or read the archives at http://lists.python.org/pipermail/code-quality/
+Pylint. Subscribe at https://mail.python.org/mailman/listinfo/code-quality/
+or read the archives at https://mail.python.org/pipermail/code-quality/
Install
-------
diff --git a/__pkginfo__.py b/__pkginfo__.py
index 997b9a5..eff50aa 100644
--- a/__pkginfo__.py
+++ b/__pkginfo__.py
@@ -18,7 +18,7 @@
modname = distname = 'pylint'
-numversion = (1, 0, 0)
+numversion = (1, 1, 0)
version = '.'.join([str(num) for num in numversion])
install_requires = ['logilab-common >= 0.53.0', 'astroid >= 0.24.3']
@@ -30,18 +30,18 @@ mailinglist = "mailto://python-projects@lists.logilab.org"
author = 'Logilab'
author_email = 'python-projects@lists.logilab.org'
-classifiers = ['Development Status :: 4 - Beta',
- 'Environment :: Console',
- 'Intended Audience :: Developers',
- 'License :: OSI Approved :: GNU General Public License (GPL)',
- 'Operating System :: OS Independent',
- 'Programming Language :: Python',
- 'Programming Language :: Python :: 2',
- 'Programming Language :: Python :: 3',
- 'Topic :: Software Development :: Debuggers',
- 'Topic :: Software Development :: Quality Assurance',
- 'Topic :: Software Development :: Testing',
- ]
+classifiers = ['Development Status :: 4 - Beta',
+ 'Environment :: Console',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: GNU General Public License (GPL)',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Programming Language :: Python :: 2',
+ 'Programming Language :: Python :: 3',
+ 'Topic :: Software Development :: Debuggers',
+ 'Topic :: Software Development :: Quality Assurance',
+ 'Topic :: Software Development :: Testing',
+ ]
long_desc = """\
diff --git a/checkers/__init__.py b/checkers/__init__.py
index 27dc364..1d0aa42 100644
--- a/checkers/__init__.py
+++ b/checkers/__init__.py
@@ -99,10 +99,6 @@ class BaseChecker(OptionsProviderMixIn, ASTWalker):
"""add a message of a given type"""
self.linter.add_message(msg_id, line, node, args)
- def package_dir(self):
- """return the base directory for the analysed package"""
- return dirname(self.linter.base_file)
-
# dummy methods implementing the IChecker interface
def open(self):
diff --git a/checkers/base.py b/checkers/base.py
index 6aae709..5df0477 100644
--- a/checkers/base.py
+++ b/checkers/base.py
@@ -52,6 +52,10 @@ NO_REQUIRED_DOC_RGX = re.compile('__.*__')
REVERSED_METHODS = (('__getitem__', '__len__'),
('__reversed__', ))
+BAD_FUNCTIONS = ['map', 'filter', 'apply']
+if sys.version_info < (3, 0):
+ BAD_FUNCTIONS.append('input')
+
del re
def in_loop(node):
@@ -83,12 +87,19 @@ def _loop_exits_early(loop):
# in orelse.
for child in loop.body:
if isinstance(child, loop_nodes):
+ # break statement may be in orelse of child loop.
+ for orelse in (child.orelse or ()):
+ for _ in orelse.nodes_of_class(astroid.Break, skip_klass=loop_nodes):
+ return True
continue
for _ in child.nodes_of_class(astroid.Break, skip_klass=loop_nodes):
return True
return False
-
+if sys.version_info < (3, 0):
+ PROPERTY_CLASSES = set(('__builtin__.property', 'abc.abstractproperty'))
+else:
+ PROPERTY_CLASSES = set(('builtins.property', 'abc.abstractproperty'))
def _determine_function_name_type(node):
"""Determine the name type whose regex the a function's name should match.
@@ -109,8 +120,7 @@ def _determine_function_name_type(node):
(isinstance(decorator, astroid.Getattr) and
decorator.attrname == 'abstractproperty')):
infered = safe_infer(decorator)
- if (infered and
- infered.qname() in ('__builtin__.property', 'abc.abstractproperty')):
+ if infered and infered.qname() in PROPERTY_CLASSES:
return 'attr'
# If the function is decorated using the prop_method.{setter,getter}
# form, treat it like an attribute as well.
@@ -251,7 +261,7 @@ class BasicErrorChecker(_BasicChecker):
not (v is None or
(isinstance(v, astroid.Const) and v.value is None) or
(isinstance(v, astroid.Name) and v.name == 'None')
- ) ]:
+ )]:
self.add_message('return-in-init', node=node)
elif node.is_generator():
# make sure we don't mix non-None returns and yields
@@ -432,13 +442,13 @@ functions, methods
'comma'}
),
('bad-functions',
- {'default' : ('map', 'filter', 'apply', 'input'),
+ {'default' : BAD_FUNCTIONS,
'type' :'csv', 'metavar' : '<builtin function names>',
'help' : 'List of builtins function names that should not be '
'used, separated by a comma'}
),
)
- reports = ( ('RP0101', 'Statistics by type', report_by_type_stats), )
+ reports = (('RP0101', 'Statistics by type', report_by_type_stats),)
def __init__(self, linter):
_BasicChecker.__init__(self, linter)
diff --git a/checkers/classes.py b/checkers/classes.py
index fd76146..fc09021 100644
--- a/checkers/classes.py
+++ b/checkers/classes.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2003-2012 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
@@ -183,7 +183,7 @@ class ClassChecker(BaseChecker):
options = (('ignore-iface-methods',
{'default' : (#zope interface
'isImplementedBy', 'deferred', 'extends', 'names',
- 'namesAndDescriptions', 'queryDescriptionFor', 'getBases',
+ 'namesAndDescriptions', 'queryDescriptionFor', 'getBases',
'getDescriptionFor', 'getDoc', 'getName', 'getTaggedValue',
'getTaggedValueTags', 'isEqualOrExtendedBy', 'setTaggedValue',
'isImplementedByInstancesOf',
@@ -355,10 +355,10 @@ a metaclass class method.'}
positional = sum(1 for arg in node.args.args if arg.name != 'self')
if positional < 3 and not node.args.vararg:
self.add_message('bad-context-manager',
- node=node)
+ node=node)
elif positional > 3:
self.add_message('bad-context-manager',
- node=node)
+ node=node)
def leave_function(self, node):
"""on method node, check if this method couldn't be a function
diff --git a/checkers/design_analysis.py b/checkers/design_analysis.py
index f3b5882..11defbf 100644
--- a/checkers/design_analysis.py
+++ b/checkers/design_analysis.py
@@ -26,42 +26,6 @@ import re
# regexp for ignored argument name
IGNORED_ARGUMENT_NAMES = re.compile('_.*')
-SPECIAL_METHODS = [('Context manager', set(('__enter__',
- '__exit__',))),
- ('Container', set(('__len__',
- '__getitem__',))),
- ('Mutable container', set(('__setitem__',
- '__delitem__',))),
- ]
-
-class SpecialMethodChecker(object):
- """A functor that checks for consistency of a set of special methods"""
- def __init__(self, methods_found, on_error):
- """Stores the set of __x__ method names that were found in the
- class and a callable that will be called with args to R0024 if
- the check fails
- """
- self.methods_found = methods_found
- self.on_error = on_error
-
- def __call__(self, methods_required, protocol):
- """Checks the set of method names given to __init__ against the set
- required.
-
- If they are all present, returns true.
- If they are all absent, returns false.
- If some are present, reports the error and returns false.
- """
- required_methods_found = methods_required & self.methods_found
- if required_methods_found == methods_required:
- return True
- if required_methods_found:
- required_methods_missing = methods_required - self.methods_found
- self.on_error((protocol,
- ', '.join(sorted(required_methods_found)),
- ', '.join(sorted(required_methods_missing))))
- return False
-
def class_is_abstract(klass):
"""return true if the given class node should be considered as an abstract
@@ -121,10 +85,6 @@ MSGS = {
'R0923': ('Interface not implemented',
'interface-not-implemented',
'Used when an interface class is not implemented anywhere.'),
- 'R0924': ('Badly implemented %s, implements %s but not %s',
- 'incomplete-protocol',
- 'A class implements some of the special methods for a particular \
- protocol, but not all of them')
}
@@ -289,13 +249,6 @@ class MisdesignChecker(BaseChecker):
# stop here for exception, metaclass and interface classes
if node.type != 'class':
return
- # Does the class implement special methods consitently?
- # If so, don't enforce minimum public methods.
- check_special = SpecialMethodChecker(
- special_methods, lambda args: self.add_message('R0924', node=node, args=args))
- protocols = [check_special(pmethods, pname) for pname, pmethods in SPECIAL_METHODS]
- if True in protocols:
- return
# Does the class contain more than 5 public methods ?
if nb_public_methods < self.config.min_public_methods:
self.add_message('R0903', node=node,
@@ -379,7 +332,7 @@ class MisdesignChecker(BaseChecker):
"""increments the branches counter"""
branches = 1
# don't double count If nodes coming from some 'elif'
- if node.orelse and (len(node.orelse)>1 or
+ if node.orelse and (len(node.orelse) > 1 or
not isinstance(node.orelse[0], If)):
branches += 1
self._inc_branch(branches)
diff --git a/checkers/exceptions.py b/checkers/exceptions.py
index 8ac00a5..5bb07ac 100644
--- a/checkers/exceptions.py
+++ b/checkers/exceptions.py
@@ -25,6 +25,23 @@ from pylint.checkers import BaseChecker
from pylint.checkers.utils import is_empty, is_raising, check_messages
from pylint.interfaces import IAstroidChecker
+def infer_bases(klass):
+ """ Fully infer the bases of the klass node.
+
+ This doesn't use .ancestors(), because we need
+ the non-inferable nodes (YES nodes),
+ which can't be retrieved from .ancestors()
+ """
+ for base in klass.bases:
+ try:
+ inferit = base.infer().next()
+ except astroid.InferenceError:
+ continue
+ if inferit is YES:
+ yield inferit
+ else:
+ for base in infer_bases(inferit):
+ yield base
OVERGENERAL_EXCEPTIONS = ('Exception',)
@@ -50,7 +67,7 @@ MSGS = {
'catching-non-exception',
'Used when a class which doesn\'t inherit from \
BaseException is used as an exception in an except clause.'),
-
+
'W0701': ('Raising a string exception',
'raising-string',
'Used when a string exception is raised.'),
@@ -141,10 +158,10 @@ class ExceptionsChecker(BaseChecker):
isinstance(expr, (astroid.List, astroid.Dict, astroid.Tuple,
astroid.Module, astroid.Function)):
self.add_message('E0702', node=node, args=expr.name)
- elif ( (isinstance(expr, astroid.Name) and expr.name == 'NotImplemented')
- or (isinstance(expr, astroid.CallFunc) and
- isinstance(expr.func, astroid.Name) and
- expr.func.name == 'NotImplemented') ):
+ elif ((isinstance(expr, astroid.Name) and expr.name == 'NotImplemented')
+ or (isinstance(expr, astroid.CallFunc) and
+ isinstance(expr.func, astroid.Name) and
+ expr.func.name == 'NotImplemented')):
self.add_message('E0711', node=node)
elif isinstance(expr, astroid.BinOp) and expr.op == '%':
self.add_message('W0701', node=node)
@@ -211,12 +228,19 @@ class ExceptionsChecker(BaseChecker):
and exc.root().name == EXCEPTIONS_MODULE
and nb_handlers == 1 and not is_raising(handler.body)):
self.add_message('W0703', args=exc.name, node=handler.type)
-
+
if (not inherit_from_std_ex(exc) and
exc.root().name != BUILTINS_NAME):
- self.add_message('catching-non-exception',
- node=handler.type,
- args=(exc.name, ))
+ # try to see if the exception is based on a C based
+ # exception, by infering all the base classes and
+ # looking for inference errors
+ bases = infer_bases(exc)
+ fully_infered = all(inferit is not YES
+ for inferit in bases)
+ if fully_infered:
+ self.add_message('catching-non-exception',
+ node=handler.type,
+ args=(exc.name, ))
exceptions_classes += excs
diff --git a/checkers/format.py b/checkers/format.py
index 1cf0edc..aab2320 100644
--- a/checkers/format.py
+++ b/checkers/format.py
@@ -21,19 +21,43 @@ http://www.python.org/doc/essays/styleguide.html
Some parts of the process_token method is based from The Tab Nanny std module.
"""
-import re, sys
+import keyword
+import sys
import tokenize
+
if not hasattr(tokenize, 'NL'):
raise ValueError("tokenize.NL doesn't exist -- tokenize module too old")
-from logilab.common.textutils import pretty_match
from astroid import nodes
-from pylint.interfaces import ITokenChecker, IAstroidChecker
+from pylint.interfaces import ITokenChecker, IAstroidChecker, IRawChecker
from pylint.checkers import BaseTokenChecker
from pylint.checkers.utils import check_messages
from pylint.utils import WarningScope, OPTION_RGX
+_KEYWORD_TOKENS = ['assert', 'del', 'elif', 'except', 'for', 'if', 'in', 'not',
+ 'raise', 'return', 'while', 'yield']
+if sys.version_info < (3, 0):
+ _KEYWORD_TOKENS.append('print')
+
+_SPACED_OPERATORS = ['==', '<', '>', '!=', '<>', '<=', '>=',
+ '+=', '-=', '*=', '**=', '/=', '//=', '&=', '|=', '^=',
+ '%=', '>>=', '<<=']
+_OPENING_BRACKETS = ['(', '[', '{']
+_CLOSING_BRACKETS = [')', ']', '}']
+
+_EOL = frozenset([tokenize.NEWLINE, tokenize.NL, tokenize.COMMENT])
+
+# Whitespace checking policy constants
+_MUST = 0
+_MUST_NOT = 1
+_IGNORE = 2
+
+# Whitespace checking config constants
+_DICT_SEPARATOR = 'dict-separator'
+_TRAILING_COMMA = 'trailing-comma'
+_NO_SPACE_CHECK_CHOICES = [_TRAILING_COMMA, _DICT_SEPARATOR]
+
MSGS = {
'C0301': ('Line too long (%s/%s)',
'line-too-long',
@@ -64,22 +88,20 @@ MSGS = {
'multiple-statements',
'Used when more than on statement are found on the same line.',
{'scope': WarningScope.NODE}),
- 'C0322': ('Operator not preceded by a space\n%s',
- 'no-space-before-operator',
- 'Used when one of the following operator (!= | <= | == | >= | < '
- '| > | = | \\+= | -= | \\*= | /= | %) is not preceded by a space.',
- {'scope': WarningScope.NODE}),
- 'C0323': ('Operator not followed by a space\n%s',
- 'no-space-after-operator',
- 'Used when one of the following operator (!= | <= | == | >= | < '
- '| > | = | \\+= | -= | \\*= | /= | %) is not followed by a space.',
- {'scope': WarningScope.NODE}),
- 'C0324': ('Comma not followed by a space\n%s',
- 'no-space-after-comma',
- 'Used when a comma (",") is not followed by a space.',
- {'scope': WarningScope.NODE}),
+ 'C0325' : ('Unnecessary parens after %r keyword',
+ 'superfluous-parens',
+ 'Used when a single item in parentheses follows an if, for, or '
+ 'other keyword.'),
+ 'C0326': ('%s space %s %s %s\n%s',
+ 'bad-whitespace',
+ ('Used when a wrong number of spaces is used around an operator, '
+ 'bracket or block opener.'),
+ {'old_names': [('C0323', 'no-space-after-operator'),
+ ('C0324', 'no-space-after-comma'),
+ ('C0322', 'no-space-before-operator')]})
}
+
if sys.version_info < (3, 0):
MSGS.update({
@@ -99,74 +121,21 @@ if sys.version_info < (3, 0):
{'scope': WarningScope.NODE}),
})
-# simple quoted string rgx
-SQSTRING_RGX = r'"([^"\\]|\\.)*?"'
-# simple apostrophed rgx
-SASTRING_RGX = r"'([^'\\]|\\.)*?'"
-# triple quoted string rgx
-TQSTRING_RGX = r'"""([^"]|("(?!"")))*?(""")'
-# triple apostrophe'd string rgx
-TASTRING_RGX = r"'''([^']|('(?!'')))*?(''')"
-
-# finally, the string regular expression
-STRING_RGX = re.compile('(%s)|(%s)|(%s)|(%s)' % (TQSTRING_RGX, TASTRING_RGX,
- SQSTRING_RGX, SASTRING_RGX),
- re.MULTILINE|re.DOTALL)
-
-COMMENT_RGX = re.compile("#.*$", re.M)
-
-OPERATORS = r'!=|<=|==|>=|<|>|=|\+=|-=|\*=|/=|%'
-
-OP_RGX_MATCH_1 = r'[^(]*(?<!\s|\^|<|>|=|\+|-|\*|/|!|%%|&|\|)(%s).*' % OPERATORS
-OP_RGX_SEARCH_1 = r'(?<!\s|\^|<|>|=|\+|-|\*|/|!|%%|&|\|)(%s)' % OPERATORS
-OP_RGX_MATCH_2 = r'[^(]*(%s)(?!\s|=|>|<).*' % OPERATORS
-OP_RGX_SEARCH_2 = r'(%s)(?!\s|=|>)' % OPERATORS
+def _underline_token(token):
+ length = token[3][1] - token[2][1]
+ offset = token[2][1]
+ return token[4] + (' ' * offset) + ('^' * length)
-BAD_CONSTRUCT_RGXS = (
- (re.compile(OP_RGX_MATCH_1, re.M),
- re.compile(OP_RGX_SEARCH_1, re.M),
- 'C0322'),
-
- (re.compile(OP_RGX_MATCH_2, re.M),
- re.compile(OP_RGX_SEARCH_2, re.M),
- 'C0323'),
-
- (re.compile(r'.*,[^(\s|\]|}|\))].*', re.M),
- re.compile(r',[^\s)]', re.M),
- 'C0324'),
- )
-
-
-def get_string_coords(line):
- """return a list of string positions (tuple (start, end)) in the line
- """
- result = []
- for match in re.finditer(STRING_RGX, line):
- result.append( (match.start(), match.end()) )
- return result
-
-def in_coords(match, string_coords):
- """return true if the match is in the string coord"""
- mstart = match.start()
- for start, end in string_coords:
- if mstart >= start and mstart < end:
- return True
- return False
-
-def check_line(line):
- """check a line for a bad construction
- if it founds one, return a message describing the problem
- else return None
- """
- cleanstr = COMMENT_RGX.sub('', STRING_RGX.sub('', line))
- for rgx_match, rgx_search, msg_id in BAD_CONSTRUCT_RGXS:
- if rgx_match.match(cleanstr):
- string_positions = get_string_coords(line)
- for match in re.finditer(rgx_search, line):
- if not in_coords(match, string_positions):
- return msg_id, pretty_match(match, line.rstrip())
+def _column_distance(token1, token2):
+ if token1 == token2:
+ return 0
+ if token2[3] < token1[3]:
+ token1, token2 = token2, token1
+ if token1[3][0] != token2[2][0]:
+ return None
+ return token2[2][1] - token1[3][1]
class FormatChecker(BaseTokenChecker):
@@ -177,7 +146,7 @@ class FormatChecker(BaseTokenChecker):
* use of <> instead of !=
"""
- __implements__ = (ITokenChecker, IAstroidChecker)
+ __implements__ = (ITokenChecker, IAstroidChecker, IRawChecker)
# configuration section name
name = 'format'
@@ -193,6 +162,16 @@ class FormatChecker(BaseTokenChecker):
'default': r'^\s*(# )?<?https?://\S+>?$',
'help': ('Regexp for a line that is allowed to be longer than '
'the limit.')}),
+ ('single-line-if-stmt',
+ {'default': False, 'type' : 'yn', 'metavar' : '<y_or_n>',
+ 'help' : ('Allow the body of an if to be on the same '
+ 'line as the test if there is no else.')}),
+ ('no-space-check',
+ {'default': ','.join(_NO_SPACE_CHECK_CHOICES),
+ 'type': 'multiple_choice',
+ 'choices': _NO_SPACE_CHECK_CHOICES,
+ 'help': ('List of optional constructs for which whitespace '
+ 'checking is disabled')}),
('max-module-lines',
{'default' : 1000, 'type' : 'int', 'metavar' : '<int>',
'help': 'Maximum number of lines in a module'}
@@ -213,6 +192,223 @@ class FormatChecker(BaseTokenChecker):
self._lines[line_num] = line.split('\n')[0]
self.check_lines(line, line_num)
+ def process_module(self, module):
+ self._keywords_with_parens = set()
+ for node in module.body:
+ if (isinstance(node, nodes.From) and node.modname == '__future__'
+ and any(name == 'print_function' for name, _ in node.names)):
+ self._keywords_with_parens.add('print')
+
+ def _check_keyword_parentheses(self, tokens, start):
+ """Check that there are not unnecessary parens after a keyword.
+
+ Parens are unnecessary if there is exactly one balanced outer pair on a
+ line, and it is followed by a colon, and contains no commas (i.e. is not a
+ tuple).
+
+ Args:
+ tokens: list of Tokens; the entire list of Tokens.
+ start: int; the position of the keyword in the token list.
+ """
+ # If the next token is not a paren, we're fine.
+ if tokens[start+1][1] != '(':
+ return
+
+ found_and_or = False
+ depth = 0
+ keyword_token = tokens[start][1]
+ line_num = tokens[start][2][0]
+
+ for i in xrange(start, len(tokens) - 1):
+ token = tokens[i]
+
+ # If we hit a newline, then assume any parens were for continuation.
+ if token[0] == tokenize.NL:
+ return
+
+ if token[1] == '(':
+ depth += 1
+ elif token[1] == ')':
+ depth -= 1
+ if not depth:
+ # ')' can't happen after if (foo), since it would be a syntax error.
+ if (tokens[i+1][1] in (':', ')', ']', '}', 'in') or
+ tokens[i+1][0] in (tokenize.NEWLINE, tokenize.ENDMARKER,
+ tokenize.COMMENT)):
+ # The empty tuple () is always accepted.
+ if i == start + 2:
+ return
+ if keyword_token == 'not':
+ if not found_and_or:
+ self.add_message('C0325', line=line_num,
+ args=keyword_token)
+ elif keyword_token in ('return', 'yield'):
+ self.add_message('C0325', line=line_num,
+ args=keyword_token)
+ elif keyword_token not in self._keywords_with_parens:
+ if not (tokens[i+1][1] == 'in' and found_and_or):
+ self.add_message('C0325', line=line_num,
+ args=keyword_token)
+ return
+ elif depth == 1:
+ # This is a tuple, which is always acceptable.
+ if token[1] == ',':
+ return
+ # 'and' and 'or' are the only boolean operators with lower precedence
+ # than 'not', so parens are only required when they are found.
+ elif token[1] in ('and', 'or'):
+ found_and_or = True
+ # A yield inside an expression must always be in parentheses,
+ # quit early without error.
+ elif token[1] == 'yield':
+ return
+ # A generator expression always has a 'for' token in it, and
+ # the 'for' token is only legal inside parens when it is in a
+ # generator expression. The parens are necessary here, so bail
+ # without an error.
+ elif token[1] == 'for':
+ return
+
+ def _opening_bracket(self, tokens, i):
+ self._bracket_stack.append(tokens[i][1])
+ # Special case: ignore slices
+ if tokens[i][1] == '[' and tokens[i+1][1] == ':':
+ return
+
+ if (i > 0 and (tokens[i-1][0] == tokenize.NAME and
+ not (keyword.iskeyword(tokens[i-1][1]))
+ or tokens[i-1][1] in _CLOSING_BRACKETS)):
+ self._check_space(tokens, i, (_MUST_NOT, _MUST_NOT))
+ else:
+ self._check_space(tokens, i, (_IGNORE, _MUST_NOT))
+
+ def _closing_bracket(self, tokens, i):
+ self._bracket_stack.pop()
+ # Special case: ignore slices
+ if tokens[i-1][1] == ':' and tokens[i][1] == ']':
+ return
+ policy_before = _MUST_NOT
+ if tokens[i][1] in _CLOSING_BRACKETS and tokens[i-1][1] == ',':
+ if _TRAILING_COMMA in self.config.no_space_check:
+ policy_before = _IGNORE
+
+ self._check_space(tokens, i, (policy_before, _IGNORE))
+
+ def _check_equals_spacing(self, tokens, i):
+ """Check the spacing of a single equals sign."""
+ if self._inside_brackets('(') or self._inside_brackets('lambda'):
+ self._check_space(tokens, i, (_MUST_NOT, _MUST_NOT))
+ else:
+ self._check_space(tokens, i, (_MUST, _MUST))
+
+ def _open_lambda(self, tokens, i): # pylint:disable=unused-argument
+ self._bracket_stack.append('lambda')
+
+ def _handle_colon(self, tokens, i):
+ # Special case: ignore slices
+ if self._inside_brackets('['):
+ return
+ if (self._inside_brackets('{') and
+ _DICT_SEPARATOR in self.config.no_space_check):
+ policy = (_IGNORE, _IGNORE)
+ else:
+ policy = (_MUST_NOT, _MUST)
+ self._check_space(tokens, i, policy)
+
+ if self._inside_brackets('lambda'):
+ self._bracket_stack.pop()
+
+ def _handle_comma(self, tokens, i):
+ # Only require a following whitespace if this is
+ # not a hanging comma before a closing bracket.
+ if tokens[i+1][1] in _CLOSING_BRACKETS:
+ self._check_space(tokens, i, (_MUST_NOT, _IGNORE))
+ else:
+ self._check_space(tokens, i, (_MUST_NOT, _MUST))
+
+ def _check_surrounded_by_space(self, tokens, i):
+ """Check that a binary operator is surrounded by exactly one space."""
+ self._check_space(tokens, i, (_MUST, _MUST))
+
+ def _check_space(self, tokens, i, policies):
+ def _policy_string(policy):
+ if policy == _MUST:
+ return 'Exactly one', 'required'
+ else:
+ return 'No', 'allowed'
+
+ def _name_construct(token):
+ if tokens[i][1] == ',':
+ return 'comma'
+ elif tokens[i][1] == ':':
+ return ':'
+ elif tokens[i][1] in '()[]{}':
+ return 'bracket'
+ elif tokens[i][1] in ('<', '>', '<=', '>=', '!='):
+ return 'comparison'
+ else:
+ if self._inside_brackets('('):
+ return 'keyword argument assignment'
+ else:
+ return 'assignment'
+
+ good_space = [True, True]
+ pairs = [(tokens[i-1], tokens[i]), (tokens[i], tokens[i+1])]
+
+ for other_idx, (policy, token_pair) in enumerate(zip(policies, pairs)):
+ if token_pair[other_idx][0] in _EOL or policy == _IGNORE:
+ continue
+
+ distance = _column_distance(*token_pair)
+ if distance is None:
+ continue
+ good_space[other_idx] = (
+ (policy == _MUST and distance == 1) or
+ (policy == _MUST_NOT and distance == 0))
+
+ warnings = []
+ if not any(good_space) and policies[0] == policies[1]:
+ warnings.append((policies[0], 'around'))
+ else:
+ for ok, policy, position in zip(good_space, policies, ('before', 'after')):
+ if not ok:
+ warnings.append((policy, position))
+ for policy, position in warnings:
+ construct = _name_construct(tokens[i])
+ count, state = _policy_string(policy)
+ self.add_message('C0326', line=tokens[i][2][0],
+ args=(count, state, position, construct,
+ _underline_token(tokens[i])))
+
+ def _inside_brackets(self, left):
+ return self._bracket_stack[-1] == left
+
+ def _prepare_token_dispatcher(self):
+ raw = [
+ (_KEYWORD_TOKENS,
+ self._check_keyword_parentheses),
+
+ (_OPENING_BRACKETS, self._opening_bracket),
+
+ (_CLOSING_BRACKETS, self._closing_bracket),
+
+ (['='], self._check_equals_spacing),
+
+ (_SPACED_OPERATORS, self._check_surrounded_by_space),
+
+ ([','], self._handle_comma),
+
+ ([':'], self._handle_colon),
+
+ (['lambda'], self._open_lambda),
+ ]
+
+ dispatch = {}
+ for tokens, handler in raw:
+ for token in tokens:
+ dispatch[token] = handler
+ return dispatch
+
def process_tokens(self, tokens):
"""process tokens and search for :
@@ -222,6 +418,7 @@ class FormatChecker(BaseTokenChecker):
_ optionally bad construct (if given, bad_construct must be a compiled
regular expression).
"""
+ self._bracket_stack = [None]
indent = tokenize.INDENT
dedent = tokenize.DEDENT
newline = tokenize.NEWLINE
@@ -233,7 +430,8 @@ class FormatChecker(BaseTokenChecker):
self._lines = {}
self._visited_lines = {}
new_line_delay = False
- for (tok_type, token, start, _, line) in tokens:
+ token_handlers = self._prepare_token_dispatcher()
+ for idx, (tok_type, token, start, _, line) in enumerate(tokens):
if new_line_delay:
new_line_delay = False
self.new_line(tok_type, line, line_num, junk)
@@ -292,11 +490,18 @@ class FormatChecker(BaseTokenChecker):
check_equal = 0
self.check_indent_level(line, indents[-1], line_num)
+ try:
+ handler = token_handlers[token]
+ except KeyError:
+ pass
+ else:
+ handler(tokens, idx)
+
line_num -= 1 # to be ok with "wc -l"
if line_num > self.config.max_module_lines:
self.add_message('C0302', args=line_num, line=1)
- @check_messages('C0321' ,'C03232', 'C0323', 'C0324')
+ @check_messages('C0321', 'C03232', 'C0323', 'C0324')
def visit_default(self, node):
"""check the node line number and check it if not yet done"""
if not node.is_statement:
@@ -307,16 +512,19 @@ class FormatChecker(BaseTokenChecker):
if prev_sibl is not None:
prev_line = prev_sibl.fromlineno
else:
- prev_line = node.parent.statement().fromlineno
+ # The line on which a finally: occurs in a try/finally
+ # is not directly represented in the AST. We infer it
+ # by taking the last line of the body and adding 1, which
+ # should be the line of finally:
+ if (isinstance(node.parent, nodes.TryFinally)
+ and node in node.parent.finalbody):
+ prev_line = node.parent.body[0].tolineno + 1
+ else:
+ prev_line = node.parent.statement().fromlineno
line = node.fromlineno
assert line, node
if prev_line == line and self._visited_lines.get(line) != 2:
- # py2.5 try: except: finally:
- if not (isinstance(node, nodes.TryExcept)
- and isinstance(node.parent, nodes.TryFinally)
- and node.fromlineno == node.parent.fromlineno):
- self.add_message('C0321', node=node)
- self._visited_lines[line] = 2
+ self._check_multi_statement_line(node, line)
return
if line in self._visited_lines:
return
@@ -332,13 +540,23 @@ class FormatChecker(BaseTokenChecker):
lines.append(self._lines[line].rstrip())
except KeyError:
lines.append('')
- try:
- msg_def = check_line('\n'.join(lines))
- if msg_def:
- self.add_message(msg_def[0], node=node, args=msg_def[1])
- except KeyError:
- # FIXME: internal error !
- pass
+
+ def _check_multi_statement_line(self, node, line):
+ """Check for lines containing multiple statements."""
+ # Do not warn about multiple nested context managers
+ # in with statements.
+ if isinstance(node, nodes.With):
+ return
+ # For try... except... finally..., the two nodes
+ # appear to be on the same line due to how the AST is built.
+ if (isinstance(node, nodes.TryExcept) and
+ isinstance(node.parent, nodes.TryFinally)):
+ return
+ if (isinstance(node.parent, nodes.If) and not node.parent.orelse
+ and self.config.single_line_if_stmt):
+ return
+ self.add_message('C0321', node=node)
+ self._visited_lines[line] = 2
@check_messages('W0333')
def visit_backquote(self, node):
@@ -388,7 +606,7 @@ class FormatChecker(BaseTokenChecker):
self.add_message('W0312', args=args, line=line_num)
return level
suppl += string[0]
- string = string [1:]
+ string = string[1:]
if level != expected or suppl:
i_type = 'spaces'
if indent[0] == '\t':
diff --git a/checkers/imports.py b/checkers/imports.py
index 1dd7787..b0a9872 100644
--- a/checkers/imports.py
+++ b/checkers/imports.py
@@ -93,7 +93,7 @@ def dependencies_graph(filename, dep_info):
"""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 sorted(dep_info.iteritems()):
done[modname] = 1
@@ -301,11 +301,10 @@ given file (report RP0402 must not be disabled)'}
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"""
diff --git a/checkers/logging.py b/checkers/logging.py
index 6986ca4..d1f9d36 100644
--- a/checkers/logging.py
+++ b/checkers/logging.py
@@ -91,7 +91,7 @@ class LoggingChecker(checkers.BaseChecker):
and ancestor.parent.name == 'logging')))]
except astroid.exceptions.InferenceError:
return
- if (node.func.expr.name != self._logging_name and not logger_class):
+ if node.func.expr.name != self._logging_name and not logger_class:
return
self._check_convenience_methods(node)
self._check_log_methods(node)
@@ -129,7 +129,7 @@ class LoggingChecker(checkers.BaseChecker):
node: AST node to be checked.
format_arg: Index of the format string in the node arguments.
"""
- num_args = self._count_supplied_tokens(node.args[format_arg + 1:])
+ num_args = _count_supplied_tokens(node.args[format_arg + 1:])
if not num_args:
# If no args were supplied, then all format strings are valid -
# don't check any further.
@@ -147,9 +147,10 @@ class LoggingChecker(checkers.BaseChecker):
# Keyword checking on logging strings is complicated by
# special keywords - out of scope.
return
- except utils.UnsupportedFormatCharacter, e:
- c = format_string[e.index]
- self.add_message('E1200', node=node, args=(c, ord(c), e.index))
+ except utils.UnsupportedFormatCharacter, ex:
+ char = format_string[ex.index]
+ self.add_message('E1200', node=node,
+ args=(char, ord(char), ex.index))
return
except utils.IncompleteFormatString:
self.add_message('E1201', node=node)
@@ -159,20 +160,21 @@ class LoggingChecker(checkers.BaseChecker):
elif num_args < required_num_args:
self.add_message('E1206', node=node)
- def _count_supplied_tokens(self, args):
- """Counts the number of tokens in an args list.
- The Python log functions allow for special keyword arguments: func,
- exc_info and extra. To handle these cases correctly, we only count
- arguments that aren't keywords.
+def _count_supplied_tokens(args):
+ """Counts the number of tokens in an args list.
- Args:
- args: List of AST nodes that are arguments for a log format string.
+ The Python log functions allow for special keyword arguments: func,
+ exc_info and extra. To handle these cases correctly, we only count
+ arguments that aren't keywords.
- Returns:
- Number of AST nodes that aren't keywords.
- """
- return sum(1 for arg in args if not isinstance(arg, astroid.Keyword))
+ Args:
+ args: List of AST nodes that are arguments for a log format string.
+
+ Returns:
+ Number of AST nodes that aren't keywords.
+ """
+ return sum(1 for arg in args if not isinstance(arg, astroid.Keyword))
def register(linter):
diff --git a/checkers/newstyle.py b/checkers/newstyle.py
index 9832195..ff9bbc2 100644
--- a/checkers/newstyle.py
+++ b/checkers/newstyle.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2005-2006 LOGILAB S.A. (Paris, FRANCE).
+# Copyright (c) 2005-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
@@ -37,7 +37,8 @@ MSGS = {
'E1004': ('Missing argument to super()',
'missing-super-argument',
'Used when the super builtin didn\'t receive an \
- argument on Python 2'),
+ argument on Python 2',
+ {'maxversion': (3, 0)}),
'W1001': ('Use of "property" on an old style class',
'property-on-old-class',
'Used when PyLint detect the use of the builtin "property" \
diff --git a/checkers/raw_metrics.py b/checkers/raw_metrics.py
index a8e4367..23e45b0 100644
--- a/checkers/raw_metrics.py
+++ b/checkers/raw_metrics.py
@@ -68,11 +68,11 @@ class RawMetricsChecker(BaseTokenChecker):
# configuration section name
name = 'metrics'
# configuration options
- options = ( )
+ options = ()
# messages
msgs = {}
# reports
- reports = ( ('RP0701', 'Raw metrics', report_raw_stats), )
+ reports = (('RP0701', 'Raw metrics', report_raw_stats),)
def __init__(self, linter):
BaseTokenChecker.__init__(self, linter)
diff --git a/checkers/similar.py b/checkers/similar.py
index 26b3725..8d755fa 100644
--- a/checkers/similar.py
+++ b/checkers/similar.py
@@ -63,15 +63,15 @@ class Similar(object):
duplicate = no_duplicates.setdefault(num, [])
for couples in duplicate:
if (lineset1, idx1) in couples or (lineset2, idx2) in couples:
- couples.add( (lineset1, idx1) )
- couples.add( (lineset2, idx2) )
+ couples.add((lineset1, idx1))
+ couples.add((lineset2, idx2))
break
else:
- duplicate.append( set([(lineset1, idx1), (lineset2, idx2)]) )
+ duplicate.append(set([(lineset1, idx1), (lineset2, idx2)]))
sims = []
for num, ensembles in no_duplicates.iteritems():
for couples in ensembles:
- sims.append( (num, couples) )
+ sims.append((num, couples))
sims.sort()
sims.reverse()
return sims
@@ -104,7 +104,7 @@ class Similar(object):
while index1 < len(lineset1):
skip = 1
num = 0
- for index2 in find( lineset1[index1] ):
+ for index2 in find(lineset1[index1]):
non_blank = 0
for num, ((_, line1), (_, line2)) in enumerate(
izip(lines1(index1), lines2(index2))):
@@ -210,7 +210,7 @@ class LineSet(object):
index = {}
for line_no, line in enumerate(self._stripped_lines):
if line:
- index.setdefault(line, []).append( line_no )
+ index.setdefault(line, []).append(line_no)
return index
@@ -260,7 +260,7 @@ class SimilarChecker(BaseChecker, Similar):
),
)
# reports
- reports = ( ('RP0801', 'Duplication', report_similarities), )
+ reports = (('RP0801', 'Duplication', report_similarities),)
def __init__(self, linter=None):
BaseChecker.__init__(self, linter)
@@ -349,9 +349,9 @@ def Run(argv=None):
usage()
elif opt in ('-i', '--ignore-comments'):
ignore_comments = True
- elif opt in ('--ignore-docstrings'):
+ elif opt in ('--ignore-docstrings',):
ignore_docstrings = True
- elif opt in ('--ignore-imports'):
+ elif opt in ('--ignore-imports',):
ignore_imports = True
if not args:
usage(1)
diff --git a/checkers/stdlib.py b/checkers/stdlib.py
index 07e1fbe..b63760c 100644
--- a/checkers/stdlib.py
+++ b/checkers/stdlib.py
@@ -21,7 +21,7 @@ import sys
import astroid
from pylint.interfaces import IAstroidChecker
-from pylint.checkers import BaseChecker, BaseTokenChecker
+from pylint.checkers import BaseChecker
from pylint.checkers import utils
_VALID_OPEN_MODE_REGEX = r'^(r?U|[rwa]\+?b?)$'
diff --git a/checkers/strings.py b/checkers/strings.py
index 42563da..c6bf960 100644
--- a/checkers/strings.py
+++ b/checkers/strings.py
@@ -66,11 +66,11 @@ MSGS = {
'E1305': ("Too many arguments for format string",
"too-many-format-args",
"Used when a format string that uses unnamed conversion \
- specifiers is given too few arguments."),
+ specifiers is given too many arguments."),
'E1306': ("Not enough arguments for format string",
"too-few-format-args",
"Used when a format string that uses unnamed conversion \
- specifiers is given too many arguments"),
+ specifiers is given too few arguments"),
}
OTHER_NODES = (astroid.Const, astroid.List, astroid.Backquote,
@@ -233,7 +233,7 @@ class StringConstantChecker(BaseTokenChecker):
if c in '\'\"':
quote_char = c
break
- prefix = token[:i].lower() # markers like u, b, r.
+ prefix = token[:i].lower() # markers like u, b, r.
after_prefix = token[i:]
if after_prefix[:3] == after_prefix[-3:] == 3 * quote_char:
string_body = after_prefix[3:-3]
diff --git a/checkers/typecheck.py b/checkers/typecheck.py
index 6988359..2e3785e 100644
--- a/checkers/typecheck.py
+++ b/checkers/typecheck.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2006-2010 LOGILAB S.A. (Paris, FRANCE).
+# Copyright (c) 2006-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
@@ -292,7 +292,7 @@ accessed. Python regular expressions are accepted.'}
# Built-in functions have no argument information.
return
- if len( called.argnames() ) != len( set( called.argnames() ) ):
+ if len(called.argnames()) != len(set(called.argnames())):
# Duplicate parameter name (see E9801). We can't really make sense
# of the function call in this case, so just return.
return
diff --git a/checkers/utils.py b/checkers/utils.py
index 5e028f2..7387711 100644
--- a/checkers/utils.py
+++ b/checkers/utils.py
@@ -60,7 +60,7 @@ def clobber_in_except(node):
(False, None) otherwise.
"""
if isinstance(node, astroid.AssAttr):
- return (True, (node.attrname, 'object %r' % (node.expr.name,)))
+ return (True, (node.attrname, 'object %r' % (node.expr.as_string(),)))
elif isinstance(node, astroid.AssName):
name = node.name
if is_builtin(name):
@@ -69,8 +69,9 @@ def clobber_in_except(node):
scope, stmts = node.lookup(name)
if (stmts and
not isinstance(stmts[0].ass_type(),
- (astroid.Assign, astroid.AugAssign, astroid.ExceptHandler))):
- return (True, (name, 'outer scope (line %s)' % (stmts[0].fromlineno,)))
+ (astroid.Assign, astroid.AugAssign,
+ astroid.ExceptHandler))):
+ return (True, (name, 'outer scope (line %s)' % stmts[0].fromlineno))
return (False, None)
@@ -153,8 +154,10 @@ def is_defined_before(var_node):
elif isinstance(_node, astroid.With):
for expr, vars in _node.items:
if expr.parent_of(var_node):
- break
- if vars and vars.name == varname:
+ break
+ if (vars and
+ isinstance(vars, astroid.AssName) and
+ vars.name == varname):
return True
elif isinstance(_node, (astroid.Lambda, astroid.Function)):
if _node.args.is_argument(varname):
@@ -162,6 +165,11 @@ def is_defined_before(var_node):
if getattr(_node, 'name', None) == varname:
return True
break
+ elif isinstance(_node, astroid.ExceptHandler):
+ if isinstance(_node.name, astroid.AssName):
+ ass_node = _node.name
+ if ass_node.name == varname:
+ return True
_node = _node.parent
# possibly multiple statements on the same line using semi colon separator
stmt = var_node.statement()
@@ -171,7 +179,7 @@ def is_defined_before(var_node):
for ass_node in _node.nodes_of_class(astroid.AssName):
if ass_node.name == varname:
return True
- for imp_node in _node.nodes_of_class( (astroid.From, astroid.Import)):
+ for imp_node in _node.nodes_of_class((astroid.From, astroid.Import)):
if varname in [name[1] or name[0] for name in imp_node.names]:
return True
_node = _node.previous_sibling()
@@ -296,52 +304,52 @@ def parse_format_string(format_string):
return (i, format_string[i])
i = 0
while i < len(format_string):
- c = format_string[i]
- if c == '%':
- i, c = next_char(i)
+ char = format_string[i]
+ if char == '%':
+ i, char = next_char(i)
# Parse the mapping key (optional).
key = None
- if c == '(':
+ if char == '(':
depth = 1
- i, c = next_char(i)
+ i, char = next_char(i)
key_start = i
while depth != 0:
- if c == '(':
+ if char == '(':
depth += 1
- elif c == ')':
+ elif char == ')':
depth -= 1
- i, c = next_char(i)
+ i, char = next_char(i)
key_end = i - 1
key = format_string[key_start:key_end]
# Parse the conversion flags (optional).
- while c in '#0- +':
- i, c = next_char(i)
+ while char in '#0- +':
+ i, char = next_char(i)
# Parse the minimum field width (optional).
- if c == '*':
+ if char == '*':
num_args += 1
- i, c = next_char(i)
+ i, char = next_char(i)
else:
- while c in string.digits:
- i, c = next_char(i)
+ while char in string.digits:
+ i, char = next_char(i)
# Parse the precision (optional).
- if c == '.':
- i, c = next_char(i)
- if c == '*':
+ if char == '.':
+ i, char = next_char(i)
+ if char == '*':
num_args += 1
- i, c = next_char(i)
+ i, char = next_char(i)
else:
- while c in string.digits:
- i, c = next_char(i)
+ while char in string.digits:
+ i, char = next_char(i)
# Parse the length modifier (optional).
- if c in 'hlL':
- i, c = next_char(i)
+ if char in 'hlL':
+ i, char = next_char(i)
# Parse the conversion type (mandatory).
- if c not in 'diouxXeEfFgGcrs%':
+ if char not in 'diouxXeEfFgGcrs%':
raise UnsupportedFormatCharacter(i)
if key:
keys.add(key)
- elif c != '%':
+ elif char != '%':
num_args += 1
i += 1
return keys, num_args
diff --git a/checkers/variables.py b/checkers/variables.py
index 0d35884..90b7fe7 100644
--- a/checkers/variables.py
+++ b/checkers/variables.py
@@ -51,6 +51,20 @@ def overridden_method(klass, name):
return meth_node
return None
+def _get_unpacking_extra_info(node, infered):
+ """return extra information to add to the message for unpacking-non-sequence
+ and unbalanced-tuple-unpacking errors
+ """
+ more = ''
+ infered_module = infered.root().name
+ if node.root().name == infered_module:
+ if node.lineno == infered.lineno:
+ more = ' %s' % infered.as_string()
+ elif infered.lineno:
+ more = ' defined at line %s' % infered.lineno
+ elif infered.lineno:
+ more = ' defined at line %s of %s' % (infered.lineno, infered_module)
+ return more
MSGS = {
'E0601': ('Using variable %r before assignment',
@@ -120,13 +134,12 @@ MSGS = {
the loop.'),
'W0632': ('Possible unbalanced tuple unpacking with '
- 'sequence at line %s: '
+ 'sequence%s: '
'left side has %d label(s), right side has %d value(s)',
'unbalanced-tuple-unpacking',
'Used when there is an unbalanced tuple unpacking in assignment'),
- 'W0633': ('Attempting to unpack a non-sequence with '
- 'non-sequence at line %s',
+ 'W0633': ('Attempting to unpack a non-sequence%s',
'unpacking-non-sequence',
'Used when something which is not '
'a sequence is used in an unpack assignment'),
@@ -556,7 +569,7 @@ builtins. Remember that you should avoid to define new builtins when possible.'
"""
if not isinstance(node.targets[0], (astroid.Tuple, astroid.List)):
return
-
+
targets = node.targets[0].itered()
if any(not isinstance(target_node, astroid.AssName)
for target_node in targets):
@@ -572,41 +585,30 @@ builtins. Remember that you should avoid to define new builtins when possible.'
""" Check for unbalanced tuple unpacking
and unpacking non sequences.
"""
- if isinstance(infered, (astroid.Tuple, astroid.List)):
+ if infered is astroid.YES:
+ return
+ if isinstance(infered, (astroid.Tuple, astroid.List)):
+ # attempt to check unpacking is properly balanced
values = infered.itered()
if len(targets) != len(values):
- if node.root().name == infered.root().name:
- location = infered.lineno or 'unknown'
- else:
- location = '%s (%s)' % (infered.lineno or 'unknown',
- infered.root().name)
-
- self.add_message('unbalanced-tuple-unpacking',
- node=node,
- args=(location,
- len(targets),
+ self.add_message('unbalanced-tuple-unpacking', node=node,
+ args=(_get_unpacking_extra_info(node, infered),
+ len(targets),
len(values)))
- else:
- if infered is astroid.YES:
- return
-
+ # attempt to check unpacking may be possible (ie RHS is iterable)
+ elif isinstance(infered, astroid.Instance):
for meth in ('__iter__', '__getitem__'):
try:
infered.getattr(meth)
+ break
except astroid.NotFoundError:
continue
- else:
- break
- else:
- if node.root().name == infered.root().name:
- location = infered.lineno or 'unknown'
- else:
- location = '%s (%s)' % (infered.lineno or 'unknown',
- infered.root().name)
-
- self.add_message('unpacking-non-sequence',
- node=node,
- args=(location, ))
+ else:
+ self.add_message('unpacking-non-sequence', node=node,
+ args=(_get_unpacking_extra_info(node, infered),))
+ else:
+ self.add_message('unpacking-non-sequence', node=node,
+ args=(_get_unpacking_extra_info(node, infered),))
def _check_module_attrs(self, node, module, module_names):
diff --git a/config.py b/config.py
index 192f254..2195703 100644
--- a/config.py
+++ b/config.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE).
+# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE).
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
@@ -16,6 +16,7 @@
* pylintrc
* pylint.d (PYLINTHOME)
"""
+from __future__ import with_statement
import pickle
import os
@@ -34,12 +35,6 @@ elif USER_HOME == '~':
else:
PYLINT_HOME = join(USER_HOME, '.pylint.d')
-if not exists(PYLINT_HOME):
- try:
- os.mkdir(PYLINT_HOME)
- except OSError:
- print >> sys.stderr, 'Unable to create directory %s' % PYLINT_HOME
-
def get_pdata_path(base_name, recurs):
"""return the path of the file which should contain old search data for the
given base_name with the given options values
@@ -55,7 +50,8 @@ def load_results(base):
"""
data_file = get_pdata_path(base, 1)
try:
- return pickle.load(open(data_file))
+ with open(data_file) as stream:
+ return pickle.load(stream)
except:
return {}
@@ -66,9 +62,15 @@ else:
def save_results(results, base):
"""pickle results"""
+ if not exists(PYLINT_HOME):
+ try:
+ os.mkdir(PYLINT_HOME)
+ except OSError:
+ print >> sys.stderr, 'Unable to create directory %s' % PYLINT_HOME
data_file = get_pdata_path(base, 1)
try:
- pickle.dump(results, open(data_file, _PICK_MOD))
+ with open(data_file, _PICK_MOD) as stream:
+ pickle.dump(results, stream)
except (IOError, OSError), ex:
print >> sys.stderr, 'Unable to create file %s: %s' % (data_file, ex)
@@ -107,12 +109,12 @@ PYLINTRC = find_pylintrc()
ENV_HELP = '''
The following environment variables are used:
* PYLINTHOME
- path to the directory where data of persistent run will be stored. If not
-found, it defaults to ~/.pylint.d/ or .pylint.d (in the current working
+ Path to the directory where the persistent for the run will be stored. If
+not found, it defaults to ~/.pylint.d/ or .pylint.d (in the current working
directory).
* PYLINTRC
- path to the configuration file. If not found, it will use the first
-existing file among (~/.pylintrc, /etc/pylintrc).
+ Path to the configuration file. See the documentation for the method used
+to search for configuration file.
''' % globals()
# evaluation messages #########################################################
diff --git a/debian.intrepid/rules b/debian.intrepid/rules
deleted file mode 100755
index 81b90c3..0000000
--- a/debian.intrepid/rules
+++ /dev/null
@@ -1,79 +0,0 @@
-#!/usr/bin/make -f
-# Sample debian/rules that uses debhelper.
-# GNU copyright 1997 to 1999 by Joey Hess.
-#
-# adapted by Logilab for automatic generation by debianize
-# (part of the devtools project, http://www.logilab.org/projects/devtools)
-#
-# Copyright (c) 2003-2009 LOGILAB S.A. (Paris, FRANCE).
-# http://www.logilab.fr/ -- mailto:contact@logilab.fr
-
-# Uncomment this to turn on verbose mode.
-#export DH_VERBOSE=1
-
-build: build-stamp
-build-stamp:
- dh_testdir
- NO_SETUPTOOLS=1 python setup.py -q build
- touch build-stamp
-
-clean:
- dh_testdir
- dh_testroot
-
- NO_SETUPTOOLS=1 python setup.py clean
-
- find . -name "*.pyc" -delete
-
- rm -rf build
-
- dh_clean build-stamp
-
-install: build
- dh_testdir
- dh_testroot
- dh_clean -k
- dh_installdirs
-
- NO_SETUPTOOLS=1 python setup.py -q install --no-compile \
- --root=$(CURDIR)/debian/pylint \
-
-
- rm -rf debian/pylint/usr/lib/python*/*-packages/pylint/test
-
- # fixes shebangs
- for exec in pylint pylint-gui symilar ; do \
- if head -1 debian/pylint/usr/bin/$$exec | grep "^#! */usr/bin" | grep "python" >/dev/null ; then \
- sed -i "s@^#! */usr/bin/env \+python\$$@#!/usr/bin/python@" debian/pylint/usr/bin/$$exec; \
- fi ; \
- chmod a+x debian/pylint/usr/bin/$$exec; \
- done
-
- install -m 644 elisp/pylint.el debian/pylint/usr/share/emacs/site-lisp/pylint/
-
- # install tests
- #(cd test && find . -type f -not \( -path '*/CVS/*' -or -name '*.pyc' \) -exec install -D --mode=644 {} ../debian/pylint/usr/share/doc/pylint/test/{} \;)
-
-# Build architecture-independent files here.
-binary-indep: build install
- dh_testdir
- dh_testroot
- dh_install -i
- dh_pysupport -i
- dh_installchangelogs -i ChangeLog
- dh_installexamples -i
- dh_installdocs -i
- dh_installman -i
- dh_installemacsen
- dh_link -i
- dh_compress -i -X.py -X.ini -X.xml -Xtest
- dh_fixperms -i
- dh_installdeb -i
- dh_gencontrol -i
- dh_md5sums -i
- dh_builddeb -i
-
-binary-arch:
-
-binary: binary-indep
-.PHONY: build clean binary binary-indep binary-arch
diff --git a/debian.jaunty/rules b/debian.jaunty/rules
deleted file mode 100755
index 81b90c3..0000000
--- a/debian.jaunty/rules
+++ /dev/null
@@ -1,79 +0,0 @@
-#!/usr/bin/make -f
-# Sample debian/rules that uses debhelper.
-# GNU copyright 1997 to 1999 by Joey Hess.
-#
-# adapted by Logilab for automatic generation by debianize
-# (part of the devtools project, http://www.logilab.org/projects/devtools)
-#
-# Copyright (c) 2003-2009 LOGILAB S.A. (Paris, FRANCE).
-# http://www.logilab.fr/ -- mailto:contact@logilab.fr
-
-# Uncomment this to turn on verbose mode.
-#export DH_VERBOSE=1
-
-build: build-stamp
-build-stamp:
- dh_testdir
- NO_SETUPTOOLS=1 python setup.py -q build
- touch build-stamp
-
-clean:
- dh_testdir
- dh_testroot
-
- NO_SETUPTOOLS=1 python setup.py clean
-
- find . -name "*.pyc" -delete
-
- rm -rf build
-
- dh_clean build-stamp
-
-install: build
- dh_testdir
- dh_testroot
- dh_clean -k
- dh_installdirs
-
- NO_SETUPTOOLS=1 python setup.py -q install --no-compile \
- --root=$(CURDIR)/debian/pylint \
-
-
- rm -rf debian/pylint/usr/lib/python*/*-packages/pylint/test
-
- # fixes shebangs
- for exec in pylint pylint-gui symilar ; do \
- if head -1 debian/pylint/usr/bin/$$exec | grep "^#! */usr/bin" | grep "python" >/dev/null ; then \
- sed -i "s@^#! */usr/bin/env \+python\$$@#!/usr/bin/python@" debian/pylint/usr/bin/$$exec; \
- fi ; \
- chmod a+x debian/pylint/usr/bin/$$exec; \
- done
-
- install -m 644 elisp/pylint.el debian/pylint/usr/share/emacs/site-lisp/pylint/
-
- # install tests
- #(cd test && find . -type f -not \( -path '*/CVS/*' -or -name '*.pyc' \) -exec install -D --mode=644 {} ../debian/pylint/usr/share/doc/pylint/test/{} \;)
-
-# Build architecture-independent files here.
-binary-indep: build install
- dh_testdir
- dh_testroot
- dh_install -i
- dh_pysupport -i
- dh_installchangelogs -i ChangeLog
- dh_installexamples -i
- dh_installdocs -i
- dh_installman -i
- dh_installemacsen
- dh_link -i
- dh_compress -i -X.py -X.ini -X.xml -Xtest
- dh_fixperms -i
- dh_installdeb -i
- dh_gencontrol -i
- dh_md5sums -i
- dh_builddeb -i
-
-binary-arch:
-
-binary: binary-indep
-.PHONY: build clean binary binary-indep binary-arch
diff --git a/debian.lenny/rules b/debian.lenny/rules
deleted file mode 100755
index 81b90c3..0000000
--- a/debian.lenny/rules
+++ /dev/null
@@ -1,79 +0,0 @@
-#!/usr/bin/make -f
-# Sample debian/rules that uses debhelper.
-# GNU copyright 1997 to 1999 by Joey Hess.
-#
-# adapted by Logilab for automatic generation by debianize
-# (part of the devtools project, http://www.logilab.org/projects/devtools)
-#
-# Copyright (c) 2003-2009 LOGILAB S.A. (Paris, FRANCE).
-# http://www.logilab.fr/ -- mailto:contact@logilab.fr
-
-# Uncomment this to turn on verbose mode.
-#export DH_VERBOSE=1
-
-build: build-stamp
-build-stamp:
- dh_testdir
- NO_SETUPTOOLS=1 python setup.py -q build
- touch build-stamp
-
-clean:
- dh_testdir
- dh_testroot
-
- NO_SETUPTOOLS=1 python setup.py clean
-
- find . -name "*.pyc" -delete
-
- rm -rf build
-
- dh_clean build-stamp
-
-install: build
- dh_testdir
- dh_testroot
- dh_clean -k
- dh_installdirs
-
- NO_SETUPTOOLS=1 python setup.py -q install --no-compile \
- --root=$(CURDIR)/debian/pylint \
-
-
- rm -rf debian/pylint/usr/lib/python*/*-packages/pylint/test
-
- # fixes shebangs
- for exec in pylint pylint-gui symilar ; do \
- if head -1 debian/pylint/usr/bin/$$exec | grep "^#! */usr/bin" | grep "python" >/dev/null ; then \
- sed -i "s@^#! */usr/bin/env \+python\$$@#!/usr/bin/python@" debian/pylint/usr/bin/$$exec; \
- fi ; \
- chmod a+x debian/pylint/usr/bin/$$exec; \
- done
-
- install -m 644 elisp/pylint.el debian/pylint/usr/share/emacs/site-lisp/pylint/
-
- # install tests
- #(cd test && find . -type f -not \( -path '*/CVS/*' -or -name '*.pyc' \) -exec install -D --mode=644 {} ../debian/pylint/usr/share/doc/pylint/test/{} \;)
-
-# Build architecture-independent files here.
-binary-indep: build install
- dh_testdir
- dh_testroot
- dh_install -i
- dh_pysupport -i
- dh_installchangelogs -i ChangeLog
- dh_installexamples -i
- dh_installdocs -i
- dh_installman -i
- dh_installemacsen
- dh_link -i
- dh_compress -i -X.py -X.ini -X.xml -Xtest
- dh_fixperms -i
- dh_installdeb -i
- dh_gencontrol -i
- dh_md5sums -i
- dh_builddeb -i
-
-binary-arch:
-
-binary: binary-indep
-.PHONY: build clean binary binary-indep binary-arch
diff --git a/debian/changelog b/debian/changelog
index 3e46fff..f9d42f2 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+pylint (1.1.0-1) unstable; urgency=low
+
+ * new upstream release
+
+ -- Sylvain Thénault <sylvain.thenault@logilab.fr> Sun, 22 Dec 2013 23:44:15 +0100
+
pylint (1.0.0-1) unstable; urgency=low
* new upstream release
diff --git a/doc/logo.png b/doc/logo.png
new file mode 100644
index 0000000..15c1c6c
--- /dev/null
+++ b/doc/logo.png
Binary files differ
diff --git a/doc/run.rst b/doc/run.rst
index 4b0ccfa..1fb95cd 100644
--- a/doc/run.rst
+++ b/doc/run.rst
@@ -87,13 +87,32 @@ list of values (which are generally used to override a regular
expression in special cases). For a full list of options, use ``--help``
Specifying all the options suitable for your setup and coding
-standards can be tedious, so it is possible to use a rc file to
-specify the default values. Pylint looks for ``/etc/pylintrc`` and
-``~/.pylintrc``. The ``--generate-rcfile`` option will generate a
-commented configuration file according to the current configuration on
-standard output and exit. You can put other options before this one to
-use them in the configuration, or start with the default values and
-hand tune the configuration.
+standards can be tedious, so it is possible to use a configuration file to
+specify the default values. You can specify a configuration file on the
+command line using the ``--rcfile`` option. Otherwise, Pylint searches for a
+configuration file in the following order and uses the first one it finds:
+
+#. ``pylintrc`` in the current working directory
+#. If the current working directory is in a Python module, Pylint searches \
+ up the hierarchy of Python modules until it finds a ``pylintrc`` file. \
+ This allows you to specify coding standards on a module-by-module \
+ basis. Of course, a directory is judged to be a Python module if it \
+ contains an ``__init__.py`` file.
+#. The file named by environment variable ``PYLINTRC``
+#. ``.pylintrc`` in your home directory, unless you have no home directory or \
+ your home directory is ``/root``
+#. ``.pylintrc`` in the current working directory
+#. ``/etc/pylintrc``
+
+The ``--generate-rcfile`` option will generate a commented configuration file
+on standard output according to the current configuration and exit. This
+includes:
+
+* Any configuration file found as explained above
+* Options appearing before ``--generate-rcfile`` on the Pylint command line
+
+Of course you can also start with the default values and hand tune the
+configuration.
Other useful global options include:
@@ -106,5 +125,3 @@ Other useful global options include:
--list-msgs Generate pylint's messages.
--full-documentation Generate pylint's full documentation, in reST
format.
-
-
diff --git a/epylint.py b/epylint.py
index d22d89c..24baa61 100755
--- a/epylint.py
+++ b/epylint.py
@@ -47,6 +47,7 @@ its output.
"""
import sys, os, re
+import os.path as osp
from subprocess import Popen, PIPE
@@ -67,33 +68,36 @@ def lint(filename, options=None):
the tree)
"""
# traverse downwards until we are out of a python package
- fullPath = os.path.abspath(filename)
- parentPath, childPath = os.path.dirname(fullPath), os.path.basename(fullPath)
+ full_path = osp.abspath(filename)
+ parent_path = osp.dirname(full_path)
+ child_path = osp.basename(full_path)
- while parentPath != "/" and os.path.exists(os.path.join(parentPath, '__init__.py')):
- childPath = os.path.join(os.path.basename(parentPath), childPath)
- parentPath = os.path.dirname(parentPath)
+ while parent_path != "/" and osp.exists(osp.join(parent_path, '__init__.py')):
+ child_path = osp.join(osp.basename(parent_path), child_path)
+ parent_path = osp.dirname(parent_path)
# Start pylint
# Ensure we use the python and pylint associated with the running epylint
- lintPath = os.path.join(os.path.dirname(__file__), 'lint.py')
+ from pylint import lint as lint_mod
+ lint_path = lint_mod.__file__
options = options or ['--disable=C,R,I']
- cmd = [sys.executable, lintPath] + options + ['--msg-template',
- '{path}:{line}: [{symbol}, {obj}] {msg}', '-r', 'n', childPath]
- process = Popen(cmd, stdout=PIPE, cwd=parentPath, universal_newlines=True)
+ cmd = [sys.executable, lint_path] + options + ['--msg-template',
+ '{path}:{line}: [{symbol}, {obj}] {msg}', '-r', 'n', child_path]
+ process = Popen(cmd, stdout=PIPE, cwd=parent_path, universal_newlines=True)
# The parseable line format is '%(path)s:%(line)s: [%(sigle)s%(obj)s] %(msg)s'
# NOTE: This would be cleaner if we added an Emacs reporter to pylint.reporters.text ..
regex = re.compile(r"\[(?P<type>[WE])(?P<remainder>.*?)\]")
- def _replacement(mObj):
+ def _replacement(match_object):
"Alter to include 'Error' or 'Warning'"
- if mObj.group("type") == "W":
+ if match_object.group("type") == "W":
replacement = "Warning"
else:
replacement = "Error"
# replace as "Warning (W0511, funcName): Warning Text"
- return "%s (%s%s):" % (replacement, mObj.group("type"), mObj.group("remainder"))
+ return "%s (%s%s):" % (replacement, match_object.group("type"),
+ match_object.group("remainder"))
for line in process.stdout:
# remove pylintrc warning
@@ -102,7 +106,7 @@ def lint(filename, options=None):
line = regex.sub(_replacement, line, 1)
# modify the file name thats output to reverse the path traversal we made
parts = line.split(":")
- if parts and parts[0] == childPath:
+ if parts and parts[0] == child_path:
line = ":".join([filename] + parts[1:])
print line,
@@ -166,7 +170,7 @@ def Run():
if len(sys.argv) == 1:
print "Usage: %s <filename> [options]" % sys.argv[0]
sys.exit(1)
- elif not os.path.exists(sys.argv[1]):
+ elif not osp.exists(sys.argv[1]):
print "%s does not exist" % sys.argv[1]
sys.exit(1)
else:
diff --git a/gui.py b/gui.py
index 0ee5974..1011ceb 100644
--- a/gui.py
+++ b/gui.py
@@ -56,28 +56,28 @@ class BasicStream(object):
self.contents = []
self.outdict = {}
self.currout = None
- self.nextTitle = None
+ self.next_title = None
def write(self, text):
"""write text to the stream"""
if re.match('^--+$', text.strip()) or re.match('^==+$', text.strip()):
if self.currout:
- self.outdict[self.currout].remove(self.nextTitle)
+ self.outdict[self.currout].remove(self.next_title)
self.outdict[self.currout].pop()
- self.currout = self.nextTitle
+ self.currout = self.next_title
self.outdict[self.currout] = ['']
if text.strip():
- self.nextTitle = text.strip()
+ self.next_title = text.strip()
- if text.startswith('\n'):
+ if text.startswith(os.linesep):
self.contents.append('')
if self.currout:
self.outdict[self.currout].append('')
- self.contents[-1] += text.strip('\n')
+ self.contents[-1] += text.strip(os.linesep)
if self.currout:
- self.outdict[self.currout][-1] += text.strip('\n')
- if text.endswith('\n') and text.strip():
+ self.outdict[self.currout][-1] += text.strip(os.linesep)
+ if text.endswith(os.linesep) and text.strip():
self.contents.append('')
if self.currout:
self.outdict[self.currout].append('')
@@ -85,8 +85,8 @@ class BasicStream(object):
def fix_contents(self):
"""finalize what the contents of the dict should look like before output"""
for item in self.outdict:
- numEmpty = self.outdict[item].count('')
- for _ in xrange(numEmpty):
+ num_empty = self.outdict[item].count('')
+ for _ in xrange(num_empty):
self.outdict[item].remove('')
if self.outdict[item]:
self.outdict[item].pop(0)
@@ -105,7 +105,7 @@ class BasicStream(object):
self.contents = []
self.outdict = {}
self.currout = None
- self.nextTitle = None
+ self.next_title = None
class LintGui(object):
@@ -126,7 +126,7 @@ class LintGui(object):
self.tabs = {}
self.report_stream = BasicStream(self)
#gui objects
- self.lbMessages = None
+ self.lb_messages = None
self.showhistory = None
self.results = None
self.btnRun = None
@@ -171,14 +171,14 @@ class LintGui(object):
rightscrollbar.pack(side=RIGHT, fill=Y)
bottomscrollbar = Scrollbar(msg_frame, orient=HORIZONTAL)
bottomscrollbar.pack(side=BOTTOM, fill=X)
- self.lbMessages = Listbox(msg_frame,
+ self.lb_messages = Listbox(msg_frame,
yscrollcommand=rightscrollbar.set,
xscrollcommand=bottomscrollbar.set,
bg="white")
- self.lbMessages.bind("<Double-Button-1>", self.show_sourcefile)
- self.lbMessages.pack(expand=True, fill=BOTH)
- rightscrollbar.config(command=self.lbMessages.yview)
- bottomscrollbar.config(command=self.lbMessages.xview)
+ self.lb_messages.bind("<Double-Button-1>", self.show_sourcefile)
+ self.lb_messages.pack(expand=True, fill=BOTH)
+ rightscrollbar.config(command=self.lb_messages.yview)
+ bottomscrollbar.config(command=self.lb_messages.xview)
#History ListBoxes
rightscrollbar2 = Scrollbar(history_frame)
@@ -199,18 +199,18 @@ class LintGui(object):
self.status = Label(self.root, text="", bd=1, relief=SUNKEN, anchor=W)
self.status.pack(side=BOTTOM, fill=X)
- #labels
- self.lblRatingLabel = Label(rating_frame, text='Rating:')
- self.lblRatingLabel.pack(side=LEFT)
- self.lblRating = Label(rating_frame, textvariable=self.rating)
- self.lblRating.pack(side=LEFT)
+ #labelbl_ratingls
+ lbl_rating_label = Label(rating_frame, text='Rating:')
+ lbl_rating_label.pack(side=LEFT)
+ lbl_rating = Label(rating_frame, textvariable=self.rating)
+ lbl_rating.pack(side=LEFT)
Label(mid_frame, text='Recently Used:').pack(side=LEFT)
Label(top_frame, text='Module or package').pack(side=LEFT)
#file textbox
- self.txtModule = Entry(top_frame, background='white')
- self.txtModule.bind('<Return>', self.run_lint)
- self.txtModule.pack(side=LEFT, expand=True, fill=X)
+ self.txt_module = Entry(top_frame, background='white')
+ self.txt_module.bind('<Return>', self.run_lint)
+ self.txt_module.pack(side=LEFT, expand=True, fill=X)
#results box
rightscrollbar = Scrollbar(res_frame)
@@ -227,8 +227,8 @@ class LintGui(object):
#buttons
Button(top_frame, text='Open', command=self.file_open).pack(side=LEFT)
- Button(top_frame, text='Open Package',
- command=(lambda : self.file_open(package=True))).pack(side=LEFT)
+ Button(top_frame, text='Open Package',
+ command=(lambda: self.file_open(package=True))).pack(side=LEFT)
self.btnRun = Button(top_frame, text='Run', command=self.run_lint)
self.btnRun.pack(side=LEFT)
@@ -269,45 +269,53 @@ class LintGui(object):
#check boxes
self.box = StringVar()
# XXX should be generated
- report = Radiobutton(radio_frame, text="Report", variable=self.box,
- value="Report", command=self.refresh_results_window)
- rawMet = Radiobutton(radio_frame, text="Raw metrics", variable=self.box,
- value="Raw metrics", command=self.refresh_results_window)
- dup = Radiobutton(radio_frame, text="Duplication", variable=self.box,
- value="Duplication", command=self.refresh_results_window)
- ext = Radiobutton(radio_frame, text="External dependencies",
- variable=self.box, value="External dependencies",
- command=self.refresh_results_window)
- stat = Radiobutton(radio_frame, text="Statistics by type",
- variable=self.box, value="Statistics by type",
- command=self.refresh_results_window)
- msgCat = Radiobutton(radio_frame, text="Messages by category",
- variable=self.box, value="Messages by category",
- command=self.refresh_results_window)
- msg = Radiobutton(radio_frame, text="Messages", variable=self.box,
- value="Messages", command=self.refresh_results_window)
- sourceFile = Radiobutton(radio_frame, text="Source File", variable=self.box,
- value="Source File", command=self.refresh_results_window)
+ report = Radiobutton(
+ radio_frame, text="Report", variable=self.box,
+ value="Report", command=self.refresh_results_window)
+ raw_met = Radiobutton(
+ radio_frame, text="Raw metrics", variable=self.box,
+ value="Raw metrics", command=self.refresh_results_window)
+ dup = Radiobutton(
+ radio_frame, text="Duplication", variable=self.box,
+ value="Duplication", command=self.refresh_results_window)
+ ext = Radiobutton(
+ radio_frame, text="External dependencies",
+ variable=self.box, value="External dependencies",
+ command=self.refresh_results_window)
+ stat = Radiobutton(
+ radio_frame, text="Statistics by type",
+ variable=self.box, value="Statistics by type",
+ command=self.refresh_results_window)
+ msg_cat = Radiobutton(
+ radio_frame, text="Messages by category",
+ variable=self.box, value="Messages by category",
+ command=self.refresh_results_window)
+ msg = Radiobutton(
+ radio_frame, text="Messages", variable=self.box,
+ value="Messages", command=self.refresh_results_window)
+ source_file = Radiobutton(
+ radio_frame, text="Source File", variable=self.box,
+ value="Source File", command=self.refresh_results_window)
report.select()
report.grid(column=0, row=0, sticky=W)
- rawMet.grid(column=1, row=0, sticky=W)
+ raw_met.grid(column=1, row=0, sticky=W)
dup.grid(column=2, row=0, sticky=W)
msg.grid(column=3, row=0, sticky=W)
stat.grid(column=0, row=1, sticky=W)
- msgCat.grid(column=1, row=1, sticky=W)
+ msg_cat.grid(column=1, row=1, sticky=W)
ext.grid(column=2, row=1, sticky=W)
- sourceFile.grid(column=3, row=1, sticky=W)
+ source_file.grid(column=3, row=1, sticky=W)
#dictionary for check boxes and associated error term
self.msg_type_dict = {
- 'I' : lambda : self.information_box.get() == 1,
- 'C' : lambda : self.convention_box.get() == 1,
- 'R' : lambda : self.refactor_box.get() == 1,
- 'E' : lambda : self.error_box.get() == 1,
- 'W' : lambda : self.warning_box.get() == 1,
- 'F' : lambda : self.fatal_box.get() == 1
+ 'I': lambda: self.information_box.get() == 1,
+ 'C': lambda: self.convention_box.get() == 1,
+ 'R': lambda: self.refactor_box.get() == 1,
+ 'E': lambda: self.error_box.get() == 1,
+ 'W': lambda: self.warning_box.get() == 1,
+ 'F': lambda: self.fatal_box.get() == 1
}
- self.txtModule.focus_set()
+ self.txt_module.focus_set()
def select_recent_file(self, event):
@@ -318,21 +326,21 @@ class LintGui(object):
selected = self.showhistory.curselection()
item = self.showhistory.get(selected)
#update module
- self.txtModule.delete(0, END)
- self.txtModule.insert(0, item)
+ self.txt_module.delete(0, END)
+ self.txt_module.insert(0, item)
def refresh_msg_window(self):
"""refresh the message window with current output"""
#clear the window
- self.lbMessages.delete(0, END)
+ self.lb_messages.delete(0, END)
self.visible_msgs = []
for msg in self.msgs:
- if (self.msg_type_dict.get(msg.C)()):
+ if self.msg_type_dict.get(msg.C)():
self.visible_msgs.append(msg)
msg_str = convert_to_string(msg)
- self.lbMessages.insert(END, msg_str)
+ self.lb_messages.insert(END, msg_str)
fg_color = COLORS.get(msg_str[:3], 'black')
- self.lbMessages.itemconfigure(END, fg=fg_color)
+ self.lb_messages.itemconfigure(END, fg=fg_color)
def refresh_results_window(self):
"""refresh the results window with current output"""
@@ -357,12 +365,12 @@ class LintGui(object):
self.msgs.append(msg)
#displaying msg if message type is selected in check box
- if (self.msg_type_dict.get(msg.C)()):
+ if self.msg_type_dict.get(msg.C)():
self.visible_msgs.append(msg)
msg_str = convert_to_string(msg)
- self.lbMessages.insert(END, msg_str)
+ self.lb_messages.insert(END, msg_str)
fg_color = COLORS.get(msg_str[:3], 'black')
- self.lbMessages.itemconfigure(END, fg=fg_color)
+ self.lb_messages.itemconfigure(END, fg=fg_color)
except Queue.Empty:
pass
@@ -399,12 +407,12 @@ class LintGui(object):
if filename == ():
return
- self.txtModule.delete(0, END)
- self.txtModule.insert(0, filename)
+ self.txt_module.delete(0, END)
+ self.txt_module.insert(0, filename)
def update_filenames(self):
"""update the list of recent filenames"""
- filename = self.txtModule.get()
+ filename = self.txt_module.get()
if not filename:
filename = os.getcwd()
if filename+'\n' in self.filenames:
@@ -437,14 +445,14 @@ class LintGui(object):
self.update_filenames()
self.root.configure(cursor='watch')
self.reporter = GUIReporter(self, output=self.report_stream)
- module = self.txtModule.get()
+ module = self.txt_module.get()
if not module:
module = os.getcwd()
#cleaning up msgs and windows
self.msgs = []
self.visible_msgs = []
- self.lbMessages.delete(0, END)
+ self.lb_messages.delete(0, END)
self.tabs = {}
self.results.delete(0, END)
self.btnRun.config(state=DISABLED)
@@ -464,7 +472,7 @@ class LintGui(object):
self.root.configure(cursor='')
def show_sourcefile(self, event=None):
- selected = self.lbMessages.curselection()
+ selected = self.lb_messages.curselection()
if not selected:
return
diff --git a/lint.py b/lint.py
index e5e89e5..4cf5059 100644
--- a/lint.py
+++ b/lint.py
@@ -82,48 +82,49 @@ MSGS = {
module (unable to find it for instance).'),
'F0002': ('%s: %s',
'astroid-error',
- 'Used when an unexpected error occurred while building the Astroid \
- representation. This is usually accompanied by a traceback. \
- Please report such errors !'),
+ 'Used when an unexpected error occurred while building the '
+ 'Astroid representation. This is usually accompanied by a '
+ 'traceback. Please report such errors !'),
'F0003': ('ignored builtin module %s',
'ignored-builtin-module',
- 'Used to indicate that the user asked to analyze a builtin module \
- which has been skipped.'),
+ 'Used to indicate that the user asked to analyze a builtin '
+ 'module which has been skipped.'),
'F0004': ('unexpected inferred value %s',
'unexpected-inferred-value',
- 'Used to indicate that some value of an unexpected type has been \
- inferred.'),
+ 'Used to indicate that some value of an unexpected type has been '
+ 'inferred.'),
'F0010': ('error while code parsing: %s',
'parse-error',
- 'Used when an exception occured while building the Astroid \
- representation which could be handled by astroid.'),
+ 'Used when an exception occured while building the Astroid '
+ 'representation which could be handled by astroid.'),
'I0001': ('Unable to run raw checkers on built-in module %s',
'raw-checker-failed',
- 'Used to inform that a built-in module has not been checked \
- using the raw checkers.'),
+ 'Used to inform that a built-in module has not been checked '
+ 'using the raw checkers.'),
'I0010': ('Unable to consider inline option %r',
'bad-inline-option',
- 'Used when an inline option is either badly formatted or can\'t \
- be used inside modules.'),
+ 'Used when an inline option is either badly formatted or can\'t '
+ 'be used inside modules.'),
'I0011': ('Locally disabling %s',
'locally-disabled',
- 'Used when an inline option disables a message or a messages \
- category.'),
+ 'Used when an inline option disables a message or a messages '
+ 'category.'),
'I0012': ('Locally enabling %s',
'locally-enabled',
- 'Used when an inline option enables a message or a messages \
- category.'),
+ 'Used when an inline option enables a message or a messages '
+ 'category.'),
'I0013': ('Ignoring entire file',
'file-ignored',
'Used to inform that the file will not be checked'),
- 'I0014': ('Used deprecated directive "pylint:disable-all" or "pylint:disable=all"',
+ 'I0014': ('Used deprecated directive "pylint:disable-all" or '
+ '"pylint:disable=all"',
'deprecated-disable-all',
'You should preferably use "pylint:skip-file" as this directive '
- 'has a less confusing name. Do this only if you are sure that all '
- 'people running Pylint on your code have version >= 0.26'),
+ 'has a less confusing name. Do this only if you are sure that '
+ 'all people running Pylint on your code have version >= 0.26'),
'I0020': ('Suppressed %s (from line %d)',
'suppressed-message',
'A message was triggered on a line, but suppressed explicitly '
@@ -134,7 +135,11 @@ MSGS = {
'useless-suppression',
'Reported when a message is explicitly disabled for a line or '
'a block of code, but never triggered.'),
-
+ 'I0022': ('Deprecated pragma "pylint:disable-msg" or "pylint:enable-msg"',
+ 'deprecated-pragma',
+ 'You should preferably use "pylint:disable" or "pylint:enable" '
+ 'instead of the deprecated suppression pragma style '
+ '"pylint:disable-msg" or "pylint:enable-msg"'),
'E0001': ('%s',
'syntax-error',
@@ -177,8 +182,8 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn,
return (('ignore',
{'type' : 'csv', 'metavar' : '<file>[,<file>...]',
'dest' : 'black_list', 'default' : ('CVS',),
- 'help' : 'Add files or directories to the blacklist. \
-They should be base names, not paths.'}),
+ 'help' : 'Add files or directories to the blacklist. '
+ 'They should be base names, not paths.'}),
('persistent',
{'default': True, 'type' : 'yn', 'metavar' : '<y_or_n>',
'level': 1,
@@ -187,8 +192,9 @@ They should be base names, not paths.'}),
('load-plugins',
{'type' : 'csv', 'metavar' : '<modules>', 'default' : (),
'level': 1,
- 'help' : 'List of plugins (as comma separated values of \
-python modules names) to load, usually to register additional checkers.'}),
+ 'help' : 'List of plugins (as comma separated values of '
+ 'python modules names) to load, usually to register '
+ 'additional checkers.'}),
('output-format',
{'default': 'text', 'type': 'string', 'metavar' : '<format>',
@@ -202,22 +208,23 @@ python modules names) to load, usually to register additional checkers.'}),
('files-output',
{'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>',
'group': 'Reports', 'level': 1,
- 'help' : 'Put messages in a separate file for each module / \
-package specified on the command line instead of printing them on stdout. \
-Reports (if any) will be written in a file name "pylint_global.[txt|html]".'}),
+ 'help' : 'Put messages in a separate file for each module / '
+ 'package specified on the command line instead of printing '
+ 'them on stdout. Reports (if any) will be written in a file '
+ 'name "pylint_global.[txt|html]".'}),
('reports',
{'default': 1, 'type' : 'yn', 'metavar' : '<y_or_n>',
'short': 'r',
'group': 'Reports',
- 'help' : 'Tells whether to display a full report or only the\
- messages'}),
+ 'help' : 'Tells whether to display a full report or only the '
+ 'messages'}),
('evaluation',
{'type' : 'string', 'metavar' : '<python_expression>',
'group': 'Reports', 'level': 1,
- 'default': '10.0 - ((float(5 * error + warning + refactor + \
-convention) / statement) * 10)',
+ 'default': '10.0 - ((float(5 * error + warning + refactor + '
+ 'convention) / statement) * 10)',
'help' : 'Python expression which should return a note less \
than 10 (10 is the highest note). You have access to the variables errors \
warning, statement which respectively contain the number of errors / warnings\
@@ -227,8 +234,8 @@ warning, statement which respectively contain the number of errors / warnings\
('comment',
{'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>',
'group': 'Reports', 'level': 1,
- 'help' : 'Add a comment according to your evaluation note. \
-This is used by the global evaluation report (RP0004).'}),
+ 'help' : 'Add a comment according to your evaluation note. '
+ 'This is used by the global evaluation report (RP0004).'}),
('enable',
{'type' : 'csv', 'metavar': '<msg ids>',
@@ -272,7 +279,8 @@ This is used by the global evaluation report (RP0004).'}),
('Reports', 'Options related to output formating and reporting'),
)
- def __init__(self, options=(), reporter=None, option_groups=(), pylintrc=None):
+ def __init__(self, options=(), reporter=None, option_groups=(),
+ pylintrc=None):
# some stuff has to be done before ancestors initialization...
#
# checkers / reporter / astroid manager
@@ -369,7 +377,8 @@ This is used by the global evaluation report (RP0004).'}),
"""overridden from configuration.OptionsProviderMixin to handle some
special options
"""
- if optname in self._options_methods or optname in self._bw_options_methods:
+ if optname in self._options_methods or \
+ optname in self._bw_options_methods:
if value:
try:
meth = self._options_methods[optname]
@@ -379,9 +388,9 @@ This is used by the global evaluation report (RP0004).'}),
optname, optname.split('-')[0]), DeprecationWarning)
value = check_csv(None, optname, value)
if isinstance(value, (list, tuple)):
- for _id in value :
+ for _id in value:
meth(_id)
- else :
+ else:
meth(value)
elif optname == 'output-format':
self._reporter_name = value
@@ -452,7 +461,8 @@ This is used by the global evaluation report (RP0004).'}),
match = OPTION_RGX.search(line)
if match is None:
continue
- if match.group(1).strip() == "disable-all" or match.group(1).strip() == 'skip-file':
+ if match.group(1).strip() == "disable-all" or \
+ match.group(1).strip() == 'skip-file':
if match.group(1).strip() == "disable-all":
self.add_message('I0014', line=start[0])
self.add_message('I0013', line=start[0])
@@ -470,9 +480,8 @@ This is used by the global evaluation report (RP0004).'}),
meth = self._options_methods[opt]
except KeyError:
meth = self._bw_options_methods[opt]
- warn('%s is deprecated, replace it with %s (%s, line %s)' % (
- opt, opt.split('-')[0], self.current_file, line),
- DeprecationWarning)
+ # found a "(dis|en)able-msg" pragma deprecated suppresssion
+ self.add_message('deprecated-pragma', line=start[0])
for msgid in splitstrip(value):
try:
if (opt, msgid) == ('disable', 'all'):
@@ -518,7 +527,7 @@ This is used by the global evaluation report (RP0004).'}),
if first <= lineno <= last:
# Set state for all lines for this block, if the
# warning is applied to nodes.
- if self._messages[msgid].scope == WarningScope.NODE:
+ if self.check_message_id(msgid).scope == WarningScope.NODE:
if lineno > firstchildlineno:
state = True
first_, last_ = node.block_range(lineno)
@@ -563,6 +572,22 @@ This is used by the global evaluation report (RP0004).'}),
checker.active_msgs = messages
return neededcheckers
+ def should_analyze_file(self, modname, path): # pylint: disable=unused-argument
+ """Returns whether or not a module should be checked.
+
+ This implementation returns True for all inputs, indicating that all
+ files should be linted.
+
+ Subclasses may override this method to indicate that modules satisfying
+ certain conditions should not be linted.
+
+ :param str modname: The name of the module to be checked.
+ :param str path: The full path to the source code of the module.
+ :returns: True if the module should be checked.
+ :rtype: bool
+ """
+ return True
+
def check(self, files_or_modules):
"""main checking entry: check a list of files or modules from their
name.
@@ -582,12 +607,14 @@ This is used by the global evaluation report (RP0004).'}),
# build ast and check modules or packages
for descr in self.expand_files(files_or_modules):
modname, filepath = descr['name'], descr['path']
+ if not self.should_analyze_file(modname, filepath):
+ continue
if self.config.files_output:
reportfile = 'pylint_%s.%s' % (modname, self.reporter.extension)
self.reporter.set_output(open(reportfile, 'w'))
self.set_current_module(modname, filepath)
# get the module representation
- astroid = self.get_astroid(filepath, modname)
+ astroid = self.get_ast(filepath, modname)
if astroid is None:
continue
self.base_name = descr['basename']
@@ -639,8 +666,8 @@ This is used by the global evaluation report (RP0004).'}),
self._raw_module_msgs_state = {}
self._ignored_msgs = {}
- def get_astroid(self, filepath, modname):
- """return a astroid representation for a module"""
+ def get_ast(self, filepath, modname):
+ """return a ast(roid) representation for a module"""
try:
return MANAGER.ast_from_file(filepath, modname, source=True)
except SyntaxError, ex:
@@ -689,8 +716,8 @@ This is used by the global evaluation report (RP0004).'}),
def open(self):
"""initialize counters"""
- self.stats = { 'by_module' : {},
- 'by_msg' : {},
+ self.stats = {'by_module' : {},
+ 'by_msg' : {},
}
for msg_cat in MSG_TYPES.itervalues():
self.stats[msg_cat] = 0
@@ -717,6 +744,8 @@ This is used by the global evaluation report (RP0004).'}),
# save results if persistent run
if self.config.persistent:
config.save_results(self.stats, self.base_name)
+ else:
+ self.reporter.on_close(self.stats, {})
# specific reports ########################################################
@@ -921,7 +950,7 @@ them in the generated configuration.'''}),
('generate-man',
{'action' : 'callback', 'callback' : self.cb_generate_manpage,
'group': 'Commands',
- 'help' : "Generate pylint's man page.",'hide': True}),
+ 'help' : "Generate pylint's man page.", 'hide': True}),
('errors-only',
{'action' : 'callback', 'callback' : self.cb_error_mode,
@@ -1001,7 +1030,8 @@ are done by default'''}),
if self.linter.config.profile:
print >> sys.stderr, '** profiled run'
import cProfile, pstats
- cProfile.runctx('linter.check(%r)' % args, globals(), locals(), 'stones.prof' )
+ cProfile.runctx('linter.check(%r)' % args, globals(), locals(),
+ 'stones.prof')
data = pstats.Stats('stones.prof')
data.strip_dirs()
data.sort_stats('time', 'calls')
diff --git a/man/pyreverse.1 b/man/pyreverse.1
index d37f83d..a68ceb5 100644
--- a/man/pyreverse.1
+++ b/man/pyreverse.1
@@ -10,7 +10,7 @@ them.
.SH DESCRIPTION
.B pyreverse
-is a python source analyzer. It parses a python packages and produces UML
+is a python source analyzer. It parses python packages and produces UML
diagrams in different output formats. (dot, all formats available for dot,
and vcg).
With different options, you can have fine tuning on what and how modules,
@@ -45,10 +45,10 @@ modes using '+' like 'SPECIAL+OTHER'. Correct modes are :
- 'PUB_ONLY' : filter all non public attributes (default)
- 'ALL' : no filter
- 'SPECIAL' : filter Python special functions except constructor
- - 'OTHER' : filter protected and private attributes [currentt: PUB_ONLY]
+ - 'OTHER' : filter protected and private attributes [current: PUB_ONLY]
.IP "-d<file>, --diadefs=<file>"
-create diagram according to the diagrams definitions in <file>
+create diagram according to the diagram definitions in <file>
.IP "-c <class>, --class=<class>"
create a class diagram with all classes related to <class> [current: none]
the class must be in the file <modules>. By default, this will include all
@@ -69,8 +69,8 @@ show recursively all associated off all associated classes [current: none]
.IP "-b, --builtin"
include builtin objects in representation of classes [current: False]
.IP "-m [yn], --module-names=[yn]"
-include module name in representation of classes. This will include full
-module path in the class name. [current: none]
+include module name in representation of classes. This will include the
+full module path in the class name. [current: none]
.IP "-k, --only-classnames"
don't show attributes and methods in the class boxes;
@@ -83,15 +83,15 @@ are all formats that dot can produce and
[default: dot]
.SH EXAMPLES
- Here are some examples for command line options :
+Here are some examples for command line options:
.IP "pyreverse <project> -a1 -s1 -m"
\-a1 \-s1 will include one level of ancestor and associated classes in the
diagram of the <project> modules, while \-m will show the full module
-path of each class. You can use the same way the
+path of each class. You can use the
.B -a, -s, -A, -S
-options.
+options in the same way.
Note that on class diagrams (using
.B -c
) \-a and \-s will rather reduce than enlarge your diagram.
diff --git a/pyreverse/diadefslib.py b/pyreverse/diadefslib.py
index da21e1f..795be8b 100644
--- a/pyreverse/diadefslib.py
+++ b/pyreverse/diadefslib.py
@@ -41,7 +41,7 @@ class DiaDefGenerator(object):
"""get title for objects"""
title = node.name
if self.module_names:
- title = '%s.%s' % (node.root().name, title)
+ title = '%s.%s' % (node.root().name, title)
return title
def _set_option(self, option):
diff --git a/pyreverse/diagrams.py b/pyreverse/diagrams.py
index 7cb5930..360fdb1 100644
--- a/pyreverse/diagrams.py
+++ b/pyreverse/diagrams.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2004-2012 LOGILAB S.A. (Paris, FRANCE).
+# Copyright (c) 2004-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
@@ -19,16 +19,8 @@
import astroid
from pylint.pyreverse.utils import is_interface, FilterMixIn
-def set_counter(value):
- """Figure counter (re)set"""
- Figure._UID_COUNT = value
-
-class Figure:
+class Figure(object):
"""base class for counter handling"""
- _UID_COUNT = 0
- def __init__(self):
- Figure._UID_COUNT += 1
- self.fig_id = Figure._UID_COUNT
class Relationship(Figure):
"""a relation ship from an object in the diagram to another
@@ -62,6 +54,11 @@ class ClassDiagram(Figure, FilterMixIn):
self._nodes = {}
self.depends = []
+ def get_relationships(self, role):
+ # sorted to get predictable (hence testable) results
+ return sorted(self.relationships.get(role, ()),
+ key=lambda x: (x.from_object.fig_id, x.to_object.fig_id))
+
def add_relationship(self, from_object, to_object,
relation_type, name=None):
"""create a relation ship
@@ -175,7 +172,7 @@ class ClassDiagram(Figure, FilterMixIn):
for value in values:
if value is astroid.YES:
continue
- if isinstance( value, astroid.Instance):
+ if isinstance(value, astroid.Instance):
value = value._proxied
try:
ass_obj = self.object_from_node(value)
@@ -221,7 +218,7 @@ class PackageDiagram(ClassDiagram):
"""add dependencies created by from-imports
"""
mod_name = node.root().name
- obj = self.module( mod_name )
+ obj = self.module(mod_name)
if from_module not in obj.node.depends:
obj.node.depends.append(from_module)
diff --git a/pyreverse/main.py b/pyreverse/main.py
index cdd6607..5b9e8df 100644
--- a/pyreverse/main.py
+++ b/pyreverse/main.py
@@ -1,4 +1,4 @@
-# # Copyright (c) 2000-2012 LOGILAB S.A. (Paris, FRANCE).
+# # Copyright (c) 2000-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
@@ -48,11 +48,11 @@ dict(short='c', action="append", metavar="<class>", dest="classes", default=[],
this uses by default the options -ASmy")),
("show-ancestors",
-dict(short="a", action="store", metavar='<ancestor>', type='int',
+dict(short="a", action="store", metavar='<ancestor>', type='int',
help='show <ancestor> generations of ancestor classes not in <projects>')),
("all-ancestors",
dict(short="A", default=None,
- help="show all ancestors off all classes in <projects>") ),
+ help="show all ancestors off all classes in <projects>")),
("show-associated",
dict(short='s', action="store", metavar='<ass_level>', type='int',
help='show <ass_level> levels of associated classes not in <projects>')),
diff --git a/pyreverse/utils.py b/pyreverse/utils.py
index 976f704..ea90e05 100644
--- a/pyreverse/utils.py
+++ b/pyreverse/utils.py
@@ -106,8 +106,8 @@ MODES = {
'SPECIAL' : _SPECIAL,
'OTHER' : _PROTECTED + _PRIVATE,
}
-VIS_MOD = {'special': _SPECIAL, 'protected': _PROTECTED, \
- 'private': _PRIVATE, 'public': 0 }
+VIS_MOD = {'special': _SPECIAL, 'protected': _PROTECTED,
+ 'private': _PRIVATE, 'public': 0}
class FilterMixIn(object):
"""filter nodes according to a mode and nodes' visibility
@@ -127,5 +127,5 @@ class FilterMixIn(object):
"""return true if the node should be treated
"""
visibility = get_visibility(getattr(node, 'name', node))
- return not (self.__mode & VIS_MOD[visibility] )
+ return not (self.__mode & VIS_MOD[visibility])
diff --git a/pyreverse/writer.py b/pyreverse/writer.py
index dd00850..d4b9937 100644
--- a/pyreverse/writer.py
+++ b/pyreverse/writer.py
@@ -44,30 +44,33 @@ class DiagramWriter(object):
def write_packages(self, diagram):
"""write a package diagram"""
- for obj in diagram.modules():
- label = self.get_title(obj)
- self.printer.emit_node(obj.fig_id, label=label, shape='box')
+ # sorted to get predictable (hence testable) results
+ for i, obj in enumerate(sorted(diagram.modules(), key=lambda x: x.title)):
+ self.printer.emit_node(i, label=self.get_title(obj), shape='box')
+ obj.fig_id = i
# package dependencies
- for rel in diagram.relationships.get('depends', ()):
+ for rel in diagram.get_relationships('depends'):
self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id,
**self.pkg_edges)
def write_classes(self, diagram):
"""write a class diagram"""
- for obj in diagram.objects:
- self.printer.emit_node(obj.fig_id, **self.get_values(obj) )
+ # sorted to get predictable (hence testable) results
+ for i, obj in enumerate(sorted(diagram.objects, key=lambda x: x.title)):
+ self.printer.emit_node(i, **self.get_values(obj))
+ obj.fig_id = i
# inheritance links
- for rel in diagram.relationships.get('specialization', ()):
+ for rel in diagram.get_relationships('specialization'):
self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id,
- **self.inh_edges)
+ **self.inh_edges)
# implementation links
- for rel in diagram.relationships.get('implements', ()):
+ for rel in diagram.get_relationships('implements'):
self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id,
- **self.imp_edges)
+ **self.imp_edges)
# generate associations
- for rel in diagram.relationships.get('association', ()):
+ for rel in diagram.get_relationships('association'):
self.printer.emit_edge(rel.from_object.fig_id, rel.to_object.fig_id,
- label=rel.name, **self.ass_edges)
+ label=rel.name, **self.ass_edges)
def set_printer(self, file_name, basename):
"""set printer"""
@@ -92,10 +95,11 @@ class DotWriter(DiagramWriter):
def __init__(self, config):
styles = [dict(arrowtail='none', arrowhead="open"),
- dict(arrowtail = "none", arrowhead='empty'),
- dict(arrowtail="node", arrowhead='empty', style='dashed'),
+ dict(arrowtail='none', arrowhead='empty'),
+ dict(arrowtail='node', arrowhead='empty', style='dashed'),
dict(fontcolor='green', arrowtail='none',
- arrowhead='diamond', style='solid') ]
+ arrowhead='diamond', style='solid'),
+ ]
DiagramWriter.__init__(self, config, styles)
def set_printer(self, file_name, basename):
@@ -114,17 +118,17 @@ class DotWriter(DiagramWriter):
The label contains all attributes and methods
"""
- label = obj.title
+ label = obj.title
if obj.shape == 'interface':
- label = u"«interface»\\n%s" % label
+ label = u'«interface»\\n%s' % label
if not self.config.only_classnames:
- label = r"%s|%s\l|" % (label, r"\l".join(obj.attrs) )
+ label = r'%s|%s\l|' % (label, r'\l'.join(obj.attrs))
for func in obj.methods:
label = r'%s%s()\l' % (label, func.name)
label = '{%s}' % label
if is_exception(obj.node):
- return dict(fontcolor="red", label=label, shape="record")
- return dict(label=label, shape="record")
+ return dict(fontcolor='red', label=label, shape='record')
+ return dict(label=label, shape='record')
def close_graph(self):
"""print the dot graph into <file_name>"""
@@ -142,7 +146,8 @@ class VCGWriter(DiagramWriter):
dict(arrowstyle='solid', backarrowstyle='none',
linestyle='dotted', backarrowsize=10),
dict(arrowstyle='solid', backarrowstyle='none',
- textcolor='green') ]
+ textcolor='green'),
+ ]
DiagramWriter.__init__(self, config, styles)
def set_printer(self, file_name, basename):
@@ -177,7 +182,7 @@ class VCGWriter(DiagramWriter):
methods = [func.name for func in obj.methods]
# box width for UML like diagram
maxlen = max(len(name) for name in [obj.title] + methods + attrs)
- line = "_" * (maxlen + 2)
+ line = '_' * (maxlen + 2)
label = r'%s\n\f%s' % (label, line)
for attr in attrs:
label = r'%s\n\f08%s' % (label, attr)
diff --git a/reporters/text.py b/reporters/text.py
index 555efc8..614fcdb 100644
--- a/reporters/text.py
+++ b/reporters/text.py
@@ -75,8 +75,8 @@ class ParseableTextReporter(TextReporter):
line_format = '{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}'
def __init__(self, output=None):
- warnings.warn('%s output format is deprecated. This is equivalent to --msg-template=%s'
- % (self.name, self.line_format))
+ warnings.warn('%s output format is deprecated. This is equivalent '
+ 'to --msg-template=%s' % (self.name, self.line_format))
TextReporter.__init__(self, output)
diff --git a/setup.py b/setup.py
index c5286f5..e4b0ae8 100644
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# pylint: disable=W0404,W0622,W0704,W0613
-# copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of pylint.
@@ -39,9 +39,11 @@ except ImportError:
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
@@ -133,6 +135,7 @@ class MyInstallLib(install_lib.install_lib):
if sys.version_info >= (3, 0):
# 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)
run_2to3([dest])
@@ -167,21 +170,20 @@ def install(**kwargs):
'symilar = pylint:run_symilar',
]}
kwargs['packages'] = packages
- return setup(name = distname,
- version = version,
- license = license,
- description = description,
- long_description = long_description,
- author = author,
- author_email = author_email,
- url = web,
- scripts = ensure_scripts(scripts),
- data_files = data_files,
- ext_modules = ext_modules,
- cmdclass = {'install_lib': MyInstallLib,
- 'build_py': build_py},
- **kwargs
- )
+ return setup(name=distname,
+ version=version,
+ license=license,
+ description=description,
+ long_description=long_description,
+ author=author,
+ author_email=author_email,
+ url=web,
+ scripts=ensure_scripts(scripts),
+ data_files=data_files,
+ ext_modules=ext_modules,
+ cmdclass={'install_lib': MyInstallLib,
+ 'build_py': build_py},
+ **kwargs)
if __name__ == '__main__' :
install()
diff --git a/test/data/classes_No_Name.dot b/test/data/classes_No_Name.dot
index 975ecf6..e75553e 100644
--- a/test/data/classes_No_Name.dot
+++ b/test/data/classes_No_Name.dot
@@ -1,12 +1,12 @@
digraph "classes_No_Name" {
charset="utf-8"
rankdir=BT
-"4" [shape="record", label="{Ancestor|attr : str\lcls_member\l|set_value()\lget_value()\l}"];
-"5" [shape="record", label="{Specialization|relation\ltop : str\lTYPE : str\l|}"];
-"8" [shape="record", label="{«interface»\nInterface|\l|get_value()\lset_value()\l}"];
-"9" [shape="record", label="{DoNothing|\l|}"];
-"5" -> "4" [arrowtail="none", arrowhead="empty"];
-"4" -> "8" [arrowtail="node", style="dashed", arrowhead="empty"];
-"9" -> "4" [arrowhead="diamond", style="solid", arrowtail="none", fontcolor="green", label="cls_member"];
-"9" -> "5" [arrowhead="diamond", style="solid", arrowtail="none", fontcolor="green", label="relation"];
+"0" [label="{Ancestor|attr : str\lcls_member\l|set_value()\lget_value()\l}", shape="record"];
+"1" [label="{DoNothing|\l|}", shape="record"];
+"2" [label="{«interface»\nInterface|\l|get_value()\lset_value()\l}", shape="record"];
+"3" [label="{Specialization|relation\ltop : str\lTYPE : str\l|}", shape="record"];
+"3" -> "0" [arrowhead="empty", arrowtail="none"];
+"0" -> "2" [arrowhead="empty", arrowtail="node", style="dashed"];
+"1" -> "0" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="cls_member", style="solid"];
+"1" -> "3" [arrowhead="diamond", arrowtail="none", fontcolor="green", label="relation", style="solid"];
}
diff --git a/test/data/packages_No_Name.dot b/test/data/packages_No_Name.dot
index 19416a4..1ceeb72 100644
--- a/test/data/packages_No_Name.dot
+++ b/test/data/packages_No_Name.dot
@@ -1,8 +1,8 @@
digraph "packages_No_Name" {
charset="utf-8"
rankdir=BT
-"3" [shape="box", label="data.clientmodule_test"];
-"6" [shape="box", label="data"];
-"7" [shape="box", label="data.suppliermodule_test"];
-"3" -> "7" [arrowtail="none", arrowhead="open"];
+"0" [label="data", shape="box"];
+"1" [label="data.clientmodule_test", shape="box"];
+"2" [label="data.suppliermodule_test", shape="box"];
+"1" -> "2" [arrowhead="open", arrowtail="none"];
}
diff --git a/test/input/func_assert_2uple.py b/test/input/func_assert_2uple.py
index a6746ce..de93d3b 100644
--- a/test/input/func_assert_2uple.py
+++ b/test/input/func_assert_2uple.py
@@ -8,4 +8,4 @@ assert (1 == 1, ), "no error"
assert (1 == 1, )
assert (1 == 1, 2 == 2, 3 == 5), "no error"
assert ()
-assert (True,'error msg') #this should generate a warning
+assert (True, 'error msg') #this should generate a warning
diff --git a/test/input/func_block_disable_msg.py b/test/input/func_block_disable_msg.py
index cce04a4..2551823 100644
--- a/test/input/func_block_disable_msg.py
+++ b/test/input/func_block_disable_msg.py
@@ -91,7 +91,7 @@ class Foo(object):
eris = 5
if eris:
- print ("In block")
+ print "In block"
# pylint: disable=E1101
# no error
diff --git a/test/input/func_catching_non_exception.py b/test/input/func_catching_non_exception.py
index e6bfc57..d8e0e2d 100644
--- a/test/input/func_catching_non_exception.py
+++ b/test/input/func_catching_non_exception.py
@@ -1,5 +1,6 @@
"""test non-exceptions catched
"""
+import socket
__revision__ = 1
@@ -19,6 +20,16 @@ class MySecondGoodException(MyGoodException):
""" custom 'exception' """
pass
+class SkipException(socket.error):
+ """ This gave false positives for Python 2,
+ but not for Python 3, due to exception
+ hierarchy rewrite.
+ """
+
+class SecondSkipException(SkipException):
+ """ This too shouldn't give false
+ positives. """
+
try:
1 + 1
except MyException:
@@ -39,3 +50,7 @@ try:
except (MyGoodException, MySecondGoodException):
print "should work"
+try:
+ 1 + 3
+except (SkipException, SecondSkipException):
+ print "should work"
diff --git a/test/input/func_dangerous_default.py b/test/input/func_dangerous_default.py
index a68aaec..3e07415 100644
--- a/test/input/func_dangerous_default.py
+++ b/test/input/func_dangerous_default.py
@@ -4,11 +4,11 @@ __revision__ = ''
HEHE = {}
-def function1(value = []):
+def function1(value=[]):
"""docstring"""
print value
-def function2(value = HEHE):
+def function2(value=HEHE):
"""docstring"""
print value
@@ -16,24 +16,24 @@ def function3(value):
"""docstring"""
print value
-def function4(value = set()):
+def function4(value=set()):
"""set is mutable and dangerous."""
print value
-def function5(value = frozenset()):
+def function5(value=frozenset()):
"""frozenset is immutable and safe."""
print value
GLOBAL_SET = set()
-def function6(value = GLOBAL_SET):
+def function6(value=GLOBAL_SET):
"""set is mutable and dangerous."""
print value
-def function7(value = dict()):
+def function7(value=dict()):
"""dict is mutable and dangerous."""
print value
-def function8(value = list()):
+def function8(value=list()):
"""list is mutable and dangerous."""
print value
diff --git a/test/input/func_format.py b/test/input/func_format.py
index a4eed23..828a180 100644
--- a/test/input/func_format.py
+++ b/test/input/func_format.py
@@ -85,3 +85,24 @@ titreprojet = '<tr><td colspan="10">\
<img src="images/drapeau_vert.png" alt="Drapeau vert" />\
<strong>%s</strong></td></tr>' % aaaa
+with open('a') as a, open('b') as b:
+ pass
+
+with open('a') as a, open('b') as b: pass # multiple-statements
+
+# Well-formatted try-except-finally block.
+try:
+ pass
+except IOError, e:
+ print e
+finally:
+ pass
+
+try:
+ pass
+except IOError, e:
+ print e
+finally: pass # multiple-statements
+
+# This is not allowed by the default configuration.
+if True: print False
diff --git a/test/input/func_i0022.py b/test/input/func_i0022.py
new file mode 100644
index 0000000..f5b308f
--- /dev/null
+++ b/test/input/func_i0022.py
@@ -0,0 +1,22 @@
+"""Deprecated suppression style."""
+
+__revision__ = None
+
+a = 1 # pylint: disable=invalid-name
+b = 1 # pylint: disable-msg=invalid-name
+
+# pylint: disable=invalid-name
+c = 1
+# pylint: enable=invalid-name
+
+# pylint: disable-msg=invalid-name
+d = 1
+# pylint: enable-msg=invalid-name
+
+# pylint: disable-msg=C0103
+e = 1
+# pylint: enable-msg=C0103
+
+# pylint: disable=C0103
+f = 1
+# pylint: enable=C0103
diff --git a/test/input/func_method_could_be_function.py b/test/input/func_method_could_be_function.py
index 47b0fc5..a28e48a 100644
--- a/test/input/func_method_could_be_function.py
+++ b/test/input/func_method_could_be_function.py
@@ -1,4 +1,4 @@
-# pylint: disable=R0903,R0922,W0232,R0924
+# pylint: disable=R0903,R0922,W0232
"""test detection of method which could be a function"""
__revision__ = None
diff --git a/test/input/func_missing_super_argument_py20.py b/test/input/func_missing_super_argument_py_30.py
index ec20857..ec20857 100644
--- a/test/input/func_missing_super_argument_py20.py
+++ b/test/input/func_missing_super_argument_py_30.py
diff --git a/test/input/func_namedtuple.py b/test/input/func_namedtuple.py
new file mode 100644
index 0000000..8cfd048
--- /dev/null
+++ b/test/input/func_namedtuple.py
@@ -0,0 +1,10 @@
+"""Test namedtuple attributes.
+
+Regression test for:
+https://bitbucket.org/logilab/pylint/issue/93/pylint-crashes-on-namedtuple-attribute
+"""
+__revision__ = None
+
+from collections import namedtuple
+Thing = namedtuple('Thing', ())
+print Thing.x
diff --git a/test/input/func_noerror_classes_meth_signature.py b/test/input/func_noerror_classes_meth_signature.py
index 8f120cd..2aa5b18 100644
--- a/test/input/func_noerror_classes_meth_signature.py
+++ b/test/input/func_noerror_classes_meth_signature.py
@@ -34,5 +34,5 @@ class Sub(Super):
def ___private3(self, arg):
pass
- def method(self, param = 'abc'):
+ def method(self, param='abc'):
pass
diff --git a/test/input/func_noerror_defined_and_used_on_same_line.py b/test/input/func_noerror_defined_and_used_on_same_line.py
index 71e67b9..2ed0b1f 100644
--- a/test/input/func_noerror_defined_and_used_on_same_line.py
+++ b/test/input/func_noerror_defined_and_used_on_same_line.py
@@ -16,15 +16,15 @@ def func(xxx): return xxx
def func2(xxx): return xxx + func2(1)
-import sys; print sys.exc_info( )
+import sys; print sys.exc_info()
for i in range(10): print i
j = 4; LAMB = lambda x: x+j
-FUNC4 = lambda a, b : a != b
-FUNC3 = lambda (a, b) : a != b
+FUNC4 = lambda a, b: a != b
+FUNC3 = lambda (a, b): a != b
# test http://www.logilab.org/ticket/6954:
-with open('f') as f: print(f.read())
+with open('f') as f: print f.read()
diff --git a/test/input/func_noerror_inner_classes.py b/test/input/func_noerror_inner_classes.py
index 02391a2..84fb43d 100644
--- a/test/input/func_noerror_inner_classes.py
+++ b/test/input/func_noerror_inner_classes.py
@@ -6,7 +6,7 @@ __revision__ = "alpha"
class Aaa(object):
"""docstring"""
def __init__(self):
- self.__setattr__('a','b')
+ self.__setattr__('a', 'b')
def one_public(self):
diff --git a/test/input/func_noerror_new_style_class_py_30.py b/test/input/func_noerror_new_style_class_py_30.py
index d08ab46..87bfb9e 100644
--- a/test/input/func_noerror_new_style_class_py_30.py
+++ b/test/input/func_noerror_new_style_class_py_30.py
@@ -35,7 +35,7 @@ self.mode)
self.was_modified = True
#
- def close(self) :
+ def close(self):
"""Close the file."""
if self.verbose:
diff --git a/test/input/func_noerror_nonregr.py b/test/input/func_noerror_nonregr.py
index 992f9f3..c4c8c38 100644
--- a/test/input/func_noerror_nonregr.py
+++ b/test/input/func_noerror_nonregr.py
@@ -3,7 +3,7 @@
__revision__ = 1
-def function1(cbarg = lambda: None):
+def function1(cbarg=lambda: None):
"""
File "/usr/lib/python2.4/site-packages/logilab/astroid/scoped_nodes.py", line
391, in mularg_class # this method doesn't exist anymore
diff --git a/test/input/func_noerror_static_method.py b/test/input/func_noerror_static_method.py
index d109d2e..ea5f7d8 100644
--- a/test/input/func_noerror_static_method.py
+++ b/test/input/func_noerror_static_method.py
@@ -23,7 +23,5 @@ class MyClass(object):
class_met = classmethod(class_met)
if __name__ == '__main__':
- MyClass.static_met("var1","var2")
+ MyClass.static_met("var1", "var2")
MyClass.class_met("var1")
-
-
diff --git a/test/input/func_noerror_used_before_assignment.py b/test/input/func_noerror_used_before_assignment.py
new file mode 100644
index 0000000..6ba0b88
--- /dev/null
+++ b/test/input/func_noerror_used_before_assignment.py
@@ -0,0 +1,5 @@
+# pylint: disable = line-too-long, multiple-statements, missing-module-attribute
+"""https://bitbucket.org/logilab/pylint/issue/111/false-positive-used-before-assignment-with"""
+
+try: raise IOError(1, "a")
+except IOError, err: print err
diff --git a/test/input/func_set_literal_as_default_py27.py b/test/input/func_set_literal_as_default_py27.py
index 04fb9b5..5c7200e 100644
--- a/test/input/func_set_literal_as_default_py27.py
+++ b/test/input/func_set_literal_as_default_py27.py
@@ -2,7 +2,6 @@
__revision__ = ''
-def function1(value = {1}):
+def function1(value={1}):
"""set is mutable and dangerous."""
print value
-
diff --git a/test/input/func_special_methods.py b/test/input/func_special_methods.py
deleted file mode 100644
index 1b30e26..0000000
--- a/test/input/func_special_methods.py
+++ /dev/null
@@ -1,55 +0,0 @@
-#pylint: disable=C0111
-__revision__ = None
-
-class ContextManager(object):
- def __enter__(self):
- pass
- def __exit__(self, *args):
- pass
- def __init__(self):
- pass
-
-class BadContextManager(object):
- def __enter__(self):
- pass
- def __init__(self):
- pass
-
-class Container(object):
- def __init__(self):
- pass
- def __len__(self):
- return 0
- def __getitem__(self, key):
- pass
- def __setitem__(self, key, value):
- pass
- def __delitem__(self, key, value):
- pass
- def __iter__(self):
- pass
-
-class BadROContainer(object):
- def __init__(self):
- pass
- def __len__(self):
- return 0
- def __setitem__(self, key, value):
- pass
- def __iter__(self):
- pass
-
-
-class BadContainer(object):
- def __init__(self):
- pass
- def __len__(self):
- return 0
- def __getitem__(self, key, value):
- pass
- def __setitem__(self, key, value):
- pass
-
-class MyTuple(tuple):
- def __getitem__(self, index):
- return super(MyTuple, self).__getitem__(index)
diff --git a/test/input/func_superfluous_parens.py b/test/input/func_superfluous_parens.py
new file mode 100644
index 0000000..112ab2b
--- /dev/null
+++ b/test/input/func_superfluous_parens.py
@@ -0,0 +1,19 @@
+"""Test the superfluous-parens warning."""
+
+__revision__ = 1
+
+if (3 == 5):
+ pass
+if not (3 == 5):
+ pass
+if not (3 or 5):
+ pass
+for (x) in (1, 2, 3):
+ print x
+if (1) in (1, 2, 3):
+ pass
+if (1, 2) in (1, 2, 3):
+ pass
+DICT = {'a': 1, 'b': 2}
+del(DICT['b'])
+del DICT['a']
diff --git a/test/input/func_unpacking_non_sequence.py b/test/input/func_unpacking_non_sequence.py
index 973c40d..cd35328 100644
--- a/test/input/func_unpacking_non_sequence.py
+++ b/test/input/func_unpacking_non_sequence.py
@@ -41,7 +41,7 @@ a, b = [1, 2]
a, b = (1, 2)
a, b = set([1, 2])
a, b = {1: 2, 2: 3}
-a, b = "xy"
+a, b = "xy"
a, b = Seq()
a, b = Iter()
a, b = (number for number in range(2))
diff --git a/test/input/func_use_for_or_listcomp_var.py b/test/input/func_use_for_or_listcomp_var.py
index a8918df..3de5a72 100644
--- a/test/input/func_use_for_or_listcomp_var.py
+++ b/test/input/func_use_for_or_listcomp_var.py
@@ -24,4 +24,4 @@ for line in __revision__:
for x in []:
pass
for x in range(3):
- print (lambda : x)() # OK
+ print (lambda: x)() # OK
diff --git a/test/input/func_useless_else_on_loop.py b/test/input/func_useless_else_on_loop.py
index cec5a74..289366a 100644
--- a/test/input/func_useless_else_on_loop.py
+++ b/test/input/func_useless_else_on_loop.py
@@ -39,3 +39,17 @@ else:
print 'fat chance'
for j in range(10):
break
+
+def test_return_for2():
+ """no false positive for break in else
+
+ https://bitbucket.org/logilab/pylint/issue/117/useless-else-on-loop-false-positives
+ """
+ for i in range(10):
+ for i in range(i):
+ if i % 2:
+ break
+ else:
+ break
+ else:
+ print 'great math'
diff --git a/test/input/func_w0108.py b/test/input/func_w0108.py
index 0da9a96..0d4cc62 100644
--- a/test/input/func_w0108.py
+++ b/test/input/func_w0108.py
@@ -21,7 +21,7 @@ _ = lambda x, y, z, *args, **kwargs: _ANYARGS(x, y, z, *args, **kwargs)
# Lambdas that are *not* unnecessary and should *not* trigger warnings.
_ = lambda x: x
_ = lambda x: x()
-_ = lambda x = 4: hash(x)
+_ = lambda x=4: hash(x)
_ = lambda x, y: range(y, x)
_ = lambda x: range(5, x)
_ = lambda x, y: range(x, 5)
diff --git a/test/input/func_w0623_py_30.py b/test/input/func_w0623_py_30.py
index 7c489cc..9bccbc6 100644
--- a/test/input/func_w0623_py_30.py
+++ b/test/input/func_w0623_py_30.py
@@ -44,6 +44,8 @@ try:
pass
except KeyError, exceptions.RuntimeError: # W0623
pass
+except KeyError, exceptions.RuntimeError.args: # W0623
+ pass
except KeyError, OSError: # W0623
pass
except KeyError, MyOtherError: # W0623
@@ -63,4 +65,3 @@ except IOError, exc5: # this is fine
print exc5
except MyOtherError, exc5: # this is fine
print exc5
-
diff --git a/test/input/func_w0711.py b/test/input/func_w0711.py
index 9cc791e..97c3697 100644
--- a/test/input/func_w0711.py
+++ b/test/input/func_w0711.py
@@ -9,7 +9,7 @@ except Exception or StandardError:
print "caught1"
except Exception and StandardError:
print "caught2"
-except (Exception or StandardError):
+except Exception or StandardError:
print "caught3"
except (Exception or StandardError), exc:
print "caught4"
diff --git a/test/input/func_with_used_before_assignment.py b/test/input/func_with_used_before_assignment.py
new file mode 100644
index 0000000..c79815c
--- /dev/null
+++ b/test/input/func_with_used_before_assignment.py
@@ -0,0 +1,12 @@
+'''
+Regression test for
+https://bitbucket.org/logilab/pylint/issue/128/attributeerror-when-parsing
+'''
+from __future__ import with_statement
+__revision__ = 1
+
+def do_nothing():
+ """ empty """
+ with open("") as ctx.obj:
+ context.do()
+ context = None
diff --git a/test/input/syntax_error.py b/test/input/syntax_error.py
index 505f8dc..6c1e7a7 100644
--- a/test/input/syntax_error.py
+++ b/test/input/syntax_error.py
@@ -1,2 +1,2 @@
if False:
-print 'hop'
+print('hop')
diff --git a/test/messages/func_bad_context_manager.txt b/test/messages/func_bad_context_manager.txt
index 1634546..1b5d5d5 100644
--- a/test/messages/func_bad_context_manager.txt
+++ b/test/messages/func_bad_context_manager.txt
@@ -1,3 +1,3 @@
-E: 40:FirstBadContextManager.__exit__: __exit__ must accept 3 arguments: type, value, traceback
-E: 49:SecondBadContextManager.__exit__: __exit__ must accept 3 arguments: type, value, traceback
-E: 58:ThirdBadContextManager.__exit__: __exit__ must accept 3 arguments: type, value, traceback
+E: 40:FirstBadContextManager.__exit__: __exit__ must accept 3 arguments: type, value, traceback
+E: 49:SecondBadContextManager.__exit__: __exit__ must accept 3 arguments: type, value, traceback
+E: 58:ThirdBadContextManager.__exit__: __exit__ must accept 3 arguments: type, value, traceback
diff --git a/test/messages/func_catching_non_exception.txt b/test/messages/func_catching_non_exception.txt
index 9dc23eb..19f22ab 100644
--- a/test/messages/func_catching_non_exception.txt
+++ b/test/messages/func_catching_non_exception.txt
@@ -1,3 +1,3 @@
-E: 24: Catching an exception which doesn't inherit from BaseException: MyException
-E: 29: Catching an exception which doesn't inherit from BaseException: MyException
-E: 29: Catching an exception which doesn't inherit from BaseException: MySecondException \ No newline at end of file
+E: 35: Catching an exception which doesn't inherit from BaseException: MyException
+E: 40: Catching an exception which doesn't inherit from BaseException: MyException
+E: 40: Catching an exception which doesn't inherit from BaseException: MySecondException \ No newline at end of file
diff --git a/test/messages/func_format.txt b/test/messages/func_format.txt
index 27b8107..ce5df81 100644
--- a/test/messages/func_format.txt
+++ b/test/messages/func_format.txt
@@ -1,31 +1,46 @@
-C: 6: Operator not preceded by a space
+C: 6: Exactly one space required before assignment
notpreceded= 1
^
-C: 7: Operator not followed by a space
+C: 7: Exactly one space required after assignment
notfollowed =1
^
-C: 8: Operator not followed by a space
+C: 8: Exactly one space required after comparison
notfollowed <=1
^^
-C: 19: Comma not followed by a space
+C: 19: Exactly one space required after comma
aaaa,bbbb = 1,2
- ^^
+ ^
+C: 19: Exactly one space required after comma
+aaaa,bbbb = 1,2
+ ^
C: 24: More than one statement on a single line
-C: 26: Comma not followed by a space
+C: 26: Exactly one space required after comma
+ aaaa,bbbb = 1,2
+ ^
+C: 26: Exactly one space required after comma
aaaa,bbbb = 1,2
- ^^
-C: 27: Comma not followed by a space
+ ^
+C: 27: Exactly one space required after comma
+ aaaa,bbbb = bbbb,aaaa
+ ^
+C: 27: Exactly one space required after comma
aaaa,bbbb = bbbb,aaaa
- ^^
-C: 29: Comma not followed by a space
+ ^
+C: 29: Exactly one space required after comma
bbbb = (1,2,3)
- ^^
-C: 51:other: Operator not preceded by a space
+ ^
+C: 29: Exactly one space required after comma
+bbbb = (1,2,3)
+ ^
+C: 51: Exactly one space required before assignment
funky= funky+2
^
-C: 71:_gc_debug: Operator not preceded by a space
+C: 71: Exactly one space required before assignment
ocount[obj.__class__]+= 1
^^
-C: 73:_gc_debug: Operator not preceded by a space
+C: 73: Exactly one space required around assignment
ocount[obj.__class__]=1
^
+C: 91: More than one statement on a single line
+C:105: More than one statement on a single line
+C:108: More than one statement on a single line
diff --git a/test/messages/func_i0022.txt b/test/messages/func_i0022.txt
new file mode 100644
index 0000000..477335c
--- /dev/null
+++ b/test/messages/func_i0022.txt
@@ -0,0 +1,13 @@
+I: 5: Locally disabling C0103
+I: 5: Suppressed 'invalid-name' (from line 5)
+I: 6: Deprecated pragma "pylint:disable-msg" or "pylint:enable-msg"
+I: 6: Deprecated pragma "pylint:disable-msg" or "pylint:enable-msg"
+I: 6: Suppressed 'invalid-name' (from line 6)
+I: 9: Suppressed 'invalid-name' (from line 8)
+I: 12: Deprecated pragma "pylint:disable-msg" or "pylint:enable-msg"
+I: 13: Suppressed 'invalid-name' (from line 12)
+I: 14: Deprecated pragma "pylint:disable-msg" or "pylint:enable-msg"
+I: 16: Deprecated pragma "pylint:disable-msg" or "pylint:enable-msg"
+I: 17: Suppressed 'invalid-name' (from line 16)
+I: 18: Deprecated pragma "pylint:disable-msg" or "pylint:enable-msg"
+I: 21: Suppressed 'invalid-name' (from line 20)
diff --git a/test/messages/func_missing_super_argument_py20.txt b/test/messages/func_missing_super_argument_py_30.txt
index 5c97f0d..5c97f0d 100644
--- a/test/messages/func_missing_super_argument_py20.txt
+++ b/test/messages/func_missing_super_argument_py_30.txt
diff --git a/test/messages/func_namedtuple.txt b/test/messages/func_namedtuple.txt
new file mode 100644
index 0000000..d1ef2b1
--- /dev/null
+++ b/test/messages/func_namedtuple.txt
@@ -0,0 +1 @@
+E: 10: Class 'Thing' has no 'x' member
diff --git a/test/messages/func_special_methods.txt b/test/messages/func_special_methods.txt
deleted file mode 100644
index f7e88ae..0000000
--- a/test/messages/func_special_methods.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-R: 12:BadContextManager: Badly implemented Context manager, implements __enter__ but not __exit__
-R: 12:BadContextManager: Too few public methods (0/2)
-R: 32:BadROContainer: Badly implemented Container, implements __len__ but not __getitem__
-R: 32:BadROContainer: Badly implemented Mutable container, implements __setitem__ but not __delitem__
-R: 32:BadROContainer: Too few public methods (0/2)
-R: 43:BadContainer: Badly implemented Mutable container, implements __setitem__ but not __delitem__
diff --git a/test/messages/func_superfluous_parens.txt b/test/messages/func_superfluous_parens.txt
new file mode 100644
index 0000000..77ff186
--- /dev/null
+++ b/test/messages/func_superfluous_parens.txt
@@ -0,0 +1,5 @@
+C: 5: Unnecessary parens after 'if' keyword
+C: 7: Unnecessary parens after 'not' keyword
+C: 11: Unnecessary parens after 'for' keyword
+C: 13: Unnecessary parens after 'if' keyword
+C: 18: Unnecessary parens after 'del' keyword
diff --git a/test/messages/func_toolonglines_py30.txt b/test/messages/func_toolonglines_py30.txt
new file mode 100644
index 0000000..7678258
--- /dev/null
+++ b/test/messages/func_toolonglines_py30.txt
@@ -0,0 +1,4 @@
+C: 1: Line too long (90/80)
+C: 2: Line too long (94/80)
+C: 17: Line too long (83/80)
+C: 25: Line too long (94/80)
diff --git a/test/messages/func_unbalanced_tuple_unpacking.txt b/test/messages/func_unbalanced_tuple_unpacking.txt
index 91880e7..c9311cc 100644
--- a/test/messages/func_unbalanced_tuple_unpacking.txt
+++ b/test/messages/func_unbalanced_tuple_unpacking.txt
@@ -1,5 +1,6 @@
-W: 9:do_stuff: Possible unbalanced tuple unpacking with sequence at line 9: left side has 2 label(s), right side has 3 value(s)
-W: 14:do_stuff1: Possible unbalanced tuple unpacking with sequence at line 14: left side has 2 label(s), right side has 3 value(s)
-W: 19:do_stuff2: Possible unbalanced tuple unpacking with sequence at line 19: left side has 2 label(s), right side has 3 value(s)
-W: 50:do_stuff7: Possible unbalanced tuple unpacking with sequence at line 46: left side has 2 label(s), right side has 3 value(s)
-W: 69:do_stuff9: Possible unbalanced tuple unpacking with sequence at line 7 (input.unpacking): left side has 2 label(s), right side has 3 value(s) \ No newline at end of file
+W: 9:do_stuff: Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s)
+W: 14:do_stuff1: Possible unbalanced tuple unpacking with sequence [1, 2, 3]: left side has 2 label(s), right side has 3 value(s)
+W: 19:do_stuff2: Possible unbalanced tuple unpacking with sequence (1, 2, 3): left side has 2 label(s), right side has 3 value(s)
+W: 50:do_stuff7: Possible unbalanced tuple unpacking with sequence defined at line 46: left side has 2 label(s), right side has 3 value(s)
+W: 69:do_stuff9: Possible unbalanced tuple unpacking with sequence defined at line 7 of input.unpacking: left side has 2 label(s), right side has 3 value(s)
+
diff --git a/test/messages/func_unpacking_non_sequence.txt b/test/messages/func_unpacking_non_sequence.txt
index 5023de3..55a7745 100644
--- a/test/messages/func_unpacking_non_sequence.txt
+++ b/test/messages/func_unpacking_non_sequence.txt
@@ -1,7 +1,8 @@
-W: 61: Attempting to unpack a non-sequence with non-sequence at line 52
-W: 63: Attempting to unpack a non-sequence with non-sequence at line 63
-W: 64: Attempting to unpack a non-sequence with non-sequence at line 64
-W: 65: Attempting to unpack a non-sequence with non-sequence at line 9 (input.unpacking)
-W: 66: Attempting to unpack a non-sequence with non-sequence at line 11 (input.unpacking)
-W: 67: Attempting to unpack a non-sequence with non-sequence at line 58
-W: 68: Attempting to unpack a non-sequence with non-sequence at line unknown (sys) \ No newline at end of file
+W: 61: Attempting to unpack a non-sequence defined at line 52
+W: 62: Attempting to unpack a non-sequence
+W: 63: Attempting to unpack a non-sequence None
+W: 64: Attempting to unpack a non-sequence 1
+W: 65: Attempting to unpack a non-sequence defined at line 9 of input.unpacking
+W: 66: Attempting to unpack a non-sequence defined at line 11 of input.unpacking
+W: 67: Attempting to unpack a non-sequence defined at line 58
+W: 68: Attempting to unpack a non-sequence
diff --git a/test/messages/func_w0623_py_30.txt b/test/messages/func_w0623_py_30.txt
index b764def..a2923f1 100644
--- a/test/messages/func_w0623_py_30.txt
+++ b/test/messages/func_w0623_py_30.txt
@@ -1,11 +1,12 @@
C: 28:some_function: Invalid variable name "FOO"
C: 41: Invalid constant name "exc3"
-C: 55: Invalid variable name "OOPS"
+C: 57: Invalid variable name "OOPS"
W: 18:some_function: Redefining name 'RuntimeError' from object 'exceptions' in exception handler
W: 20:some_function: Redefining name 'OSError' from builtins in exception handler
W: 20:some_function: Unused variable 'OSError'
W: 22:some_function: Redefining name 'MyError' from outer scope (line 7) in exception handler
W: 22:some_function: Unused variable 'MyError'
W: 45: Redefining name 'RuntimeError' from object 'exceptions' in exception handler
-W: 47: Redefining name 'OSError' from builtins in exception handler
-W: 49: Redefining name 'MyOtherError' from outer scope (line 36) in exception handler
+W: 47: Redefining name 'args' from object 'exceptions.RuntimeError' in exception handler
+W: 49: Redefining name 'OSError' from builtins in exception handler
+W: 51: Redefining name 'MyOtherError' from outer scope (line 36) in exception handler
diff --git a/test/messages/func_with_used_before_assignment.txt b/test/messages/func_with_used_before_assignment.txt
new file mode 100644
index 0000000..dc6e386
--- /dev/null
+++ b/test/messages/func_with_used_before_assignment.txt
@@ -0,0 +1,2 @@
+E: 10:do_nothing: Undefined variable 'ctx'
+E: 11:do_nothing: Using variable 'context' before assignment \ No newline at end of file
diff --git a/test/regrtest_data/pygtk_import.py b/test/regrtest_data/pygtk_import.py
index 26f9e71..974035e 100644
--- a/test/regrtest_data/pygtk_import.py
+++ b/test/regrtest_data/pygtk_import.py
@@ -1,4 +1,4 @@
-#pylint: disable=R0903,R0904,R0924
+#pylint: disable=R0903,R0904
"""#10026"""
__revision__ = 1
from gtk import VBox
diff --git a/test/test_format.py b/test/test_format.py
index f14b476..b682671 100644
--- a/test/test_format.py
+++ b/test/test_format.py
@@ -19,150 +19,239 @@ Check format checker helper functions
import sys
import re
from os import linesep
+import tokenize
+import StringIO
from logilab.common.testlib import TestCase, unittest_main
+from astroid import test_utils
from pylint.checkers.format import *
-from pylint.testutils import TestReporter
-
-REPORTER = TestReporter()
-
-class StringRgxTest(TestCase):
- """test the STRING_RGX regular expression"""
-
- def test_known_values_1(self):
- self.assertEqual(STRING_RGX.sub('', '"yo"'), '')
-
- def test_known_values_2(self):
- self.assertEqual(STRING_RGX.sub('', "'yo'"), '')
-
- def test_known_values_tq_1(self):
- self.assertEqual(STRING_RGX.sub('', '"""yo"""'), '')
-
- def test_known_values_tq_2(self):
- self.assertEqual(STRING_RGX.sub('', '"""yo\n"""'), '')
-
- def test_known_values_ta_1(self):
- self.assertEqual(STRING_RGX.sub('', "'''yo'''"), '')
-
- def test_known_values_ta_2(self):
- self.assertEqual(STRING_RGX.sub('', "'''yo\n'''"), '')
-
- def test_known_values_5(self):
- self.assertEqual(STRING_RGX.sub('', r'"yo\"yo"'), '')
-
- def test_known_values_6(self):
- self.assertEqual(STRING_RGX.sub('', r"'yo\'yo'"), '')
-
- def test_known_values_7(self):
- self.assertEqual(STRING_RGX.sub('', '"yo"upi"yo"upi'), 'upiupi')
-
- def test_known_values_8(self):
- self.assertEqual(STRING_RGX.sub('', r"'yo\'yo\''"), '')
-
- def test_known_values_9(self):
- self.assertEqual(STRING_RGX.sub('', r'"yoyo\""'), '')
-
- def test_known_values_10(self):
- self.assertEqual(STRING_RGX.sub('', 'self.filterFunc = eval(\'lambda %s: %s\'%(\',\'.join(variables),formula),{},{})'),
- 'self.filterFunc = eval(%(.join(variables),formula),{},{})')
-
- def test_known_values_11(self):
- self.assertEqual(STRING_RGX.sub('', 'cond_list[index] = OLD_PROG.sub(r\'getattr(__old__,"\1")\',cond)'),
- 'cond_list[index] = OLD_PROG.sub(r,cond)')
-
-
- def test_no_crash(self):
- crash_str = """wizardBmp = ('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\n', (115, 260), None)"""
- re.sub(SQSTRING_RGX, '', crash_str)
- re.sub(TQSTRING_RGX, '', crash_str)
- re.sub(SASTRING_RGX, '', crash_str)
- re.sub(TASTRING_RGX, '', crash_str)
- self.skipTest('XXX: explain the test, remove it or assert something')
-
-if linesep != '\n':
- LINE_RGX = re.compile(linesep)
- def ulines(strings):
- return strings[0], LINE_RGX.sub('\n', strings[1])
-else:
- def ulines(strings):
- return strings
-
-class ChecklineFunctionTest(TestCase):
- """test the check_line method"""
-
- def test_known_values_opspace_1(self):
- self.assertEqual(ulines(check_line('a=1')), ('C0322', 'a=1\n ^'))
-
- def test_known_values_opspace_2(self):
- self.assertEqual(ulines(check_line('a= 1')), ('C0322', 'a= 1\n ^') )
-
- def test_known_values_opspace_3(self):
- self.assertEqual(ulines(check_line('a =1')), ('C0323', 'a =1\n ^'))
-
- def test_known_values_opspace_4(self):
- self.assertEqual(check_line('f(a=1)'), None)
-
- def test_known_values_opspace_4(self):
- self.assertEqual(check_line('f(a=1)'), None)
-
- def test_known_values_colonnl_2(self):
- self.assertEqual(check_line('a[:1]'), None)
-
- def test_known_values_colonnl_3(self):
- self.assertEqual(check_line('a[1:]'), None)
-
- def test_known_values_colonnl_4(self):
- self.assertEqual(check_line('a[1:2]'), None)
-
- def test_known_values_colonnl_5(self):
- self.assertEqual(check_line('def intersection(list1, list2):'), None)
-
- def test_known_values_colonnl_6(self):
- self.assertEqual(check_line('def intersection(list1, list2):\n'), None)
-
- def test_known_values_colonnl_7(self):
- self.assertEqual(check_line('if file[:pfx_len] == path:\n'), None)
-
- def test_known_values_colonnl_9(self):
- self.assertEqual(check_line('if file[:pfx_len[1]] == path:\n'), None)
-
- def test_known_values_colonnl_10(self):
- self.assertEqual(check_line('if file[pfx_len[1]] == path:\n'), None)
-
- def test_known_values_commaspace_1(self):
- self.assertEqual(ulines(check_line('a, b = 1,2')),
- ('C0324', 'a, b = 1,2\n ^^'))
-
- def test_known_values_commaspace_2(self):
- self.assertEqual(check_line('should_not_warn = [1, 2, 3,]\n'),
- None)
-
- def test_known_values_commaspace_3(self):
- self.assertEqual(check_line('should_not_warn = {1:2, 3:4,}\n'),
- None)
-
- def test_known_values_commaspace_4(self):
- self.assertEqual(check_line('should_not_warn = (1, 2, 3,)\n'),
- None)
-
- def test_known_values_instring_1(self):
- self.assertEqual(check_line('f("a=1")'), None)
-
- def test_known_values_instring_2(self):
- self.assertEqual(ulines(check_line('print >>1, ("a:1")')),
- ('C0323', 'print >>1, ("a:1")\n ^'))
-
- def test_known_values_all_1(self):
- self.assertEqual(ulines(check_line("self.filterFunc = eval('lambda %s: %s'%(','.join(variables),formula),{},{})")),
- ('C0324', "self.filterFunc = eval('lambda %s: %s'%(','.join(variables),formula),{},{})\n ^^"))
-
- def test_known_values_tqstring(self):
- self.assertEqual(check_line('print """<a="=")\n"""'), None)
+from pylint.testutils import CheckerTestCase, Message
+
+
+def tokenize_str(code):
+ return list(tokenize.generate_tokens(StringIO.StringIO(code).readline))
+
+
+class MultiStatementLineTest(CheckerTestCase):
+ CHECKER_CLASS = FormatChecker
+
+ def testSingleLineIfStmts(self):
+ stmt = test_utils.extract_node("""
+ if True: pass #@
+ """)
+ with self.assertAddsMessages(Message('C0321', node=stmt.body[0])):
+ self.checker.process_tokens([])
+ self.checker.visit_default(stmt.body[0])
+ self.checker.config.single_line_if_stmt = True
+ with self.assertNoMessages():
+ self.checker.process_tokens([])
+ self.checker.visit_default(stmt.body[0])
+ stmt = test_utils.extract_node("""
+ if True: pass #@
+ else:
+ pass
+ """)
+ with self.assertAddsMessages(Message('C0321', node=stmt.body[0])):
+ self.checker.process_tokens([])
+ self.checker.visit_default(stmt.body[0])
+
+ def testTryExceptFinallyNoMultipleStatement(self):
+ tree = test_utils.extract_node("""
+ try: #@
+ pass
+ except:
+ pass
+ finally:
+ pass""")
+ with self.assertNoMessages():
+ self.checker.process_tokens([])
+ self.checker.visit_default(tree.body[0])
+
+
+
+class SuperfluousParenthesesTest(CheckerTestCase):
+ CHECKER_CLASS = FormatChecker
+
+ def testCheckKeywordParensHandlesValidCases(self):
+ self.checker._keywords_with_parens = set()
+ cases = [
+ 'if foo:',
+ 'if foo():',
+ 'if (x and y) or z:',
+ 'assert foo()',
+ 'assert ()',
+ 'if (1, 2) in (3, 4):',
+ 'if (a or b) in c:',
+ 'return (x for x in x)',
+ 'if (x for x in x):',
+ 'for x in (x for x in x):',
+ 'not (foo or bar)',
+ 'not (foo or bar) and baz',
+ ]
+ with self.assertNoMessages():
+ for code in cases:
+ self.checker._check_keyword_parentheses(tokenize_str(code), 0)
+
+ def testCheckKeywordParensHandlesUnnecessaryParens(self):
+ self.checker._keywords_with_parens = set()
+ cases = [
+ (Message('C0325', line=1, args='if'),
+ 'if (foo):', 0),
+ (Message('C0325', line=1, args='if'),
+ 'if ((foo, bar)):', 0),
+ (Message('C0325', line=1, args='if'),
+ 'if (foo(bar)):', 0),
+ (Message('C0325', line=1, args='return'),
+ 'return ((x for x in x))', 0),
+ (Message('C0325', line=1, args='not'),
+ 'not (foo)', 0),
+ (Message('C0325', line=1, args='not'),
+ 'if not (foo):', 1),
+ (Message('C0325', line=1, args='if'),
+ 'if (not (foo)):', 0),
+ (Message('C0325', line=1, args='not'),
+ 'if (not (foo)):', 2),
+ ]
+ for msg, code, offset in cases:
+ with self.assertAddsMessages(msg):
+ self.checker._check_keyword_parentheses(tokenize_str(code), offset)
+
+ def testFuturePrintStatementWithoutParensWarning(self):
+ code = """from __future__ import print_function
+print('Hello world!')
+"""
+ tree = test_utils.build_module(code)
+ with self.assertNoMessages():
+ self.checker.process_module(tree)
+ self.checker.process_tokens(tokenize_str(code))
+
+
+class CheckSpaceTest(CheckerTestCase):
+ CHECKER_CLASS = FormatChecker
+
+ def testParenthesesGood(self):
+ good_cases = [
+ '(a)\n',
+ '(a * (b + c))\n',
+ '( #\na)\n',
+ ]
+ with self.assertNoMessages():
+ for code in good_cases:
+ self.checker.process_tokens(tokenize_str(code))
+
+ def testParenthesesBad(self):
+ with self.assertAddsMessages(
+ Message('C0326', line=1,
+ args=('No', 'allowed', 'after', 'bracket', '( a)\n^'))):
+ self.checker.process_tokens(tokenize_str('( a)\n'))
+
+ with self.assertAddsMessages(
+ Message('C0326', line=1,
+ args=('No', 'allowed', 'before', 'bracket', '(a )\n ^'))):
+ self.checker.process_tokens(tokenize_str('(a )\n'))
+
+ with self.assertAddsMessages(
+ Message('C0326', line=1,
+ args=('No', 'allowed', 'before', 'bracket', 'foo (a)\n ^'))):
+ self.checker.process_tokens(tokenize_str('foo (a)\n'))
+
+ with self.assertAddsMessages(
+ Message('C0326', line=1,
+ args=('No', 'allowed', 'before', 'bracket', '{1: 2} [1]\n ^'))):
+ self.checker.process_tokens(tokenize_str('{1: 2} [1]\n'))
+
+ def testTrailingCommaGood(self):
+ with self.assertNoMessages():
+ self.checker.process_tokens(tokenize_str('(a, )\n'))
+ self.checker.process_tokens(tokenize_str('(a,)\n'))
+
+ self.checker.config.no_space_check = []
+ with self.assertNoMessages():
+ self.checker.process_tokens(tokenize_str('(a,)\n'))
+
+ def testTrailingCommaBad(self):
+ self.checker.config.no_space_check = []
+ with self.assertAddsMessages(
+ Message('C0326', line=1,
+ args=('No', 'allowed', 'before', 'bracket', '(a, )\n ^'))):
+ self.checker.process_tokens(tokenize_str('(a, )\n'))
+
+ def testComma(self):
+ with self.assertAddsMessages(
+ Message('C0326', line=1,
+ args=('No', 'allowed', 'before', 'comma', '(a , b)\n ^'))):
+ self.checker.process_tokens(tokenize_str('(a , b)\n'))
+
+ def testSpacesAllowedInsideSlices(self):
+ good_cases = [
+ '[a:b]\n',
+ '[a : b]\n',
+ '[a : ]\n',
+ '[:a]\n',
+ '[:]\n',
+ '[::]\n',
+ ]
+ with self.assertNoMessages():
+ for code in good_cases:
+ self.checker.process_tokens(tokenize_str(code))
+
+ def testKeywordSpacingGood(self):
+ with self.assertNoMessages():
+ self.checker.process_tokens(tokenize_str('foo(foo=bar)\n'))
+ self.checker.process_tokens(tokenize_str('lambda x=1: x\n'))
+
+ def testKeywordSpacingBad(self):
+ with self.assertAddsMessages(
+ Message('C0326', line=1,
+ args=('No', 'allowed', 'before', 'keyword argument assignment',
+ '(foo =bar)\n ^'))):
+ self.checker.process_tokens(tokenize_str('(foo =bar)\n'))
+
+ with self.assertAddsMessages(
+ Message('C0326', line=1,
+ args=('No', 'allowed', 'after', 'keyword argument assignment',
+ '(foo= bar)\n ^'))):
+ self.checker.process_tokens(tokenize_str('(foo= bar)\n'))
+
+ with self.assertAddsMessages(
+ Message('C0326', line=1,
+ args=('No', 'allowed', 'around', 'keyword argument assignment',
+ '(foo = bar)\n ^'))):
+ self.checker.process_tokens(tokenize_str('(foo = bar)\n'))
+
+ def testOperatorSpacingGood(self):
+ good_cases = [
+ 'a = b\n'
+ 'a < b\n'
+ 'a\n< b\n',
+ ]
+ with self.assertNoMessages():
+ for code in good_cases:
+ self.checker.process_tokens(tokenize_str(code))
+
+ def testOperatorSpacingBad(self):
+ with self.assertAddsMessages(
+ Message('C0326', line=1,
+ args=('Exactly one', 'required', 'before', 'comparison', 'a< b\n ^'))):
+ self.checker.process_tokens(tokenize_str('a< b\n'))
+
+ with self.assertAddsMessages(
+ Message('C0326', line=1,
+ args=('Exactly one', 'required', 'after', 'comparison', 'a <b\n ^'))):
+ self.checker.process_tokens(tokenize_str('a <b\n'))
+
+ with self.assertAddsMessages(
+ Message('C0326', line=1,
+ args=('Exactly one', 'required', 'around', 'comparison', 'a<b\n ^'))):
+ self.checker.process_tokens(tokenize_str('a<b\n'))
+
+ with self.assertAddsMessages(
+ Message('C0326', line=1,
+ args=('Exactly one', 'required', 'around', 'comparison', 'a< b\n ^'))):
+ self.checker.process_tokens(tokenize_str('a< b\n'))
- def test_known_values_tastring(self):
- self.assertEqual(check_line("print '''<a='=')\n'''"), None)
if __name__ == '__main__':
unittest_main()
diff --git a/test/test_func.py b/test/test_func.py
index 56bb82c..e8c746d 100644
--- a/test/test_func.py
+++ b/test/test_func.py
@@ -42,19 +42,12 @@ class LintTestNonExistentModuleTC(LintTestUsingModule):
_get_expected = lambda self: 'F: 1: No module named %snonexistent%s\n' % (quote, quote)
tags = testlib.Tags(('generated','pylint_input_%s' % module))
-class LintTestNonExistentFileTC(LintTestUsingFile):
- module = join(INPUT_DIR, 'nonexistent.py')
- _get_expected = lambda self: 'F: 1: No module named %s\n' % self.module[len(getcwd())+1 :]
- tags = testlib.Tags(('generated', 'pylint_input_%s' % module))
- def test_functionality(self):
- self._test([self.module])
-
class TestTests(testlib.TestCase):
"""check that all testable messages have been checked"""
@testlib.tag('coverage')
def test_exhaustivity(self):
# skip fatal messages
- todo = [msgid for msgid in linter._messages if msgid[0] != 'F']
+ todo = [msg.msgid for msg in linter.messages if msg.msgid[0] != 'F']
for msgid in test_reporter.message_ids:
try:
todo.remove(msgid)
@@ -89,7 +82,7 @@ def cb_file(*args):
return base_cb_file(*args)
callbacks = [cb_test_gen(LintTestUsingModule),
- cb_file]
+ cb_file]
# Gen tests
@@ -103,8 +96,6 @@ def gen_tests(filter_rgx):
if is_to_run('nonexistent'):
tests.append(LintTestNonExistentModuleTC)
- if not MODULES_ONLY:
- tests.append(LintTestNonExistentFileTC)
tests.append(LintBuiltinModuleTest)
diff --git a/test/test_import_graph.py b/test/test_import_graph.py
index f9aba99..a1dfdc8 100644
--- a/test/test_import_graph.py
+++ b/test/test_import_graph.py
@@ -51,9 +51,9 @@ class ImportCheckerTC(TestCase):
l.global_set_option('ignore', ('func_unknown_encoding.py',))
try:
l.check('input')
- self.assert_(exists('import.dot'))
- self.assert_(exists('ext_import.dot'))
- self.assert_(exists('int_import.dot'))
+ self.assertTrue(exists('import.dot'))
+ self.assertTrue(exists('ext_import.dot'))
+ self.assertTrue(exists('int_import.dot'))
finally:
for fname in ('import.dot', 'ext_import.dot', 'int_import.dot'):
try:
diff --git a/test/test_misc.py b/test/test_misc.py
index d2c03c0..a2cba9b 100644
--- a/test/test_misc.py
+++ b/test/test_misc.py
@@ -15,7 +15,7 @@
"""
Tests for the misc checker.
"""
-
+import sys
import tempfile
import os
import contextlib
@@ -31,7 +31,11 @@ def create_file_backed_module(code):
# because on Windows the file must be closed before writing to it,
# see http://bugs.python.org/issue14243
fd, tmp = tempfile.mkstemp()
- os.write(fd, code)
+ if sys.version_info >= (3, 0):
+ # erff
+ os.write(fd, bytes(code, 'ascii'))
+ else:
+ os.write(fd, code)
try:
module = test_utils.build_module(code)
@@ -53,7 +57,7 @@ class FixmeTest(CheckerTestCase):
Message(msg_id='W0511', line=2, args=u'FIXME')):
self.checker.process_module(module)
- def test_emtpy_fixme_regex(self):
+ def test_empty_fixme_regex(self):
self.checker.config.notes = []
with create_file_backed_module(
"""a = 1
diff --git a/test/unittest_lint.py b/test/unittest_lint.py
index 7612ef4..44278e2 100644
--- a/test/unittest_lint.py
+++ b/test/unittest_lint.py
@@ -77,6 +77,16 @@ class PyLinterTC(TestCase):
checkers.initialize(self.linter)
self.linter.set_reporter(TestReporter())
+ def _compare_messages(self, desc, msg, checkerref=False):
+ # replace \r\n with \n, because
+ # logilab.common.textutils.normalize_text
+ # uses os.linesep, which will
+ # not properly compare with triple
+ # quoted multilines used in these tests
+ self.assertMultiLineEqual(desc,
+ msg.format_help(checkerref=checkerref)
+ .replace('\r\n', '\n'))
+
def test_check_message_id(self):
self.assertIsInstance(self.linter.check_message_id('F0001'),
MessageDefinition)
@@ -85,32 +95,32 @@ class PyLinterTC(TestCase):
def test_message_help(self):
msg = self.linter.check_message_id('F0001')
- self.assertMultiLineEqual(
- ''':F0001 (fatal):
+ self._compare_messages(
+ ''':fatal (F0001):
Used when an error occurred preventing the analysis of a module (unable to
find it for instance). This message belongs to the master checker.''',
- msg.format_help(checkerref=True))
- self.assertMultiLineEqual(
- ''':F0001 (fatal):
+ msg, checkerref=True)
+ self._compare_messages(
+ ''':fatal (F0001):
Used when an error occurred preventing the analysis of a module (unable to
find it for instance).''',
- msg.format_help(checkerref=False))
+ msg, checkerref=False)
def test_message_help_minmax(self):
# build the message manually to be python version independant
msg = build_message_def(self.linter._checkers['typecheck'][0],
'E1122', checkers.typecheck.MSGS['E1122'])
- self.assertMultiLineEqual(
- ''':E1122 (duplicate-keyword-arg): *Duplicate keyword argument %r in function call*
+ self._compare_messages(
+ ''':duplicate-keyword-arg (E1122): *Duplicate keyword argument %r in function call*
Used when a function call passes the same keyword argument multiple times.
This message belongs to the typecheck checker. It can't be emitted when using
Python >= 2.6.''',
- msg.format_help(checkerref=True))
- self.assertMultiLineEqual(
- ''':E1122 (duplicate-keyword-arg): *Duplicate keyword argument %r in function call*
+ msg, checkerref=True)
+ self._compare_messages(
+ ''':duplicate-keyword-arg (E1122): *Duplicate keyword argument %r in function call*
Used when a function call passes the same keyword argument multiple times.
This message can't be emitted when using Python >= 2.6.''',
- msg.format_help(checkerref=False))
+ msg, checkerref=False)
def test_enable_message(self):
linter = self.linter
@@ -170,7 +180,7 @@ class PyLinterTC(TestCase):
linter.open()
filepath = join(INPUTDIR, 'func_block_disable_msg.py')
linter.set_current_module('func_block_disable_msg')
- astroid = linter.get_astroid(filepath, 'func_block_disable_msg')
+ astroid = linter.get_ast(filepath, 'func_block_disable_msg')
linter.process_tokens(tokenize_module(astroid))
orig_state = linter._module_msgs_state.copy()
linter._module_msgs_state = {}
@@ -214,7 +224,7 @@ class PyLinterTC(TestCase):
self.assertEqual(17, linter._suppression_mapping['W0613', 18])
self.assertEqual(30, linter._suppression_mapping['E1101', 33])
- self.assert_(('E1101', 46) not in linter._suppression_mapping)
+ self.assertTrue(('E1101', 46) not in linter._suppression_mapping)
self.assertEqual(1, linter._suppression_mapping['C0302', 18])
self.assertEqual(1, linter._suppression_mapping['C0302', 50])
# This is tricky. While the disable in line 106 is disabling
@@ -261,7 +271,7 @@ class PyLinterTC(TestCase):
finally:
sys.stdout = sys.__stdout__
# cursory examination of the output: we're mostly testing it completes
- self.assertTrue(':C0112 (empty-docstring): *Empty %s docstring*' in output)
+ self.assertIn(':empty-docstring (C0112): *Empty %s docstring*', output)
def test_lint_ext_module_with_file_output(self):
self.linter.set_reporter(text.TextReporter())
@@ -282,6 +292,13 @@ class PyLinterTC(TestCase):
except:
pass
+ def test_lint_should_analyze_file(self):
+ self.linter.set_reporter(text.TextReporter())
+ self.linter.config.files_output = True
+ self.linter.should_analyze_file = lambda *args: False
+ self.linter.check('os')
+ self.assertFalse(os.path.exists('pylint_os.txt'))
+
def test_enable_report(self):
self.assertEqual(self.linter.report_is_enabled('RP0001'), True)
self.linter.disable('RP0001')
@@ -362,6 +379,23 @@ class PyLinterTC(TestCase):
['C: 1: Line too long (1/2)', 'C: 2: Line too long (3/4)'],
self.linter.reporter.messages)
+ def test_add_renamed_message(self):
+ self.linter.add_renamed_message('C9999', 'old-bad-name', 'invalid-name')
+ self.assertEqual('invalid-name',
+ self.linter.check_message_id('C9999').symbol)
+ self.assertEqual('invalid-name',
+ self.linter.check_message_id('old-bad-name').symbol)
+
+ def test_renamed_message_register(self):
+ class Checker(object):
+ msgs = {'W1234': ('message', 'msg-symbol', 'msg-description',
+ {'old_names': [('W0001', 'old-symbol')]})}
+ self.linter.register_messages(Checker())
+ self.assertEqual('msg-symbol',
+ self.linter.check_message_id('W0001').symbol)
+ self.assertEqual('msg-symbol',
+ self.linter.check_message_id('old-symbol').symbol)
+
class ConfigTC(TestCase):
diff --git a/test/unittest_pyreverse_diadefs.py b/test/unittest_pyreverse_diadefs.py
index ebcf371..a42d73a 100644
--- a/test/unittest_pyreverse_diadefs.py
+++ b/test/unittest_pyreverse_diadefs.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2000-2004 LOGILAB S.A. (Paris, FRANCE).
+# Copyright (c) 2000-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
@@ -26,15 +26,10 @@ from astroid.inspector import Linker
from pylint.pyreverse.diadefslib import *
-from utils import Config
+from unittest_pyreverse_writer import Config, get_project
-def astroid_wrapper(func, modname):
- return func(modname)
-
-PROJECT = MANAGER.project_from_files(['data'], astroid_wrapper)
-
-CONFIG = Config()
-HANDLER = DiadefsHandler(CONFIG)
+PROJECT = get_project('data')
+HANDLER = DiadefsHandler(Config())
def _process_classes(classes):
"""extract class names of a list"""
@@ -43,7 +38,7 @@ def _process_classes(classes):
def _process_relations(relations):
"""extract relation indices from a relation list"""
result = []
- for rel_type, rels in relations.iteritems():
+ for rel_type, rels in relations.iteritems():
for rel in rels:
result.append( (rel_type, rel.from_object.title,
rel.to_object.title) )
@@ -54,7 +49,7 @@ def _process_relations(relations):
class DiaDefGeneratorTC(unittest.TestCase):
def test_option_values(self):
"""test for ancestor, associated and module options"""
- handler = DiadefsHandler( Config())
+ handler = DiadefsHandler(Config())
df_h = DiaDefGenerator(Linker(PROJECT), handler)
cl_config = Config()
cl_config.classes = ['Specialization']
@@ -125,15 +120,15 @@ class DefaultDiadefGeneratorTC(unittest.TestCase):
different classes possibly in different modules"""
# XXX should be catching pyreverse environnement problem but doesn't
# pyreverse doesn't extracts the relations but this test ok
- project = MANAGER.project_from_files(['data'], astroid_wrapper)
- handler = DiadefsHandler( Config() )
+ project = get_project('data')
+ handler = DiadefsHandler(Config())
diadefs = handler.get_diadefs(project, Linker(project, tag=True) )
cd = diadefs[1]
relations = _process_relations(cd.relationships)
self.assertEqual(relations, self._should_rels)
def test_known_values2(self):
- project = MANAGER.project_from_files(['data.clientmodule_test'], astroid_wrapper)
+ project = get_project('data.clientmodule_test')
dd = DefaultDiadefGenerator(Linker(project), HANDLER).visit(project)
self.assertEqual(len(dd), 1)
keys = [d.TYPE for d in dd]
@@ -158,7 +153,7 @@ class ClassDiadefGeneratorTC(unittest.TestCase):
(True, special),
(True, 'data.suppliermodule_test.DoNothing'),
])
-
+
def test_known_values2(self):
HANDLER.config.module_names = False
cd = ClassDiadefGenerator(Linker(PROJECT), HANDLER).class_diagram(PROJECT, 'data.clientmodule_test.Specialization')
diff --git a/test/unittest_pyreverse_writer.py b/test/unittest_pyreverse_writer.py
index a4e283e..b850679 100644
--- a/test/unittest_pyreverse_writer.py
+++ b/test/unittest_pyreverse_writer.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2000-2008 LOGILAB S.A. (Paris, FRANCE).
+# Copyright (c) 2000-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
@@ -17,37 +17,96 @@
unittest for visitors.diadefs and extensions.diadefslib modules
"""
-from os.path import abspath, dirname, join
-from astroid.inspector import Linker
+
+import os
+import sys
+import codecs
+from os.path import join, dirname, abspath
+from difflib import unified_diff
+
from logilab.common.testlib import TestCase, unittest_main
+from astroid import MANAGER
+from astroid.inspector import Linker
+
from pylint.pyreverse.diadefslib import DefaultDiadefGenerator, DiadefsHandler
-from pylint.pyreverse.diagrams import set_counter
from pylint.pyreverse.writer import DotWriter
-
from pylint.pyreverse.utils import get_visibility
-from utils import FileTC, build_file_case, get_project, Config
-project = get_project(join(dirname(abspath(__file__)), 'data'))
-linker = Linker(project)
-set_counter(0)
-config = Config()
+_DEFAULTS = {
+ 'all_ancestors': None, 'show_associated': None,
+ 'module_names': None,
+ 'output_format': 'dot', 'diadefs_file': None, 'quiet': 0,
+ 'show_ancestors': None, 'classes': (), 'all_associated': None,
+ 'mode': 'PUB_ONLY', 'show_builtin': False, 'only_classnames': False
+ }
+
+class Config(object):
+ """config object for tests"""
+ def __init__(self):
+ for attr, value in _DEFAULTS.items():
+ setattr(self, attr, value)
+
+
+def _file_lines(path):
+ # we don't care about the actual encoding, but python3 forces us to pick one
+ lines = [line.strip() for line in codecs.open(path, encoding='latin1').readlines()
+ if (line.find('squeleton generated by ') == -1 and
+ not line.startswith('__revision__ = "$Id:'))]
+ return [line for line in lines if line]
+
+def get_project(module, name=None):
+ """return a astroid project representation"""
+ # flush cache
+ MANAGER._modules_by_name = {}
+ def _astroid_wrapper(func, modname):
+ return func(modname)
+ return MANAGER.project_from_files([module], _astroid_wrapper,
+ project_name=name)
+
+CONFIG = Config()
+
+class DotWriterTC(TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ project = get_project(cls.datadir)
+ linker = Linker(project)
+ handler = DiadefsHandler(CONFIG)
+ dd = DefaultDiadefGenerator(linker, handler).visit(project)
+ for diagram in dd:
+ diagram.extract_relationships()
+ writer = DotWriter(CONFIG)
+ writer.write(dd)
-handler = DiadefsHandler(config)
-dd = DefaultDiadefGenerator(linker, handler).visit(project)
-for diagram in dd:
- diagram.extract_relationships()
+ @classmethod
+ def tearDownClass(cls):
+ for fname in ('packages_No_Name.dot', 'classes_No_Name.dot',):
+ try:
+ os.remove(fname)
+ except:
+ continue
-class DotWriterTC(FileTC):
+ def _test_same_file(self, generated_file):
+ expected_file = self.datapath(generated_file)
+ generated = _file_lines(generated_file)
+ expected = _file_lines(expected_file)
+ generated = '\n'.join(generated)
+ expected = '\n'.join(expected)
+ files = "\n *** expected : %s, generated : %s \n" % (
+ expected_file, generated_file)
+ self.assertEqual(expected, generated, '%s%s' % (
+ files, '\n'.join(line for line in unified_diff(
+ expected.splitlines(), generated.splitlines() ))) )
+ os.remove(generated_file)
+
+ def test_package_diagram(self):
+ self._test_same_file('packages_No_Name.dot')
+
+ def test_class_diagram(self):
+ self._test_same_file('classes_No_Name.dot')
- generated_files = ('packages_No_Name.dot', 'classes_No_Name.dot',)
- def setUp(self):
- FileTC.setUp(self)
- writer = DotWriter(config)
- writer.write(dd)
-
-build_file_case(DotWriterTC)
class GetVisibilityTC(TestCase):
@@ -60,16 +119,17 @@ class GetVisibilityTC(TestCase):
for name in ["__g_", "____dsf", "__23_9"]:
got = get_visibility(name)
self.assertEqual(got, 'private',
- 'got %s instead of private for value %s' % (got, name))
+ 'got %s instead of private for value %s' % (got, name))
def test_public(self):
self.assertEqual(get_visibility('simple'), 'public')
def test_protected(self):
- for name in ["_","__", "___", "____", "_____", "___e__", "_nextsimple", "_filter_it_"]:
+ for name in ["_","__", "___", "____", "_____", "___e__",
+ "_nextsimple", "_filter_it_"]:
got = get_visibility(name)
self.assertEqual(got, 'protected',
- 'got %s instead of protected for value %s' % (got, name))
+ 'got %s instead of protected for value %s' % (got, name))
if __name__ == '__main__':
diff --git a/test/utils.py b/test/utils.py
deleted file mode 100644
index 34e1d44..0000000
--- a/test/utils.py
+++ /dev/null
@@ -1,81 +0,0 @@
-"""some pylint test utilities
-"""
-
-import os
-import sys
-from os.path import join, dirname, abspath
-import codecs
-
-from logilab.common.testlib import TestCase
-from astroid import MANAGER
-
-
-def _astroid_wrapper(func, modname):
- return func(modname)
-
-
-def _sorted_file(path):
- # we don't care about the actual encoding, but python3 forces us to pick one
- lines = [line.strip() for line in codecs.open(path, encoding='latin1').readlines()
- if (line.find('squeleton generated by ') == -1 and
- not line.startswith('__revision__ = "$Id:'))]
- lines = [line for line in lines if line]
- lines.sort()
- return '\n'.join(lines)
-
-def get_project(module, name=None):
- """return a astroid project representation
- """
- manager = MANAGER
- # flush cache
- manager._modules_by_name = {}
- return manager.project_from_files([module], _astroid_wrapper,
- project_name=name)
-
-DEFAULTS = {'all_ancestors': None, 'show_associated': None,
- 'module_names': None,
- 'output_format': 'dot', 'diadefs_file': None, 'quiet': 0,
- 'show_ancestors': None, 'classes': (), 'all_associated': None,
- 'mode': 'PUB_ONLY', 'show_builtin': False, 'only_classnames': False}
-
-class Config(object):
- """config object for tests"""
- def __init__(self):
- for attr, value in DEFAULTS.items():
- setattr(self, attr, value)
-
-class FileTC(TestCase):
- """base test case for testing file output"""
-
- generated_files = ()
-
- def setUp(self):
- self.expected_files = [join(dirname(abspath(__file__)), 'data', file)
- for file in self.generated_files]
-
- def tearDown(self):
- for fname in self.generated_files:
- try:
- os.remove(fname)
- except:
- continue
-
- def _test_same_file(self, index):
- generated_file = self.generated_files[index]
- expected_file = self.expected_files[index]
- generated = _sorted_file(generated_file)
- expected = _sorted_file(expected_file)
-
- from difflib import unified_diff
- files = "\n *** expected : %s, generated : %s \n" % (
- expected_file, generated_file)
- self.assertEqual(expected, generated, '%s%s' % (
- files, '\n'.join(line for line in unified_diff(
- expected.splitlines(), generated.splitlines() ))) )
- os.remove(generated_file)
-
-
-def build_file_case(filetc):
- for i in range(len(filetc.generated_files)):
- setattr(filetc, 'test_same_file_%s' %i,
- lambda self, index=i: self._test_same_file(index))
diff --git a/testutils.py b/testutils.py
index e939e51..a61fa7f 100644
--- a/testutils.py
+++ b/testutils.py
@@ -38,6 +38,7 @@ from pylint.lint import PyLinter
SYS_VERS_STR = '%d%d' % sys.version_info[:2]
TITLE_UNDERLINES = ['', '=', '-', '.']
PREFIX = abspath(dirname(__file__))
+PY3K = sys.version_info[0] == 3
def fix_path():
sys.path.insert(0, PREFIX)
@@ -99,6 +100,10 @@ class TestReporter(BaseReporter):
if obj:
obj = ':%s' % obj
sigle = msg_id[0]
+ if PY3K and linesep != '\n':
+ # 2to3 writes os.linesep instead of using
+ # the previosly used line separators
+ msg = msg.replace('\r\n', '\n')
self.messages.append('%s:%3s%s: %s' % (sigle, line, obj, msg))
def finalize(self):
@@ -150,7 +155,7 @@ class CheckerTestCase(testlib.TestCase):
def setUp(self):
self.linter = UnittestLinter()
- self.checker = self.CHECKER_CLASS(self.linter)
+ self.checker = self.CHECKER_CLASS(self.linter) # pylint: disable=not-callable
for key, value in self.CONFIG.iteritems():
setattr(self.checker.config, key, value)
self.checker.open()
@@ -175,7 +180,7 @@ class CheckerTestCase(testlib.TestCase):
msg = ('Expected messages did not match actual.\n'
'Expected:\n%s\nGot:\n%s' % ('\n'.join(repr(m) for m in messages),
'\n'.join(repr(m) for m in got)))
- self.assertEqual(got, list(messages), msg)
+ self.assertEqual(list(messages), got, msg)
# Init
@@ -213,10 +218,10 @@ class LintTestUsingModule(testlib.TestCase):
_TEST_TYPE = 'module'
def shortDescription(self):
- values = { 'mode' : self._TEST_TYPE,
- 'input': self.module,
- 'pkg': self.package,
- 'cls': self.__class__.__name__}
+ values = {'mode' : self._TEST_TYPE,
+ 'input': self.module,
+ 'pkg': self.package,
+ 'cls': self.__class__.__name__}
if self.package == self.DEFAULT_PACKAGE:
msg = '%(mode)s test of input file "%(input)s" (%(cls)s)'
@@ -246,14 +251,14 @@ class LintTestUsingModule(testlib.TestCase):
ex.__str__ = exception_str
raise
got = self.linter.reporter.finalize()
- self.assertMultiLineEqual(got, self._get_expected())
+ self.assertMultiLineEqual(self._get_expected(), got)
def _get_expected(self):
if self.module.startswith('func_noerror_'):
expected = ''
else:
- output = open(self.output)
+ output = open(self.output, 'U')
expected = output.read().strip() + '\n'
output.close()
return expected
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..9c19dac
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,10 @@
+[tox]
+# official list is
+#envlist = py25, py26, py27, py32, py33
+# though 2.5 support is known to be broken...
+envlist = py27, py33
+[testenv]
+deps =
+ logilab-common
+ astroid
+commands = pytest -t {envsitepackagesdir}/pylint/test/ \ No newline at end of file
diff --git a/utils.py b/utils.py
index 875ac10..59e22a5 100644
--- a/utils.py
+++ b/utils.py
@@ -133,7 +133,7 @@ def build_message_def(checker, msgid, msg_tuple):
class MessageDefinition(object):
def __init__(self, checker, msgid, msg, descr, symbol, scope,
- minversion=None, maxversion=None):
+ minversion=None, maxversion=None, old_names=None):
self.checker = checker
assert len(msgid) == 5, 'Invalid message id %s' % msgid
assert msgid[0] in MSG_TYPES, \
@@ -145,6 +145,7 @@ class MessageDefinition(object):
self.scope = scope
self.minversion = minversion
self.maxversion = maxversion
+ self.old_names = old_names or []
def may_be_emitted(self):
"""return True if message may be emitted using the current interpreter"""
@@ -189,10 +190,15 @@ class MessagesHandlerMixIn(object):
"""
def __init__(self):
- # dictionary of registered messages
+ # Primary registry for all active messages (i.e. all messages
+ # that can be emitted by pylint for the underlying Python
+ # version). It contains the 1:1 mapping from symbolic names
+ # to message definition objects.
self._messages = {}
- # dictionary from string symbolic id to Message object.
- self._messages_by_symbol = {}
+ # Maps alternative names (numeric IDs, deprecated names) to
+ # message definitions. May contain several names for each definition
+ # object.
+ self._alternative_names = {}
self._msgs_state = {}
self._module_msgs_state = {} # None
self._raw_module_msgs_state = {}
@@ -201,6 +207,16 @@ class MessagesHandlerMixIn(object):
self._ignored_msgs = {}
self._suppression_mapping = {}
+ def add_renamed_message(self, old_id, old_symbol, new_symbol):
+ """Register the old ID and symbol for a warning that was renamed.
+
+ This allows users to keep using the old ID/symbol in suppressions.
+ """
+ msg = self.check_message_id(new_symbol)
+ msg.old_names.append((old_id, old_symbol))
+ self._alternative_names[old_id] = msg
+ self._alternative_names[old_symbol] = msg
+
def register_messages(self, checker):
"""register a dictionary of messages
@@ -213,10 +229,10 @@ class MessagesHandlerMixIn(object):
chkid = None
for msgid, msg_tuple in checker.msgs.iteritems():
msg = build_message_def(checker, msgid, msg_tuple)
- assert msg.symbol not in self._messages_by_symbol, \
+ assert msg.symbol not in self._messages, \
'Message symbol %r is already defined' % msg.symbol
# avoid duplicate / malformed ids
- assert msg.msgid not in self._messages, \
+ assert msg.msgid not in self._alternative_names, \
'Message id %r is already defined' % msgid
assert chkid is None or chkid == msg.msgid[1:3], \
'Inconsistent checker part in message id %r' % msgid
@@ -224,8 +240,11 @@ class MessagesHandlerMixIn(object):
if not msg.may_be_emitted():
self._msgs_state[msg.msgid] = False
continue
- self._messages[msg.msgid] = msg
- self._messages_by_symbol[msg.symbol] = msg
+ self._messages[msg.symbol] = msg
+ self._alternative_names[msg.msgid] = msg
+ for old_id, old_symbol in msg.old_names:
+ self._alternative_names[old_id] = msg
+ self._alternative_names[old_symbol] = msg
self._msgs_by_category.setdefault(msg.msgid[0], []).append(msg.msgid)
def disable(self, msgid, scope='package', line=None):
@@ -246,7 +265,7 @@ class MessagesHandlerMixIn(object):
if msgid.lower() in self._checkers:
for checker in self._checkers[msgid.lower()]:
for _msgid in checker.msgs:
- if _msgid in self._messages:
+ if _msgid in self._alternative_names:
self.disable(_msgid, scope, line)
return
# msgid is report id?
@@ -312,13 +331,14 @@ class MessagesHandlerMixIn(object):
Raises UnknownMessage if the message id is not defined.
"""
- if msgid in self._messages_by_symbol:
- return self._messages_by_symbol[msgid]
- msgid = msgid.upper()
- try:
- return self._messages[msgid]
- except KeyError:
- raise UnknownMessage('No such message id %s' % msgid)
+ if msgid[1:].isdigit():
+ msgid = msgid.upper()
+ for source in (self._alternative_names, self._messages):
+ try:
+ return source[msgid]
+ except KeyError:
+ pass
+ raise UnknownMessage('No such message id %s' % msgid)
def get_msg_display_string(self, msgid):
"""Generates a user-consumable representation of a message.
@@ -335,14 +355,19 @@ class MessagesHandlerMixIn(object):
except (KeyError, TypeError):
return MSG_STATE_SCOPE_CONFIG
- def is_message_enabled(self, msgid, line=None):
+ def is_message_enabled(self, msg_descr, line=None):
"""return true if the message associated to the given message id is
enabled
msgid may be either a numeric or symbolic message id.
"""
- if msgid in self._messages_by_symbol:
- msgid = self._messages_by_symbol[msgid].msgid
+ try:
+ msgid = self.check_message_id(msg_descr).msgid
+ except UnknownMessage:
+ # The linter checks for messages that are not registered
+ # due to version mismatch, just treat them as message IDs
+ # for now.
+ msgid = msg_descr
if line is None:
return self._msgs_state.get(msgid, True)
try:
@@ -474,7 +499,7 @@ class MessagesHandlerMixIn(object):
print title
print '~' * len(title)
for msgid, msg in sorted(msgs.iteritems(),
- key=lambda (k,v): (_MSG_ORDER.index(k[0]), k)):
+ key=lambda (k, v): (_MSG_ORDER.index(k[0]), k)):
msg = build_message_def(checker, msgid, msg)
print msg.format_help(checkerref=False)
print
@@ -487,14 +512,16 @@ class MessagesHandlerMixIn(object):
print
print
+ @property
+ def messages(self):
+ """The list of all active messages."""
+ return self._messages.itervalues()
+
def list_messages(self):
"""output full messages list documentation in ReST format"""
- msgids = []
- for msgid in self._messages:
- msgids.append(msgid)
- msgids.sort()
- for msgid in msgids:
- print self.check_message_id(msgid).format_help(checkerref=False)
+ msgs = sorted(self._messages.itervalues(), key=lambda msg: msg.msgid)
+ for msg in msgs:
+ print msg.format_help(checkerref=False)
print
@@ -515,7 +542,7 @@ class ReportsHandlerMixIn(object):
checker is the checker defining the report
"""
reportid = reportid.upper()
- self._reports.setdefault(checker, []).append( (reportid, r_title, r_cb) )
+ self._reports.setdefault(checker, []).append((reportid, r_title, r_cb))
def enable_report(self, reportid):
"""disable the report of the given id"""
@@ -585,24 +612,24 @@ def expand_modules(files_or_modules, black_list):
try:
filepath = file_from_modpath(modname.split('.'))
if filepath is None:
- errors.append( {'key' : 'F0003', 'mod': modname} )
+ errors.append({'key' : 'F0003', 'mod': modname})
continue
except (ImportError, SyntaxError), ex:
# FIXME p3k : the SyntaxError is a Python bug and should be
# removed as soon as possible http://bugs.python.org/issue10588
- errors.append( {'key': 'F0001', 'mod': modname, 'ex': ex} )
+ errors.append({'key': 'F0001', 'mod': modname, 'ex': ex})
continue
filepath = normpath(filepath)
- result.append( {'path': filepath, 'name': modname,
- 'basepath': filepath, 'basename': modname} )
+ result.append({'path': filepath, 'name': modname,
+ 'basepath': filepath, 'basename': modname})
if not (modname.endswith('.__init__') or modname == '__init__') \
and '__init__.py' in filepath:
for subfilepath in get_module_files(dirname(filepath), black_list):
if filepath == subfilepath:
continue
submodname = '.'.join(modpath_from_file(subfilepath))
- result.append( {'path': subfilepath, 'name': submodname,
- 'basepath': filepath, 'basename': modname} )
+ result.append({'path': subfilepath, 'name': submodname,
+ 'basepath': filepath, 'basename': modname})
return result, errors