summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.hgignore1
-rw-r--r--CONTRIBUTORS.txt11
-rw-r--r--ChangeLog52
-rw-r--r--__init__.py2
-rw-r--r--__pkginfo__.py13
-rw-r--r--checkers/__init__.py3
-rw-r--r--checkers/base.py182
-rw-r--r--checkers/classes.py62
-rw-r--r--checkers/design_analysis.py2
-rw-r--r--checkers/exceptions.py39
-rw-r--r--checkers/format.py4
-rw-r--r--checkers/imports.py16
-rw-r--r--checkers/logging.py40
-rw-r--r--checkers/misc.py2
-rw-r--r--checkers/newstyle.py17
-rw-r--r--checkers/raw_metrics.py2
-rw-r--r--checkers/similar.py2
-rw-r--r--checkers/stdlib.py2
-rw-r--r--checkers/strings.py28
-rw-r--r--checkers/typecheck.py119
-rw-r--r--checkers/utils.py4
-rw-r--r--checkers/variables.py4
-rw-r--r--config.py4
-rw-r--r--doc/faq.rst27
-rw-r--r--doc/index.rst8
-rw-r--r--doc/options.rst139
-rw-r--r--doc/plugins.rst122
-rw-r--r--doc/run.rst9
-rw-r--r--elisp/pylint.el4
-rwxr-xr-xepylint.py25
-rw-r--r--gui.py2
-rw-r--r--interfaces.py2
-rw-r--r--lint.py42
-rw-r--r--man/pylint.1109
-rw-r--r--pyreverse/diadefslib.py2
-rw-r--r--pyreverse/diagrams.py6
-rw-r--r--pyreverse/main.py2
-rw-r--r--pyreverse/utils.py2
-rw-r--r--pyreverse/writer.py2
-rw-r--r--reporters/__init__.py6
-rw-r--r--reporters/html.py2
-rw-r--r--reporters/text.py2
-rw-r--r--setup.py13
-rw-r--r--test/data/classes_No_Name.dot4
-rw-r--r--test/input/func_bad_slots.py57
-rw-r--r--test/input/func_class_access_protected_members.py2
-rw-r--r--test/input/func_ctor_arguments.py63
-rw-r--r--test/input/func_docstring.py22
-rw-r--r--test/input/func_eval_used.py13
-rw-r--r--test/input/func_f0401.py2
-rw-r--r--test/input/func_name_checking.py2
-rw-r--r--test/input/func_newstyle_super.py9
-rw-r--r--test/input/func_noerror_abstract_method.py20
-rw-r--r--test/input/func_noerror_abstract_method_py30.py19
-rw-r--r--test/input/func_return_yield_mix_py_33.py (renamed from test/input/func_return_yield_mix.py)0
-rw-r--r--test/input/func_unpack_exception_py_30.py4
-rw-r--r--test/input/func_w0109.py7
-rw-r--r--test/input/func_w0401_package/__init__.py2
-rw-r--r--test/input/func_w0401_package/all_the_things.py8
-rw-r--r--test/input/func_w0401_package/thing1.py3
-rw-r--r--test/input/func_w0401_package/thing2.py7
-rw-r--r--test/input/func_w0402.py3
-rw-r--r--test/input/func_w0613.py2
-rw-r--r--test/input/func_w0623_py30.py2
-rw-r--r--test/messages/func_arguments.txt18
-rw-r--r--test/messages/func_bad_slots.txt4
-rw-r--r--test/messages/func_ctor_arguments.txt17
-rw-r--r--test/messages/func_docstring.txt10
-rw-r--r--test/messages/func_e0205.txt2
-rw-r--r--test/messages/func_eval_used.txt4
-rw-r--r--test/messages/func_kwoa_py30.txt4
-rw-r--r--test/messages/func_newstyle_super.txt13
-rw-r--r--test/messages/func_newstyle_super_py30.txt7
-rw-r--r--test/messages/func_return_yield_mix_py_33.txt (renamed from test/messages/func_return_yield_mix.txt)0
-rw-r--r--test/messages/func_unpacking_non_sequence.txt1
-rw-r--r--test/messages/func_w0109.txt1
-rw-r--r--test/messages/func_w0401_package.txt1
-rw-r--r--test/messages/func_w0402.txt3
-rw-r--r--test/smoketest.py13
-rw-r--r--test/test_base.py118
-rw-r--r--test/test_format.py6
-rw-r--r--test/test_func.py34
-rw-r--r--test/test_logging.py49
-rw-r--r--test/test_misc.py6
-rw-r--r--test/test_regr.py2
-rw-r--r--test/test_utils.py2
-rw-r--r--test/unittest_checkers_utils.py2
-rw-r--r--test/unittest_lint.py61
-rw-r--r--test/unittest_pyreverse_diadefs.py2
-rw-r--r--test/unittest_pyreverse_writer.py2
-rw-r--r--test/unittest_reporting.py2
-rw-r--r--testutils.py68
-rw-r--r--tox.ini4
-rw-r--r--utils.py34
94 files changed, 1415 insertions, 463 deletions
diff --git a/.hgignore b/.hgignore
index e5660d0..f03a730 100644
--- a/.hgignore
+++ b/.hgignore
@@ -9,3 +9,4 @@
^dist/
^pylint.egg-info/
.tox
+(^|/)\..*\.sw[a-z]$
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 8e5d5b4..8b28ae3 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -5,8 +5,12 @@ Order doesn't matter (not that much, at least ;)
* Sylvain Thenault (Logilab): main author / maintainer
-* Torsten Marek (Google): maintainer, main GPyLint developper and (GooglePylint)
- upstream integrator
+* Torsten Marek (Google): maintainer, contributor
+
+* Claudiu Popa: maintainer, contributor
+
+* Daniel Balparda (Google): GPyLint maintainer (Google's pylint variant),
+ various patches
* Martin Pool (Google): warnings for anomalous backslashes, symbolic names for
messages (like 'unused'), etc
@@ -17,7 +21,8 @@ Order doesn't matter (not that much, at least ;)
* Sandro Tosi: Debian packaging
-* Claudiu Popa, Mads Kiilerich, Boris Feld: various patches
+* Mads Kiilerich, Boris Feld, Bill Wendling, Sebastian Ulrich:
+ various patches
* Brian van den Broek: windows installation documentation
diff --git a/ChangeLog b/ChangeLog
index d79d105..f9082ac 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,8 +2,37 @@ ChangeLog for Pylint
====================
--
- * Add ablity to accept --msg-template strings when using HTML output
- addresses issue #135.
+
+ * Do not crash with UnknownMessage if an unknown message ID/name appears
+ in disable or enable in the configuration. Patch by Cole Robinson.
+ Fixes bitbucket issue #170.
+
+ * Add new warning 'eval-used', checking that the builtin function `eval`
+ was used.
+
+ * Make it possible to show a naming hint for invalid name by setting
+ include-naming-hint. Also make the naming hints configurable. Fixes
+ BitBucket issue #138.
+
+ * Added support for enforcing multiple, but consistent name styles for
+ different name types inside a single module; based on a patch written
+ by morbo@google.com.
+
+ * Also warn about empty docstrings on overridden methods; contributed
+ by sebastianu@google.com.
+
+ * Also inspect arguments to constructor calls, and emit relevant
+ warnings; contributed by sebastianu@google.com.
+
+ * Added a new configuration option logging-modules to make the list
+ of module names that can be checked for 'logging-not-lazy' et. al.
+ configurable; contributed by morbo@google.com.
+
+ * ensure init-hooks is evaluated before other options, notably load-plugins
+ (#166)
+
+ * Python 2.5 support restored: fixed small issues preventing pylint to run
+ on python 2.5. Bitbucket issues #50 and #62.
* bitbucket #128: pylint doesn't crash when looking
for used-before-assignment in context manager
@@ -28,7 +57,24 @@ ChangeLog for Pylint
* Add a new warning 'abstract-class-instantiated' for checking
that abstract classes created with `abc` module and
- with abstract methods are instantied.
+ with abstract methods are instantied.
+
+ * Do not warn about 'return-arg-in-generator' in Python 3.3+.
+
+ * Do not warn about 'abstract-method' when the abstract method
+ is implemented through assignment (#155).
+
+ * Improve cyclic import detection in the case of packages, patch by Buck
+ Golemon
+
+ * Add new warnings for checking proper class __slots__:
+ 'invalid-slots-object' and 'invalid-slots'.
+
+ * Search for rc file in `~/.config/pylintrc` if `~/.pylintrc`
+ doesn't exists (#121)
+
+ * Don't register the newstyle checker w/ python >= 3
+
2013-12-22 -- 1.1.0
* Add new check for use of deprecated pragma directives "pylint:disable-msg"
diff --git a/__init__.py b/__init__.py
index dfb4386..eed1b62 100644
--- a/__init__.py
+++ b/__init__.py
@@ -12,7 +12,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import sys
def run_pylint():
diff --git a/__pkginfo__.py b/__pkginfo__.py
index 8d4d65e..0aa48a9 100644
--- a/__pkginfo__.py
+++ b/__pkginfo__.py
@@ -1,5 +1,5 @@
# pylint: disable=W0622,C0103
-# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE).
+# Copyright (c) 2003-2014 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
@@ -13,20 +13,25 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""pylint packaging information"""
+import sys
modname = distname = 'pylint'
numversion = (1, 1, 0)
version = '.'.join([str(num) for num in numversion])
-install_requires = ['logilab-common >= 0.53.0', 'astroid >= 1.0.1']
+if sys.version_info < (2, 6):
+ install_requires = ['logilab-common >= 0.53.0', 'astroid >= 1.0.1',
+ 'StringFormat']
+else:
+ install_requires = ['logilab-common >= 0.53.0', 'astroid >= 1.0.1']
license = 'GPL'
description = "python code static checker"
web = 'http://www.pylint.org'
-mailinglist = "mailto://python-projects@lists.logilab.org"
+mailinglist = "mailto://code-quality@python.org"
author = 'Logilab'
author_email = 'python-projects@lists.logilab.org'
diff --git a/checkers/__init__.py b/checkers/__init__.py
index 1d0aa42..9346904 100644
--- a/checkers/__init__.py
+++ b/checkers/__init__.py
@@ -12,7 +12,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""utilities methods and classes for checkers
Base id of standard checkers (used in msg and report ids):
@@ -41,7 +41,6 @@ messages nor reports. XXX not true, emit a 07 report !
import sys
import tokenize
import warnings
-from os.path import dirname
from astroid.utils import ASTWalker
from logilab.common.configuration import OptionsProviderMixIn
diff --git a/checkers/base.py b/checkers/base.py
index 6795457..497aa40 100644
--- a/checkers/base.py
+++ b/checkers/base.py
@@ -13,7 +13,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""basic checker for Python code"""
import sys
@@ -52,11 +52,15 @@ NO_REQUIRED_DOC_RGX = re.compile('__.*__')
REVERSED_METHODS = (('__getitem__', '__len__'),
('__reversed__', ))
+PY33 = sys.version_info >= (3, 3)
BAD_FUNCTIONS = ['map', 'filter', 'apply']
if sys.version_info < (3, 0):
BAD_FUNCTIONS.append('input')
BAD_FUNCTIONS.append('file')
+# Name categories that are always consistent with all naming conventions.
+EXEMPT_NAME_CATEGORIES = {'exempt', 'ignore'}
+
del re
def in_loop(node):
@@ -241,7 +245,8 @@ class BasicErrorChecker(_BasicChecker):
'return-arg-in-generator',
'Used when a "return" statement with an argument is found '
'outside in a generator function or method (e.g. with some '
- '"yield" statements).'),
+ '"yield" statements).',
+ {'maxversion': (3, 3)}),
'E0107': ("Use of the non-existent %s operator",
'nonexistent-operator',
"Used when you attempt to use the C-style pre-increment or"
@@ -292,11 +297,12 @@ class BasicErrorChecker(_BasicChecker):
self.add_message('return-in-init', node=node)
elif node.is_generator():
# make sure we don't mix non-None returns and yields
- for retnode in returns:
- if isinstance(retnode.value, astroid.Const) and \
- retnode.value.value is not None:
- self.add_message('return-arg-in-generator', node=node,
- line=retnode.fromlineno)
+ if not PY33:
+ for retnode in returns:
+ if isinstance(retnode.value, astroid.Const) and \
+ retnode.value.value is not None:
+ self.add_message('return-arg-in-generator', node=node,
+ line=retnode.fromlineno)
# Check for duplicate names
args = set()
for name in node.argnames():
@@ -447,6 +453,12 @@ functions, methods
'exec-used',
'Used when you use the "exec" statement (function for Python 3), to discourage its \
usage. That doesn\'t mean you can not use it !'),
+ 'W0123': ('Use of eval',
+ 'eval-used',
+ 'Used when you use the "eval" function, to discourage its '
+ 'usage. Consider using `ast.literal_eval` for safely evaluating '
+ 'strings containing Python expressions '
+ 'from untrusted sources. '),
'W0141': ('Used builtin function %r',
'bad-builtin',
'Used when a black listed builtin function is used (see the '
@@ -677,7 +689,7 @@ functions, methods
"""just print a warning on exec statements"""
self.add_message('exec-used', node=node)
- @check_messages('bad-builtin', 'star-args',
+ @check_messages('bad-builtin', 'star-args', 'eval-used',
'exec-used', 'missing-reversed-argument',
'bad-reversed-sequence')
def visit_callfunc(self, node):
@@ -694,6 +706,8 @@ functions, methods
self.add_message('exec-used', node=node)
elif name == 'reversed':
self._check_reversed(node)
+ elif name == 'eval':
+ self.add_message('eval-used', node=node)
if name in self.config.bad_functions:
self.add_message('bad-builtin', node=node, args=name)
if node.starargs or node.kwargs:
@@ -808,79 +822,47 @@ functions, methods
# everything else is not a proper sequence for reversed()
self.add_message('bad-reversed-sequence', node=node)
+_NAME_TYPES = {
+ 'module': (MOD_NAME_RGX, 'module'),
+ 'const': (CONST_NAME_RGX, 'constant'),
+ 'class': (CLASS_NAME_RGX, 'class'),
+ 'function': (DEFAULT_NAME_RGX, 'function'),
+ 'method': (DEFAULT_NAME_RGX, 'method'),
+ 'attr': (DEFAULT_NAME_RGX, 'attribute'),
+ 'argument': (DEFAULT_NAME_RGX, 'argument'),
+ 'variable': (DEFAULT_NAME_RGX, 'variable'),
+ 'class_attribute': (CLASS_ATTRIBUTE_RGX, 'class attribute'),
+ 'inlinevar': (COMP_VAR_RGX, 'inline iteration'),
+}
+
+def _create_naming_options():
+ name_options = []
+ for name_type, (rgx, human_readable_name) in _NAME_TYPES.iteritems():
+ name_type = name_type.replace('_', '-')
+ name_options.append((
+ '%s-rgx' % (name_type,),
+ {'default': rgx, 'type': 'regexp', 'metavar': '<regexp>',
+ 'help': 'Regular expression matching correct %s names' % (human_readable_name,)}))
+ name_options.append((
+ '%s-name-hint' % (name_type,),
+ {'default': rgx.pattern, 'type': 'string', 'metavar': '<string>',
+ 'help': 'Naming hint for %s names' % (human_readable_name,)}))
+
+ return tuple(name_options)
+
class NameChecker(_BasicChecker):
msgs = {
'C0102': ('Black listed name "%s"',
'blacklisted-name',
'Used when the name is listed in the black list (unauthorized \
names).'),
- 'C0103': ('Invalid %s name "%s"',
+ 'C0103': ('Invalid %s name "%s"%s',
'invalid-name',
'Used when the name doesn\'t match the regular expression \
associated to its type (constant, variable, class...).'),
}
- options = (('module-rgx',
- {'default' : MOD_NAME_RGX,
- 'type' :'regexp', 'metavar' : '<regexp>',
- 'help' : 'Regular expression which should only match correct '
- 'module names'}
- ),
- ('const-rgx',
- {'default' : CONST_NAME_RGX,
- 'type' :'regexp', 'metavar' : '<regexp>',
- 'help' : 'Regular expression which should only match correct '
- 'module level names'}
- ),
- ('class-rgx',
- {'default' : CLASS_NAME_RGX,
- 'type' :'regexp', 'metavar' : '<regexp>',
- 'help' : 'Regular expression which should only match correct '
- 'class names'}
- ),
- ('function-rgx',
- {'default' : DEFAULT_NAME_RGX,
- 'type' :'regexp', 'metavar' : '<regexp>',
- 'help' : 'Regular expression which should only match correct '
- 'function names'}
- ),
- ('method-rgx',
- {'default' : DEFAULT_NAME_RGX,
- 'type' :'regexp', 'metavar' : '<regexp>',
- 'help' : 'Regular expression which should only match correct '
- 'method names'}
- ),
- ('attr-rgx',
- {'default' : DEFAULT_NAME_RGX,
- 'type' :'regexp', 'metavar' : '<regexp>',
- 'help' : 'Regular expression which should only match correct '
- 'instance attribute names'}
- ),
- ('argument-rgx',
- {'default' : DEFAULT_NAME_RGX,
- 'type' :'regexp', 'metavar' : '<regexp>',
- 'help' : 'Regular expression which should only match correct '
- 'argument names'}),
- ('variable-rgx',
- {'default' : DEFAULT_NAME_RGX,
- 'type' :'regexp', 'metavar' : '<regexp>',
- 'help' : 'Regular expression which should only match correct '
- 'variable names'}
- ),
- ('class-attribute-rgx',
- {'default' : CLASS_ATTRIBUTE_RGX,
- 'type' :'regexp', 'metavar' : '<regexp>',
- 'help' : 'Regular expression which should only match correct '
- 'attribute names in class bodies'}
- ),
- ('inlinevar-rgx',
- {'default' : COMP_VAR_RGX,
- 'type' :'regexp', 'metavar' : '<regexp>',
- 'help' : 'Regular expression which should only match correct '
- 'list comprehension / generator expression variable \
- names'}
- ),
- # XXX use set
+ options = (# XXX use set
('good-names',
{'default' : ('i', 'j', 'k', 'ex', 'Run', '_'),
'type' :'csv', 'metavar' : '<names>',
@@ -893,7 +875,24 @@ class NameChecker(_BasicChecker):
'help' : 'Bad variable names which should always be refused, '
'separated by a comma'}
),
- )
+ ('name-group',
+ {'default' : (),
+ 'type' :'csv', 'metavar' : '<name1:name2>',
+ 'help' : ('Colon-delimited sets of names that determine each'
+ ' other\'s naming style when the name regexes'
+ ' allow several styles.')}
+ ),
+ ('include-naming-hint',
+ {'default': False, 'type' : 'yn', 'metavar' : '<y_or_n>',
+ 'help': 'Include a hint for the correct naming format with invalid-name'}
+ ),
+ ) + _create_naming_options()
+
+
+ def __init__(self, linter):
+ _BasicChecker.__init__(self, linter)
+ self._name_category = {}
+ self._name_group = {}
def open(self):
self.stats = self.linter.add_stats(badname_module=0,
@@ -904,6 +903,9 @@ class NameChecker(_BasicChecker):
badname_inlinevar=0,
badname_argument=0,
badname_class_attribute=0)
+ for group in self.config.name_group:
+ for name_type in group.split(':'):
+ self._name_group[name_type] = 'group_%s' % (group,)
@check_messages('blacklisted-name', 'invalid-name')
def visit_module(self, node):
@@ -965,6 +967,14 @@ class NameChecker(_BasicChecker):
else:
self._recursive_check_names(arg.elts, node)
+ def _find_name_group(self, node_type):
+ return self._name_group.get(node_type, node_type)
+
+ def _is_multi_naming_match(self, match):
+ return (match is not None and
+ match.lastgroup is not None and
+ match.lastgroup not in EXEMPT_NAME_CATEGORIES)
+
def _check_name(self, node_type, name, node):
"""check for a name using the type's regexp"""
if is_inside_except(node):
@@ -978,13 +988,21 @@ class NameChecker(_BasicChecker):
self.add_message('blacklisted-name', node=node, args=name)
return
regexp = getattr(self.config, node_type + '_rgx')
- if regexp.match(name) is None:
- type_label = {'inlinedvar': 'inlined variable',
- 'const': 'constant',
- 'attr': 'attribute',
- 'class_attribute': 'class attribute'
- }.get(node_type, node_type)
- self.add_message('invalid-name', node=node, args=(type_label, name))
+ match = regexp.match(name)
+
+ if self._is_multi_naming_match(match):
+ name_group = self._find_name_group(node_type)
+ if name_group not in self._name_category:
+ self._name_category[name_group] = match.lastgroup
+ elif self._name_category[name_group] != match.lastgroup:
+ match = None
+
+ if match is None:
+ type_label = _NAME_TYPES[node_type][1]
+ hint = ''
+ if self.config.include_naming_hint:
+ hint = ' (hint: %s)' % (getattr(self.config, node_type + '_name_hint'))
+ self.add_message('invalid-name', node=node, args=(type_label, name, hint))
self.stats['badname_' + node_type] += 1
@@ -1041,15 +1059,17 @@ class DocStringChecker(_BasicChecker):
isinstance(ancestor[node.name], astroid.Function):
overridden = True
break
- if not overridden:
- self._check_docstring(ftype, node)
+ self._check_docstring(ftype, node,
+ report_missing=not overridden)
else:
self._check_docstring(ftype, node)
- def _check_docstring(self, node_type, node):
+ def _check_docstring(self, node_type, node, report_missing=True):
"""check the node has a non empty docstring"""
docstring = node.doc
if docstring is None:
+ if not report_missing:
+ return
if node.body:
lines = node.body[-1].lineno - node.body[0].lineno + 1
else:
diff --git a/checkers/classes.py b/checkers/classes.py
index fc09021..85a8507 100644
--- a/checkers/classes.py
+++ b/checkers/classes.py
@@ -12,7 +12,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""classes checker for Python code
"""
from __future__ import generators
@@ -30,6 +30,7 @@ if sys.version_info >= (3, 0):
NEXT_METHOD = '__next__'
else:
NEXT_METHOD = 'next'
+ITER_METHODS = ('__iter__', '__getitem__')
def class_is_abstract(node):
"""return true if the given class node should be considered as an abstract
@@ -49,7 +50,7 @@ MSGS = {
compatibility for an unexpected reason. Please report this kind \
if you don\'t make sense of it.'),
- 'E0202': ('An attribute affected in %s line %s hide this method',
+ 'E0202': ('An attribute defined in %s line %s hides this method',
'method-hidden',
'Used when a class defines a method which is hidden by an '
'instance attribute from an ancestor class or set by some '
@@ -156,7 +157,15 @@ MSGS = {
'bad-context-manager',
'Used when the __exit__ special method, belonging to a \
context manager, does not accept 3 arguments \
- (type, value, traceback).')
+ (type, value, traceback).'),
+ 'E0236': ('Invalid object %r in __slots__, must contain '
+ 'only non empty strings',
+ 'invalid-slots-object',
+ 'Used when an invalid (non-string) object occurs in __slots__.'),
+ 'E0238': ('Invalid __slots__ object',
+ 'invalid-slots',
+ 'Used when an invalid __slots__ is found in class. '
+ 'Only a string, an iterable or a sequence is permitted.')
}
@@ -240,6 +249,7 @@ a metaclass class method.'}
node.local_attr('__init__')
except astroid.NotFoundError:
self.add_message('W0232', args=node, node=node)
+ self._check_slots(node)
@check_messages('E0203', 'W0201')
def leave_class(self, cnode):
@@ -333,6 +343,49 @@ a metaclass class method.'}
elif node.name == '__exit__':
self._check_exit(node)
+ def _check_slots(self, node):
+ if '__slots__' not in node.locals:
+ return
+ for slots in node.igetattr('__slots__'):
+ # check if __slots__ is a valid type
+ for meth in ITER_METHODS:
+ try:
+ slots.getattr(meth)
+ break
+ except astroid.NotFoundError:
+ continue
+ else:
+ self.add_message('invalid-slots', node=node)
+ continue
+
+ if isinstance(slots, astroid.Const):
+ # a string, ignore the following checks
+ continue
+ if not hasattr(slots, 'itered'):
+ # we can't obtain the values, maybe a .deque?
+ continue
+
+ if isinstance(slots, astroid.Dict):
+ values = [item[0] for item in slots.items]
+ else:
+ values = slots.itered()
+ if values is YES:
+ return
+
+ for elt in values:
+ if elt is YES:
+ continue
+ if (not isinstance(elt, astroid.Const) or
+ not isinstance(elt.value, str)):
+ self.add_message('invalid-slots-object',
+ args=elt.as_string(),
+ node=elt)
+ continue
+ if not elt.value:
+ self.add_message('invalid-slots-object',
+ args=elt.as_string(),
+ node=elt)
+
def _check_iter(self, node):
try:
infered = node.infer_call_result(node)
@@ -568,6 +621,9 @@ a metaclass class method.'}
continue
# owner is not this class, it must be a parent class
# check that the ancestor's method is not abstract
+ if method.name in node.locals:
+ # it is redefined as an attribute or with a descriptor
+ continue
if method.is_abstract(pass_is_abstract=False):
self.add_message('W0223', node=node,
args=(method.name, owner.name))
diff --git a/checkers/design_analysis.py b/checkers/design_analysis.py
index 11defbf..cfd2d80 100644
--- a/checkers/design_analysis.py
+++ b/checkers/design_analysis.py
@@ -12,7 +12,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""check for signs of poor design"""
from astroid import Function, If, InferenceError
diff --git a/checkers/exceptions.py b/checkers/exceptions.py
index 367aee2..30b3fa8 100644
--- a/checkers/exceptions.py
+++ b/checkers/exceptions.py
@@ -11,7 +11,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""exceptions handling (raising, catching, exceptions classes) checker
"""
import sys
@@ -134,8 +134,8 @@ class ExceptionsChecker(BaseChecker):
),
)
- @check_messages('W0701', 'W0710', 'E0702', 'E0710', 'E0711',
- 'bad-exception-context')
+ @check_messages('raising-string', 'nonstandard-exception', 'raising-bad-type',
+ 'raising-non-exception', 'notimplemented-raised', 'bad-exception-context')
def visit_raise(self, node):
"""visit raise possibly inferring value"""
# ignore empty raise
@@ -172,22 +172,22 @@ class ExceptionsChecker(BaseChecker):
if isinstance(expr, astroid.Const):
value = expr.value
if isinstance(value, str):
- self.add_message('W0701', node=node)
+ self.add_message('raising-string', node=node)
else:
- self.add_message('E0702', node=node,
+ self.add_message('raising-bad-type', node=node,
args=value.__class__.__name__)
elif (isinstance(expr, astroid.Name) and \
expr.name in ('None', 'True', 'False')) or \
isinstance(expr, (astroid.List, astroid.Dict, astroid.Tuple,
astroid.Module, astroid.Function)):
- self.add_message('E0702', node=node, args=expr.name)
+ self.add_message('raising-bad-type', 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')):
- self.add_message('E0711', node=node)
+ self.add_message('notimplemented-raised', node=node)
elif isinstance(expr, astroid.BinOp) and expr.op == '%':
- self.add_message('W0701', node=node)
+ self.add_message('raising-string', node=node)
elif isinstance(expr, (Instance, astroid.Class)):
if isinstance(expr, Instance):
expr = expr._proxied
@@ -195,23 +195,24 @@ class ExceptionsChecker(BaseChecker):
not inherit_from_std_ex(expr) and
expr.root().name != BUILTINS_NAME):
if expr.newstyle:
- self.add_message('E0710', node=node)
+ self.add_message('raising-non-exception', node=node)
else:
- self.add_message('W0710', node=node)
+ self.add_message('nonstandard-exception', node=node)
else:
value_found = False
else:
value_found = False
return value_found
- @check_messages('W0712')
+ @check_messages('unpacking-in-except')
def visit_excepthandler(self, node):
"""Visit an except handler block and check for exception unpacking."""
if isinstance(node.name, (astroid.Tuple, astroid.List)):
- self.add_message('W0712', node=node)
+ self.add_message('unpacking-in-except', node=node)
- @check_messages('W0702', 'W0703', 'W0704', 'W0711', 'E0701', 'catching-non-exception')
+ @check_messages('bare-except', 'broad-except', 'pointless-except', 'binary-op-exception', 'bad-except-order',
+ 'catching-non-exception')
def visit_tryexcept(self, node):
"""check for empty except"""
exceptions_classes = []
@@ -219,18 +220,18 @@ class ExceptionsChecker(BaseChecker):
for index, handler in enumerate(node.handlers):
# single except doing nothing but "pass" without else clause
if nb_handlers == 1 and is_empty(handler.body) and not node.orelse:
- self.add_message('W0704', node=handler.type or handler.body[0])
+ self.add_message('pointless-except', node=handler.type or handler.body[0])
if handler.type is None:
if nb_handlers == 1 and not is_raising(handler.body):
- self.add_message('W0702', node=handler)
+ self.add_message('bare-except', node=handler)
# check if a "except:" is followed by some other
# except
elif index < (nb_handlers - 1):
msg = 'empty except clause should always appear last'
- self.add_message('E0701', node=node, args=msg)
+ self.add_message('bad-except-order', node=node, args=msg)
elif isinstance(handler.type, astroid.BoolOp):
- self.add_message('W0711', node=handler, args=handler.type.op)
+ self.add_message('binary-op-exception', node=handler, args=handler.type.op)
else:
try:
excs = list(unpack_infer(handler.type))
@@ -246,11 +247,11 @@ class ExceptionsChecker(BaseChecker):
if previous_exc in exc_ancestors:
msg = '%s is an ancestor class of %s' % (
previous_exc.name, exc.name)
- self.add_message('E0701', node=handler.type, args=msg)
+ self.add_message('bad-except-order', node=handler.type, args=msg)
if (exc.name in self.config.overgeneral_exceptions
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)
+ self.add_message('broad-except', args=exc.name, node=handler.type)
if (not inherit_from_std_ex(exc) and
exc.root().name != BUILTINS_NAME):
diff --git a/checkers/format.py b/checkers/format.py
index aab2320..abe38a5 100644
--- a/checkers/format.py
+++ b/checkers/format.py
@@ -11,7 +11,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""Python code format's checker.
By default try to follow Guido's style guide :
@@ -344,7 +344,7 @@ class FormatChecker(BaseTokenChecker):
return ':'
elif tokens[i][1] in '()[]{}':
return 'bracket'
- elif tokens[i][1] in ('<', '>', '<=', '>=', '!='):
+ elif tokens[i][1] in ('<', '>', '<=', '>=', '!=', '=='):
return 'comparison'
else:
if self._inside_brackets('('):
diff --git a/checkers/imports.py b/checkers/imports.py
index b0a9872..fd897a8 100644
--- a/checkers/imports.py
+++ b/checkers/imports.py
@@ -12,11 +12,11 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""imports checkers for Python code"""
from logilab.common.graph import get_cycles, DotBackend
-from logilab.common.modutils import is_standard_module
+from logilab.common.modutils import get_module_part, is_standard_module
from logilab.common.ureports import VerbatimText, Paragraph
import astroid
@@ -248,6 +248,9 @@ given file (report RP0402 must not be disabled)'}
and prev.modname == '__future__'):
self.add_message('W0410', node=node)
return
+ for name, _ in node.names:
+ if name == '*':
+ self.add_message('W0401', args=basename, node=node)
modnode = node.root()
importedmodnode = self.get_imported_module(modnode, node, basename)
if importedmodnode is None:
@@ -255,11 +258,9 @@ given file (report RP0402 must not be disabled)'}
self._check_relative_import(modnode, node, importedmodnode, basename)
self._check_deprecated_module(node, basename)
for name, _ in node.names:
- if name == '*':
- self.add_message('W0401', args=basename, node=node)
- continue
- self._add_imported_module(node, '%s.%s' % (importedmodnode.name, name))
- self._check_reimport(node, name, basename, node.level)
+ if name != '*':
+ self._add_imported_module(node, '%s.%s' % (importedmodnode.name, name))
+ self._check_reimport(node, name, basename, node.level)
def get_imported_module(self, modnode, importnode, modname):
try:
@@ -291,6 +292,7 @@ given file (report RP0402 must not be disabled)'}
def _add_imported_module(self, node, importedmodname):
"""notify an imported module, used to analyze dependencies"""
+ importedmodname = get_module_part(importedmodname)
context_name = node.root().name
if context_name == importedmodname:
# module importing itself !
diff --git a/checkers/logging.py b/checkers/logging.py
index d1f9d36..cc8967d 100644
--- a/checkers/logging.py
+++ b/checkers/logging.py
@@ -10,7 +10,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""checker for use of Python logging
"""
@@ -61,21 +61,45 @@ class LoggingChecker(checkers.BaseChecker):
name = 'logging'
msgs = MSGS
+ options = (('logging-modules',
+ {'default' : ('logging',),
+ 'type' : 'csv',
+ 'metavar' : '<comma separated list>',
+ 'help' : ('Logging modules to check that the string format '
+ 'arguments are in logging function parameter format')}
+ ),
+ )
+
def visit_module(self, unused_node):
"""Clears any state left in this checker from last module checked."""
# The code being checked can just as easily "import logging as foo",
# so it is necessary to process the imports and store in this field
# what name the logging module is actually given.
- self._logging_name = None
+ self._logging_names = set()
+ logging_mods = self.config.logging_modules
+
+ self._logging_modules = set(logging_mods)
+ self._from_imports = {}
+ for logging_mod in logging_mods:
+ parts = logging_mod.rsplit('.', 1)
+ if len(parts) > 1:
+ self._from_imports[parts[0]] = parts[1]
+
+ def visit_from(self, node):
+ """Checks to see if a module uses a non-Python logging module."""
+ try:
+ logging_name = self._from_imports[node.modname]
+ for module, as_name in node.names:
+ if module == logging_name:
+ self._logging_names.add(as_name or module)
+ except KeyError:
+ pass
def visit_import(self, node):
"""Checks to see if this module uses Python's built-in logging."""
for module, as_name in node.names:
- if module == 'logging':
- if as_name:
- self._logging_name = as_name
- else:
- self._logging_name = 'logging'
+ if module in self._logging_modules:
+ self._logging_names.add(as_name or module)
@check_messages(*(MSGS.keys()))
def visit_callfunc(self, node):
@@ -91,7 +115,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 not in self._logging_names and not logger_class:
return
self._check_convenience_methods(node)
self._check_log_methods(node)
diff --git a/checkers/misc.py b/checkers/misc.py
index 6995909..9c49825 100644
--- a/checkers/misc.py
+++ b/checkers/misc.py
@@ -10,7 +10,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
""" Copyright (c) 2000-2010 LOGILAB S.A. (Paris, FRANCE).
http://www.logilab.fr/ -- mailto:contact@logilab.fr
diff --git a/checkers/newstyle.py b/checkers/newstyle.py
index ff9bbc2..e583126 100644
--- a/checkers/newstyle.py
+++ b/checkers/newstyle.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2005-2013 LOGILAB S.A. (Paris, FRANCE).
+# Copyright (c) 2005-2014 LOGILAB S.A. (Paris, FRANCE).
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This program is free software; you can redistribute it and/or modify it under
@@ -12,7 +12,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""check for new / old style related problems
"""
import sys
@@ -37,8 +37,7 @@ MSGS = {
'E1004': ('Missing argument to super()',
'missing-super-argument',
'Used when the super builtin didn\'t receive an \
- argument on Python 2',
- {'maxversion': (3, 0)}),
+ argument on Python 2'),
'W1001': ('Use of "property" on an old style class',
'property-on-old-class',
'Used when PyLint detect the use of the builtin "property" \
@@ -122,15 +121,17 @@ class NewStyleConflictChecker(BaseChecker):
except astroid.InferenceError:
continue
- if supcls is None and sys.version_info[0] == 2:
+ if supcls is None:
self.add_message('missing-super-argument', node=call)
continue
if klass is not supcls:
- supcls = getattr(supcls, 'name', supcls)
- self.add_message('E1003', node=call, args=(supcls, ))
+ self.add_message('E1003', node=call,
+ args=(call.args[0].name, ))
def register(linter):
"""required method to auto register this checker """
- linter.register_checker(NewStyleConflictChecker(linter))
+ import sys
+ if sys.version_info < (3, 0):
+ linter.register_checker(NewStyleConflictChecker(linter))
diff --git a/checkers/raw_metrics.py b/checkers/raw_metrics.py
index 23e45b0..71fecf6 100644
--- a/checkers/raw_metrics.py
+++ b/checkers/raw_metrics.py
@@ -12,7 +12,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
""" Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE).
http://www.logilab.fr/ -- mailto:contact@logilab.fr
diff --git a/checkers/similar.py b/checkers/similar.py
index 8d755fa..cf671bf 100644
--- a/checkers/similar.py
+++ b/checkers/similar.py
@@ -13,7 +13,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""a similarities / code duplication command line tool and pylint checker
"""
import sys
diff --git a/checkers/stdlib.py b/checkers/stdlib.py
index b63760c..8cb78f4 100644
--- a/checkers/stdlib.py
+++ b/checkers/stdlib.py
@@ -12,7 +12,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""Checkers for various standard library functions."""
import re
diff --git a/checkers/strings.py b/checkers/strings.py
index c6bf960..663d61d 100644
--- a/checkers/strings.py
+++ b/checkers/strings.py
@@ -14,7 +14,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""Checker for string formatting operations.
"""
@@ -102,15 +102,15 @@ class StringFormatChecker(BaseChecker):
utils.parse_format_string(format_string)
except utils.UnsupportedFormatCharacter, e:
c = format_string[e.index]
- self.add_message('E1300', node=node, args=(c, ord(c), e.index))
+ self.add_message('bad-format-character', node=node, args=(c, ord(c), e.index))
return
except utils.IncompleteFormatString:
- self.add_message('E1301', node=node)
+ self.add_message('truncated-format-string', node=node)
return
if required_keys and required_num_args:
# The format string uses both named and unnamed format
# specifiers.
- self.add_message('E1302', node=node)
+ self.add_message('mixed-format-string', node=node)
elif required_keys:
# The format string uses only named format specifiers.
# Check that the RHS of the % operator is a mapping object
@@ -125,7 +125,7 @@ class StringFormatChecker(BaseChecker):
if isinstance(key, basestring):
keys.add(key)
else:
- self.add_message('W1300', node=node, args=key)
+ self.add_message('bad-format-string-key', node=node, args=key)
else:
# One of the keys was something other than a
# constant. Since we can't tell what it is,
@@ -135,13 +135,13 @@ class StringFormatChecker(BaseChecker):
if not unknown_keys:
for key in required_keys:
if key not in keys:
- self.add_message('E1304', node=node, args=key)
+ self.add_message('missing-format-string-key', node=node, args=key)
for key in keys:
if key not in required_keys:
- self.add_message('W1301', node=node, args=key)
+ self.add_message('unused-format-string-key', node=node, args=key)
elif isinstance(args, OTHER_NODES + (astroid.Tuple,)):
type_name = type(args).__name__
- self.add_message('E1303', node=node, args=type_name)
+ self.add_message('format-needs-mapping', node=node, args=type_name)
# else:
# The RHS of the format specifier is a name or
# expression. It may be a mapping object, so
@@ -162,9 +162,9 @@ class StringFormatChecker(BaseChecker):
num_args = None
if num_args is not None:
if num_args > required_num_args:
- self.add_message('E1305', node=node)
+ self.add_message('too-many-format-args', node=node)
elif num_args < required_num_args:
- self.add_message('E1306', node=node)
+ self.add_message('too-few-format-args', node=node)
class StringMethodsChecker(BaseChecker):
@@ -189,7 +189,7 @@ class StringMethodsChecker(BaseChecker):
if not isinstance(arg, astroid.Const):
return
if len(arg.value) != len(set(arg.value)):
- self.add_message('E1310', node=node,
+ self.add_message('bad-str-strip-call', node=node,
args=(func.bound.name, func.name))
@@ -282,9 +282,11 @@ class StringConstantChecker(BaseTokenChecker):
elif _PY3K and 'b' not in prefix:
pass # unicode by default
else:
- self.add_message('W1402', line=start_row, args=(match, ))
+ self.add_message('anomalous-unicode-escape-in-string',
+ line=start_row, args=(match, ))
elif next_char not in self.ESCAPE_CHARACTERS:
- self.add_message('W1401', line=start_row, args=(match, ))
+ self.add_message('anomalous-backslash-in-string',
+ line=start_row, args=(match, ))
# Whether it was a valid escape or not, backslash followed by
# another character can always be consumed whole: the second
# character can never be the start of a new backslash escape.
diff --git a/checkers/typecheck.py b/checkers/typecheck.py
index 2e3785e..a775e6c 100644
--- a/checkers/typecheck.py
+++ b/checkers/typecheck.py
@@ -12,7 +12,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""try to find more bugs in the code using astroid inference capabilities
"""
@@ -48,34 +48,81 @@ MSGS = {
'Used when an assignment is done on a function call but the \
inferred function returns nothing but None.'),
- 'E1120': ('No value passed for parameter %s in function call',
+ 'E1120': ('No value for argument %s in %s call',
'no-value-for-parameter',
'Used when a function call passes too few arguments.'),
- 'E1121': ('Too many positional arguments for function call',
+ 'E1121': ('Too many positional arguments for %s call',
'too-many-function-args',
'Used when a function call passes too many positional \
arguments.'),
- 'E1122': ('Duplicate keyword argument %r in function call',
+ 'E1122': ('Duplicate keyword argument %r in %s call',
'duplicate-keyword-arg',
'Used when a function call passes the same keyword argument \
multiple times.',
{'maxversion': (2, 6)}),
- 'E1123': ('Passing unexpected keyword argument %r in function call',
+ 'E1123': ('Unexpected keyword argument %r in %s call',
'unexpected-keyword-arg',
'Used when a function call passes a keyword argument that \
doesn\'t correspond to one of the function\'s parameter names.'),
- 'E1124': ('Parameter %r passed as both positional and keyword argument',
+ 'E1124': ('Argument %r passed by position and keyword in %s call',
'redundant-keyword-arg',
'Used when a function call would result in assigning multiple \
values to a function parameter, one value from a positional \
argument and one from a keyword argument.'),
- 'E1125': ('Missing mandatory keyword argument %r',
+ 'E1125': ('Missing mandatory keyword argument %r in %s call',
'missing-kwoa',
- 'Used when a function call doesn\'t pass a mandatory \
- keyword-only argument.',
+ ('Used when a function call does not pass a mandatory'
+ ' keyword-only argument.'),
{'minversion': (3, 0)}),
}
+def _determine_callable(callable_obj):
+ # Note that BoundMethod is a subclass of UnboundMethod (huh?), so must
+ # come first in this 'if..else'.
+ if isinstance(callable_obj, astroid.BoundMethod):
+ # Bound methods have an extra implicit 'self' argument.
+ return callable_obj, 1, 'method'
+ elif isinstance(callable_obj, astroid.UnboundMethod):
+ if callable_obj.decorators is not None:
+ for d in callable_obj.decorators.nodes:
+ if isinstance(d, astroid.Name) and d.name == 'classmethod':
+ # Class methods have an extra implicit 'cls' argument.
+ return called, 1, 'class method'
+ elif isinstance(d, astroid.Name) and d.name == 'staticmethod':
+ return called, 0, 'static method'
+ else:
+ return callable_obj, 0, 'unbound method'
+ elif isinstance(callable_obj, astroid.Function):
+ return callable_obj, 0, 'function'
+ elif isinstance(callable_obj, astroid.Lambda):
+ return callable_obj, 0, 'lambda'
+ elif isinstance(callable_obj, astroid.Class):
+ # Class instantiation, lookup __new__ instead.
+ # If we only find object.__new__, we can safely check __init__
+ # instead.
+ try:
+ # Use the last definition of __new__.
+ new = callable_obj.local_attr('__new__')[-1]
+ except astroid.NotFoundError:
+ new = None
+
+ if not new or new.parent.name == 'object':
+ try:
+ # Use the last definition of __init__.
+ callable_obj = callable_obj.local_attr('__init__')[-1]
+ except astroid.NotFoundError:
+ # do nothing, covered by no-init.
+ raise ValueError
+ else:
+ callable_obj = new
+
+ if not isinstance(callable_obj, astroid.Function):
+ raise ValueError
+ # both have an extra implicit 'cls'/'self' argument.
+ return callable_obj, 1, 'constructor'
+ else:
+ raise ValueError
+
class TypeChecker(BaseChecker):
"""try to find bugs in the code using type inference
"""
@@ -132,7 +179,7 @@ accessed. Python regular expressions are accepted.'}
def visit_delattr(self, node):
self.visit_getattr(node)
- @check_messages('E1101', 'E1103')
+ @check_messages('no-member', 'maybe-no-member')
def visit_getattr(self, node):
"""check that the accessed attribute exists
@@ -211,14 +258,14 @@ accessed. Python regular expressions are accepted.'}
continue
done.add(actual)
if inference_failure:
- msgid = 'E1103'
+ msgid = 'maybe-no-member'
else:
- msgid = 'E1101'
+ msgid = 'no-member'
self.add_message(msgid, node=node,
args=(owner.display_type(), name,
node.attrname))
- @check_messages('E1111', 'W1111')
+ @check_messages('assignment-from-no-return', 'assignment-from-none')
def visit_assign(self, node):
"""check that if assigning to a function call, the function is
possibly returning something valuable
@@ -236,14 +283,14 @@ accessed. Python regular expressions are accepted.'}
returns = list(function_node.nodes_of_class(astroid.Return,
skip_klass=astroid.Function))
if len(returns) == 0:
- self.add_message('E1111', node=node)
+ self.add_message('assignment-from-no-return', node=node)
else:
for rnode in returns:
if not (isinstance(rnode.value, astroid.Const)
and rnode.value.value is None):
break
else:
- self.add_message('W1111', node=node)
+ self.add_message('assignment-from-none', node=node)
@check_messages(*(MSGS.keys()))
def visit_callfunc(self, node):
@@ -251,7 +298,6 @@ accessed. Python regular expressions are accepted.'}
and that the arguments passed to the function match the parameters in
the inferred function's definition
"""
-
# Build the set of keyword arguments, checking for duplicate keywords,
# and count the positional arguments.
keyword_args = set()
@@ -260,7 +306,7 @@ accessed. Python regular expressions are accepted.'}
if isinstance(arg, astroid.Keyword):
keyword = arg.arg
if keyword in keyword_args:
- self.add_message('E1122', node=node, args=keyword)
+ self.add_message('duplicate-keyword-arg', node=node, args=keyword)
keyword_args.add(keyword)
else:
num_positional_args += 1
@@ -268,26 +314,15 @@ accessed. Python regular expressions are accepted.'}
called = safe_infer(node.func)
# only function, generator and object defining __call__ are allowed
if called is not None and not called.callable():
- self.add_message('E1102', node=node, args=node.func.as_string())
-
- # Note that BoundMethod is a subclass of UnboundMethod (huh?), so must
- # come first in this 'if..else'.
- if isinstance(called, astroid.BoundMethod):
- # Bound methods have an extra implicit 'self' argument.
- num_positional_args += 1
- elif isinstance(called, astroid.UnboundMethod):
- if called.decorators is not None:
- for d in called.decorators.nodes:
- if isinstance(d, astroid.Name) and (d.name == 'classmethod'):
- # Class methods have an extra implicit 'cls' argument.
- num_positional_args += 1
- break
- elif (isinstance(called, astroid.Function) or
- isinstance(called, astroid.Lambda)):
- pass
- else:
- return
+ self.add_message('not-callable', node=node, args=node.func.as_string())
+ try:
+ called, implicit_args, callable_name = _determine_callable(called)
+ except ValueError:
+ # Any error occurred during determining the function type, most of
+ # those errors are handled by different warnings.
+ return
+ num_positional_args += implicit_args
if called.args.args is None:
# Built-in functions have no argument information.
return
@@ -342,7 +377,7 @@ accessed. Python regular expressions are accepted.'}
break
else:
# Too many positional arguments.
- self.add_message('E1121', node=node)
+ self.add_message('too-many-function-args', node=node, args=(callable_name,))
break
# 2. Match the keyword arguments.
@@ -351,13 +386,13 @@ accessed. Python regular expressions are accepted.'}
i = parameter_name_to_index[keyword]
if parameters[i][1]:
# Duplicate definition of function parameter.
- self.add_message('E1124', node=node, args=keyword)
+ self.add_message('redundant-keyword-arg', node=node, args=(keyword, callable_name))
else:
parameters[i][1] = True
elif keyword in kwparams:
if kwparams[keyword][1]: # XXX is that even possible?
# Duplicate definition of function parameter.
- self.add_message('E1124', node=node, args=keyword)
+ self.add_message('redundant-keyword-arg', node=node, args=(keyword, callable_name))
else:
kwparams[keyword][1] = True
elif called.args.kwarg is not None:
@@ -365,7 +400,7 @@ accessed. Python regular expressions are accepted.'}
pass
else:
# Unexpected keyword argument.
- self.add_message('E1123', node=node, args=keyword)
+ self.add_message('unexpected-keyword-arg', node=node, args=(keyword, callable_name))
# 3. Match the *args, if any. Note that Python actually processes
# *args _before_ any keyword arguments, but we wait until after
@@ -402,12 +437,12 @@ accessed. Python regular expressions are accepted.'}
display_name = '<tuple>'
else:
display_name = repr(name)
- self.add_message('E1120', node=node, args=display_name)
+ self.add_message('no-value-for-parameter', node=node, args=(display_name, callable_name))
for name in kwparams:
defval, assigned = kwparams[name]
if defval is None and not assigned:
- self.add_message('E1125', node=node, args=name)
+ self.add_message('missing-kwoa', node=node, args=(name, callable_name))
def register(linter):
diff --git a/checkers/utils.py b/checkers/utils.py
index 728893e..e7d85d4 100644
--- a/checkers/utils.py
+++ b/checkers/utils.py
@@ -14,7 +14,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""some functions that may be useful for various checkers
"""
@@ -407,7 +407,7 @@ def get_argument_from_call(callfunc_node, position=None, keyword=None):
try:
if position is not None and not isinstance(callfunc_node.args[position], astroid.Keyword):
return callfunc_node.args[position]
- except IndexError as error:
+ except IndexError, error:
raise NoSuchArgumentError(error)
if keyword:
for arg in callfunc_node.args:
diff --git a/checkers/variables.py b/checkers/variables.py
index d5bb715..7c489e8 100644
--- a/checkers/variables.py
+++ b/checkers/variables.py
@@ -12,7 +12,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""variables checkers for Python code
"""
import os
@@ -233,7 +233,7 @@ builtins. Remember that you should avoid to define new builtins when possible.'
self.add_message('undefined-all-variable',
args=elt_name,
node=elt)
- except SyntaxError as exc:
+ except SyntaxError, exc:
# don't yield an syntax-error warning,
# because it will be later yielded
# when the file will be checked
diff --git a/config.py b/config.py
index 2195703..992c293 100644
--- a/config.py
+++ b/config.py
@@ -10,7 +10,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""utilities for Pylint configuration :
* pylintrc
@@ -97,6 +97,8 @@ def find_pylintrc():
pylintrc = ".pylintrc"
else:
pylintrc = join(user_home, '.pylintrc')
+ if not isfile(pylintrc):
+ pylintrc = join(user_home, '.config', 'pylintrc')
if not isfile(pylintrc):
if isfile('/etc/pylintrc'):
pylintrc = '/etc/pylintrc'
diff --git a/doc/faq.rst b/doc/faq.rst
index ce5102d..e3cd73e 100644
--- a/doc/faq.rst
+++ b/doc/faq.rst
@@ -123,7 +123,7 @@ the rc file
For example::
- pylint --disable=W0702,C0103 --class-rgx='[A-Z][a-z]+' --generate-rcfile
+ pylint --disable=bare-except,invalid-name --class-rgx='[A-Z][a-z]+' --generate-rcfile
3.4 I'd rather not run Pylint from the command line. Can I integrate it with my editor?
---------------------------------------------------------------------------------------
@@ -137,7 +137,7 @@ Much probably. Read http://docs.pylint.org/ide-integration
-----------------------------------------------------------
Yes, this feature has been added in Pylint 0.11. This may be done by
-adding "#pylint: disable=W0123,E4567" at the desired block level
+adding "#pylint: disable=some-message,another-one" at the desired block level
or at the end of the desired line of code
4.2 Is there a way to disable a message for a particular module only?
@@ -147,8 +147,8 @@ Yes, you can disable or enable (globally disabled) messages at the
module level by adding the corresponding option in a comment at the
top of the file: ::
- # pylint: disable=W0401, E0202
- # pylint: enable=C0302
+ # pylint: disable=wildcard-import, method-hidden
+ # pylint: enable=too-many-lines
4.3 How can I tell Pylint to never check a given module?
--------------------------------------------------------
@@ -158,9 +158,9 @@ module. Pylint 0.26.1 and up have renamed that directive to
"#pylint: skip-file" (but the first version will be kept for backward
compatibility).
-In order to ease finding which modules are ignored a Information-level
-message I0013 is emited. With recent versions of Pylint, if you use
-the old syntax, an additional I0014 message is emited.
+In order to ease finding which modules are ignored a Information-level message
+`file-ignored` is emited. With recent versions of Pylint, if you use the old
+syntax, an additional `deprecated-disable-all` message is emited.
4.4 Do I have to remember all these numbers?
--------------------------------------------
@@ -182,12 +182,15 @@ variable for unused argument ("_" and "dummy" by default).
4.6 What is the format of the configuration file?
---------------------------------------------------
-Pylint uses ConfigParser from the standard library to parse the configuration file.
-It means that if you need to disable a lot of messages, you can use tricks like: ::
+Pylint uses ConfigParser from the standard library to parse the configuration
+file. It means that if you need to disable a lot of messages, you can use
+tricks like: ::
- disable= W0401, # because I do not want it
- E0202, # I have a good reason, trust me
- C0302 # that's it
+ # disable wildcard-import, method-hidden and too-many-lines because I do
+ # not want it
+ disable= wildcard-import,
+ method-hidden,
+ too-many-lines
4.7 Why do I get a lot of spurious "unused variables messages" when using psyobj from psyco_?
----------------------------------------------------------------------------------------------
diff --git a/doc/index.rst b/doc/index.rst
index 790ff93..7b8725c 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -13,8 +13,10 @@ https://bitbucket.org/logilab/pylint
output
message-control
features
+ options
extend
ide-integration
+ plugins
contribute
tutorial
@@ -29,12 +31,6 @@ Content wanted
It would be nice to include in the documentation the following information:
-- pylint plugins (how to write one, how to install a 3rd party plugin, how to
- configure Pylint to run it)
-
- pylint brain project : what it is, how to install it...
Please send your pull requests via bitbucket if you can help with the above.
-
-
-
diff --git a/doc/options.rst b/doc/options.rst
new file mode 100644
index 0000000..4a159f2
--- /dev/null
+++ b/doc/options.rst
@@ -0,0 +1,139 @@
+.. -*- coding: utf-8 -*-
+
+===============
+ Configuration
+===============
+
+Naming Styles
+-------------
+
+PyLint recognizes a number of different name types internally. With a few
+exceptions, the type of the name is governed by the location the assignment to a
+name is found in, and not the type of object assigned.
+
+``module``
+ Module and package names, same as the file names.
+``const``
+ Module-level constants, any variable defined at module level that is not bound to a class object.
+``class``
+ Names in ``class`` statements, as well as names bound to class objects at module level.
+``function``
+ Functions, toplevel or nested in functions or methods.
+``method``
+ Methods, functions defined in class bodies. Includes static and class methods.
+``attr``
+ Attributes created on class instances inside methods.
+``argument``
+ Arguments to any function type, including lambdas.
+``variable``
+ Local variables in function scopes.
+``class-attribute``
+ Attributes defined in class bodies.
+``inlinevar``
+ Loop variables in list comprehensions and generator expressions.
+
+For each naming style, a separate regular expression matching valid names of
+this type can be defined. By default, the regular expressions will enforce PEP8
+names.
+
+Regular expressions for the names are anchored at the beginning, any anchor for
+the end must be supplied explicitly. Any name not matching the regular
+expression will lead to an instance of ``invalid-name``.
+
+
+.. option:: --module-rgx=<regex>
+
+ Default value: ``[a-z_][a-z0-9_]{2,30}$``
+
+.. option:: --const-rgx=<regex>
+
+ Default value: ``[a-z_][a-z0-9_]{2,30}$``
+
+.. option:: --class-rgx=<regex>
+
+ Default value: ``'[A-Z_][a-zA-Z0-9]+$``
+
+.. option:: --function-rgx=<regex>
+
+ Default value: ``[a-z_][a-z0-9_]{2,30}$``
+
+.. option:: --method-rgx=<regex>
+
+ Default value: ``[a-z_][a-z0-9_]{2,30}$``
+
+.. option:: --attr-rgx=<regex>
+
+ Default value: ``[a-z_][a-z0-9_]{2,30}$``
+
+.. option:: --argument-rgx=<regex>
+
+ Default value: ``[a-z_][a-z0-9_]{2,30}$``
+
+.. option:: --variable-rgx=<regex>
+
+ Default value: ``[a-z_][a-z0-9_]{2,30}$``
+
+.. option:: --class-attribute-rgx=<regex>
+
+ Default value: ``([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$``
+
+.. option:: --inlinevar-rgx=<regex>
+
+ Default value: ``[A-Za-z_][A-Za-z0-9_]*$``
+
+Multiple Naming Styles
+^^^^^^^^^^^^^^^^^^^^^^
+
+Large code bases that have been worked on for multiple years often exhibit an
+evolution in style as well. In some cases, modules can be in the same package,
+but still have different naming style based on the stratum they belong to.
+However, intra-module consistency should still be required, to make changes
+inside a single file easier. For this case, PyLint supports regular expression
+with several named capturing group.
+
+The capturing group of the first valid match taints the module and enforces the
+same group to be triggered on every subsequent occurrence of this name.
+
+Consider the following (simplified) example::
+
+ pylint --function-rgx='(?:(?P<snake>[a-z_]+)|(?P<camel>_?[A-Z]+))$' sample.py
+
+The regular expression defines two naming styles, ``snake`` for snake-case
+names, and ``camel`` for camel-case names.
+
+In ``sample.py``, the function name on line 1 will taint the module and enforce
+the match of named group ``snake`` for the remainder of the module::
+
+ def trigger_snake_case(arg):
+ ...
+
+ def InvalidCamelCase(arg):
+ ...
+
+ def valid_snake_case(arg):
+ ...
+
+Because of this, the name on line 4 will trigger an ``invalid-name`` warning,
+even though the name matches the given regex.
+
+Matches named ``exempt`` or ``ignore`` can be used for non-tainting names, to
+prevent built-in or interface-dictated names to trigger certain naming styles.
+
+.. option:: --name-group=<name1:name2:...,...>
+
+ Default value: empty
+
+ Format: comma-separated groups of colon-separated names.
+
+ This option can be used to combine name styles. For example, ``function:method`` enforces that functions and methods use the same style, and a style triggered by either name type carries over to the other. This requires that the regular expression for the combined name types use the same group names.
+
+Name Hints
+^^^^^^^^^^
+
+.. option:: --include-naming-hint=y|n
+
+ Default: off
+
+ Include a hint for the correct name format with every ``invalid-name`` warning.
+
+ Name hints default to the regular expression, but can be separately configured with the ``--<name-type>-hint`` options.
diff --git a/doc/plugins.rst b/doc/plugins.rst
new file mode 100644
index 0000000..052c13f
--- /dev/null
+++ b/doc/plugins.rst
@@ -0,0 +1,122 @@
+.. -*- coding: utf-8 -*-
+
+=======
+Plugins
+=======
+
+Why write a plugin?
+-------------------
+
+Pylint is a static analysis tool and Python is a dynamically typed language.
+So there will be cases where Pylint cannot analyze files properly (this problem
+can happen in statically typed languages also if reflection or dynamic
+evaluation is used). Plugin is a way to tell Pylint how to handle such cases,
+since only the user would know what needs to be done.
+
+Example
+-------
+
+Let us run Pylint on a module from the Python source: `warnings.py`_ and see what happens:
+
+.. sourcecode:: bash
+
+ amitdev$ pylint -E Lib/warnings.py
+ E:297,36: Instance of 'WarningMessage' has no 'message' member (no-member)
+ E:298,36: Instance of 'WarningMessage' has no 'filename' member (no-member)
+ E:298,51: Instance of 'WarningMessage' has no 'lineno' member (no-member)
+ E:298,64: Instance of 'WarningMessage' has no 'line' member (no-member)
+
+
+Did we catch a genuine error? Let's open the code and look at ``WarningMessage`` class:
+
+.. sourcecode:: python
+
+ class WarningMessage(object):
+
+ """Holds the result of a single showwarning() call."""
+
+ _WARNING_DETAILS = ("message", "category", "filename", "lineno", "file",
+ "line")
+
+ def __init__(self, message, category, filename, lineno, file=None,
+ line=None):
+ local_values = locals()
+ for attr in self._WARNING_DETAILS:
+ setattr(self, attr, local_values[attr])
+ self._category_name = category.__name__ if category else None
+
+ def __str__(self):
+ ...
+
+Ah, the fields (``message``, ``category`` etc) are not defined statically on the class.
+Instead they are added using ``setattr``. Pylint would have a tough time figuring
+this out.
+
+Enter Plugin
+------------
+
+We can write a plugin to tell Pylint about how to analyze this properly. A
+plugin is a module which should have a function ``register`` and takes the
+`lint`_ module as input. So a basic hello-world plugin can be implemented as:
+
+.. sourcecode:: python
+
+ # Inside hello_plugin.py
+ def register(linter):
+ print 'Hello world'
+
+We can run this plugin by placing this module in the PYTHONPATH and invoking as:
+
+.. sourcecode:: bash
+
+ amitdev$ pylint -E --load-plugins hello_plugin foo.py
+ Hello world
+
+Back to our example: one way to fix that would be to transform the ``WarningMessage`` class
+and set the attributes using a plugin so that Pylint can see them. This can be done by
+registering a transform function. We can transform any node in the parsed AST like
+Module, Class, Function etc. In our case we need to transform a class. It can be done so:
+
+.. sourcecode:: python
+
+ from astroid import MANAGER
+ from astroid import scoped_nodes
+
+ def register(linter):
+ pass
+
+ def transform(cls):
+ if cls.name == 'WarningMessage':
+ import warnings
+ for f in warnings.WarningMessage._WARNING_DETAILS:
+ cls.locals[f] = [scoped_nodes.Class(f, None)]
+
+ MANAGER.register_transform(scoped_nodes.Class, transform)
+
+Let's go through the plugin. First, we need to register a class transform, which
+is done via the ``register_transform`` function in ``MANAGER``. It takes the node
+type and function as parameters. We need to change a class, so we use ``scoped_nodes.Class``.
+We also pass a ``transform`` function which does the actual transformation.
+
+``transform`` function is simple as well. If the class is ``WarningMessage`` then we
+add the attributes to its locals (we are not bothered about type of attributes, so setting
+them as class will do. But we could set them to any type we want). That's it.
+
+Note: We don't need to do anything in the ``register`` function of the plugin since we
+are not modifying anything in the linter itself.
+
+Lets run Pylint with this plugin and see:
+
+.. sourcecode:: bash
+
+ amitdev$ pylint -E --load-plugins warning_plugin Lib/warnings.py
+ amitdev$
+
+All the false positives associated with ``WarningMessage`` are now gone. This is just
+an example, any code transformation can be done by plugins. See `nodes`_ and `scoped_nodes`_
+for details about all node types that can be transformed.
+
+.. _`warnings.py`: http://hg.python.org/cpython/file/2.7/Lib/warnings.py
+.. _`scoped_nodes`: https://bitbucket.org/logilab/astroid/src/64026ffc0d94fe09e4bdc2bf5efaab29444645e7/scoped_nodes.py?at=default
+.. _`nodes`: https://bitbucket.org/logilab/astroid/src/64026ffc0d94fe09e4bdc2bf5efaab29444645e7/nodes.py?at=default
+.. _`lint`: https://bitbucket.org/logilab/pylint/src/f2acea7b640def0237513f66e3de5fa3de73f2de/lint.py?at=default \ No newline at end of file
diff --git a/doc/run.rst b/doc/run.rst
index 1fb95cd..4e752b0 100644
--- a/doc/run.rst
+++ b/doc/run.rst
@@ -99,9 +99,12 @@ configuration file in the following order and uses the first one it finds:
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
+#. if you have a home directory which isn't ``/root``:
+
+ #. ``.pylintrc`` in your home directory
+ #. ``.config/pylintrc`` in your home directory
+
+ else, ``.pylintrc`` in the current working directory
#. ``/etc/pylintrc``
The ``--generate-rcfile`` option will generate a commented configuration file
diff --git a/elisp/pylint.el b/elisp/pylint.el
index 09c22e6..17132e4 100644
--- a/elisp/pylint.el
+++ b/elisp/pylint.el
@@ -19,8 +19,8 @@
;;
;; You should have received a copy of the GNU General Public License along
;; with your copy of Emacs; see the file COPYING. If not, write to the Free
-;; Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-;; 02111-1307, USA.
+;; Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+;; MA 02110-1301, USA
;;; Commentary:
;;
diff --git a/epylint.py b/epylint.py
index 24baa61..ca245cc 100755
--- a/epylint.py
+++ b/epylint.py
@@ -13,7 +13,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""Emacs and Flymake compatible Pylint.
This script is for integration with emacs and is compatible with flymake mode.
@@ -81,29 +81,16 @@ def lint(filename, options=None):
from pylint import lint as lint_mod
lint_path = lint_mod.__file__
options = options or ['--disable=C,R,I']
- cmd = [sys.executable, lint_path] + options + ['--msg-template',
- '{path}:{line}: [{symbol}, {obj}] {msg}', '-r', 'n', child_path]
+ cmd = [sys.executable, lint_path] + options + [
+ '--msg-template', '{path}:{line}: {category} ({msg_id}, {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(match_object):
- "Alter to include 'Error' or 'Warning'"
- if match_object.group("type") == "W":
- replacement = "Warning"
- else:
- replacement = "Error"
- # replace as "Warning (W0511, funcName): Warning Text"
- return "%s (%s%s):" % (replacement, match_object.group("type"),
- match_object.group("remainder"))
-
for line in process.stdout:
# remove pylintrc warning
if line.startswith("No config file found"):
continue
- 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] == child_path:
@@ -174,7 +161,7 @@ def Run():
print "%s does not exist" % sys.argv[1]
sys.exit(1)
else:
- sys.exit(lint(sys.argv[1], sys.argv[1:]))
+ sys.exit(lint(sys.argv[1], sys.argv[2:]))
if __name__ == '__main__':
diff --git a/gui.py b/gui.py
index 1011ceb..a882a2b 100644
--- a/gui.py
+++ b/gui.py
@@ -12,7 +12,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""Tkinker gui for pylint"""
import os
diff --git a/interfaces.py b/interfaces.py
index e0754ce..50f2c83 100644
--- a/interfaces.py
+++ b/interfaces.py
@@ -9,7 +9,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""Interfaces for PyLint objects"""
from logilab.common.interface import Interface
diff --git a/lint.py b/lint.py
index 4cf5059..0e1afa0 100644
--- a/lint.py
+++ b/lint.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE).
+# Copyright (c) 2003-2014 LOGILAB S.A. (Paris, FRANCE).
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This program is free software; you can redistribute it and/or modify it under
@@ -12,7 +12,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
""" %prog [options] module_or_package
Check that a module satisfies a coding standard (and more !).
@@ -29,6 +29,7 @@
# import this first to avoid builtin namespace pollution
from pylint.checkers import utils
+import functools
import sys
import os
import tokenize
@@ -389,7 +390,7 @@ warning, statement which respectively contain the number of errors / warnings\
value = check_csv(None, optname, value)
if isinstance(value, (list, tuple)):
for _id in value:
- meth(_id)
+ meth(_id, ignore_unknown=True)
else:
meth(value)
elif optname == 'output-format':
@@ -622,7 +623,7 @@ warning, statement which respectively contain the number of errors / warnings\
self._ignore_file = False
# fix the current file (if the source file was not available or
# if it's actually a c extension)
- self.current_file = astroid.file
+ self.current_file = astroid.file # pylint: disable=maybe-no-member
self.check_astroid_module(astroid, walker, rawcheckers, tokencheckers)
self._add_suppression_messages()
# notify global end
@@ -873,15 +874,20 @@ def preprocess_options(args, search_for):
option, val = arg[2:], None
try:
cb, takearg = search_for[option]
+ except KeyError:
+ i += 1
+ else:
del args[i]
if takearg and val is None:
if i >= len(args) or args[i].startswith('-'):
- raise ArgumentPreprocessingError(arg)
+ msg = 'Option %s expects a value' % option
+ raise ArgumentPreprocessingError(msg)
val = args[i]
del args[i]
+ elif not takearg and val is not None:
+ msg = "Option %s doesn't expects a value" % option
+ raise ArgumentPreprocessingError(msg)
cb(option, val)
- except KeyError:
- i += 1
else:
i += 1
@@ -901,12 +907,13 @@ group are mutually exclusive.'),
self._plugins = []
try:
preprocess_options(args, {
- # option: (callback, takearg)
- 'rcfile': (self.cb_set_rcfile, True),
- 'load-plugins': (self.cb_add_plugins, True),
- })
+ # option: (callback, takearg)
+ 'init-hooks': (cb_init_hook, True),
+ 'rcfile': (self.cb_set_rcfile, True),
+ 'load-plugins': (self.cb_add_plugins, True),
+ })
except ArgumentPreprocessingError, ex:
- print >> sys.stderr, 'Argument %s expects a value.' % (ex.args[0],)
+ print >> sys.stderr, ex
sys.exit(32)
self.linter = linter = self.LinterClass((
@@ -916,8 +923,9 @@ group are mutually exclusive.'),
'help' : 'Specify a configuration file.'}),
('init-hook',
- {'action' : 'callback', 'type' : 'string', 'metavar': '<code>',
- 'callback' : cb_init_hook, 'level': 1,
+ {'action' : 'callback', 'callback' : lambda *args: 1,
+ 'type' : 'string', 'metavar': '<code>',
+ 'level': 1,
'help' : 'Python code to execute, usually for sys.path \
manipulation such as pygtk.require().'}),
@@ -1043,11 +1051,11 @@ are done by default'''}),
sys.exit(self.linter.msg_status)
def cb_set_rcfile(self, name, value):
- """callback for option preprocessing (i.e. before optik parsing)"""
+ """callback for option preprocessing (i.e. before option parsing)"""
self._rcfile = value
def cb_add_plugins(self, name, value):
- """callback for option preprocessing (i.e. before optik parsing)"""
+ """callback for option preprocessing (i.e. before option parsing)"""
self._plugins.extend(splitstrip(value))
def cb_error_mode(self, *args, **kwargs):
@@ -1086,7 +1094,7 @@ are done by default'''}),
self.linter.list_messages()
sys.exit(0)
-def cb_init_hook(option, optname, value, parser):
+def cb_init_hook(optname, value):
"""exec arbitrary code to set sys.path for instance"""
exec value
diff --git a/man/pylint.1 b/man/pylint.1
index b190e68..f80c61c 100644
--- a/man/pylint.1
+++ b/man/pylint.1
@@ -1,4 +1,4 @@
-.TH pylint 1 "2013-4-16" pylint
+.TH pylint 1 "2014-4-11" pylint
.SH NAME
.B pylint
\- python code static checker
@@ -45,7 +45,7 @@ Python code to execute, usually for sys.path manipulation such as pygtk.require(
.IP "--errors-only, -E"
In error mode, checkers without error messages are disabled and for others, only the ERROR messages are displayed, and no reports are done by default
.IP "--ignore=<file>[,<file>...]"
-Add files or directories to the blacklist. They should be base names, not paths. [current: CVS]
+Add files or directories to the blacklist. They should be base names, not paths. [current: .hg,test]
.IP "--persistent=<y_or_n>"
Pickle collected data for later comparisons. [current: yes]
.IP "--load-plugins=<modules>"
@@ -70,10 +70,6 @@ Disable the message, report, category or checker with the given id(s). You can e
.SH REPORTS
.IP "--output-format=<format>, -f <format>"
Set the output format. Available formats are text, parseable, colorized, msvs (visual studio) and html. You can also give a reporter class, eg mypackage.mymodule.MyReporterClass. [current: text]
-.IP "--include-ids=<y_or_n>, -i <y_or_n>"
-Include message's id in output [current: no]
-.IP "--symbols=<y_or_n>, -s <y_or_n>"
-Include symbolic ids of messages in output [current: no]
.IP "--files-output=<y_or_n>"
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]". [current: no]
.IP "--reports=<y_or_n>, -r <y_or_n>"
@@ -82,6 +78,8 @@ Tells whether to display a full report or only the messages [current: yes]
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 messages and the total number of statements analyzed. This is used by the global evaluation report (RP0004). [current: 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)]
.IP "--comment=<y_or_n>"
Add a comment according to your evaluation note. This is used by the global evaluation report (RP0004). [current: no]
+.IP "--msg-template=<template>"
+Template used to display messages. This is a python new-style format string used to format the message information. See doc for all details
.SH IMPORTS
.IP "--deprecated-modules=<modules>"
@@ -93,6 +91,10 @@ Create a graph of external dependencies in the given file (report RP0402 must no
.IP "--int-import-graph=<file.dot>"
Create a graph of internal dependencies in the given file (report RP0402 must not be disabled) [current: none]
+.SH LOGGING
+.IP "--logging-modules=<comma separated list>"
+Logging modules to check that the string format arguments are in logging function parameter format [current: logging]
+
.SH DESIGN
.IP "--max-args=<int>"
Maximum number of arguments for function / method [current: 5]
@@ -102,7 +104,7 @@ Argument names that match this expression will be ignored. Default to name with
Maximum number of locals for function / method body [current: 15]
.IP "--max-returns=<int>"
Maximum number of return / yield for function / method body [current: 6]
-.IP "--max-branchs=<int>"
+.IP "--max-branches=<int>"
Maximum number of branch for function / method body [current: 12]
.IP "--max-statements=<int>"
Maximum number of statements in function / method body [current: 50]
@@ -118,6 +120,12 @@ Maximum number of public methods for a class (see R0904). [current: 20]
.SH FORMAT
.IP "--max-line-length=<int>"
Maximum number of characters on a single line. [current: 80]
+.IP "--ignore-long-lines=<regexp>"
+Regexp for a line that is allowed to be longer than the limit. [current: ^\s*(# )?<?https?://\S+>?$]
+.IP "--single-line-if-stmt=<y_or_n>"
+Allow the body of an if to be on the same line as the test if there is no else. [current: no]
+.IP "--no-space-check=NO_SPACE_CHECK"
+List of optional constructs for which whitespace checking is disabled [current: trailing-comma,dict-separator]
.IP "--max-module-lines=<int>"
Maximum number of lines in a module [current: 1000]
.IP "--indent-string=<string>"
@@ -128,30 +136,62 @@ String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 ta
Required attributes for module, separated by a comma [current: none]
.IP "--bad-functions=<builtin function names>"
List of builtins function names that should not be used, separated by a comma [current: map,filter,apply,input]
-.IP "--module-rgx=<regexp>"
-Regular expression which should only match correct module names [current: (([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$]
-.IP "--const-rgx=<regexp>"
-Regular expression which should only match correct module level names [current: (([A-Z_][A-Z0-9_]*)|(__.*__))$]
-.IP "--class-rgx=<regexp>"
-Regular expression which should only match correct class names [current: [A-Z_][a-zA-Z0-9]+$]
-.IP "--function-rgx=<regexp>"
-Regular expression which should only match correct function names [current: [a-z_][a-z0-9_]{2,30}$]
-.IP "--method-rgx=<regexp>"
-Regular expression which should only match correct method names [current: [a-z_][a-z0-9_]{2,30}$]
-.IP "--attr-rgx=<regexp>"
-Regular expression which should only match correct instance attribute names [current: [a-z_][a-z0-9_]{2,30}$]
-.IP "--argument-rgx=<regexp>"
-Regular expression which should only match correct argument names [current: [a-z_][a-z0-9_]{2,30}$]
-.IP "--variable-rgx=<regexp>"
-Regular expression which should only match correct variable names [current: [a-z_][a-z0-9_]{2,30}$]
-.IP "--inlinevar-rgx=<regexp>"
-Regular expression which should only match correct list comprehension / generator expression variable names [current: [A-Za-z_][A-Za-z0-9_]*$]
.IP "--good-names=<names>"
Good variable names which should always be accepted, separated by a comma [current: i,j,k,ex,Run,_]
.IP "--bad-names=<names>"
Bad variable names which should always be refused, separated by a comma [current: foo,bar,baz,toto,tutu,tata]
+.IP "--name-group=<name1:name2>"
+Colon-delimited sets of names that determine each other's naming style when the name regexes allow several styles. [current: none]
+.IP "--include-naming-hint=<y_or_n>"
+Include a hint for the correct naming format with invalid-name [current: no]
+.IP "--function-rgx=<regexp>"
+Regular expression matching correct function names [current: [a-z_][a-z0-9_]{2,30}$]
+.IP "--function-name-hint=<string>"
+Naming hint for function names [current: [a-z_][a-z0-9_]{2,30}$]
+.IP "--variable-rgx=<regexp>"
+Regular expression matching correct variable names [current: [a-z_][a-z0-9_]{2,30}$]
+.IP "--variable-name-hint=<string>"
+Naming hint for variable names [current: [a-z_][a-z0-9_]{2,30}$]
+.IP "--const-rgx=<regexp>"
+Regular expression matching correct constant names [current: (([A-Z_][A-Z0-9_]*)|(__.*__))$]
+.IP "--const-name-hint=<string>"
+Naming hint for constant names [current: (([A-Z_][A-Z0-9_]*)|(__.*__))$]
+.IP "--attr-rgx=<regexp>"
+Regular expression matching correct attribute names [current: [a-z_][a-z0-9_]{2,30}$]
+.IP "--attr-name-hint=<string>"
+Naming hint for attribute names [current: [a-z_][a-z0-9_]{2,30}$]
+.IP "--argument-rgx=<regexp>"
+Regular expression matching correct argument names [current: [a-z_][a-z0-9_]{2,30}$]
+.IP "--argument-name-hint=<string>"
+Naming hint for argument names [current: [a-z_][a-z0-9_]{2,30}$]
+.IP "--class-attribute-rgx=<regexp>"
+Regular expression matching correct class attribute names [current: ([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$]
+.IP "--class-attribute-name-hint=<string>"
+Naming hint for class attribute names [current: ([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$]
+.IP "--inlinevar-rgx=<regexp>"
+Regular expression matching correct inline iteration names [current: [A-Za-z_][A-Za-z0-9_]*$]
+.IP "--inlinevar-name-hint=<string>"
+Naming hint for inline iteration names [current: [A-Za-z_][A-Za-z0-9_]*$]
+.IP "--class-rgx=<regexp>"
+Regular expression matching correct class names [current: [A-Z_][a-zA-Z0-9]+$]
+.IP "--class-name-hint=<string>"
+Naming hint for class names [current: [A-Z_][a-zA-Z0-9]+$]
+.IP "--module-rgx=<regexp>"
+Regular expression matching correct module names [current: (([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$]
+.IP "--module-name-hint=<string>"
+Naming hint for module names [current: (([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$]
+.IP "--method-rgx=<regexp>"
+Regular expression matching correct method names [current: [a-z_][a-z0-9_]{2,30}$]
+.IP "--method-name-hint=<string>"
+Naming hint for method names [current: [a-z_][a-z0-9_]{2,30}$]
.IP "--no-docstring-rgx=<regexp>"
-Regular expression which should only match functions or classes name which do not require a docstring [current: __.*__]
+Regular expression which should only match function or class names that do not require a docstring. [current: __.*__]
+.IP "--docstring-min-length=<int>"
+Minimum line length for functions/classes that require docstrings, shorter ones are exempt. [current: -1]
+
+.SH EXCEPTIONS
+.IP "--overgeneral-exceptions=<comma-separated class names>"
+Exceptions that will emit a warning when being caught. Defaults to "Exception" [current: Exception]
.SH SIMILARITIES
.IP "--min-similarity-lines=<int>"
@@ -195,20 +235,16 @@ List of valid names for the first argument in a class method. [current: cls]
.IP "--valid-metaclass-classmethod-first-arg=<argument names>"
List of valid names for the first argument in a metaclass class method. [current: mcs]
-.SH EXCEPTIONS
-.IP "--overgeneral-exceptions=<comma-separated class names>"
-Exceptions that will emit a warning when being caught. Defaults to "Exception" [current: Exception]
-
.SH ENVIRONMENT VARIABLES
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.
.SH OUTPUT
Using the default text output, the message format is :
@@ -241,11 +277,8 @@ been issued by analysing pylint output status code
.SH BUGS
Please report bugs on the project's mailing list:
-mailto://python-projects@lists.logilab.org
+mailto://code-quality@python.org
.SH AUTHOR
Logilab <python-projects@lists.logilab.org>
-.SH COPYRIGHT
-Logilab S.A.
-
diff --git a/pyreverse/diadefslib.py b/pyreverse/diadefslib.py
index 795be8b..46d0f19 100644
--- a/pyreverse/diadefslib.py
+++ b/pyreverse/diadefslib.py
@@ -12,7 +12,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""handle diagram generation options for class diagram or default diagrams
"""
diff --git a/pyreverse/diagrams.py b/pyreverse/diagrams.py
index 360fdb1..6dde9a1 100644
--- a/pyreverse/diagrams.py
+++ b/pyreverse/diagrams.py
@@ -12,7 +12,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""diagram objects
"""
@@ -85,11 +85,11 @@ class ClassDiagram(Figure, FilterMixIn):
if names:
node_name = "%s : %s" % (node_name, ", ".join(names))
attrs.append(node_name)
- return attrs
+ return sorted(attrs)
def get_methods(self, node):
"""return visible methods"""
- return [m for m in node.values()
+ return [m for m in sorted(node.values(), key=lambda n: n.name)
if isinstance(m, astroid.Function) and self.show_attr(m.name)]
def add_object(self, title, node):
diff --git a/pyreverse/main.py b/pyreverse/main.py
index 5b9e8df..7801835 100644
--- a/pyreverse/main.py
+++ b/pyreverse/main.py
@@ -12,7 +12,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
%prog [options] <packages>
diff --git a/pyreverse/utils.py b/pyreverse/utils.py
index ea90e05..3d12d41 100644
--- a/pyreverse/utils.py
+++ b/pyreverse/utils.py
@@ -12,7 +12,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
generic classes/functions for pyreverse core/extensions
"""
diff --git a/pyreverse/writer.py b/pyreverse/writer.py
index d4b9937..6b35548 100644
--- a/pyreverse/writer.py
+++ b/pyreverse/writer.py
@@ -13,7 +13,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""Utilities for creating VCG and Dot diagrams"""
from logilab.common.vcgutils import VCGPrinter
diff --git a/reporters/__init__.py b/reporters/__init__.py
index 53064c7..a767a05 100644
--- a/reporters/__init__.py
+++ b/reporters/__init__.py
@@ -10,7 +10,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""utilities methods and classes for reporters"""
import sys
@@ -28,6 +28,10 @@ if sys.version_info >= (3, 0):
def cmp(a, b):
return (a > b) - (a < b)
+if sys.version_info < (2, 6):
+ import stringformat
+ stringformat.init(True)
+
def diff_string(old, new):
"""given a old and new int value, return a string representing the
difference
diff --git a/reporters/html.py b/reporters/html.py
index 1c39bc6..4bf33c5 100644
--- a/reporters/html.py
+++ b/reporters/html.py
@@ -10,7 +10,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""HTML reporter"""
import sys
diff --git a/reporters/text.py b/reporters/text.py
index 614fcdb..bd99837 100644
--- a/reporters/text.py
+++ b/reporters/text.py
@@ -10,7 +10,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""Plain text reporters:
:text: the default one grouping messages by module
diff --git a/setup.py b/setup.py
index e4b0ae8..5352833 100644
--- a/setup.py
+++ b/setup.py
@@ -52,7 +52,7 @@ sys.modules.pop('__pkginfo__', None)
__pkginfo__ = __import__("__pkginfo__")
# import required features
from __pkginfo__ import modname, version, license, description, \
- web, author, author_email
+ web, author, author_email, classifiers
distname = getattr(__pkginfo__, 'distname', modname)
scripts = getattr(__pkginfo__, 'scripts', [])
@@ -131,7 +131,15 @@ class MyInstallLib(install_lib.install_lib):
else:
exclude = set()
shutil.rmtree(dest, ignore_errors=True)
- shutil.copytree(directory, dest, ignore=lambda dir, names: list(set(names) & exclude))
+ shutil.copytree(directory, dest)
+ # since python2.5's copytree doesn't support the ignore
+ # parameter, the following loop to remove the exclude set
+ # was added
+ for (dirpath, dirnames, filenames) in os.walk(dest):
+ for n in filenames:
+ if n in exclude:
+ os.remove(os.path.join(dirpath, n))
+
if sys.version_info >= (3, 0):
# process manually python file in include_dirs (test data)
@@ -179,6 +187,7 @@ def install(**kwargs):
author_email=author_email,
url=web,
scripts=ensure_scripts(scripts),
+ classifiers=classifiers,
data_files=data_files,
ext_modules=ext_modules,
cmdclass={'install_lib': MyInstallLib,
diff --git a/test/data/classes_No_Name.dot b/test/data/classes_No_Name.dot
index e75553e..51b42e7 100644
--- a/test/data/classes_No_Name.dot
+++ b/test/data/classes_No_Name.dot
@@ -1,10 +1,10 @@
digraph "classes_No_Name" {
charset="utf-8"
rankdir=BT
-"0" [label="{Ancestor|attr : str\lcls_member\l|set_value()\lget_value()\l}", shape="record"];
+"0" [label="{Ancestor|attr : str\lcls_member\l|get_value()\lset_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" [label="{Specialization|TYPE : str\lrelation\ltop : 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"];
diff --git a/test/input/func_bad_slots.py b/test/input/func_bad_slots.py
new file mode 100644
index 0000000..16f7a79
--- /dev/null
+++ b/test/input/func_bad_slots.py
@@ -0,0 +1,57 @@
+""" Checks that classes uses valid __slots__ """
+
+# pylint: disable=too-few-public-methods, missing-docstring
+
+from collections import deque
+
+def func():
+ if True:
+ return ("a", "b", "c")
+ else:
+ return [str(var) for var in range(3)]
+
+__revision__ = 0
+
+class NotIterable(object):
+ def __iter_(self):
+ """ do nothing """
+
+class Good(object):
+ __slots__ = ()
+
+class SecondGood(object):
+ __slots__ = []
+
+class ThirdGood(object):
+ __slots__ = ['a']
+
+class FourthGood(object):
+ __slots__ = ('a%s' % i for i in range(10))
+
+class FifthGood(object):
+ __slots__ = "a"
+
+class SixthGood(object):
+ __slots__ = deque(["a", "b", "c"])
+
+class SeventhGood(object):
+ __slots__ = {"a": "b", "c": "d"}
+
+class Bad(object):
+ __slots__ = list
+
+class SecondBad(object):
+ __slots__ = 1
+
+class ThirdBad(object):
+ __slots__ = ('a', 2)
+
+class FourthBad(object):
+ __slots__ = NotIterable()
+
+class FifthBad(object):
+ __slots__ = ("a", "b", "")
+
+class PotentiallyGood(object):
+ __slots__ = func()
+ \ No newline at end of file
diff --git a/test/input/func_class_access_protected_members.py b/test/input/func_class_access_protected_members.py
index 123c5ef..f2ad5b7 100644
--- a/test/input/func_class_access_protected_members.py
+++ b/test/input/func_class_access_protected_members.py
@@ -26,7 +26,7 @@ class Subclass(MyClass):
def __init__(self):
MyClass._protected = 5
-INST = MyClass()
+INST = Subclass()
INST.attr = 1
print INST.attr
INST._protected = 2
diff --git a/test/input/func_ctor_arguments.py b/test/input/func_ctor_arguments.py
new file mode 100644
index 0000000..987a930
--- /dev/null
+++ b/test/input/func_ctor_arguments.py
@@ -0,0 +1,63 @@
+"""Test function argument checker on __init__
+
+Based on test/input/func_arguments.py
+"""
+# pylint: disable=C0111,R0903,W0231
+__revision__ = ''
+
+class Class1Arg(object):
+ def __init__(self, first_argument):
+ """one argument function"""
+
+class Class3Arg(object):
+ def __init__(self, first_argument, second_argument, third_argument):
+ """three arguments function"""
+
+class ClassDefaultArg(object):
+ def __init__(self, one=1, two=2):
+ """function with default value"""
+
+class Subclass1Arg(Class1Arg):
+ pass
+
+class ClassAllArgs(Class1Arg):
+ def __init__(self, *args, **kwargs):
+ pass
+
+class ClassMultiInheritance(Class1Arg, Class3Arg):
+ pass
+
+class ClassNew(object):
+ def __new__(cls, first_argument, kwarg=None):
+ return first_argument, kwarg
+
+Class1Arg(420)
+Class1Arg()
+Class1Arg(1337, 347)
+
+Class3Arg(420, 789)
+Class3Arg()
+Class3Arg(1337, 347, 456)
+Class3Arg('bab', 'bebe', None, 5.6)
+
+ClassDefaultArg(1, two=5)
+ClassDefaultArg(two=5)
+
+Class1Arg(bob=4)
+ClassDefaultArg(1, 4, coin="hello")
+
+ClassDefaultArg(1, one=5)
+
+Subclass1Arg(420)
+Subclass1Arg()
+Subclass1Arg(1337, 347)
+
+ClassAllArgs()
+ClassAllArgs(1, 2, 3, even=4, more=5)
+
+ClassMultiInheritance(1)
+ClassMultiInheritance(1, 2, 3)
+
+ClassNew(1, kwarg=1)
+ClassNew(1, 2, 3)
+ClassNew(one=2)
diff --git a/test/input/func_docstring.py b/test/input/func_docstring.py
index c7930f2..e73d8a3 100644
--- a/test/input/func_docstring.py
+++ b/test/input/func_docstring.py
@@ -2,6 +2,9 @@
__revision__ = ''
+def function0():
+ """"""
+
def function1(value):
# missing docstring
print value
@@ -37,6 +40,10 @@ class AAAA(object):
""" yeah !"""
pass
+ def method3(self):
+ """"""
+ pass
+
def __init__(self):
pass
@@ -46,6 +53,21 @@ class DDDD(AAAA):
def __init__(self):
AAAA.__init__(self)
+ def method2(self):
+ """"""
+ pass
+
+ def method3(self):
+ pass
+
+ def method4(self):
+ pass
+
# pylint: disable=missing-docstring
def function4():
pass
+
+# pylint: disable=empty-docstring
+def function5():
+ """"""
+ pass
diff --git a/test/input/func_eval_used.py b/test/input/func_eval_used.py
new file mode 100644
index 0000000..c58b69c
--- /dev/null
+++ b/test/input/func_eval_used.py
@@ -0,0 +1,13 @@
+"""test for eval usage"""
+
+__revision__ = 0
+
+eval('os.listdir(".")')
+eval('os.listdir(".")', globals={})
+
+eval('os.listdir(".")', globals=globals())
+
+def func():
+ """ eval in local scope"""
+ eval('b = 1')
+
diff --git a/test/input/func_f0401.py b/test/input/func_f0401.py
index f8443fa..c663431 100644
--- a/test/input/func_f0401.py
+++ b/test/input/func_f0401.py
@@ -1,4 +1,4 @@
-"""tset failed import
+"""test failed import
"""
__revision__ = 0
diff --git a/test/input/func_name_checking.py b/test/input/func_name_checking.py
index ce7f439..d19d946 100644
--- a/test/input/func_name_checking.py
+++ b/test/input/func_name_checking.py
@@ -21,7 +21,7 @@ def run():
def __init__(self):
pass
bbb = 1
- return Aaa(bbb)
+ return bbb, Aaa
A = None
diff --git a/test/input/func_newstyle_super.py b/test/input/func_newstyle_super.py
index fdba69c..a0aaa0a 100644
--- a/test/input/func_newstyle_super.py
+++ b/test/input/func_newstyle_super.py
@@ -1,5 +1,7 @@
-# pylint: disable=R0903
+# pylint: disable=R0903,import-error
"""check use of super"""
+
+from unknown import Missing
__revision__ = None
class Aaaa:
@@ -29,3 +31,8 @@ class Py3kWrongSuper(Py3kAaaa):
"""new style"""
def __init__(self):
super(NewAaaa, self).__init__()
+
+class WrongNameRegression(Py3kAaaa):
+ """ test a regression with the message """
+ def __init__(self):
+ super(Missing, self).__init__()
diff --git a/test/input/func_noerror_abstract_method.py b/test/input/func_noerror_abstract_method.py
new file mode 100644
index 0000000..18228c6
--- /dev/null
+++ b/test/input/func_noerror_abstract_method.py
@@ -0,0 +1,20 @@
+""" This should not warn about `prop` being abstract in Child """
+
+# pylint: disable=too-few-public-methods,abstract-class-little-used
+
+__revision__ = None
+
+import abc
+
+class Parent(object):
+ """ Class """
+ __metaclass__ = abc.ABCMeta
+
+ @property
+ @abc.abstractmethod
+ def prop(self):
+ """ Abstract """
+
+class Child(Parent):
+ """ No warning for the following. """
+ prop = property(lambda self: 1)
diff --git a/test/input/func_noerror_abstract_method_py30.py b/test/input/func_noerror_abstract_method_py30.py
new file mode 100644
index 0000000..c237cb4
--- /dev/null
+++ b/test/input/func_noerror_abstract_method_py30.py
@@ -0,0 +1,19 @@
+""" This should not warn about `prop` being abstract in Child """
+
+# pylint: disable=too-few-public-methods,abstract-class-little-used,no-init,old-style-class
+
+__revision__ = None
+
+import abc
+
+class Parent(metaclass=abc.ABCMeta):
+ """ Class """
+
+ @property
+ @abc.abstractmethod
+ def prop(self):
+ """ Abstract """
+
+class Child(Parent):
+ """ No warning for the following. """
+ prop = property(lambda self: 1)
diff --git a/test/input/func_return_yield_mix.py b/test/input/func_return_yield_mix_py_33.py
index 1a3cd5d..1a3cd5d 100644
--- a/test/input/func_return_yield_mix.py
+++ b/test/input/func_return_yield_mix_py_33.py
diff --git a/test/input/func_unpack_exception_py_30.py b/test/input/func_unpack_exception_py_30.py
index cf2e237..356915e 100644
--- a/test/input/func_unpack_exception_py_30.py
+++ b/test/input/func_unpack_exception_py_30.py
@@ -6,8 +6,8 @@ def new_style():
"""Some exceptions can be unpacked."""
try:
pass
- except IOError as (errno, message): # this is fine
+ except IOError, (errno, message): # this is fine
print errno, message
- except IOError as (new_style, tuple): # W0623 twice
+ except IOError, (new_style, tuple): # W0623 twice
print new_style, tuple
diff --git a/test/input/func_w0109.py b/test/input/func_w0109.py
deleted file mode 100644
index ba7a679..0000000
--- a/test/input/func_w0109.py
+++ /dev/null
@@ -1,7 +0,0 @@
-"""test empty docstrings
-"""
-
-__revision__ = ''
-
-def function():
- """"""
diff --git a/test/input/func_w0401_package/__init__.py b/test/input/func_w0401_package/__init__.py
new file mode 100644
index 0000000..dedef66
--- /dev/null
+++ b/test/input/func_w0401_package/__init__.py
@@ -0,0 +1,2 @@
+"""Our big package."""
+__revision__ = None
diff --git a/test/input/func_w0401_package/all_the_things.py b/test/input/func_w0401_package/all_the_things.py
new file mode 100644
index 0000000..ef0ca99
--- /dev/null
+++ b/test/input/func_w0401_package/all_the_things.py
@@ -0,0 +1,8 @@
+"""All the things!"""
+__revision__ = None
+from .thing1 import THING1
+from .thing2 import THING2
+from .thing2 import THING1_PLUS_THING2
+
+_ = (THING1, THING2, THING1_PLUS_THING2)
+del _
diff --git a/test/input/func_w0401_package/thing1.py b/test/input/func_w0401_package/thing1.py
new file mode 100644
index 0000000..34972a7
--- /dev/null
+++ b/test/input/func_w0401_package/thing1.py
@@ -0,0 +1,3 @@
+"""The first thing."""
+__revision__ = None
+THING1 = "I am thing1"
diff --git a/test/input/func_w0401_package/thing2.py b/test/input/func_w0401_package/thing2.py
new file mode 100644
index 0000000..836cd9c
--- /dev/null
+++ b/test/input/func_w0401_package/thing2.py
@@ -0,0 +1,7 @@
+"""The second thing."""
+__revision__ = None
+from .all_the_things import THING1
+
+THING2 = "I am thing2"
+THING1_PLUS_THING2 = "%s, plus %s" % (THING1, THING2)
+
diff --git a/test/input/func_w0402.py b/test/input/func_w0402.py
index 53752db..d2707ee 100644
--- a/test/input/func_w0402.py
+++ b/test/input/func_w0402.py
@@ -3,6 +3,9 @@
__revision__ = 0
from input.func_fixme import *
+# This is an unresolved import which still generates the wildcard-import
+# warning.
+from unknown.package import *
def abcd():
"""use imports"""
diff --git a/test/input/func_w0613.py b/test/input/func_w0613.py
index 4d51d10..1df71dd 100644
--- a/test/input/func_w0613.py
+++ b/test/input/func_w0613.py
@@ -14,7 +14,7 @@ class AAAA(object):
def method(self, arg):
"""dummy method"""
print self
- def __init__(self):
+ def __init__(self, *unused_args, **unused_kwargs):
pass
@classmethod
diff --git a/test/input/func_w0623_py30.py b/test/input/func_w0623_py30.py
index 163256b..f6c5f57 100644
--- a/test/input/func_w0623_py30.py
+++ b/test/input/func_w0623_py30.py
@@ -12,5 +12,5 @@ def some_function():
try:
{}["a"]
- except KeyError as some_function: # W0623
+ except KeyError, some_function: # W0623
pass
diff --git a/test/messages/func_arguments.txt b/test/messages/func_arguments.txt
index 33dbf56..642d212 100644
--- a/test/messages/func_arguments.txt
+++ b/test/messages/func_arguments.txt
@@ -1,11 +1,11 @@
-E: 18: No value passed for parameter 'first_argument' in function call
+E: 18: No value for argument 'first_argument' in function call
E: 19: Too many positional arguments for function call
-E: 21: No value passed for parameter 'third_argument' in function call
-E: 22: No value passed for parameter 'first_argument' in function call
-E: 22: No value passed for parameter 'second_argument' in function call
-E: 22: No value passed for parameter 'third_argument' in function call
+E: 21: No value for argument 'third_argument' in function call
+E: 22: No value for argument 'first_argument' in function call
+E: 22: No value for argument 'second_argument' in function call
+E: 22: No value for argument 'third_argument' in function call
E: 24: Too many positional arguments for function call
-E: 31: No value passed for parameter 'first_argument' in function call
-E: 31: Passing unexpected keyword argument 'bob' in function call
-E: 32: Passing unexpected keyword argument 'coin' in function call
-E: 34: Parameter 'one' passed as both positional and keyword argument
+E: 31: No value for argument 'first_argument' in function call
+E: 31: Unexpected keyword argument 'bob' in function call
+E: 32: Unexpected keyword argument 'coin' in function call
+E: 34: Argument 'one' passed by position and keyword in function call
diff --git a/test/messages/func_bad_slots.txt b/test/messages/func_bad_slots.txt
new file mode 100644
index 0000000..ec44646
--- /dev/null
+++ b/test/messages/func_bad_slots.txt
@@ -0,0 +1,4 @@
+E: 43:SecondBad: Invalid __slots__ object
+E: 47:ThirdBad: Invalid object '2' in __slots__, must contain only non empty strings
+E: 49:FourthBad: Invalid __slots__ object
+E: 53:FifthBad: Invalid object "''" in __slots__, must contain only non empty strings \ No newline at end of file
diff --git a/test/messages/func_ctor_arguments.txt b/test/messages/func_ctor_arguments.txt
new file mode 100644
index 0000000..b8d62b1
--- /dev/null
+++ b/test/messages/func_ctor_arguments.txt
@@ -0,0 +1,17 @@
+E: 35: No value for argument 'first_argument' in constructor call
+E: 36: Too many positional arguments for constructor call
+E: 38: No value for argument 'third_argument' in constructor call
+E: 39: No value for argument 'first_argument' in constructor call
+E: 39: No value for argument 'second_argument' in constructor call
+E: 39: No value for argument 'third_argument' in constructor call
+E: 41: Too many positional arguments for constructor call
+E: 46: No value for argument 'first_argument' in constructor call
+E: 46: Unexpected keyword argument 'bob' in constructor call
+E: 47: Unexpected keyword argument 'coin' in constructor call
+E: 49: Argument 'one' passed by position and keyword in constructor call
+E: 52: No value for argument 'first_argument' in constructor call
+E: 53: Too many positional arguments for constructor call
+E: 59: Too many positional arguments for constructor call
+E: 62: Too many positional arguments for constructor call
+E: 63: No value for argument 'first_argument' in constructor call
+E: 63: Unexpected keyword argument 'one' in constructor call
diff --git a/test/messages/func_docstring.txt b/test/messages/func_docstring.txt
index 2b0b191..932df5e 100644
--- a/test/messages/func_docstring.txt
+++ b/test/messages/func_docstring.txt
@@ -1,4 +1,8 @@
C: 1: Missing module docstring
-C: 5:function1: Missing function docstring
-C: 17:AAAA: Missing class docstring
-C: 33:AAAA.method1: Missing method docstring
+C: 5:function0: Empty function docstring
+C: 8:function1: Missing function docstring
+C: 20:AAAA: Missing class docstring
+C: 36:AAAA.method1: Missing method docstring
+C: 43:AAAA.method3: Empty method docstring
+C: 56:DDDD.method2: Empty method docstring
+C: 63:DDDD.method4: Missing method docstring
diff --git a/test/messages/func_e0205.txt b/test/messages/func_e0205.txt
index 494f3c3..c7402ce 100644
--- a/test/messages/func_e0205.txt
+++ b/test/messages/func_e0205.txt
@@ -1,2 +1,2 @@
-E: 14:Cdef.abcd: An attribute affected in input.func_e0205 line 10 hide this method
+E: 14:Cdef.abcd: An attribute defined in input.func_e0205 line 10 hides this method
diff --git a/test/messages/func_eval_used.txt b/test/messages/func_eval_used.txt
new file mode 100644
index 0000000..ab65307
--- /dev/null
+++ b/test/messages/func_eval_used.txt
@@ -0,0 +1,4 @@
+W: 5: Use of eval
+W: 6: Use of eval
+W: 8: Use of eval
+W: 12:func: Use of eval \ No newline at end of file
diff --git a/test/messages/func_kwoa_py30.txt b/test/messages/func_kwoa_py30.txt
index 5ccdf00..08dd8c5 100644
--- a/test/messages/func_kwoa_py30.txt
+++ b/test/messages/func_kwoa_py30.txt
@@ -1,5 +1,5 @@
-E: 10: Missing mandatory keyword argument 'foo'
+E: 10: Missing mandatory keyword argument 'foo' in function call
E: 10: Too many positional arguments for function call
-E: 12: Missing mandatory keyword argument 'foo'
+E: 12: Missing mandatory keyword argument 'foo' in function call
E: 12: Too many positional arguments for function call
W: 3:function: Redefining name 'foo' from outer scope (line 9)
diff --git a/test/messages/func_newstyle_super.txt b/test/messages/func_newstyle_super.txt
index a0192d7..b51869b 100644
--- a/test/messages/func_newstyle_super.txt
+++ b/test/messages/func_newstyle_super.txt
@@ -1,6 +1,7 @@
-C: 5:Aaaa: Old-style class defined.
-E: 7:Aaaa.hop: Use of super on an old style class
-E: 11:Aaaa.__init__: Use of super on an old style class
-E: 21:NewAaaa.__init__: Bad first argument 'object' given to super()
-E: 26:Py3kAaaa.__init__: Missing argument to super()
-E: 31:Py3kWrongSuper.__init__: Bad first argument 'NewAaaa' given to super()
+C: 7:Aaaa: Old-style class defined.
+E: 9:Aaaa.hop: Use of super on an old style class
+E: 13:Aaaa.__init__: Use of super on an old style class
+E: 23:NewAaaa.__init__: Bad first argument 'object' given to super()
+E: 28:Py3kAaaa.__init__: Missing argument to super()
+E: 33:Py3kWrongSuper.__init__: Bad first argument 'NewAaaa' given to super()
+E: 38:WrongNameRegression.__init__: Bad first argument 'Missing' given to super() \ No newline at end of file
diff --git a/test/messages/func_newstyle_super_py30.txt b/test/messages/func_newstyle_super_py30.txt
index 4a08d88..246d265 100644
--- a/test/messages/func_newstyle_super_py30.txt
+++ b/test/messages/func_newstyle_super_py30.txt
@@ -1,3 +1,4 @@
-C: 5:Aaaa: Old-style class defined.
-E: 21:NewAaaa.__init__: Bad first argument 'object' given to super()
-E: 31:Py3kWrongSuper.__init__: Bad first argument 'NewAaaa' given to super()
+C: 7:Aaaa: Old-style class defined.
+E: 23:NewAaaa.__init__: Bad first argument 'object' given to super()
+E: 33:Py3kWrongSuper.__init__: Bad first argument 'NewAaaa' given to super()
+E: 38:WrongNameRegression.__init__: Bad first argument 'Missing' given to super()
diff --git a/test/messages/func_return_yield_mix.txt b/test/messages/func_return_yield_mix_py_33.txt
index d81ce5c..d81ce5c 100644
--- a/test/messages/func_return_yield_mix.txt
+++ b/test/messages/func_return_yield_mix_py_33.txt
diff --git a/test/messages/func_unpacking_non_sequence.txt b/test/messages/func_unpacking_non_sequence.txt
index 55a7745..4099e29 100644
--- a/test/messages/func_unpacking_non_sequence.txt
+++ b/test/messages/func_unpacking_non_sequence.txt
@@ -6,3 +6,4 @@ 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_w0109.txt b/test/messages/func_w0109.txt
deleted file mode 100644
index bcb97aa..0000000
--- a/test/messages/func_w0109.txt
+++ /dev/null
@@ -1 +0,0 @@
-C: 6:function: Empty function docstring
diff --git a/test/messages/func_w0401_package.txt b/test/messages/func_w0401_package.txt
new file mode 100644
index 0000000..4b1145b
--- /dev/null
+++ b/test/messages/func_w0401_package.txt
@@ -0,0 +1 @@
+R: 1: Cyclic import (input.func_w0401_package.all_the_things -> input.func_w0401_package.thing2)
diff --git a/test/messages/func_w0402.txt b/test/messages/func_w0402.txt
index cf06fc4..453fc06 100644
--- a/test/messages/func_w0402.txt
+++ b/test/messages/func_w0402.txt
@@ -1,2 +1,3 @@
+F: 8: Unable to import 'unknown.package'
W: 5: Wildcard import input.func_fixme
-
+W: 8: Wildcard import unknown.package
diff --git a/test/smoketest.py b/test/smoketest.py
index 25f30fd..26e2c9a 100644
--- a/test/smoketest.py
+++ b/test/smoketest.py
@@ -9,7 +9,7 @@
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import sys
from os.path import join, dirname, abspath
@@ -39,7 +39,16 @@ class RunTC(TestCase):
try:
Run(args, reporter=reporter)
except SystemExit, ex:
- self.assertEqual(ex.code, code)
+ if reporter:
+ output = reporter.out.getvalue()
+ elif hasattr(out, 'getvalue'):
+ output = out.getvalue()
+ else:
+ output = None
+ msg = 'expected output status %s, got %s' % (code, ex.code)
+ if output is not None:
+ msg = '%s. Below pylint output: \n%s' % (msg, output)
+ self.assertEqual(ex.code, code, msg)
else:
self.fail('expected system exit')
finally:
diff --git a/test/test_base.py b/test/test_base.py
index 9bd3aa5..972c783 100644
--- a/test/test_base.py
+++ b/test/test_base.py
@@ -4,46 +4,46 @@ import re
from astroid import test_utils
from pylint.checkers import base
-from pylint.testutils import CheckerTestCase, Message
+from pylint.testutils import CheckerTestCase, Message, set_config
class DocstringTest(CheckerTestCase):
CHECKER_CLASS = base.DocStringChecker
- def testMissingDocstringModule(self):
+ def test_missing_docstring_module(self):
module = test_utils.build_module("")
with self.assertAddsMessages(Message('missing-docstring', node=module, args=('module',))):
self.checker.visit_module(module)
- def testEmptyDocstringModule(self):
+ def test_empty_docstring_module(self):
module = test_utils.build_module("''''''")
with self.assertAddsMessages(Message('empty-docstring', node=module, args=('module',))):
self.checker.visit_module(module)
- def testEmptyDocstringFunction(self):
+ def test_empty_docstring_function(self):
func = test_utils.extract_node("""
def func(tion):
pass""")
with self.assertAddsMessages(Message('missing-docstring', node=func, args=('function',))):
self.checker.visit_function(func)
- def testShortFunctionNoDocstring(self):
- self.checker.config.docstring_min_length = 2
+ @set_config(docstring_min_length=2)
+ def test_short_function_no_docstring(self):
func = test_utils.extract_node("""
def func(tion):
pass""")
with self.assertNoMessages():
self.checker.visit_function(func)
- def testFunctionNoDocstringByName(self):
- self.checker.config.docstring_min_length = 2
+ @set_config(docstring_min_length=2)
+ def test_function_no_docstring_by_name(self):
func = test_utils.extract_node("""
def __fun__(tion):
pass""")
with self.assertNoMessages():
self.checker.visit_function(func)
- def testClassNoDocstring(self):
+ def test_class_no_docstring(self):
klass = test_utils.extract_node("""
class Klass(object):
pass""")
@@ -57,11 +57,32 @@ class NameCheckerTest(CheckerTestCase):
'bad_names': set(),
}
- def testPropertyNames(self):
+ @set_config(include_naming_hint=True)
+ def test_naming_hint(self):
+ const = test_utils.extract_node("""
+ const = "CONSTANT" #@
+ """)
+ with self.assertAddsMessages(
+ Message('invalid-name', node=const.targets[0],
+ args=('constant', 'const', ' (hint: (([A-Z_][A-Z0-9_]*)|(__.*__))$)'))):
+ self.checker.visit_assname(const.targets[0])
+
+ @set_config(include_naming_hint=True,
+ const_name_hint='CONSTANT')
+ def test_naming_hint_configured_hint(self):
+ const = test_utils.extract_node("""
+ const = "CONSTANT" #@
+ """)
+ with self.assertAddsMessages(
+ Message('invalid-name', node=const.targets[0],
+ args=('constant', 'const', ' (hint: CONSTANT)'))):
+ self.checker.visit_assname(const.targets[0])
+
+ @set_config(attr_rgx=re.compile('[A-Z]+'))
+ def test_property_names(self):
# If a method is annotated with @property, it's name should
# match the attr regex. Since by default the attribute regex is the same
# as the method regex, we override it here.
- self.checker.config.attr_rgx = re.compile('[A-Z]+')
methods = test_utils.extract_node("""
import abc
@@ -82,11 +103,11 @@ class NameCheckerTest(CheckerTestCase):
self.checker.visit_function(methods[0])
self.checker.visit_function(methods[2])
with self.assertAddsMessages(Message('invalid-name', node=methods[1],
- args=('attribute', 'bar'))):
+ args=('attribute', 'bar', ''))):
self.checker.visit_function(methods[1])
- def testPropertySetters(self):
- self.checker.config.attr_rgx = re.compile('[A-Z]+')
+ @set_config(attr_rgx=re.compile('[A-Z]+'))
+ def test_property_setters(self):
method = test_utils.extract_node("""
class FooClass(object):
@property
@@ -99,7 +120,7 @@ class NameCheckerTest(CheckerTestCase):
with self.assertNoMessages():
self.checker.visit_function(method)
- def testModuleLevelNames(self):
+ def test_module_level_names(self):
assign = test_utils.extract_node("""
import collections
Class = collections.namedtuple("a", ("b", "c")) #@
@@ -130,6 +151,73 @@ class NameCheckerTest(CheckerTestCase):
self.checker.visit_assname(assign.targets[0])
+class MultiNamingStyleTest(CheckerTestCase):
+ CHECKER_CLASS = base.NameChecker
+
+ MULTI_STYLE_RE = re.compile('(?:(?P<UP>[A-Z]+)|(?P<down>[a-z]+))$')
+
+ @set_config(class_rgx=MULTI_STYLE_RE)
+ def test_multi_name_detection_first(self):
+ classes = test_utils.extract_node("""
+ class CLASSA(object): #@
+ pass
+ class classb(object): #@
+ pass
+ class CLASSC(object): #@
+ pass
+ """)
+ with self.assertAddsMessages(Message('invalid-name', node=classes[1], args=('class', 'classb', ''))):
+ for cls in classes:
+ self.checker.visit_class(cls)
+
+ @set_config(class_rgx=MULTI_STYLE_RE)
+ def test_multi_name_detection_first_invalid(self):
+ classes = test_utils.extract_node("""
+ class class_a(object): #@
+ pass
+ class classb(object): #@
+ pass
+ class CLASSC(object): #@
+ pass
+ """)
+ with self.assertAddsMessages(Message('invalid-name', node=classes[0], args=('class', 'class_a', '')),
+ Message('invalid-name', node=classes[2], args=('class', 'CLASSC', ''))):
+ for cls in classes:
+ self.checker.visit_class(cls)
+
+ @set_config(method_rgx=MULTI_STYLE_RE,
+ function_rgx=MULTI_STYLE_RE,
+ name_group=('function:method',))
+ def test_multi_name_detection_group(self):
+ function_defs = test_utils.extract_node("""
+ class First(object):
+ def func(self): #@
+ pass
+
+ def FUNC(): #@
+ pass
+ """, module_name='test')
+ with self.assertAddsMessages(Message('invalid-name', node=function_defs[1], args=('function', 'FUNC', ''))):
+ for func in function_defs:
+ self.checker.visit_function(func)
+
+ @set_config(function_rgx=re.compile('(?:(?P<ignore>FOO)|(?P<UP>[A-Z]+)|(?P<down>[a-z]+))$'))
+ def test_multi_name_detection_exempt(self):
+ function_defs = test_utils.extract_node("""
+ def FOO(): #@
+ pass
+ def lower(): #@
+ pass
+ def FOO(): #@
+ pass
+ def UPPER(): #@
+ pass
+ """)
+ with self.assertAddsMessages(Message('invalid-name', node=function_defs[3], args=('function', 'UPPER', ''))):
+ for func in function_defs:
+ self.checker.visit_function(func)
+
+
if __name__ == '__main__':
from logilab.common.testlib import unittest_main
unittest_main()
diff --git a/test/test_format.py b/test/test_format.py
index b682671..9494f75 100644
--- a/test/test_format.py
+++ b/test/test_format.py
@@ -9,7 +9,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
""" Copyright (c) 2000-2011 LOGILAB S.A. (Paris, FRANCE).
http://www.logilab.fr/ -- mailto:contact@logilab.fr
@@ -27,7 +27,7 @@ from astroid import test_utils
from pylint.checkers.format import *
-from pylint.testutils import CheckerTestCase, Message
+from pylint.testutils import CheckerTestCase, Message, set_config
def tokenize_str(code):
@@ -171,8 +171,8 @@ class CheckSpaceTest(CheckerTestCase):
with self.assertNoMessages():
self.checker.process_tokens(tokenize_str('(a,)\n'))
+ @set_config(no_space_check=[])
def testTrailingCommaBad(self):
- self.checker.config.no_space_check = []
with self.assertAddsMessages(
Message('C0326', line=1,
args=('No', 'allowed', 'before', 'bracket', '(a, )\n ^'))):
diff --git a/test/test_func.py b/test/test_func.py
index 5590dc9..807878a 100644
--- a/test/test_func.py
+++ b/test/test_func.py
@@ -12,7 +12,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""functional/non regression tests for pylint"""
import unittest
@@ -25,7 +25,7 @@ from os.path import abspath, dirname, join
from logilab.common import testlib
from pylint.testutils import (make_tests, LintTestUsingModule, LintTestUsingFile,
- cb_test_gen, linter, test_reporter)
+ LintTestUpdate, cb_test_gen, linter, test_reporter)
PY3K = sys.version_info >= (3, 0)
@@ -75,24 +75,19 @@ class LintBuiltinModuleTest(LintTestUsingModule):
def test_functionality(self):
self._test(['sys'])
-# Callbacks
-
-base_cb_file = cb_test_gen(LintTestUsingFile)
-
-def cb_file(*args):
- if MODULES_ONLY:
- return None
- else:
- return base_cb_file(*args)
-
-callbacks = [cb_test_gen(LintTestUsingModule),
- cb_file]
-
-# Gen tests
def gen_tests(filter_rgx):
+ if UPDATE:
+ callbacks = [cb_test_gen(LintTestUpdate)]
+ else:
+ callbacks = [cb_test_gen(LintTestUsingModule)]
+ if not MODULES_ONLY:
+ callbacks.append(cb_test_gen(LintTestUsingFile))
tests = make_tests(INPUT_DIR, MSG_DIR, filter_rgx, callbacks)
+ if UPDATE:
+ return tests
+
if filter_rgx:
is_to_run = re.compile(filter_rgx).search
else:
@@ -113,19 +108,22 @@ def gen_tests(filter_rgx):
FILTER_RGX = None
MODULES_ONLY = False
+UPDATE = False
def suite():
return testlib.TestSuite([unittest.makeSuite(test, suiteClass=testlib.TestSuite)
for test in gen_tests(FILTER_RGX)])
-del LintTestUsingModule
-del LintTestUsingFile
if __name__=='__main__':
if '-m' in sys.argv:
MODULES_ONLY = True
sys.argv.remove('-m')
+ if '-u' in sys.argv:
+ UPDATE = True
+ sys.argv.remove('-u')
+
if len(sys.argv) > 1:
FILTER_RGX = sys.argv[1]
del sys.argv[1]
diff --git a/test/test_logging.py b/test/test_logging.py
new file mode 100644
index 0000000..fe7e638
--- /dev/null
+++ b/test/test_logging.py
@@ -0,0 +1,49 @@
+# Copyright 2014 Google Inc. All Rights Reserved.
+"""
+Unittest for the logging checker.
+"""
+from logilab.common.testlib import unittest_main
+from astroid import test_utils
+
+from pylint.checkers import logging
+
+from pylint.testutils import CheckerTestCase, Message, set_config
+
+
+class LoggingModuleDetectionTest(CheckerTestCase):
+ CHECKER_CLASS = logging.LoggingChecker
+
+ def test_detects_standard_logging_module(self):
+ stmts = test_utils.extract_node("""
+ import logging #@
+ logging.warn('%s' % '%s') #@
+ """)
+ self.checker.visit_module(None)
+ self.checker.visit_import(stmts[0])
+ with self.assertAddsMessages(Message('W1201', node=stmts[1])):
+ self.checker.visit_callfunc(stmts[1])
+
+ def test_detects_renamed_standard_logging_module(self):
+ stmts = test_utils.extract_node("""
+ import logging as blogging #@
+ blogging.warn('%s' % '%s') #@
+ """)
+ self.checker.visit_module(None)
+ self.checker.visit_import(stmts[0])
+ with self.assertAddsMessages(Message('W1201', node=stmts[1])):
+ self.checker.visit_callfunc(stmts[1])
+
+ @set_config(logging_modules=['logging', 'my.logging'])
+ def test_nonstandard_logging_module(self):
+ stmts = test_utils.extract_node("""
+ from my import logging as blogging #@
+ blogging.warn('%s' % '%s') #@
+ """)
+ self.checker.visit_module(None)
+ self.checker.visit_import(stmts[0])
+ with self.assertAddsMessages(Message('W1201', node=stmts[1])):
+ self.checker.visit_callfunc(stmts[1])
+
+
+if __name__ == '__main__':
+ unittest_main()
diff --git a/test/test_misc.py b/test/test_misc.py
index e71b26e..5a10936 100644
--- a/test/test_misc.py
+++ b/test/test_misc.py
@@ -11,7 +11,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
Tests for the misc checker.
"""
@@ -23,7 +23,7 @@ import contextlib
from logilab.common.testlib import unittest_main
from astroid import test_utils
from pylint.checkers import misc, variables
-from pylint.testutils import CheckerTestCase, Message, linter
+from pylint.testutils import CheckerTestCase, Message, linter, set_config
@contextlib.contextmanager
@@ -58,8 +58,8 @@ class FixmeTest(CheckerTestCase):
Message(msg_id='W0511', line=2, args=u'FIXME')):
self.checker.process_module(module)
+ @set_config(notes=[])
def test_empty_fixme_regex(self):
- self.checker.config.notes = []
with create_file_backed_module(
"""a = 1
# fixme
diff --git a/test/test_regr.py b/test/test_regr.py
index 93aef06..0349481 100644
--- a/test/test_regr.py
+++ b/test/test_regr.py
@@ -12,7 +12,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""non regression tests for pylint, which requires a too specific configuration
to be incorporated in the automatic functional test framework
"""
diff --git a/test/test_utils.py b/test/test_utils.py
index fc35c3e..d8c4c67 100644
--- a/test/test_utils.py
+++ b/test/test_utils.py
@@ -10,7 +10,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
from logilab.common.testlib import TestCase
from astroid import test_utils
diff --git a/test/unittest_checkers_utils.py b/test/unittest_checkers_utils.py
index f7b0e80..d8ebd3b 100644
--- a/test/unittest_checkers_utils.py
+++ b/test/unittest_checkers_utils.py
@@ -12,7 +12,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""test the pylint.checkers.utils module
"""
diff --git a/test/unittest_lint.py b/test/unittest_lint.py
index 44278e2..a7213bd 100644
--- a/test/unittest_lint.py
+++ b/test/unittest_lint.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE).
+# Copyright (c) 2003-2014 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
@@ -10,7 +10,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import sys
import os
@@ -49,23 +49,6 @@ class GetNoteMessageTC(TestCase):
HERE = abspath(dirname(__file__))
INPUTDIR = join(HERE, 'input')
-class RunTC(TestCase):
-
- def _test_run(self, args, exit_code=1, no_exit_fail=True):
- sys.stdout = sys.sterr = StringIO()
- try:
- try:
- Run(args)
- except SystemExit, ex:
- print sys.stdout.getvalue()
- self.assertEqual(ex.code, exit_code)
- else:
- if no_exit_fail:
- self.fail()
- finally:
- sys.stdout = sys.__stdout__
- sys.stderr = sys.__stderr__
-
class PyLinterTC(TestCase):
@@ -82,7 +65,7 @@ class PyLinterTC(TestCase):
# logilab.common.textutils.normalize_text
# uses os.linesep, which will
# not properly compare with triple
- # quoted multilines used in these tests
+ # quoted multilines used in these tests
self.assertMultiLineEqual(desc,
msg.format_help(checkerref=checkerref)
.replace('\r\n', '\n'))
@@ -111,13 +94,13 @@ class PyLinterTC(TestCase):
msg = build_message_def(self.linter._checkers['typecheck'][0],
'E1122', checkers.typecheck.MSGS['E1122'])
self._compare_messages(
- ''':duplicate-keyword-arg (E1122): *Duplicate keyword argument %r in function call*
+ ''':duplicate-keyword-arg (E1122): *Duplicate keyword argument %r in %s 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, checkerref=True)
self._compare_messages(
- ''':duplicate-keyword-arg (E1122): *Duplicate keyword argument %r in function call*
+ ''':duplicate-keyword-arg (E1122): *Duplicate keyword argument %r in %s 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, checkerref=False)
@@ -381,9 +364,9 @@ class PyLinterTC(TestCase):
def test_add_renamed_message(self):
self.linter.add_renamed_message('C9999', 'old-bad-name', 'invalid-name')
- self.assertEqual('invalid-name',
+ self.assertEqual('invalid-name',
self.linter.check_message_id('C9999').symbol)
- self.assertEqual('invalid-name',
+ self.assertEqual('invalid-name',
self.linter.check_message_id('old-bad-name').symbol)
def test_renamed_message_register(self):
@@ -391,11 +374,16 @@ class PyLinterTC(TestCase):
msgs = {'W1234': ('message', 'msg-symbol', 'msg-description',
{'old_names': [('W0001', 'old-symbol')]})}
self.linter.register_messages(Checker())
- self.assertEqual('msg-symbol',
+ self.assertEqual('msg-symbol',
self.linter.check_message_id('W0001').symbol)
- self.assertEqual('msg-symbol',
+ self.assertEqual('msg-symbol',
self.linter.check_message_id('old-symbol').symbol)
-
+
+ def test_init_hooks_called_before_load_plugins(self):
+ self.assertRaises(RuntimeError,
+ Run, ['--load-plugins', 'unexistant', '--init-hooks', 'raise RuntimeError'])
+ self.assertRaises(RuntimeError,
+ Run, ['--init-hooks', 'raise RuntimeError', '--load-plugins', 'unexistant'])
class ConfigTC(TestCase):
@@ -473,7 +461,6 @@ class ConfigTC(TestCase):
os.chdir(HERE)
rmtree(chroot)
-
def test_pylintrc_parentdir_no_package(self):
chroot = tempfile.mkdtemp()
@@ -509,7 +496,7 @@ class PreprocessOptionsTC(TestCase):
def _callback(self, name, value):
self.args.append((name, value))
- def test_preprocess(self):
+ def test_value_equal(self):
self.args = []
preprocess_options(['--foo', '--bar=baz', '--qu=ux'],
{'foo' : (self._callback, False),
@@ -517,7 +504,14 @@ class PreprocessOptionsTC(TestCase):
self.assertEqual(
[('foo', None), ('qu', 'ux')], self.args)
- def test_preprocessing_error(self):
+ def test_value_space(self):
+ self.args = []
+ preprocess_options(['--qu', 'ux'],
+ {'qu' : (self._callback, True)})
+ self.assertEqual(
+ [('qu', 'ux')], self.args)
+
+ def test_error_missing_expected_value(self):
self.assertRaises(
ArgumentPreprocessingError,
preprocess_options,
@@ -529,6 +523,13 @@ class PreprocessOptionsTC(TestCase):
['--foo', '--bar'],
{'bar' : (None, True)})
+ def test_error_unexpected_value(self):
+ self.assertRaises(
+ ArgumentPreprocessingError,
+ preprocess_options,
+ ['--foo', '--bar=spam', '--qu=ux'],
+ {'bar' : (None, False)})
+
if __name__ == '__main__':
unittest_main()
diff --git a/test/unittest_pyreverse_diadefs.py b/test/unittest_pyreverse_diadefs.py
index a42d73a..0f67e36 100644
--- a/test/unittest_pyreverse_diadefs.py
+++ b/test/unittest_pyreverse_diadefs.py
@@ -12,7 +12,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
unittest for the extensions.diadefslib modules
"""
diff --git a/test/unittest_pyreverse_writer.py b/test/unittest_pyreverse_writer.py
index b850679..19d94ab 100644
--- a/test/unittest_pyreverse_writer.py
+++ b/test/unittest_pyreverse_writer.py
@@ -12,7 +12,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""
unittest for visitors.diadefs and extensions.diadefslib modules
"""
diff --git a/test/unittest_reporting.py b/test/unittest_reporting.py
index 8fda31d..3dd0d0a 100644
--- a/test/unittest_reporting.py
+++ b/test/unittest_reporting.py
@@ -10,7 +10,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
import os
from os.path import join, dirname, abspath
diff --git a/testutils.py b/testutils.py
index a61fa7f..1658e10 100644
--- a/testutils.py
+++ b/testutils.py
@@ -12,17 +12,19 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""functional/non regression tests for pylint"""
+from __future__ import with_statement
import collections
import contextlib
+import functools
import sys
import re
from glob import glob
from os import linesep
-from os.path import abspath, dirname, join, basename, splitext
+from os.path import abspath, basename, dirname, isdir, join, splitext
from cStringIO import StringIO
from logilab.common import testlib
@@ -75,7 +77,8 @@ def get_tests_info(input_dir, msg_dir, prefix, suffix):
if py_rest.isdigit() and SYS_VERS_STR >= py_rest:
break
else:
- outfile = None
+ # This will provide an error message indicating the missing filename.
+ outfile = join(msg_dir, fbase + '.txt')
result.append((infile, outfile))
return result
@@ -146,6 +149,23 @@ class UnittestLinter(object):
def add_stats(self, **kwargs):
for name, value in kwargs.iteritems():
self.stats[name] = value
+ return self.stats
+
+
+def set_config(**kwargs):
+ """Decorator for setting config values on a checker."""
+ def _Wrapper(fun):
+ @functools.wraps(fun)
+ def _Forward(self):
+ for key, value in kwargs.iteritems():
+ setattr(self.checker.config, key, value)
+ if isinstance(self, CheckerTestCase):
+ # reopen checker in case, it may be interested in configuration change
+ self.checker.open()
+ fun(self)
+
+ return _Forward
+ return _Wrapper
class CheckerTestCase(testlib.TestCase):
@@ -159,7 +179,6 @@ class CheckerTestCase(testlib.TestCase):
for key, value in self.CONFIG.iteritems():
setattr(self.checker.config, key, value)
self.checker.open()
- self.checker.stats = self.linter.stats
@contextlib.contextmanager
def assertNoMessages(self):
@@ -236,6 +255,9 @@ class LintTestUsingModule(testlib.TestCase):
for name, file in self.depends]
self._test(tocheck)
+ def _check_result(self, got):
+ self.assertMultiLineEqual(self._get_expected(), got)
+
def _test(self, tocheck):
if INFO_TEST_RGX.match(self.module):
self.linter.enable('I')
@@ -250,29 +272,44 @@ class LintTestUsingModule(testlib.TestCase):
print ex
ex.__str__ = exception_str
raise
- got = self.linter.reporter.finalize()
- self.assertMultiLineEqual(self._get_expected(), got)
+ self._check_result(self.linter.reporter.finalize())
+ def _has_output(self):
+ return not self.module.startswith('func_noerror_')
def _get_expected(self):
- if self.module.startswith('func_noerror_'):
- expected = ''
+ if self._has_output() and self.output:
+ with open(self.output, 'U') as fobj:
+ return fobj.read().strip() + '\n'
else:
- output = open(self.output, 'U')
- expected = output.read().strip() + '\n'
- output.close()
- return expected
+ return ''
class LintTestUsingFile(LintTestUsingModule):
_TEST_TYPE = 'file'
def test_functionality(self):
- tocheck = [join(self.INPUT_DIR, self.module + '.py')]
+ importable = join(self.INPUT_DIR, self.module)
+ # python also prefers packages over simple modules.
+ if not isdir(importable):
+ importable += '.py'
+ tocheck = [importable]
if self.depends:
tocheck += [join(self.INPUT_DIR, name) for name, _file in self.depends]
self._test(tocheck)
+class LintTestUpdate(LintTestUsingModule):
+
+ _TEST_TYPE = 'update'
+
+ def _check_result(self, got):
+ if self._has_output():
+ if got != self._get_expected():
+ if not self.output:
+ self.output = join(self.MSG_DIR, '%s.txt' % (self.module,))
+ with open(self.output, 'w') as fobj:
+ fobj.write(got)
+
# Callback
def cb_test_gen(base_class):
@@ -299,8 +336,9 @@ def make_tests(input_dir, msg_dir, filter_rgx, callbacks):
else:
is_to_run = lambda x: 1
tests = []
- for module_file, messages_file in get_tests_info(input_dir, msg_dir,
- 'func_', '.py'):
+ for module_file, messages_file in (
+ get_tests_info(input_dir, msg_dir, 'func_', '')
+ ):
if not is_to_run(module_file):
continue
base = module_file.replace('func_', '').replace('.py', '')
diff --git a/tox.ini b/tox.ini
index 9c19dac..f22de6e 100644
--- a/tox.ini
+++ b/tox.ini
@@ -6,5 +6,5 @@ envlist = py27, py33
[testenv]
deps =
logilab-common
- astroid
-commands = pytest -t {envsitepackagesdir}/pylint/test/ \ No newline at end of file
+ hg+https://bitbucket.org/logilab/astroid/
+commands = pytest -t {envsitepackagesdir}/pylint/test/
diff --git a/utils.py b/utils.py
index 59e22a5..e1cb3a8 100644
--- a/utils.py
+++ b/utils.py
@@ -12,7 +12,7 @@
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""some various utilities and helper classes, most of them used in the
main pylint class
"""
@@ -247,7 +247,7 @@ class MessagesHandlerMixIn(object):
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):
+ def disable(self, msgid, scope='package', line=None, ignore_unknown=False):
"""don't output message of the given id"""
assert scope in ('package', 'module')
# handle disable=all by disabling all categories
@@ -272,8 +272,15 @@ class MessagesHandlerMixIn(object):
if msgid.lower().startswith('rp'):
self.disable_report(msgid)
return
- # msgid is a symbolic or numeric msgid.
- msg = self.check_message_id(msgid)
+
+ try:
+ # msgid is a symbolic or numeric msgid.
+ msg = self.check_message_id(msgid)
+ except UnknownMessage:
+ if ignore_unknown:
+ return
+ raise
+
if scope == 'module':
assert line > 0
try:
@@ -290,7 +297,7 @@ class MessagesHandlerMixIn(object):
self.config.disable_msg = [mid for mid, val in msgs.iteritems()
if not val]
- def enable(self, msgid, scope='package', line=None):
+ def enable(self, msgid, scope='package', line=None, ignore_unknown=False):
"""reenable message of the given id"""
assert scope in ('package', 'module')
catid = category_id(msgid)
@@ -309,8 +316,15 @@ class MessagesHandlerMixIn(object):
if msgid.lower().startswith('rp'):
self.enable_report(msgid)
return
- # msgid is a symbolic or numeric msgid.
- msg = self.check_message_id(msgid)
+
+ try:
+ # msgid is a symbolic or numeric msgid.
+ msg = self.check_message_id(msgid)
+ except UnknownMessage:
+ if ignore_unknown:
+ return
+ raise
+
if scope == 'module':
assert line > 0
try:
@@ -400,6 +414,8 @@ class MessagesHandlerMixIn(object):
"""
msg_info = self.check_message_id(msg_descr)
msgid = msg_info.msgid
+ # backward compatibility, message may not have a symbol
+ symbol = msg_info.symbol or msgid
# Fatal messages and reports are special, the node/scope distinction
# does not apply to them.
if msgid[0] not in _SCOPE_EXEMPT:
@@ -427,9 +443,9 @@ class MessagesHandlerMixIn(object):
self.stats[msg_cat] += 1
self.stats['by_module'][self.current_name][msg_cat] += 1
try:
- self.stats['by_msg'][msg_info.symbol] += 1
+ self.stats['by_msg'][symbol] += 1
except KeyError:
- self.stats['by_msg'][msg_info.symbol] = 1
+ self.stats['by_msg'][symbol] = 1
# expand message ?
msg = msg_info.msg
if args: