summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog32
-rw-r--r--checkers/__init__.py14
-rw-r--r--checkers/base.py27
-rw-r--r--checkers/classes.py9
-rw-r--r--checkers/design_analysis.py22
-rw-r--r--checkers/exceptions.py34
-rw-r--r--checkers/format.py11
-rw-r--r--checkers/imports.py6
-rw-r--r--checkers/misc.py2
-rw-r--r--checkers/newstyle.py8
-rw-r--r--checkers/similar.py17
-rw-r--r--checkers/typecheck.py116
-rw-r--r--checkers/utils.py42
-rw-r--r--doc/makefile7
-rw-r--r--lint.py196
-rw-r--r--reporters/html.py11
-rw-r--r--reporters/text.py24
-rw-r--r--setup.py5
-rw-r--r--test/func_test.py2
-rw-r--r--test/input/func_base_stmt_without_effect.py12
-rw-r--r--test/input/func_block_disable_msg.py890
-rw-r--r--test/messages/func_base_stmt_without_effect.txt9
-rw-r--r--test/messages/func_dotted_ancestor.txt2
-rw-r--r--test/messages/func_r0903.txt2
-rw-r--r--test/messages/func_w0302.txt2
-rw-r--r--utils.py90
26 files changed, 1296 insertions, 296 deletions
diff --git a/ChangeLog b/ChangeLog
index 846d829..3b0e613 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,10 +2,32 @@ ChangeLog for PyLint
====================
--
- * new W0212 message for access to protected member from client code
- * new W0105 message extracted from W0104 (statement seems to have no effect)
- with string
+ * usability changes:
+ - parseable, html and color options are now handled by a single
+ output-format option
+ - enable-<checkerid> and disable-all options are now handled by
+ two (exclusive) enable-checker and disable-checker options
+ taking a comma separated list of checker names as value
+ - renamed debug-mode option to errors-only
+ * started a reference user manual
+
+ * new W0212 message for access to protected member from client code
+ (close #14081)
+
+ * new W0105 and W0106 messages extracted from W0104 (statement seems
+ to have no effect) respectivly when the statement is actually string
+ (that's sometimes used instead of comments for documentation) or an
+ empty statement generated by a useless semicolumn
+
+ * reclassified W0302 to C0302
+
+ * fix so that global messages are not anymore connected to the last
+ analyzed module (close #10106)
+ * fix some bugs related to local disabling of messages
+ * fix cr/lf pb when generating the rc file on windows platforms
+
+
2006-04-19 -- 0.11.0
* fix crash caused by the exceptions checker in some case
* fix some E1101 false positive with abstract method or classes defining
@@ -18,8 +40,8 @@ ChangeLog for PyLint
* added an option in the similarity checker to ignore docstrings, enabled
by default
- * included patch Benjamin Niemann to allow block level enabling/disabling
- of messages
+ * included patch from Benjamin Niemann to allow block level
+ enabling/disabling of messages
2006-03-06 -- 0.10.0
diff --git a/checkers/__init__.py b/checkers/__init__.py
index fdf015a..de8f6fa 100644
--- a/checkers/__init__.py
+++ b/checkers/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE).
+# Copyright (c) 2003-2006 LOGILAB S.A. (Paris, FRANCE).
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This program is free software; you can redistribute it and/or modify it under
@@ -26,6 +26,7 @@ Base id of standard checkers (used in msg and report ids):
08: similar
09: design_analysis
10: newstyle
+11: typecheck
"""
__revision__ = "$Id: __init__.py,v 1.21 2005-11-21 23:08:11 syt Exp $"
@@ -75,13 +76,6 @@ class BaseChecker(OptionsProviderMixIn, ASTWalker):
"""
ASTWalker.__init__(self, self)
self.name = self.name.lower()
- if self.may_be_disabled:
- opt_name = 'enable-' + self.name
- self.options = (
- (opt_name,
- {'type' : 'yn', 'default' : 1, 'metavar': '<y_or_n>',
- 'help' : "Enable / disable this checker"})
- ,) + self.options
OptionsProviderMixIn.__init__(self)
self.linter = linter
@@ -92,14 +86,14 @@ class BaseChecker(OptionsProviderMixIn, ASTWalker):
def is_enabled(self):
"""return true if the checker is enabled"""
opt = 'enable_' + self.name
- return getattr(self.config, opt, 1)
+ return getattr(self.config, opt, True)
def enable(self, enable):
"""enable / disable this checker if true / false is given
it false values has no effect if the checker can't be disabled
"""
- if self.may_be_disabled:
+ if enable or self.may_be_disabled:
setattr(self.config, 'enable_' + self.name, enable)
def package_dir(self):
diff --git a/checkers/base.py b/checkers/base.py
index 8b103af..d8d0a9e 100644
--- a/checkers/base.py
+++ b/checkers/base.py
@@ -40,6 +40,7 @@ NO_REQUIRED_DOC_RGX = re.compile('__.*__')
del re
def in_loop(node):
+ """return True if the node is inside a kind of for loop"""
parent = node.parent
while parent is not None:
if isinstance(parent, (astng.For, astng.ListComp, astng.GenExpr)):
@@ -118,6 +119,9 @@ MSGS = {
has no effect). This is a particular case of W0104 with its \
own message so you can easily disable it if you\'re using \
those strings as documentation, instead of comments.'),
+ 'W0106': ('Unnecessary semi-column',
+ 'Used when a statement is endend by a semi-colon (";"), which \
+ isn\'t necessary (that\'s python, not C ;)'),
'W0122': ('Use of the exec statement',
'Used when you use the "exec" statement, to discourage its \
@@ -308,11 +312,24 @@ functions, methods
self._check_name('attr', attr, anodes[0])
def visit_discard(self, node):
- """check for statement without effect"""
- if isinstance(node.expr, astng.Const) and \
- isinstance(node.expr.value, basestring):
- self.add_message('W0105', node=node)
- elif not isinstance(node.expr, astng.CallFunc):
+ """check for various kind of statements without effect"""
+ expr = node.expr
+ if isinstance(node.expr, astng.Const):
+ # XXX lineno maybe dynamically set incidently
+ if expr.value is None and expr.lineno is None:
+ # const None node with lineno to None are inserted
+ # on unnecessary semi-column
+ # XXX navigate to get a correct lineno
+ brothers = list(node.parent.getChildNodes())
+ previoussibling = brothers[brothers.index(node)-1]
+ self.add_message('W0106', node=previoussibling)
+ return
+ if isinstance(expr.value, basestring):
+ # tread string statement in a separated message
+ self.add_message('W0105', node=node)
+ return
+ # ignore if this is a function call (can't predicate side effects)
+ if not isinstance(node.expr, astng.CallFunc):
self.add_message('W0104', node=node)
def visit_function(self, node):
diff --git a/checkers/classes.py b/checkers/classes.py
index 3a9e468..c1da969 100644
--- a/checkers/classes.py
+++ b/checkers/classes.py
@@ -291,6 +291,8 @@ instance attributes.'}
"""check that accessed members are defined"""
# XXX refactor, probably much simpler now that E0201 is in type checker
for attr, nodes in accessed.items():
+ # deactivate "except doesn't do anything", that's expected
+ # pylint: disable-msg=W0704
## # is it a builtin attribute ?
## if attr in ('__dict__', '__class__', '__doc__'):
## # FIXME: old class object doesn't have __class__
@@ -312,9 +314,10 @@ instance attributes.'}
# is it an instance attribute ?
try:
def_nodes = node.instance_attr(attr) # XXX
- instance_attribute = True
+ #instance_attribute = True
except astng.NotFoundError:
- instance_attribute = False
+ pass
+ #instance_attribute = False
else:
if len(def_nodes) == 1:
def_node = def_nodes[0]
@@ -443,7 +446,7 @@ instance attributes.'}
del to_call[klass]
except KeyError:
self.add_message('W0233', node=expr, args=klass.name)
- except astng.InferenceError, ex:
+ except astng.InferenceError:
continue
for klass in to_call.keys():
if klass.name == 'object':
diff --git a/checkers/design_analysis.py b/checkers/design_analysis.py
index 1d51e74..7623979 100644
--- a/checkers/design_analysis.py
+++ b/checkers/design_analysis.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE).
+# Copyright (c) 2003-2006 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
@@ -40,18 +40,24 @@ def class_is_abstract(klass):
MSGS = {
'R0901': ('Too many ancestors (%s/%s)',
- 'Used when class has too many parent classes.'),
+ 'Used when class has too many parent classes, try to reduce \
+ this to get a more simple (and so easier to use) class.'),
'R0902': ('Too many instance attributes (%s/%s)',
- 'Used when class has too many instance attributes.'),
- 'R0903': ('Not enough public methods (%s/%s)',
- 'Used when class has not enough public methods.'),
+ 'Used when class has too many instance attributes, try to reduce \
+ this to get a more simple (and so easier to use) class.'),
+ 'R0903': ('To few public methods (%s/%s)',
+ 'Used when class has to few public methods, so be sure it\'s \
+ really worth it.'),
'R0904': ('Too many public methods (%s/%s)',
- 'Used when class has too many public methods.'),
+ 'Used when class has too many public methods, try to reduce \
+ this to get a more simple (and so easier to use) class.'),
'R0911': ('Too many return statements (%s/%s)',
- 'Used when a function or method has too many return statement.'),
+ 'Used when a function or method has too many return statement, \
+ making it hard to follow.'),
'R0912': ('Too many branches (%s/%s)',
- 'Used when a function or method has too many branches.'),
+ 'Used when a function or method has too many branches, \
+ making it hard to follow.'),
'R0913': ('Too many arguments (%s/%s)',
'Used when a function or method takes too many arguments.'),
'R0914': ('Too many local variables (%s/%s)',
diff --git a/checkers/exceptions.py b/checkers/exceptions.py
index 0f5fddc..44510de 100644
--- a/checkers/exceptions.py
+++ b/checkers/exceptions.py
@@ -1,3 +1,5 @@
+# Copyright (c) 2003-2006 LOGILAB S.A. (Paris, FRANCE).
+# http://www.logilab.fr/ -- mailto:contact@logilab.fr
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
@@ -10,10 +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.
-""" Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE).
- http://www.logilab.fr/ -- mailto:contact@logilab.fr
-
-exceptions checkers for Python code
+"""exceptions handling (raising, catching, exceptions classes) checker
"""
__revision__ = '$Id: exceptions.py,v 1.27 2006-03-08 15:53:42 syt Exp $'
@@ -23,7 +22,7 @@ from logilab import astng
from logilab.astng.inference import unpack_infer
from pylint.checkers import BaseChecker
-from pylint.checkers.utils import is_empty
+from pylint.checkers.utils import is_empty, is_raising
from pylint.interfaces import IASTNGChecker
MSGS = {
@@ -51,14 +50,6 @@ MSGS = {
'Used when a variable used to raise an exception is initially \
assigned to a value which can\'t be used as an exception.'),
}
-
-def is_raising(stmt):
- """return true if the given statement node raise an exception
- """
- for node in stmt.nodes:
- if isinstance(node, astng.Raise):
- return 1
- return 0
class ExceptionsChecker(BaseChecker):
"""checks for
@@ -97,14 +88,12 @@ class ExceptionsChecker(BaseChecker):
value = unpack_infer(expr).next()
except astng.InferenceError:
return
- if value is astng.YES:
- return
- # must to be carefull since Const, Dict, .. inherit from
+ # have to be careful since Const, Dict, .. inherit from
# Instance now
- if isinstance(value, (astng.Class, astng.Module)):
- return
- if isinstance(value, astng.Instance) and \
- isinstance(value._proxied, astng.Class):
+ if (value is astng.YES or
+ isinstance(value, (astng.Class, astng.Module)) or
+ (isinstance(value, astng.Instance) and
+ isinstance(value._proxied, astng.Class))):
return
if isinstance(value, astng.Const) and \
(value.value is None or
@@ -145,10 +134,9 @@ class ExceptionsChecker(BaseChecker):
except astng.InferenceError:
continue
for exc in excs:
- if exc is astng.YES:
+ # XXX skip other non class nodes
+ if exc is astng.YES or not isinstance(exc, astng.Class):
continue
- if not isinstance(exc, astng.Class):
- continue # XXX
exc_ancestors = [anc for anc in exc.ancestors()
if isinstance(anc, astng.Class)]
for previous_exc in exceptions_classes:
diff --git a/checkers/format.py b/checkers/format.py
index 9cfd63d..cfbaf72 100644
--- a/checkers/format.py
+++ b/checkers/format.py
@@ -1,3 +1,5 @@
+# Copyright (c) 2003-2006 Sylvain Thenault (thenault@gmail.com).
+# Copyright (c) 2003-2006 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,10 +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.
-""" Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE).
- http://www.logilab.fr/ -- mailto:contact@logilab.fr
-
-Python code format's checker.
+"""Python code format's checker.
By default try to follow Guido's style guide :
@@ -37,7 +36,7 @@ from pylint.checkers import BaseRawChecker
MSGS = {
'C0301': ('Line too long (%s/%s)',
'Used when a line is longer than a given number of characters.'),
- 'W0302': ('Too many lines in module (%s)',
+ 'C0302': ('Too many lines in module (%s)', # W0302
'Used when a module has too much lines, reducing its readibility.'
),
@@ -248,7 +247,7 @@ class FormatChecker(BaseRawChecker):
self.check_indent_level(line, indents[-1], line_num)
if line_num > self.config.max_module_lines:
- self.add_message('W0302', args=line_num)
+ self.add_message('C0302', args=line_num, line=0)
def visit_default(self, node):
"""check the node line number and check it if not yet done
diff --git a/checkers/imports.py b/checkers/imports.py
index 6ca248a..8f9c972 100644
--- a/checkers/imports.py
+++ b/checkers/imports.py
@@ -228,8 +228,10 @@ given file (report R0402 must not be disabled)'}
def close(self):
"""called before visiting project (i.e set of modules)"""
- for cycle in get_cycles(self.import_graph):
- self.add_message('R0401', args=' -> '.join(cycle))
+ # don't try to compute cycles if the associated message is disabled
+ if self.linter.is_message_enabled('R0401'):
+ for cycle in get_cycles(self.import_graph):
+ self.add_message('R0401', args=' -> '.join(cycle))
def visit_import(self, node):
"""triggered when an import statement is seen"""
diff --git a/checkers/misc.py b/checkers/misc.py
index 7b59b95..9160645 100644
--- a/checkers/misc.py
+++ b/checkers/misc.py
@@ -80,7 +80,7 @@ class EncodingChecker(BaseChecker):
{'type' : 'csv', 'metavar' : '<comma separated values>',
'default' : ('FIXME', 'XXX', 'TODO'),
'help' : 'List of note tags to take in consideration, \
-separated by a comma. Default to FIXME, XXX, TODO'
+separated by a comma.'
}),
)
diff --git a/checkers/newstyle.py b/checkers/newstyle.py
index 540b60f..7383a56 100644
--- a/checkers/newstyle.py
+++ b/checkers/newstyle.py
@@ -47,10 +47,10 @@ MSGS = {
class NewStyleConflictChecker(BaseChecker):
"""checks for usage of new style capabilities on old style classes and
- other new/old styles conflicts problems
- * use of property, __slots__, super
- * "super" usage
- * raising a new style class as exception
+ other new/old styles conflicts problems
+ * use of property, __slots__, super
+ * "super" usage
+ * raising a new style class as exception
"""
__implements__ = (IASTNGChecker,)
diff --git a/checkers/similar.py b/checkers/similar.py
index f4b3a6c..903c19e 100644
--- a/checkers/similar.py
+++ b/checkers/similar.py
@@ -1,5 +1,5 @@
-# pylint: disable-msg=w0622
-# Copyright (c) 2004 LOGILAB S.A. (Paris, FRANCE).
+# pylint: disable-msg=W0622
+# Copyright (c) 2004-2006 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
@@ -32,7 +32,8 @@ from pylint.checkers import BaseChecker, table_lines_from_stats
class Similar:
"""finds copy-pasted lines of code in a project"""
- def __init__(self, min_lines=4, ignore_comments=False, ignore_docstrings=False):
+ def __init__(self, min_lines=4, ignore_comments=False,
+ ignore_docstrings=False):
self.min_lines = min_lines
self.ignore_comments = ignore_comments
self.ignore_docstrings = ignore_docstrings
@@ -79,6 +80,7 @@ class Similar:
couples.sort()
for lineset, idx in couples:
print "==%s:%s" % (lineset.name, idx)
+ # pylint: disable-msg=W0631
for line in lineset._real_lines[idx:idx+num]:
print " ", line,
nb_lignes_dupliquees += num * (len(couples)-1)
@@ -141,16 +143,18 @@ def stripped_lines(lines, ignore_comments, ignore_docstrings):
line = ''
# XXX cut when a line begins with code but end with a comment
if ignore_comments and line.startswith('#'):
- line = ''
+ line = ''
strippedlines.append(line)
return strippedlines
class LineSet:
"""Holds and indexes all the lines of a single source file"""
- def __init__(self, name, lines, ignore_comments=False, ignore_docstrings=False):
+ def __init__(self, name, lines, ignore_comments=False,
+ ignore_docstrings=False):
self.name = name
self._real_lines = lines
- self._stripped_lines = stripped_lines(lines, ignore_comments, ignore_docstrings)
+ self._stripped_lines = stripped_lines(lines, ignore_comments,
+ ignore_docstrings)
self._index = self._mk_index()
def __str__(self):
@@ -282,6 +286,7 @@ class SimilarChecker(BaseChecker, Similar):
for lineset, idx in couples:
msg.append("==%s:%s" % (lineset.name, idx))
msg.sort()
+ # pylint: disable-msg=W0631
for line in lineset._real_lines[idx:idx+num]:
msg.append(line.rstrip())
self.add_message('R0801', args=(len(couples), '\n'.join(msg)))
diff --git a/checkers/typecheck.py b/checkers/typecheck.py
index e8c4355..4324f67 100644
--- a/checkers/typecheck.py
+++ b/checkers/typecheck.py
@@ -22,6 +22,7 @@ from logilab import astng
from pylint.interfaces import IASTNGChecker
from pylint.checkers import BaseChecker
+from pylint.checkers.utils import safe_infer, is_super, display_type
MSGS = {
'E1101': ('%s %r has no %r member',
@@ -37,7 +38,6 @@ MSGS = {
infered function returns nothing but None.'),
}
-
class TypeChecker(BaseChecker):
"""try to find bugs in the code using type inference
"""
@@ -70,61 +70,64 @@ acquired-members option to ignore access to some undefined attributes.'}
'metavar' : '<members names>',
'help' : 'List of members which are usually get through \
zope\'s acquisition mecanism and so shouldn\'t trigger E0201 when accessed \
-(need zope=yes to be considered.'}
+(need zope=yes to be considered).'}
),
)
def visit_getattr(self, node):
- """check that the accessed attribute exists"""
- # if we are running in zope mode, is it an acquired attribute ?
+ """check that the accessed attribute exists
+
+ to avoid to much false positives for now, we'll consider the code as
+ correct if a single of the infered nodes has the accessed attribute.
+
+ function/method, super call and metaclasses are ignored
+ """
+ # if we are running in zope mode and this is an acquired attribute,
+ # stop there
if self.config.zope and node.attrname in self.config.acquired_members:
return
try:
infered = list(node.expr.infer())
- for owner in infered:
- # skip yes object
- if owner is astng.YES:
- continue
- # if there is ambiguity, skip None
- if len(infered) > 1 and isinstance(owner, astng.Const) \
- and owner.value is None:
- continue
- # XXX "super" call
- owner_name = getattr(owner, 'name', 'None')
- if owner_name == 'super' and \
- owner.root().name == '__builtin__':
- continue
- if getattr(owner, 'type', None) == 'metaclass':
- continue
- if self.config.ignore_mixin_members \
- and owner_name[-5:].lower() == 'mixin':
- continue
- #print owner.name, owner.root().name
- try:
- owner.getattr(node.attrname)
- except AttributeError:
- # XXX method / function
- continue
- except astng.NotFoundError:
- if isinstance(owner, astng.Instance):
- if hasattr(owner, 'has_dynamic_getattr') and owner.has_dynamic_getattr():
- continue
- # XXX
- if getattr(owner, 'name', None) == 'Values' and \
- owner.root().name == 'optparse':
- continue
- _type = 'Instance of'
- elif isinstance(owner, astng.Module):
- _type = 'Module'
- else:
- _type = 'Class'
- self.add_message('E1101', node=node,
- args=(_type, owner_name, node.attrname))
- # XXX: stop on the first found
- # this is a bad solution to fix func_noerror_socket_member.py
- break
except astng.InferenceError:
- pass
+ return
+ # list of (node, nodename) which are missing the attribute
+ missingattr = []
+ ignoremim = self.config.ignore_mixin_members
+ for owner in infered:
+ # skip yes object
+ if owner is astng.YES:
+ continue
+ # if there is ambiguity, skip None
+ if len(infered) > 1 and isinstance(owner, astng.Const) \
+ and owner.value is None:
+ continue
+ # XXX "super" / metaclass call
+ if is_super(owner) or getattr(owner, 'type', None) == 'metaclass':
+ continue
+ name = getattr(owner, 'name', 'None')
+ if ignoremim and name[-5:].lower() == 'mixin':
+ continue
+ try:
+ owner.getattr(node.attrname)
+ except AttributeError:
+ # XXX method / function
+ continue
+ except astng.NotFoundError:
+ if isinstance(owner, astng.Instance) \
+ and owner.has_dynamic_getattr():
+ continue
+ missingattr.append((owner, name))
+ continue
+ # stop on the first found
+ break
+ else:
+ # we have not found any node with the attributes, display the
+ # message for infered nodes
+ for owner, name in missingattr:
+ self.add_message('E1101', node=node,
+ args=(display_type(owner), name,
+ node.attrname))
+
def visit_assign(self, node):
"""check that if assigning to a function call, the function is
@@ -132,7 +135,7 @@ zope\'s acquisition mecanism and so shouldn\'t trigger E0201 when accessed \
"""
if not isinstance(node.expr, astng.CallFunc):
return
- function_node = self._safe_infer(node.expr.node)
+ function_node = safe_infer(node.expr.node)
# skip class, generator and uncomplete function definition
if not (isinstance(function_node, astng.Function) and
function_node.root().fully_defined()):
@@ -155,26 +158,11 @@ zope\'s acquisition mecanism and so shouldn\'t trigger E0201 when accessed \
def visit_callfunc(self, node):
"""check that called method are infered to callable objects
"""
- called = self._safe_infer(node.node)
+ called = safe_infer(node.node)
# 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.node.as_string())
- def _safe_infer(self, node):
- """return the infered value for the given node.
- Return None if inference failed or if there is some ambiguity (more than
- one node has been infered)
- """
- try:
- inferit = node.infer()
- value = inferit.next()
- except astng.InferenceError:
- return
- try:
- inferit.next()
- return # None if there is ambiguity on the infered node
- except StopIteration:
- return value
def register(linter):
"""required method to auto register this checker """
diff --git a/checkers/utils.py b/checkers/utils.py
index cb3a99b..8c6612a 100644
--- a/checkers/utils.py
+++ b/checkers/utils.py
@@ -1,6 +1,6 @@
# pylint: disable-msg=W0611
#
-# Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE).
+# Copyright (c) 2003-2006 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
@@ -30,6 +30,30 @@ except AttributeError:
COMP_NODE_TYPES = astng.ListComp
FOR_NODE_TYPES = (astng.For, astng.ListCompFor)
+def safe_infer(node):
+ """return the infered value for the given node.
+ Return None if inference failed or if there is some ambiguity (more than
+ one node has been infered)
+ """
+ try:
+ inferit = node.infer()
+ value = inferit.next()
+ except astng.InferenceError:
+ return
+ try:
+ inferit.next()
+ return # None if there is ambiguity on the infered node
+ except StopIteration:
+ return value
+
+def is_super(node):
+ """return True if the node is referencing the "super" builtin function
+ """
+ if getattr(node, 'name', None) == 'super' and \
+ node.root().name == '__builtin__':
+ return True
+ return False
+
def is_error(node):
"""return true if the function does nothing but raising an exception"""
for child_node in node.code.getChildNodes():
@@ -37,6 +61,14 @@ def is_error(node):
return True
return False
+def is_raising(stmt):
+ """return true if the given statement node raise an exception
+ """
+ for node in stmt.nodes:
+ if isinstance(node, astng.Raise):
+ return True
+ return False
+
def is_empty(node):
"""return true if the given node does nothing but 'pass'"""
for child_node in node.getChildNodes():
@@ -142,3 +174,11 @@ def overrides_a_method(class_node, name):
if name in ancestor and isinstance(ancestor[name], astng.Function):
return True
return False
+
+def display_type(node):
+ """return the type of this node for screen display"""
+ if isinstance(node, astng.Instance):
+ return 'Instance of'
+ elif isinstance(node, astng.Module):
+ return 'Module'
+ return 'Class'
diff --git a/doc/makefile b/doc/makefile
index 944893e..2e19479 100644
--- a/doc/makefile
+++ b/doc/makefile
@@ -1,10 +1,10 @@
MKHTML=mkdoc
-MKHTML_OPT=--doctype article --param toc.section.depth=1 --target html --stylesheet single-file
+MKHTML_OPT=--doctype article --param toc.section.depth=1 --target html --stylesheet single-file
SRC=.
-all: quickstart.html features.html FAQ.html examples man
+all: manual.html quickstart.html features.html FAQ.html examples man
FAQ.html: ${SRC}/FAQ.txt
${MKHTML} ${MKHTML_OPT} ${SRC}/FAQ.txt
@@ -12,6 +12,9 @@ FAQ.html: ${SRC}/FAQ.txt
quickstart.html: ${SRC}/quickstart.txt
${MKHTML} ${MKHTML_OPT} ${SRC}/quickstart.txt
+manual.html: ${SRC}/manual.txt
+ ${MKHTML} ${MKHTML_OPT} ${SRC}/manual.txt
+
features.html:
chmod u+w ${SRC}/features.txt
echo "PyLint features" > ${SRC}/features.txt
diff --git a/lint.py b/lint.py
index a77fc02..abda05b 100644
--- a/lint.py
+++ b/lint.py
@@ -38,8 +38,7 @@ import re
import tokenize
from os.path import dirname, basename, splitext, exists, isdir, join, normpath
-from logilab.common.configuration import OptionsManagerMixIn, \
- check_yn, check_csv
+from logilab.common.configuration import OptionsManagerMixIn, check_csv
from logilab.common.modutils import modpath_from_file, get_module_files, \
file_from_modpath, load_module_from_name
from logilab.common.interface import implements
@@ -66,9 +65,10 @@ from pylint.__pkginfo__ import version
OPTION_RGX = re.compile('\s*#*\s*pylint:(.*)')
-REPORTER_OPT_MAP = {'html': HTMLReporter,
+REPORTER_OPT_MAP = {'text': TextReporter,
'parseable': TextReporter2,
- 'color': ColorizedTextReporter}
+ 'colorized': ColorizedTextReporter,
+ 'html': HTMLReporter,}
# Python Linter class #########################################################
@@ -92,10 +92,10 @@ MSGS = {
'Used when an inline option is either badly formatted or can\'t \
be used inside modules.'),
- 'I0011': ('Locally disabling %r',
+ 'I0011': ('Locally disabling %s',
'Used when an inline option disable a message or a messages \
category.'),
- 'I0012': ('Locally enabling %r',
+ 'I0012': ('Locally enabling %s',
'Used when an inline option enable a message or a messages \
category.'),
@@ -105,7 +105,7 @@ be used inside modules.'),
'E0011': ('Unrecognized file option %r',
'Used when an unknown inline option is encountered.'),
'E0012': ('Bad option value %r',
- 'Used when an bad value for an inline option is encountered.'),
+ 'Used when a bad value for an inline option is encountered.'),
}
class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn,
@@ -132,6 +132,19 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn,
'help' : 'Add <file or directory> to the black list. It \
should be a base name, not a path. You may set this option multiple times.'}),
+ ('enable-checker',
+ {'type' : 'csv', 'metavar': '<checker ids>',
+ 'group': 'Messages control',
+ 'help' : 'Enable only checker(s) with the given id(s).\
+ This option conflict with the disable-checker option'}),
+
+ ('disable-checker',
+ {'type' : 'csv', 'metavar': '<checker ids>',
+ 'group': 'Messages control',
+ 'help' : 'Enable all checker(s) except those with the \
+ given id(s).\
+ This option conflict with the disable-checker option'}),
+
('persistent',
{'default': 1, 'type' : 'yn', 'metavar' : '<y_or_n>',
'help' : 'Pickle collected data for later comparisons.'}),
@@ -145,28 +158,19 @@ should be a base name, not a path. You may set this option multiple times.'}),
'help' : 'List of plugins (as comma separated values of \
python modules names) to load, usually to register additional checkers.'}),
- ('reports',
- {'default': 1, 'type' : 'yn', 'metavar' : '<y_or_n>',
- 'short': 'r',
+ ('output-format',
+ {'default': 'text', 'type': 'choice', 'metavar' : '<format>',
+ 'choices': ('text', 'parseable', 'colorized', 'html'),
+ 'short': 'f',
'group': 'Reports',
- 'help' : 'Tells wether to display a full report or only the\
- messages'}),
- ('html',
- {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>',
- 'group': 'Reports',
- 'help' : 'Use HTML as output format instead of text'}),
-
- ('parseable',
- {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>',
- 'short': 'p',
- 'group': 'Reports',
- 'help' : 'Use a parseable text output format, so your favorite\
- text editor will be able to jump to the line corresponding to a message.'}),
-
- ('color',
- {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>',
+ 'help' : 'set the output format. Available formats are text,\
+ parseable, colorized and html'}),
+
+ ('include-ids',
+ {'type' : 'yn', 'metavar' : '<y_or_n>', 'default' : 0,
+ 'short': 'i',
'group': 'Reports',
- 'help' : 'Colorizes text output using ansi escape codes'}),
+ 'help' : 'Include message\'s id in output'}),
('files-output',
{'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>',
@@ -175,6 +179,13 @@ python modules names) to load, usually to register additional checkers.'}),
package specified on the command line instead of printing them on stdout. \
Reports (if any) will be written in a file name "pylint_global.[txt|html]".'}),
+ ('reports',
+ {'default': 1, 'type' : 'yn', 'metavar' : '<y_or_n>',
+ 'short': 'r',
+ 'group': 'Reports',
+ 'help' : 'Tells wether to display a full report or only the\
+ messages'}),
+
('evaluation',
{'type' : 'string', 'metavar' : '<python_expression>',
'group': 'Reports',
@@ -192,46 +203,39 @@ warning, statement which respectivly contain the number of errors / warnings\
'help' : 'Add a comment according to your evaluation note. \
This is used by the global evaluation report (R0004).'}),
- ('include-ids',
- {'type' : 'yn', 'metavar' : '<y_or_n>', 'default' : 0,
- 'short': 'i',
+ ('enable-report',
+ {'type' : 'csv', 'metavar': '<rpt ids>',
'group': 'Reports',
- 'help' : 'Include message\'s id in output'}),
+ 'help' : 'Enable the report(s) with the given id(s).'}),
+
+ ('disable-report',
+ {'type' : 'csv', 'metavar': '<rpt ids>',
+ 'group': 'Reports',
+ 'help' : 'Disable the report(s) with the given id(s).'}),
('enable-msg-cat',
{'type' : 'csv', 'metavar': '<msg cats>',
- 'group': 'Reports',
+ 'group': 'Messages control',
'help' : 'Enable all messages in the listed categories.'}),
('disable-msg-cat',
{'type' : 'csv', 'metavar': '<msg cats>',
- 'group': 'Reports',
+ 'group': 'Messages control',
'help' : 'Disable all messages in the listed categories.'}),
('enable-msg',
{'type' : 'csv', 'metavar': '<msg ids>',
- 'group': 'Reports',
- 'help' : 'Enable the message with the given id.'}),
+ 'group': 'Messages control',
+ 'help' : 'Enable the message(s) with the given id(s).'}),
('disable-msg',
{'type' : 'csv', 'metavar': '<msg ids>',
- 'group': 'Reports',
- 'help' : 'Disable the message with the given id.'}),
-
- ('enable-report',
- {'type' : 'csv', 'metavar': '<rpt ids>',
- 'group': 'Reports',
- 'help' : 'Enable the report with the given id.'}),
-
- ('disable-report',
- {'type' : 'csv', 'metavar': '<rpt ids>',
- 'group': 'Reports',
- 'help' : 'Disable the report with the given id.'}),
-
+ 'group': 'Messages control',
+ 'help' : 'Disable the message(s) with the given id(s).'}),
)
-
option_groups = (
- ('Reports', 'Options related to messages / statistics reporting'),
+ ('Messages control', 'Options controling analysis messages'),
+ ('Reports', 'Options related to output formating and reporting'),
)
def __init__(self, options=(), reporter=None, option_groups=(),
@@ -268,10 +272,10 @@ This is used by the global evaluation report (R0004).'}),
ReportsHandlerMixIn.__init__(self)
BaseRawChecker.__init__(self)
# provided reports
- self.reports = (('R0001', 'Total errors / warnings',
- report_error_warning_stats),
+ self.reports = (('R0001', 'Messages by category',
+ report_total_messages_stats),
('R0002', '% errors / warnings by module',
- report_error_warning_by_module_stats),
+ report_messages_by_module_stats),
('R0003', 'Messages',
report_messages_stats),
('R0004', 'Global evaluation',
@@ -311,10 +315,13 @@ This is used by the global evaluation report (R0004).'}),
meth(value)
elif opt_name == 'cache-size':
self.manager.set_cache_size(int(value))
-# elif opt_name = 'load-plugins':
-# self.load_plugin_modules(get_csv(value))
- elif opt_name in REPORTER_OPT_MAP and check_yn(None, opt_name, value):
- self.set_reporter(REPORTER_OPT_MAP[opt_name]())
+ elif opt_name == 'output-format':
+ self.set_reporter(REPORTER_OPT_MAP[value.lower()]())
+ elif opt_name in ('enable-checker', 'disable-checker'):
+ if not value:
+ return
+ checkerids = [v.lower() for v in check_csv(None, opt_name, value)]
+ self.enable_checkers(checkerids, opt_name == 'enable-checker')
BaseRawChecker.set_option(self, opt_name, value, action, opt_dict)
# checkers manipulation methods ###########################################
@@ -329,25 +336,24 @@ This is used by the global evaluation report (R0004).'}),
if hasattr(checker, 'reports'):
for r_id, r_title, r_cb in checker.reports:
self.register_report(r_id, r_title, r_cb, checker)
- if checker.__doc__ is None:
- checker.__doc__ = 'no documentation available for this checker'
- need_space = 80 - (len(checker.__doc__.splitlines()[-1]) % 80)
- checker.__doc__ += """%s
-This checker also defines the following reports:
- * %s
-""" % (' ' * need_space,
- '\n * '.join(['% -76s' % ('%s: %s' % report[:2])
- for report in checker.reports]))
self.register_options_provider(checker)
if hasattr(checker, 'msgs'):
self.register_messages(checker)
- def disable_all_checkers(self):
- """disable all possible checkers """
+ def enable_checkers(self, listed, enabled):
+ """only enable/disable checkers from the given list"""
+ idmap = {}
for checker in self._checkers.keys():
- checker.enable(False)
-
+ checker.enable(not enabled)
+ idmap[checker.name] = checker
+ for checkerid in listed:
+ try:
+ checker = idmap[checkerid]
+ except KeyError:
+ raise Exception('no checker named %s' % checkerid)
+ checker.enable(enabled)
+
def disable_noerror_checkers(self):
"""disable all checkers without error messages, and the
'miscellaneous' checker which can be safely deactivated in debug
@@ -475,6 +481,7 @@ This checker also defines the following reports:
self.reporter.set_output(open(reportfile, 'w'))
self.check_file(filepath, modname, checkers)
# notify global end
+ self.set_current_module('')
for checker in rev_checkers:
checker.close()
@@ -482,7 +489,6 @@ This checker also defines the following reports:
"""check a module or package from its name
if modname is a package, recurse on its subpackages / submodules
"""
-## print 'CHECKING', filepath, modname
# get the given module representation
self.base_name = modname
self.base_file = normpath(filepath)
@@ -524,8 +530,12 @@ This checker also defines the following reports:
self.stats['by_module'][modname]['statement'] = 0
for msg_cat in MSG_TYPES.values():
self.stats['by_module'][modname][msg_cat] = 0
- self._module_msgs_state = {}
- self._module_msg_cats_state = {}
+ # XXX hack, to be correct we need to keep module_msgs_state
+ # for every analyzed module (the problem stands with localized
+ # messages which are only detected in the .close step)
+ if modname:
+ self._module_msgs_state = {}
+ self._module_msg_cats_state = {}
def get_astng(self, filepath, modname):
"""return a astng representation for a module"""
@@ -634,7 +644,7 @@ This checker also defines the following reports:
# some reporting functions ####################################################
-def report_error_warning_stats(sect, stats, old_stats):
+def report_total_messages_stats(sect, stats, old_stats):
"""make total errors / warnings report"""
lines = ['type', 'number', 'previous', 'difference']
lines += table_lines_from_stats(stats, old_stats,
@@ -657,7 +667,7 @@ def report_messages_stats(sect, stats, _):
lines += (msg_id, str(value))
sect.append(Table(children=lines, cols=2, rheaders=1))
-def report_error_warning_by_module_stats(sect, stats, _):
+def report_messages_by_module_stats(sect, stats, _):
"""make errors / warnings by modules report"""
if len(stats['by_module']) == 1:
# don't print this report when we are analysing a single module
@@ -713,7 +723,6 @@ def preprocess_options(args, search_for):
"""
for i, arg in enumerate(args):
for option in search_for:
- ##print arg, option
if arg.startswith('--%s=' % option):
search_for[option](option, arg[len(option)+3:])
del args[i]
@@ -726,6 +735,10 @@ class Run:
run(*sys.argv[1:])
"""
+ option_groups = (
+ ('Commands', 'Options which are actually commands. Options in this \
+group are mutually exclusive.'),
+ )
def __init__(self, args, reporter=None, quiet=0):
self._rcfile = None
@@ -738,36 +751,34 @@ class Run:
'type': 'string', 'metavar': '<file>',
'help' : 'Specify a configuration file.'}),
- ('disable-all',
- {'action' : 'callback',
- 'callback' : self.cb_disable_all_checkers,
- 'help' : '''Disable all possible checkers. This option should
- precede enable-* options.'''}),
-
('help-msg',
{'action' : 'callback', 'type' : 'string', 'metavar': '<msg-id>',
'callback' : self.cb_help_message,
+ 'group': 'Commands',
'help' : '''Display a help message for the given message id and \
-exit. This option may be a comma separated list.'''}),
+exit. The value may be a comma separated list of message ids.'''}),
('list-msgs',
{'action' : 'callback', 'metavar': '<msg-id>',
'callback' : self.cb_list_messages,
- 'help' : 'List and explain every available messages.'}),
+ 'group': 'Commands',
+ 'help' : "Generate pylint's full documentation."}),
('generate-rcfile',
{'action' : 'callback', 'callback' : self.cb_generate_config,
+ 'group': 'Commands',
'help' : '''Generate a sample configuration file according to \
-the current configuration. You can put other options before this one to use \
-them in the configuration. This option causes the program to exit'''}),
+the current configuration. You can put other options before this one to get \
+them in the generated configuration.'''}),
('generate-man',
{'action' : 'callback', 'callback' : self.cb_generate_manpage,
- 'help' : '''Generate a man page for pylint. This option causes \
-the program to exit'''}),
+ 'group': 'Commands',
+ 'help' : "Generate pylint's man page."}),
- ('debug-mode',
+ ('errors-only',
{'action' : 'callback', 'callback' : self.cb_debug_mode,
+ 'short': 'e',
'help' : '''In debug mode, checkers without error messages are \
disabled and for others, only the ERROR messages are displayed, and no reports \
are done by default'''}),
@@ -777,7 +788,8 @@ are done by default'''}),
'default': False,
'help' : 'Profiled execution.'}),
- ), reporter=reporter, pylintrc=self._rcfile)
+ ), option_groups=self.option_groups,
+ reporter=reporter, pylintrc=self._rcfile)
linter.quiet = quiet
# register standard checkers
from pylint import checkers
@@ -836,10 +848,6 @@ processing.
"""callback for option preprocessing (ie before optik parsing)"""
self._plugins.extend(get_csv(value))
- def cb_disable_all_checkers(self, *args, **kwargs):
- """optik callback for disabling all checkers"""
- self.linter.disable_all_checkers()
-
def cb_debug_mode(self, *args, **kwargs):
"""debug mode:
* checkers without error messages are disabled
diff --git a/reporters/html.py b/reporters/html.py
index 625c447..5fd4350 100644
--- a/reporters/html.py
+++ b/reporters/html.py
@@ -1,3 +1,5 @@
+# Copyright (c) 2003-2006 Sylvain Thenault (thenault@gmail.com).
+# Copyright (c) 2003-2006 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,10 +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.
-""" Copyright (c) 2002-2006 LOGILAB S.A. (Paris, FRANCE).
- http://www.logilab.fr/ -- mailto:contact@logilab.fr
-
-HTML reporter
+"""HTML reporter
"""
__revision__ = "$Id: html.py,v 1.14 2006-03-08 15:53:41 syt Exp $"
@@ -50,7 +49,9 @@ class HTMLReporter(BaseReporter):
def _display(self, layout):
"""launch layouts display
- overriden from BaseReporter to add a blank line...
+ overriden from BaseReporter to add insert the messages section
+ (in add_message, message is not displayed, just collected so it
+ can be displayed in an html table)
"""
if self.msgs:
# add stored messages to the layout
diff --git a/reporters/text.py b/reporters/text.py
index f4dfd2c..5bffe64 100644
--- a/reporters/text.py
+++ b/reporters/text.py
@@ -1,3 +1,5 @@
+# Copyright (c) 2003-2006 Sylvain Thenault (thenault@gmail.com).
+# Copyright (c) 2003-2006 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,10 +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.
-""" Copyright (c) 2000-2003 LOGILAB S.A. (Paris, FRANCE).
- http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""Plain text reporters:
-Plain text reporter
+* the default one grouping messages by module
+* the parseable one with module path on each message
+* an ANSI colorized text reporter
"""
__revision__ = "$Id: text.py,v 1.21 2005-12-28 00:24:35 syt Exp $"
@@ -51,8 +54,11 @@ class TextReporter(BaseReporter):
"""manage message of different type and in the context of path"""
module, obj, line = location[1:]
if not self._modules.has_key(module):
- self.writeln('************* Module %s' % module)
- self._modules[module] = 1
+ if module:
+ self.writeln('************* Module %s' % module)
+ self._modules[module] = 1
+ else:
+ self.writeln('************* %s' % module)
if obj:
obj = ':%s' % obj
if self.include_ids:
@@ -134,8 +140,12 @@ class ColorizedTextReporter(TextReporter):
module, obj, line = location[1:]
if not self._modules.has_key(module):
color, style = self._get_decoration('S')
- modsep = colorize_ansi('************* Module %s' % module,
- color, style)
+ if module:
+ modsep = colorize_ansi('************* Module %s' % module,
+ color, style)
+ else:
+ modsep = colorize_ansi('************* %s' % module,
+ color, style)
self.writeln(modsep)
self._modules[module] = 1
if obj:
diff --git a/setup.py b/setup.py
index 4127079..39c4900 100644
--- a/setup.py
+++ b/setup.py
@@ -1,5 +1,6 @@
#!/usr/bin/env python
-# pylint: disable-msg=W0142,W0403,W0404,E0611,W0613,W0622,W0622,W0704,R0904
+# pylint: disable-msg=W0142,W0403,W0404,E0611,W0613,W0622,W0622,W0704
+# pylint: disable-msg=R0904,C0103
#
# Copyright (c) 2003 LOGILAB S.A. (Paris, FRANCE).
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
@@ -58,7 +59,7 @@ try:
except ImportError:
ext_modules = None
-BASE_BLACKLIST = ('CVS', 'debian', 'dist', 'build', '__buildlog')
+BASE_BLACKLIST = ('CVS', 'debian', 'dist', 'build', '__buildlog', '.svn')
IGNORED_EXTENSIONS = ('.pyc', '.pyo', '.elc')
diff --git a/test/func_test.py b/test/func_test.py
index 3de8891..26c803d 100644
--- a/test/func_test.py
+++ b/test/func_test.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2002-2005 LOGILAB S.A. (Paris, FRANCE).
+# Copyright (c) 2003-2006 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
diff --git a/test/input/func_base_stmt_without_effect.py b/test/input/func_base_stmt_without_effect.py
index f9ff608..8eb8736 100644
--- a/test/input/func_base_stmt_without_effect.py
+++ b/test/input/func_base_stmt_without_effect.py
@@ -1,7 +1,15 @@
"""
- 'W0103': ('Statement seems to have no effect',
+ 'W0104': ('Statement seems to have no effect',
'Used when a statement doesn\'t have (or at least seems to) \
any effect.'),
+ 'W0105': ('String statement has no effect',
+ 'Used when a string is used as a statement (which of course \
+ has no effect). This is a particular case of W0104 with its \
+ own message so you can easily disable it if you\'re using \
+ those strings as documentation, instead of comments.'),
+ 'W0106': ('Unnecessary semi-column',
+ 'Used when a statement is endend by a semi-colon (";"), which \
+ isn\'t necessary (that\'s python, not C ;)'),
"""
__revision__ = ''
@@ -16,3 +24,5 @@ __revision__.lower() # ok
"""inline doc string should use a separated message"""
+
+__revision__.lower(); # unnecessary ;
diff --git a/test/input/func_block_disable_msg.py b/test/input/func_block_disable_msg.py
index 7de7acd..3394d61 100644
--- a/test/input/func_block_disable_msg.py
+++ b/test/input/func_block_disable_msg.py
@@ -1,5 +1,5 @@
+# pylint: disable-msg=C0302
"""pylint option block-disable-msg"""
-
__revision__ = None
class Foo(object):
@@ -105,31 +105,899 @@ class ClassLevelMessage(object):
self.attr0 = 1
def too_complex_but_thats_ok(self, attr1, attr2):
- """THIS Method has too much branchs but i don't care
+ """THIS Method has too much branchs and returns but i don't care
"""
- # pylint: disable-msg=R0912
+ # pylint: disable-msg=R0912,R0911
try:
attr3 = attr1+attr2
except ValueError:
attr3 = None
except:
- print 'duh', self
+ return 'duh', self
if attr1:
for i in attr1:
if attr2:
- print i
+ return i
else:
- print 'duh'
+ return 'duh'
elif attr2:
for i in attr2:
if attr2:
- print i
+ return i
else:
- print 'duh'
+ return 'duh'
else:
for i in range(15):
if attr3:
- print i
+ return i
else:
- print 'doh'
-
+ return 'doh'
+ return None
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+print 'hop, to much lines but i don\'t care'
diff --git a/test/messages/func_base_stmt_without_effect.txt b/test/messages/func_base_stmt_without_effect.txt
index e1cce0c..c00d959 100644
--- a/test/messages/func_base_stmt_without_effect.txt
+++ b/test/messages/func_base_stmt_without_effect.txt
@@ -1,4 +1,5 @@
-W: 9: Statement seems to have no effect
-W: 11: Statement seems to have no effect
-W: 15: Statement seems to have no effect
-W: 18: String statement has no effect
+W: 17: Statement seems to have no effect
+W: 19: Statement seems to have no effect
+W: 23: Statement seems to have no effect
+W: 26: String statement has no effect
+W: 28: Unnecessary semi-column
diff --git a/test/messages/func_dotted_ancestor.txt b/test/messages/func_dotted_ancestor.txt
index 84247b8..5347191 100644
--- a/test/messages/func_dotted_ancestor.txt
+++ b/test/messages/func_dotted_ancestor.txt
@@ -1 +1 @@
-R: 8:Aaaa: Not enough public methods (0/2)
+R: 8:Aaaa: To few public methods (0/2)
diff --git a/test/messages/func_r0903.txt b/test/messages/func_r0903.txt
index 6ab8888..c59f740 100644
--- a/test/messages/func_r0903.txt
+++ b/test/messages/func_r0903.txt
@@ -1 +1 @@
-R: 4:Aaaa: Not enough public methods (1/2)
+R: 4:Aaaa: To few public methods (1/2)
diff --git a/test/messages/func_w0302.txt b/test/messages/func_w0302.txt
index ae8a99f..e3d8fc9 100644
--- a/test/messages/func_w0302.txt
+++ b/test/messages/func_w0302.txt
@@ -1,2 +1,2 @@
-W: 0: Too many lines in module (1017)
+C: 0: Too many lines in module (1017)
diff --git a/utils.py b/utils.py
index 7389a2a..c728fba 100644
--- a/utils.py
+++ b/utils.py
@@ -1,5 +1,5 @@
-# Copyright (c) 2003-2005 Sylvain Thenault (thenault@gmail.com).
-# Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE).
+# Copyright (c) 2003-2006 Sylvain Thenault (thenault@gmail.com).
+# Copyright (c) 2003-2006 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
@@ -20,12 +20,15 @@ main pylint class
__revision__ = "$Id: utils.py,v 1.13 2006-04-19 09:17:40 syt Exp $"
+import sys
from os import linesep
-from logilab.astng import Module
from logilab.common.textutils import normalize_text
+from logilab.common.configuration import rest_format_section
from logilab.common.ureports import Section
+from logilab.astng import Module
+
from pylint.checkers import EmptyReport
class UnknownMessage(Exception):
@@ -55,7 +58,7 @@ def sort_checkers(checkers, enabled_only=True):
def sort_msgs(msg_ids):
"""sort message identifiers according to their category first"""
- msg_order = ['I', 'C', 'R', 'W', 'E', 'F']
+ msg_order = ['E', 'W', 'R', 'C', 'I', 'F']
def cmp_func(msgid1, msgid2):
"""comparison function for two message identifiers"""
if msgid1[0] != msgid2[0]:
@@ -117,18 +120,25 @@ class MessagesHandlerMixIn:
assert msg_id[0] in MSG_CATEGORIES, \
'Bad message type %s in %r' % (msg_id[0], msg_id)
chk_id = msg_id[1:3]
- if checker is not None:
- add = ' This message belongs to the %s checker.' % checker.name
- msg_help += add
self._messages_help[msg_id] = msg_help
self._messages[msg_id] = msg
- def get_message_help(self, msg_id):
+ def get_message_help(self, msg_id, checkerref=False):
"""return the help string for the given message id"""
msg_id = self.check_message_id(msg_id)
msg = self._messages_help[msg_id]
msg = normalize_text(' '.join(msg.split()), indent=' ')
- return '%s:\n%s' % (msg_id, msg)
+ if checkerref:
+ for checker in self._checkers:
+ if msg_id in checker.msgs:
+ msg += ' This message belongs to the %s checker.' % \
+ checker.name
+ break
+ title = self._messages[msg_id]
+ if title != '%s':
+ title = title.splitlines()[0]
+ return ':%s: *%s*\n%s' % (msg_id, title, msg)
+ return ':%s:\n%s' % (msg_id, msg)
def disable_message(self, msg_id, scope='package', line=None):
"""don't output message of the given id"""
@@ -205,7 +215,7 @@ class MessagesHandlerMixIn:
if not self._msg_cats_state.get(msg_id[0], True):
return False
if line is None:
- return self._msgs_state.get(msg_id, True)
+ return self._msgs_state.get(msg_id, True)
try:
return self._module_msgs_state[msg_id][line]
except (KeyError, TypeError):
@@ -252,7 +262,7 @@ class MessagesHandlerMixIn:
"""display help messages for the given message identifiers"""
for msg_id in msgids:
try:
- print self.get_message_help(msg_id)
+ print self.get_message_help(msg_id, True)
print
except UnknownMessage, ex:
print ex
@@ -260,26 +270,50 @@ class MessagesHandlerMixIn:
continue
def list_messages(self):
- """list available messages"""
- for checker in sort_checkers(self._checkers.keys()):
- print checker.name.capitalize()
- print '-' * len(checker.name)
- print
- if checker.__doc__: # __doc__ is None with -OO
- print 'Description'
- print '~~~~~~~~~~~'
- print linesep.join([line.strip()
- for line in checker.__doc__.splitlines()])
+ """output a full documentation in ReST format"""
+ for checker in sort_checkers(self._checkers):
+ if checker.name == 'master':
+ prefix = 'Main '
+ if checker.options:
+ for section, options in checker.options_by_section():
+ if section is None:
+ title = 'General options'
+ else:
+ title = '%s options' % section.capitalize()
+ print title
+ print '~' * len(title)
+ rest_format_section(sys.stdout, None, options)
+ print
+ else:
+ prefix = ''
+ title = '%s checker' % checker.name.capitalize()
+ print title
+ print '-' * len(title)
+ if checker.__doc__: # __doc__ is None with -OO
+ print linesep.join([line.strip()
+ for line in checker.__doc__.splitlines()])
+ if checker.options:
+ title = 'Options'
+ print title
+ print '~' * len(title)
+ for section, options in checker.options_by_section():
+ rest_format_section(sys.stdout, section, options)
+ print
+ if checker.msgs:
+ title = ('%smessages' % prefix).capitalize()
+ print title
+ print '~' * len(title)
+ for msg_id in sort_msgs(checker.msgs.keys()):
+ print self.get_message_help(msg_id, False)
print
- if not checker.msgs:
- continue
- print 'Messages'
- print '~~~~~~~~'
- for msg_id in sort_msgs(checker.msgs.keys()):
- print self.get_message_help(msg_id)
+ if getattr(checker, 'reports', None):
+ title = ('%sreports' % prefix).capitalize()
+ print title
+ print '~' * len(title)
+ for report in checker.reports:
+ print ':%s: %s' % report[:2]
print
print
- print
class ReportsHandlerMixIn: