summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichal Nowikowski <godfryd@gmail.com>2014-08-19 06:07:31 +0200
committerMichal Nowikowski <godfryd@gmail.com>2014-08-19 06:07:31 +0200
commit7b05a9b2cc946d1efac6e492c64afc5e909f4170 (patch)
tree0f2c44fd52b95f60e5b9eb9dea846077f01c2f9d
parentaa87ee9e70d9f6a4ab037b67e8646f7237acfb46 (diff)
parent073c0c5140776a1f530f3a83bc3f1c3629776723 (diff)
downloadpylint-7b05a9b2cc946d1efac6e492c64afc5e909f4170.tar.gz
merge
-rw-r--r--ChangeLog18
-rw-r--r--checkers/__init__.py6
-rw-r--r--checkers/base.py84
-rw-r--r--checkers/classes.py30
-rw-r--r--checkers/exceptions.py73
-rw-r--r--checkers/newstyle.py34
-rw-r--r--checkers/typecheck.py22
-rw-r--r--checkers/utils.py21
-rw-r--r--checkers/variables.py11
-rw-r--r--doc/installation.rst2
-rw-r--r--doc/options.rst13
-rw-r--r--interfaces.py12
-rw-r--r--lint.py47
-rw-r--r--reporters/__init__.py37
-rw-r--r--reporters/guireporter.py8
-rw-r--r--reporters/html.py6
-rw-r--r--reporters/text.py24
-rw-r--r--test/functional/access_to__name__.txt4
-rw-r--r--test/functional/anomalous_unicode_escape.txt6
-rw-r--r--test/functional/bad_continuation.txt86
-rw-r--r--test/functional/bad_open_mode.txt14
-rw-r--r--test/functional/class_members_py27.txt12
-rw-r--r--test/functional/class_members_py30.txt12
-rw-r--r--test/functional/confidence_filter.py14
-rw-r--r--test/functional/confidence_filter.rc3
-rw-r--r--test/functional/confidence_filter.txt1
-rw-r--r--test/functional/docstrings.txt16
-rw-r--r--test/functional/exception_is_binary_op.txt8
-rw-r--r--test/functional/future_unicode_literals.txt2
-rw-r--r--test/functional/import_error.txt4
-rw-r--r--test/functional/invalid_encoded_data.txt2
-rw-r--r--test/functional/invalid_exceptions_caught.py18
-rw-r--r--test/functional/invalid_exceptions_caught.txt9
-rw-r--r--test/functional/invalid_exceptions_raised.txt6
-rw-r--r--test/functional/invalid_name.txt4
-rw-r--r--test/functional/member_checks.py (renamed from test/input/func_typecheck_getattr.py)26
-rw-r--r--test/functional/member_checks.txt9
-rw-r--r--test/functional/missing_self_argument.txt4
-rw-r--r--test/functional/name_styles.txt34
-rw-r--r--test/functional/namedtuple_member_inference.txt4
-rw-r--r--test/functional/newstyle__slots__.txt2
-rw-r--r--test/functional/newstyle_properties.txt2
-rw-r--r--test/functional/old_style_class_py27.py17
-rw-r--r--test/functional/old_style_class_py27.rc2
-rw-r--r--test/functional/old_style_class_py27.txt2
-rw-r--r--test/functional/statement_without_effect.txt72
-rw-r--r--test/functional/string_formatting.txt2
-rw-r--r--test/functional/super_checks.txt14
-rw-r--r--test/functional/too_many_branches.txt2
-rw-r--r--test/functional/undefined_variable.py2
-rw-r--r--test/input/func_noerror_access_attr_before_def_false_positive.py2
-rw-r--r--test/input/func_noerror_classes_protected_member_access.py1
-rw-r--r--test/input/func_noerror_indirect_interface.py2
-rw-r--r--test/input/func_w0233.py14
-rw-r--r--test/messages/func_typecheck_getattr.txt10
-rw-r--r--test/messages/func_w0233.txt1
-rw-r--r--test/test_functional.py59
-rw-r--r--test/test_self.py4
-rw-r--r--test/unittest_checker_base.py14
-rw-r--r--test/unittest_lint.py20
-rw-r--r--test/unittest_utils.py2
-rw-r--r--testutils.py3
-rw-r--r--utils.py155
63 files changed, 763 insertions, 387 deletions
diff --git a/ChangeLog b/ChangeLog
index 7b70fa7..2897f2f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,6 +2,11 @@ ChangeLog for Pylint
====================
--
+ * Each message now comes with a confidence level attached, and
+ can be filtered base on this level. This allows to filter out
+ all messages that were emitted even though an inference failure
+ happened during checking.
+
* Improved presenting unused-import message. Closes issue #293.
* Add new checker for finding spelling errors. New messages:
@@ -63,6 +68,19 @@ ChangeLog for Pylint
* Look in the metaclass, if defined, for members not found in the current
class. Closes issue #306.
+ * Don't emit 'protected-access' if the attribute is accessed using
+ a property defined at the class level.
+
+ * Detect calls of the parent's __init__, through a binded super() call.
+
+ * Check that a class has an explicitly defined metaclass before
+ emitting 'old-style-class' for Python 2.
+
+ * Emit 'catching-non-exception' for non-class nodes. Closes issue #303.
+
+ * Order of reporting is consistent.
+
+
2014-07-26 -- 1.3.0
* Allow hanging continued indentation for implicitly concatenated
diff --git a/checkers/__init__.py b/checkers/__init__.py
index 693a5ff..8710f4a 100644
--- a/checkers/__init__.py
+++ b/checkers/__init__.py
@@ -46,6 +46,8 @@ from logilab.common.configuration import OptionsProviderMixIn
from pylint.reporters import diff_string
from pylint.utils import register_plugins
+from pylint.interfaces import UNDEFINED
+
def table_lines_from_stats(stats, old_stats, columns):
"""get values listed in <columns> from <stats> and <old_stats>,
@@ -90,9 +92,9 @@ class BaseChecker(OptionsProviderMixIn):
OptionsProviderMixIn.__init__(self)
self.linter = linter
- def add_message(self, msg_id, line=None, node=None, args=None):
+ def add_message(self, msg_id, line=None, node=None, args=None, confidence=UNDEFINED):
"""add a message of a given type"""
- self.linter.add_message(msg_id, line, node, args)
+ self.linter.add_message(msg_id, line, node, args, confidence)
# dummy methods implementing the IChecker interface
diff --git a/checkers/base.py b/checkers/base.py
index 6e5804f..298ac5f 100644
--- a/checkers/base.py
+++ b/checkers/base.py
@@ -16,13 +16,15 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""basic checker for Python code"""
+import collections
+import itertools
import sys
import astroid
from logilab.common.ureports import Table
from astroid import are_exclusive, InferenceError
import astroid.bases
-from pylint.interfaces import IAstroidChecker
+from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE, HIGH
from pylint.utils import EmptyReport
from pylint.reporters import diff_string
from pylint.checkers import BaseChecker
@@ -34,6 +36,7 @@ from pylint.checkers.utils import (
overrides_a_method,
safe_infer,
get_argument_from_call,
+ has_known_bases,
NoSuchArgumentError,
is_import_error,
)
@@ -944,6 +947,7 @@ class NameChecker(_BasicChecker):
_BasicChecker.__init__(self, linter)
self._name_category = {}
self._name_group = {}
+ self._bad_names = {}
def open(self):
self.stats = self.linter.add_stats(badname_module=0,
@@ -961,6 +965,25 @@ class NameChecker(_BasicChecker):
@check_messages('blacklisted-name', 'invalid-name')
def visit_module(self, node):
self._check_name('module', node.name.split('.')[-1], node)
+ self._bad_names = {}
+
+ def leave_module(self, node):
+ for category, all_groups in self._bad_names.iteritems():
+ if len(all_groups) < 2:
+ continue
+ groups = collections.defaultdict(list)
+ min_warnings = sys.maxint
+ for group in all_groups.itervalues():
+ groups[len(group)].append(group)
+ min_warnings = min(len(group), min_warnings)
+ if len(groups[min_warnings]) > 1:
+ by_line = sorted(groups[min_warnings],
+ key=lambda group: min(warning[0].lineno for warning in group))
+ warnings = itertools.chain(*by_line[1:])
+ else:
+ warnings = groups[min_warnings][0]
+ for args in warnings:
+ self._raise_name_warning(*args)
@check_messages('blacklisted-name', 'invalid-name')
def visit_class(self, node):
@@ -973,10 +996,15 @@ class NameChecker(_BasicChecker):
def visit_function(self, node):
# Do not emit any warnings if the method is just an implementation
# of a base class method.
- if node.is_method() and overrides_a_method(node.parent.frame(), node.name):
- return
+ confidence = HIGH
+ if node.is_method():
+ if overrides_a_method(node.parent.frame(), node.name):
+ return
+ confidence = (INFERENCE if has_known_bases(node.parent.frame())
+ else INFERENCE_FAILURE)
+
self._check_name(_determine_function_name_type(node),
- node.name, node)
+ node.name, node, confidence)
# Check argument names
args = node.args.args
if args is not None:
@@ -1025,12 +1053,22 @@ class NameChecker(_BasicChecker):
def _find_name_group(self, node_type):
return self._name_group.get(node_type, node_type)
- def _is_multi_naming_match(self, match):
+ def _is_multi_naming_match(self, match, node_type, confidence):
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):
+ match.lastgroup not in EXEMPT_NAME_CATEGORIES
+ and (node_type != 'method' or confidence != INFERENCE_FAILURE))
+
+ def _raise_name_warning(self, node, node_type, name, confidence):
+ 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),
+ confidence=confidence)
+ self.stats['badname_' + node_type] += 1
+
+ def _check_name(self, node_type, name, node, confidence=HIGH):
"""check for a name using the type's regexp"""
if is_inside_except(node):
clobbering, _ = clobber_in_except(node)
@@ -1045,20 +1083,14 @@ class NameChecker(_BasicChecker):
regexp = getattr(self.config, node_type + '_rgx')
match = regexp.match(name)
- if self._is_multi_naming_match(match):
+ if self._is_multi_naming_match(match, node_type, confidence):
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
+ bad_name_group = self._bad_names.setdefault(name_group, {})
+ warnings = bad_name_group.setdefault(match.lastgroup, [])
+ warnings.append((node, node_type, name, confidence))
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
+ self._raise_name_warning(node, node_type, name, confidence)
class DocStringChecker(_BasicChecker):
@@ -1109,6 +1141,8 @@ class DocStringChecker(_BasicChecker):
ftype = node.is_method() and 'method' or 'function'
if isinstance(node.parent.frame(), astroid.Class):
overridden = False
+ confidence = (INFERENCE if has_known_bases(node.parent.frame())
+ else INFERENCE_FAILURE)
# check if node is from a method overridden by its ancestor
for ancestor in node.parent.frame().ancestors():
if node.name in ancestor and \
@@ -1116,11 +1150,13 @@ class DocStringChecker(_BasicChecker):
overridden = True
break
self._check_docstring(ftype, node,
- report_missing=not overridden)
+ report_missing=not overridden,
+ confidence=confidence)
else:
self._check_docstring(ftype, node)
- def _check_docstring(self, node_type, node, report_missing=True):
+ def _check_docstring(self, node_type, node, report_missing=True,
+ confidence=HIGH):
"""check the node has a non empty docstring"""
docstring = node.doc
if docstring is None:
@@ -1146,10 +1182,12 @@ class DocStringChecker(_BasicChecker):
return
elif func.bound.name in ('str', 'unicode', 'bytes'):
return
- self.add_message('missing-docstring', node=node, args=(node_type,))
+ self.add_message('missing-docstring', node=node, args=(node_type,),
+ confidence=confidence)
elif not docstring.strip():
self.stats['undocumented_'+node_type] += 1
- self.add_message('empty-docstring', node=node, args=(node_type,))
+ self.add_message('empty-docstring', node=node, args=(node_type,),
+ confidence=confidence)
class PassChecker(_BasicChecker):
diff --git a/checkers/classes.py b/checkers/classes.py
index 90b2fbe..d9ebd8d 100644
--- a/checkers/classes.py
+++ b/checkers/classes.py
@@ -27,7 +27,7 @@ from pylint.interfaces import IAstroidChecker
from pylint.checkers import BaseChecker
from pylint.checkers.utils import (
PYMETHODS, overrides_a_method, check_messages, is_attr_private,
- is_attr_protected, node_frame_class, safe_infer)
+ is_attr_protected, node_frame_class, safe_infer, is_builtin_object)
if sys.version_info >= (3, 0):
NEXT_METHOD = '__next__'
@@ -581,6 +581,23 @@ a metaclass class method.'}
# We are in a class, one remaining valid cases, Klass._attr inside
# Klass
if not (callee == klass.name or callee in klass.basenames):
+ # Detect property assignments in the body of the class.
+ # This is acceptable:
+ #
+ # class A:
+ # b = property(lambda: self._b)
+
+ stmt = node.parent.statement()
+ try:
+ if (isinstance(stmt, astroid.Assign) and
+ (stmt in klass.body or klass.parent_of(stmt)) and
+ isinstance(stmt.value, astroid.CallFunc) and
+ isinstance(stmt.value.func, astroid.Name) and
+ stmt.value.func.name == 'property' and
+ is_builtin_object(next(stmt.value.func.infer(), None))):
+ return
+ except astroid.InferenceError:
+ pass
self.add_message('protected-access', node=node, args=attrname)
def visit_name(self, node):
@@ -801,6 +818,17 @@ a metaclass class method.'}
klass = expr.expr.infer().next()
if klass is YES:
continue
+ # The infered klass can be super(), which was
+ # assigned to a variable and the `__init__` was called later.
+ #
+ # base = super()
+ # base.__init__(...)
+
+ if (isinstance(klass, astroid.Instance) and
+ isinstance(klass._proxied, astroid.Class) and
+ is_builtin_object(klass._proxied) and
+ klass._proxied.name == 'super'):
+ return
try:
del not_called_yet[klass]
except KeyError:
diff --git a/checkers/exceptions.py b/checkers/exceptions.py
index 81520ce..9b1a071 100644
--- a/checkers/exceptions.py
+++ b/checkers/exceptions.py
@@ -19,14 +19,44 @@ import sys
from logilab.common.compat import builtins
BUILTINS_NAME = builtins.__name__
import astroid
-from astroid import YES, Instance, unpack_infer
+from astroid import YES, Instance, unpack_infer, List, Tuple
from pylint.checkers import BaseChecker
from pylint.checkers.utils import (
is_empty, is_raising,
check_messages, inherit_from_std_ex,
- EXCEPTIONS_MODULE)
-from pylint.interfaces import IAstroidChecker
+ EXCEPTIONS_MODULE, has_known_bases)
+from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE
+
+def _annotated_unpack_infer(stmt, context=None):
+ """
+ Recursively generate nodes inferred by the given statement.
+ If the inferred value is a list or a tuple, recurse on the elements.
+ Returns an iterator which yields tuples in the format
+ ('original node', 'infered node').
+ """
+ # TODO: the same code as unpack_infer, except for the annotated
+ # return. We need this type of annotation only here and
+ # there is no point in complicating the API for unpack_infer.
+ # If the need arises, this behaviour can be promoted to unpack_infer
+ # as well.
+ if isinstance(stmt, (List, Tuple)):
+ for elt in stmt.elts:
+ for infered_elt in unpack_infer(elt, context):
+ yield elt, infered_elt
+ return
+ # if infered is a final node, return it and stop
+ infered = next(stmt.infer(context))
+ if infered is stmt:
+ yield stmt, infered
+ return
+ # else, infer recursivly, except YES object that should be returned as is
+ for infered in stmt.infer(context):
+ if infered is YES:
+ yield stmt, infered
+ else:
+ for inf_inf in unpack_infer(infered, context):
+ yield stmt, inf_inf
def infer_bases(klass):
""" Fully infer the bases of the klass node.
@@ -204,7 +234,9 @@ class ExceptionsChecker(BaseChecker):
if expr.newstyle:
self.add_message('raising-non-exception', node=node)
else:
- self.add_message('nonstandard-exception', node=node)
+ self.add_message(
+ 'nonstandard-exception', node=node,
+ confidence=INFERENCE if has_known_bases(expr) else INFERENCE_FAILURE)
else:
value_found = False
else:
@@ -255,13 +287,36 @@ class ExceptionsChecker(BaseChecker):
node=handler, args=handler.type.op)
else:
try:
- excs = list(unpack_infer(handler.type))
+ excs = list(_annotated_unpack_infer(handler.type))
except astroid.InferenceError:
continue
- for exc in excs:
- # XXX skip other non class nodes
- if exc is YES or not isinstance(exc, astroid.Class):
+ for part, exc in excs:
+ if exc is YES:
continue
+ if isinstance(exc, astroid.Instance) and inherit_from_std_ex(exc):
+ exc = exc._proxied
+ if not isinstance(exc, astroid.Class):
+ # Don't emit the warning if the infered stmt
+ # is None, but the exception handler is something else,
+ # maybe it was redefined.
+ if (isinstance(exc, astroid.Const) and
+ exc.value is None):
+ if ((isinstance(handler.type, astroid.Const) and
+ handler.type.value is None) or
+ handler.type.parent_of(exc)):
+ # If the exception handler catches None or
+ # the exception component, which is None, is
+ # defined by the entire exception handler, then
+ # emit a warning.
+ self.add_message('catching-non-exception',
+ node=handler.type,
+ args=(part.as_string(), ))
+ else:
+ self.add_message('catching-non-exception',
+ node=handler.type,
+ args=(part.as_string(), ))
+ continue
+
exc_ancestors = [anc for anc in exc.ancestors()
if isinstance(anc, astroid.Class)]
for previous_exc in exceptions_classes:
@@ -289,7 +344,7 @@ class ExceptionsChecker(BaseChecker):
node=handler.type,
args=(exc.name, ))
- exceptions_classes += excs
+ exceptions_classes += [exc for _, exc in excs]
def register(linter):
diff --git a/checkers/newstyle.py b/checkers/newstyle.py
index cc8f640..1946f94 100644
--- a/checkers/newstyle.py
+++ b/checkers/newstyle.py
@@ -19,9 +19,9 @@ import sys
import astroid
-from pylint.interfaces import IAstroidChecker
+from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE, HIGH
from pylint.checkers import BaseChecker
-from pylint.checkers.utils import check_messages
+from pylint.checkers.utils import check_messages, has_known_bases
MSGS = {
'E1001': ('Use of __slots__ on an old style class',
@@ -74,15 +74,21 @@ class NewStyleConflictChecker(BaseChecker):
@check_messages('slots-on-old-class', 'old-style-class')
def visit_class(self, node):
- """check __slots__ usage
+ """ Check __slots__ in old style classes and old
+ style class definition.
"""
if '__slots__' in node and not node.newstyle:
- self.add_message('slots-on-old-class', node=node)
+ confidence = (INFERENCE if has_known_bases(node)
+ else INFERENCE_FAILURE)
+ self.add_message('slots-on-old-class', node=node,
+ confidence=confidence)
# The node type could be class, exception, metaclass, or
# interface. Presumably, the non-class-type nodes would always
# have an explicit base class anyway.
- if not node.bases and node.type == 'class':
- self.add_message('old-style-class', node=node)
+ if not node.bases and node.type == 'class' and not node.metaclass():
+ # We use confidence HIGH here because this message should only ever
+ # be emitted for classes at the root of the inheritance hierarchyself.
+ self.add_message('old-style-class', node=node, confidence=HIGH)
@check_messages('property-on-old-class')
def visit_callfunc(self, node):
@@ -91,9 +97,12 @@ class NewStyleConflictChecker(BaseChecker):
if (isinstance(parent, astroid.Class) and
not parent.newstyle and
isinstance(node.func, astroid.Name)):
+ confidence = (INFERENCE if has_known_bases(parent)
+ else INFERENCE_FAILURE)
name = node.func.name
if name == 'property':
- self.add_message('property-on-old-class', node=node)
+ self.add_message('property-on-old-class', node=node,
+ confidence=confidence)
@check_messages('super-on-old-class', 'bad-super-call', 'missing-super-argument')
def visit_function(self, node):
@@ -111,9 +120,12 @@ class NewStyleConflictChecker(BaseChecker):
if isinstance(call, astroid.CallFunc) and \
isinstance(call.func, astroid.Name) and \
call.func.name == 'super':
+ confidence = (INFERENCE if has_known_bases(klass)
+ else INFERENCE_FAILURE)
if not klass.newstyle:
# super should not be used on an old style class
- self.add_message('super-on-old-class', node=node)
+ self.add_message('super-on-old-class', node=node,
+ confidence=confidence)
else:
# super first arg should be the class
if not call.args and sys.version_info[0] == 3:
@@ -127,7 +139,8 @@ class NewStyleConflictChecker(BaseChecker):
continue
if supcls is None:
- self.add_message('missing-super-argument', node=call)
+ self.add_message('missing-super-argument', node=call,
+ confidence=confidence)
continue
if klass is not supcls:
@@ -143,7 +156,8 @@ class NewStyleConflictChecker(BaseChecker):
if name is not None:
self.add_message('bad-super-call',
node=call,
- args=(name, ))
+ args=(name, ),
+ confidence=confidence)
def register(linter):
diff --git a/checkers/typecheck.py b/checkers/typecheck.py
index a8851a2..27ed9f5 100644
--- a/checkers/typecheck.py
+++ b/checkers/typecheck.py
@@ -23,23 +23,19 @@ import astroid
from astroid import InferenceError, NotFoundError, YES, Instance
from astroid.bases import BUILTINS
-from pylint.interfaces import IAstroidChecker
+from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE
from pylint.checkers import BaseChecker
from pylint.checkers.utils import safe_infer, is_super, check_messages
MSGS = {
'E1101': ('%s %r has no %r member',
'no-member',
- 'Used when a variable is accessed for an unexistent member.'),
+ 'Used when a variable is accessed for an unexistent member.',
+ {'old_names': [('E1103', 'maybe-no-member')]}),
'E1102': ('%s is not callable',
'not-callable',
'Used when an object being called has been inferred to a non \
callable object'),
- 'E1103': ('%s %r has no %r member (but some types could not be inferred)',
- 'maybe-no-member',
- 'Used when a variable is accessed for an unexistent member, but \
- astroid was not able to interpret all possible types of this \
- variable.'),
'E1111': ('Assigning to function call which doesn\'t return',
'assignment-from-no-return',
'Used when an assignment is done on a function call but the \
@@ -187,7 +183,7 @@ accessed. Python regular expressions are accepted.'}
def visit_delattr(self, node):
self.visit_getattr(node)
- @check_messages('no-member', 'maybe-no-member')
+ @check_messages('no-member')
def visit_getattr(self, node):
"""check that the accessed attribute exists
@@ -279,13 +275,11 @@ accessed. Python regular expressions are accepted.'}
if actual in done:
continue
done.add(actual)
- if inference_failure:
- msgid = 'maybe-no-member'
- else:
- msgid = 'no-member'
- self.add_message(msgid, node=node,
+ confidence = INFERENCE if not inference_failure else INFERENCE_FAILURE
+ self.add_message('no-member', node=node,
args=(owner.display_type(), name,
- node.attrname))
+ node.attrname),
+ confidence=confidence)
@check_messages('assignment-from-no-return', 'assignment-from-none')
def visit_assign(self, node):
diff --git a/checkers/utils.py b/checkers/utils.py
index 000aee7..af1440d 100644
--- a/checkers/utils.py
+++ b/checkers/utils.py
@@ -417,7 +417,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, error:
+ except IndexError as error:
raise NoSuchArgumentError(error)
if keyword:
for arg in callfunc_node.args:
@@ -462,3 +462,22 @@ def is_import_error(handler):
return True
except astroid.InferenceError:
continue
+
+def has_known_bases(klass):
+ """Returns true if all base classes of a class could be inferred."""
+ try:
+ return klass._all_bases_known
+ except AttributeError:
+ pass
+ try:
+ for base in klass.bases:
+ result = base.infer().next()
+ # TODO: check for A->B->A->B pattern in class structure too?
+ if not isinstance(result, astroid.Class) or result is klass or not has_known_bases(result):
+ klass._all_bases_known = False
+ return False
+ except astroid.InferenceError:
+ klass._all_bases_known = False
+ return False
+ klass._all_bases_known = True
+ return True
diff --git a/checkers/variables.py b/checkers/variables.py
index a88bf3b..9c0b15c 100644
--- a/checkers/variables.py
+++ b/checkers/variables.py
@@ -25,14 +25,14 @@ from astroid import are_exclusive, builtin_lookup, AstroidBuildingException
from logilab.common.modutils import file_from_modpath
-from pylint.interfaces import IAstroidChecker
+from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE, HIGH
from pylint.utils import get_global_option
from pylint.checkers import BaseChecker
from pylint.checkers.utils import (
PYMETHODS, is_ancestor_name, is_builtin,
is_defined_before, is_error, is_func_default, is_func_decorator,
assign_parent, check_messages, is_inside_except, clobber_in_except,
- get_all_elements)
+ get_all_elements, has_known_bases)
SPECIAL_OBJ = re.compile("^_{2}[a-z]+_{2}$")
@@ -486,6 +486,10 @@ builtins. Remember that you should avoid to define new builtins when possible.'
klass = node.parent.frame()
if is_method and (klass.type == 'interface' or node.is_abstract()):
return
+ if is_method and isinstance(klass, astroid.Class):
+ confidence = INFERENCE if has_known_bases(klass) else INFERENCE_FAILURE
+ else:
+ confidence = HIGH
authorized_rgx = self.config.dummy_variables_rgx
called_overridden = False
argnames = node.argnames()
@@ -539,7 +543,8 @@ builtins. Remember that you should avoid to define new builtins when possible.'
# don't check callback arguments XXX should be configurable
if node.name.startswith('cb_') or node.name.endswith('_cb'):
continue
- self.add_message('unused-argument', args=name, node=stmt)
+ self.add_message('unused-argument', args=name, node=stmt,
+ confidence=confidence)
else:
if stmt.parent and isinstance(stmt.parent, astroid.Assign):
if name in nonlocal_names:
diff --git a/doc/installation.rst b/doc/installation.rst
index 56115c7..10862a8 100644
--- a/doc/installation.rst
+++ b/doc/installation.rst
@@ -62,7 +62,7 @@ On Windows, once you have installed Pylint, the command line usage is ::
pylint.bat [options] module_or_package
But this will only work if *pylint.bat* is either in the current
-directory, or on your system path. (*setup.py* install install *python.bat*
+directory, or on your system path. (*setup.py* will install *python.bat*
to the *Scripts* subdirectory of your Python installation -- e.g.
C:\Python24\Scripts.) You can do any of the following to solve this:
diff --git a/doc/options.rst b/doc/options.rst
index 4a159f2..702d93c 100644
--- a/doc/options.rst
+++ b/doc/options.rst
@@ -91,8 +91,8 @@ 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.
+Rather than emitting name warnings immediately, PyLint will determine the
+prevalent naming style inside each module and enforce it on all names.
Consider the following (simplified) example::
@@ -101,16 +101,17 @@ Consider the following (simplified) example::
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::
+In ``sample.py``, the function name on line 1 and 7 will mark the module
+and enforce the match of named group ``snake`` for the remaining names in
+the module::
- def trigger_snake_case(arg):
+ def valid_snake_case(arg):
...
def InvalidCamelCase(arg):
...
- def valid_snake_case(arg):
+ def more_valid_snake_case(arg):
...
Because of this, the name on line 4 will trigger an ``invalid-name`` warning,
diff --git a/interfaces.py b/interfaces.py
index 50f2c83..ea3b40f 100644
--- a/interfaces.py
+++ b/interfaces.py
@@ -11,9 +11,21 @@
# this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""Interfaces for PyLint objects"""
+from collections import namedtuple
from logilab.common.interface import Interface
+Confidence = namedtuple('Confidence', ['name', 'description'])
+# Warning Certainties
+HIGH = Confidence('HIGH', 'No false positive possible.')
+INFERENCE = Confidence('INFERENCE', 'Warning based on inference result.')
+INFERENCE_FAILURE = Confidence('INFERENCE_FAILURE',
+ 'Warning based on inference with failures.')
+UNDEFINED = Confidence('UNDEFINED',
+ 'Warning without any associated confidence level.')
+
+CONFIDENCE_LEVELS = [HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED]
+
class IChecker(Interface):
"""This is an base interface, not designed to be used elsewhere than for
diff --git a/lint.py b/lint.py
index 5be37f3..a1ce1e0 100644
--- a/lint.py
+++ b/lint.py
@@ -25,6 +25,7 @@
Display help messages about given message identifiers and exit.
"""
+from __future__ import print_function
# import this first to avoid builtin namespace pollution
from pylint.checkers import utils #pylint: disable=unused-import
@@ -51,7 +52,7 @@ from pylint.utils import (
PyLintASTWalker, UnknownMessage, MessagesHandlerMixIn, ReportsHandlerMixIn,
MessagesStore, FileState, EmptyReport,
expand_modules, tokenize_module)
-from pylint.interfaces import IRawChecker, ITokenChecker, IAstroidChecker
+from pylint.interfaces import IRawChecker, ITokenChecker, IAstroidChecker, CONFIDENCE_LEVELS
from pylint.checkers import (BaseTokenChecker,
table_lines_from_stats,
initialize as checkers_initialize)
@@ -238,6 +239,15 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn,
'help' : 'Add a comment according to your evaluation note. '
'This is used by the global evaluation report (RP0004).'}),
+ ('confidence',
+ {'type' : 'multiple_choice', 'metavar': '<levels>',
+ 'default': '',
+ 'choices': [c.name for c in CONFIDENCE_LEVELS],
+ 'group': 'Messages control',
+ 'help' : 'Only show warnings with the listed confidence levels.'
+ ' Leave empty to show all. Valid levels: %s' % (
+ ', '.join(c.name for c in CONFIDENCE_LEVELS),)}),
+
('enable',
{'type' : 'csv', 'metavar': '<msg ids>',
'short': 'e',
@@ -404,8 +414,8 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn,
try:
BaseTokenChecker.set_option(self, optname, value, action, optdict)
except UnsupportedAction:
- print >> sys.stderr, 'option %s can\'t be read from config file' % \
- optname
+ print('option %s can\'t be read from config file' % \
+ optname, file=sys.stderr)
def register_reporter(self, reporter_class):
self._reporters[reporter_class.name] = reporter_class
@@ -624,11 +634,11 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn,
"""return a ast(roid) representation for a module"""
try:
return MANAGER.ast_from_file(filepath, modname, source=True)
- except SyntaxError, ex:
+ except SyntaxError as ex:
self.add_message('syntax-error', line=ex.lineno, args=ex.msg)
- except AstroidBuildingException, ex:
+ except AstroidBuildingException as ex:
self.add_message('parse-error', args=ex)
- except Exception, ex:
+ except Exception as ex:
import traceback
traceback.print_exc()
self.add_message('astroid-error', args=(ex.__class__, ex))
@@ -638,7 +648,7 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn,
# call raw checkers if possible
try:
tokens = tokenize_module(astroid)
- except tokenize.TokenError, ex:
+ except tokenize.TokenError as ex:
self.add_message('syntax-error', line=ex.args[1][0], args=ex.args[0])
return
@@ -709,7 +719,7 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn,
evaluation = self.config.evaluation
try:
note = eval(evaluation, {}, self.stats)
- except Exception, ex:
+ except Exception as ex:
msg = 'An exception occurred while rating: %s' % ex
else:
stats['global_note'] = note
@@ -849,8 +859,8 @@ group are mutually exclusive.'),
'rcfile': (self.cb_set_rcfile, True),
'load-plugins': (self.cb_add_plugins, True),
})
- except ArgumentPreprocessingError, ex:
- print >> sys.stderr, ex
+ except ArgumentPreprocessingError as ex:
+ print(ex, file=sys.stderr)
sys.exit(32)
self.linter = linter = self.LinterClass((
@@ -879,6 +889,12 @@ group are mutually exclusive.'),
'group': 'Commands', 'level': 1,
'help' : "Generate pylint's messages."}),
+ ('list-conf-levels',
+ {'action' : 'callback',
+ 'callback' : self.cb_list_confidence_levels,
+ 'group': 'Commands', 'level': 1,
+ 'help' : "Generate pylint's messages."}),
+
('full-documentation',
{'action' : 'callback', 'metavar': '<msg-id>',
'callback' : self.cb_full_documentation,
@@ -968,18 +984,18 @@ group are mutually exclusive.'),
linter.set_reporter(reporter)
try:
args = linter.load_command_line_configuration(args)
- except SystemExit, exc:
+ except SystemExit as exc:
if exc.code == 2: # bad options
exc.code = 32
raise
if not args:
- print linter.help()
+ print(linter.help())
sys.exit(32)
# insert current working directory to the python path to have a correct
# behaviour
linter.prepare_import_path(args)
if self.linter.config.profile:
- print >> sys.stderr, '** profiled run'
+ print('** profiled run', file=sys.stderr)
import cProfile, pstats
cProfile.runctx('linter.check(%r)' % args, globals(), locals(),
'stones.prof')
@@ -1037,6 +1053,11 @@ group are mutually exclusive.'),
self.linter.msgs_store.list_messages()
sys.exit(0)
+ def cb_list_confidence_levels(self, option, optname, value, parser):
+ for level in CONFIDENCE_LEVELS:
+ print('%-18s: %s' % level)
+ sys.exit(0)
+
def cb_init_hook(optname, value):
"""exec arbitrary code to set sys.path for instance"""
exec value
diff --git a/reporters/__init__.py b/reporters/__init__.py
index 12d193f..429155a 100644
--- a/reporters/__init__.py
+++ b/reporters/__init__.py
@@ -17,7 +17,6 @@ import sys
import locale
import os
-from pylint.utils import MSG_TYPES
from pylint import utils
@@ -28,10 +27,6 @@ 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
@@ -41,27 +36,6 @@ def diff_string(old, new):
return diff_str
-class Message(object):
- """This class represent a message to be issued by the reporters"""
-
- def __init__(self, reporter, msg_id, location, msg):
- self.msg_id = msg_id
- self.abspath, self.module, self.obj, self.line, self.column = location
- self.path = self.abspath.replace(reporter.path_strip_prefix, '')
- self.msg = msg
- self.C = msg_id[0]
- self.category = MSG_TYPES[msg_id[0]]
- self.symbol = reporter.linter.msgs_store.check_message_id(msg_id).symbol
-
- def format(self, template):
- """Format the message according to the given template.
-
- The template format is the one of the format method :
- cf. http://docs.python.org/2/library/string.html#formatstrings
- """
- return template.format(**(self.__dict__))
-
-
class BaseReporter(object):
"""base class for reporters
@@ -82,9 +56,16 @@ class BaseReporter(object):
# Build the path prefix to strip to get relative paths
self.path_strip_prefix = os.getcwd() + os.sep
+ def handle_message(self, msg):
+ """Handle a new message triggered on the current file.
+
+ Invokes the legacy add_message API by default."""
+ self.add_message(
+ msg.msg_id, (msg.abspath, msg.module, msg.obj, msg.line, msg.column),
+ msg.msg)
+
def add_message(self, msg_id, location, msg):
- """Client API to send a message"""
- # Shall we store the message objects somewhere, do some validity checking ?
+ """Deprecated, do not use."""
raise NotImplementedError
def set_output(self, output=None):
diff --git a/reporters/guireporter.py b/reporters/guireporter.py
index 331eb17..f908f76 100644
--- a/reporters/guireporter.py
+++ b/reporters/guireporter.py
@@ -3,7 +3,8 @@
import sys
from pylint.interfaces import IReporter
-from pylint.reporters import BaseReporter, Message
+from pylint.reporters import BaseReporter
+from pylint import utils
from logilab.common.ureports import TextWriter
@@ -18,10 +19,9 @@ class GUIReporter(BaseReporter):
BaseReporter.__init__(self, output)
self.gui = gui
- def add_message(self, msg_id, location, msg):
+ def handle_message(self, msg):
"""manage message of different type and in the context of path"""
- message = Message(self, msg_id, location, msg)
- self.gui.msg_queue.put(message)
+ self.gui.msg_queue.put(msg)
def _display(self, layout):
"""launch layouts display"""
diff --git a/reporters/html.py b/reporters/html.py
index 71d46eb..3e5a1a7 100644
--- a/reporters/html.py
+++ b/reporters/html.py
@@ -18,8 +18,9 @@ from cgi import escape
from logilab.common.ureports import HTMLWriter, Section, Table
+from pylint import utils
from pylint.interfaces import IReporter
-from pylint.reporters import BaseReporter, Message
+from pylint.reporters import BaseReporter
class HTMLReporter(BaseReporter):
@@ -33,9 +34,8 @@ class HTMLReporter(BaseReporter):
BaseReporter.__init__(self, output)
self.msgs = []
- def add_message(self, msg_id, location, msg):
+ def handle_message(self, msg):
"""manage message of different type and in the context of path"""
- msg = Message(self, msg_id, location, msg)
self.msgs += (msg.category, msg.module, msg.obj,
str(msg.line), str(msg.column), escape(msg.msg))
diff --git a/reporters/text.py b/reporters/text.py
index 04245f7..48b5155 100644
--- a/reporters/text.py
+++ b/reporters/text.py
@@ -23,7 +23,7 @@ from logilab.common.ureports import TextWriter
from logilab.common.textutils import colorize_ansi
from pylint.interfaces import IReporter
-from pylint.reporters import BaseReporter, Message
+from pylint.reporters import BaseReporter
TITLE_UNDERLINES = ['', '=', '-', '.']
@@ -48,16 +48,15 @@ class TextReporter(BaseReporter):
"""Convenience method to write a formated message with class default template"""
self.writeln(msg.format(self._template))
- def add_message(self, msg_id, location, msg):
+ def handle_message(self, msg):
"""manage message of different type and in the context of path"""
- m = Message(self, msg_id, location, msg)
- if m.module not in self._modules:
- if m.module:
- self.writeln('************* Module %s' % m.module)
- self._modules.add(m.module)
+ if msg.module not in self._modules:
+ if msg.module:
+ self.writeln('************* Module %s' % msg.module)
+ self._modules.add(msg.module)
else:
self.writeln('************* ')
- self.write_message(m)
+ self.write_message(msg)
def _display(self, layout):
"""launch layouts display"""
@@ -114,11 +113,10 @@ class ColorizedTextReporter(TextReporter):
except KeyError:
return None, None
- def add_message(self, msg_id, location, msg):
+ def handle_message(self, msg):
"""manage message of different types, and colorize output
using ansi escape codes
"""
- msg = Message(self, msg_id, location, msg)
if msg.module not in self._modules:
color, style = self._get_decoration('S')
if msg.module:
@@ -130,8 +128,10 @@ class ColorizedTextReporter(TextReporter):
self.writeln(modsep)
self._modules.add(msg.module)
color, style = self._get_decoration(msg.C)
- for attr in ('msg', 'symbol', 'category', 'C'):
- setattr(msg, attr, colorize_ansi(getattr(msg, attr), color, style))
+
+ msg = msg._replace(
+ **{attr: colorize_ansi(getattr(msg, attr), color, style)
+ for attr in ('msg', 'symbol', 'category', 'C')})
self.write_message(msg)
diff --git a/test/functional/access_to__name__.txt b/test/functional/access_to__name__.txt
index 2c0a953..ecf5ffd 100644
--- a/test/functional/access_to__name__.txt
+++ b/test/functional/access_to__name__.txt
@@ -1,3 +1,3 @@
old-style-class:7:Aaaa:Old-style class defined.
-no-member:10:Aaaa.__init__:Instance of 'Aaaa' has no '__name__' member
-no-member:21:NewClass.__init__:Instance of 'NewClass' has no '__name__' member
+no-member:10:Aaaa.__init__:Instance of 'Aaaa' has no '__name__' member:INFERENCE
+no-member:21:NewClass.__init__:Instance of 'NewClass' has no '__name__' member:INFERENCE
diff --git a/test/functional/anomalous_unicode_escape.txt b/test/functional/anomalous_unicode_escape.txt
index 5c1acc7..c242cb9 100644
--- a/test/functional/anomalous_unicode_escape.txt
+++ b/test/functional/anomalous_unicode_escape.txt
@@ -1,3 +1,3 @@
-anomalous-unicode-escape-in-string:5::Anomalous Unicode escape in byte string: '\u'. String constant might be missing an r or u prefix.
-anomalous-unicode-escape-in-string:6::Anomalous Unicode escape in byte string: '\U'. String constant might be missing an r or u prefix.
-anomalous-unicode-escape-in-string:8::Anomalous Unicode escape in byte string: '\N'. String constant might be missing an r or u prefix.
+anomalous-unicode-escape-in-string:5::"Anomalous Unicode escape in byte string: '\u'. String constant might be missing an r or u prefix."
+anomalous-unicode-escape-in-string:6::"Anomalous Unicode escape in byte string: '\U'. String constant might be missing an r or u prefix."
+anomalous-unicode-escape-in-string:8::"Anomalous Unicode escape in byte string: '\N'. String constant might be missing an r or u prefix."
diff --git a/test/functional/bad_continuation.txt b/test/functional/bad_continuation.txt
index 732858e..f970780 100644
--- a/test/functional/bad_continuation.txt
+++ b/test/functional/bad_continuation.txt
@@ -1,63 +1,63 @@
-bad-continuation:12::Wrong hanging indentation.
+bad-continuation:12::"Wrong hanging indentation.
] # [bad-continuation]
-| ^|
-bad-continuation:17::Wrong continued indentation.
+| ^|"
+bad-continuation:17::"Wrong continued indentation.
7, # [bad-continuation]
- | ^
-bad-continuation:25::Wrong hanging indentation.
+ | ^"
+bad-continuation:25::"Wrong hanging indentation.
'b': 2, # [bad-continuation]
- ^|
-bad-continuation:31::Wrong hanging indentation.
+ ^|"
+bad-continuation:31::"Wrong hanging indentation.
'b': 2, # [bad-continuation]
- ^|
-bad-continuation:39::Wrong continued indentation.
+ ^|"
+bad-continuation:39::"Wrong continued indentation.
# [bad-continuation] is not accepted
- | | ^
-bad-continuation:40::Wrong continued indentation.
+ | | ^"
+bad-continuation:40::"Wrong continued indentation.
'contents', # [bad-continuation] nor this.
- | ^
-bad-continuation:49::Wrong hanging indentation in dict value.
+ | ^"
+bad-continuation:49::"Wrong hanging indentation in dict value.
'value2', # [bad-continuation]
- | ^ |
-bad-continuation:59::Wrong continued indentation.
+ | ^ |"
+bad-continuation:59::"Wrong continued indentation.
'wrong', # [bad-continuation]
- ^ |
-bad-continuation:83::Wrong hanging indentation in dict value.
+ ^ |"
+bad-continuation:83::"Wrong hanging indentation in dict value.
'value1', # [bad-continuation]
-^ | |
-bad-continuation:87::Wrong hanging indentation in dict value.
+^ | |"
+bad-continuation:87::"Wrong hanging indentation in dict value.
'value1', # [bad-continuation]
- ^ | |
-bad-continuation:104::Wrong hanging indentation before block.
+ ^ | |"
+bad-continuation:104::"Wrong hanging indentation before block.
some_arg, # [bad-continuation]
- ^ |
-bad-continuation:105::Wrong hanging indentation before block.
+ ^ |"
+bad-continuation:105::"Wrong hanging indentation before block.
some_other_arg): # [bad-continuation]
- ^ |
-bad-continuation:125::Wrong continued indentation.
- "b") # [bad-continuation]
- ^ |
-bad-continuation:139::Wrong hanging indentation before block.
+ ^ |"
+bad-continuation:125::"Wrong continued indentation.
+ ""b"") # [bad-continuation]
+ ^ |"
+bad-continuation:139::"Wrong hanging indentation before block.
): pass # [bad-continuation]
-| ^|
-bad-continuation:142::Wrong continued indentation before block.
+| ^|"
+bad-continuation:142::"Wrong continued indentation before block.
2): # [bad-continuation]
- ^ |
-bad-continuation:150::Wrong continued indentation.
+ ^ |"
+bad-continuation:150::"Wrong continued indentation.
2 and # [bad-continuation]
- | ^
-bad-continuation:155::Wrong hanging indentation before block.
+ | ^"
+bad-continuation:155::"Wrong hanging indentation before block.
2): pass # [bad-continuation]
- ^ | |
-bad-continuation:162::Wrong continued indentation before block.
+ ^ | |"
+bad-continuation:162::"Wrong continued indentation before block.
2 or # [bad-continuation]
- |^ |
-bad-continuation:166::Wrong continued indentation before block.
+ |^ |"
+bad-continuation:166::"Wrong continued indentation before block.
2): pass # [bad-continuation]
- ^ | |
-bad-continuation:172::Wrong hanging indentation before block.
+ ^ | |"
+bad-continuation:172::"Wrong hanging indentation before block.
2): # [bad-continuation]
- ^ | |
-bad-continuation:183::Wrong continued indentation.
+ ^ | |"
+bad-continuation:183::"Wrong continued indentation.
2): # [bad-continuation]
- ^ |
+ ^ |"
diff --git a/test/functional/bad_open_mode.txt b/test/functional/bad_open_mode.txt
index 356e02e..d0bb8bb 100644
--- a/test/functional/bad_open_mode.txt
+++ b/test/functional/bad_open_mode.txt
@@ -1,7 +1,7 @@
-bad-open-mode:4::"rw" is not a valid mode for open.
-bad-open-mode:5::"rw" is not a valid mode for open.
-bad-open-mode:6::"rw" is not a valid mode for open.
-bad-open-mode:7::"U+" is not a valid mode for open.
-bad-open-mode:8::"rb+" is not a valid mode for open.
-bad-open-mode:9::"Uw" is not a valid mode for open.
-bad-open-mode:13::"Uwz" is not a valid mode for open.
+bad-open-mode:4::"""rw"" is not a valid mode for open."
+bad-open-mode:5::"""rw"" is not a valid mode for open."
+bad-open-mode:6::"""rw"" is not a valid mode for open."
+bad-open-mode:7::"""U+"" is not a valid mode for open."
+bad-open-mode:8::"""rb+"" is not a valid mode for open."
+bad-open-mode:9::"""Uw"" is not a valid mode for open."
+bad-open-mode:13::"""Uwz"" is not a valid mode for open."
diff --git a/test/functional/class_members_py27.txt b/test/functional/class_members_py27.txt
index 3e88b55..e5e6005 100644
--- a/test/functional/class_members_py27.txt
+++ b/test/functional/class_members_py27.txt
@@ -1,6 +1,6 @@
-no-member:14:MyClass.test:Instance of 'MyClass' has no 'incorrect' member
-no-member:15:MyClass.test:Instance of 'MyClass' has no 'havenot' member
-no-member:16:MyClass.test:Instance of 'MyClass' has no 'nonexistent1' member
-no-member:17:MyClass.test:Instance of 'MyClass' has no 'nonexistent2' member
-no-member:50::Instance of 'TestMetaclass' has no 'register' member
-no-member:51::Instance of 'UsingMetaclass' has no 'test' member
+no-member:14:MyClass.test:Instance of 'MyClass' has no 'incorrect' member:INFERENCE
+no-member:15:MyClass.test:Instance of 'MyClass' has no 'havenot' member:INFERENCE
+no-member:16:MyClass.test:Instance of 'MyClass' has no 'nonexistent1' member:INFERENCE
+no-member:17:MyClass.test:Instance of 'MyClass' has no 'nonexistent2' member:INFERENCE
+no-member:50::Instance of 'TestMetaclass' has no 'register' member:INFERENCE
+no-member:51::Instance of 'UsingMetaclass' has no 'test' member:INFERENCE
diff --git a/test/functional/class_members_py30.txt b/test/functional/class_members_py30.txt
index 0cb808f..4696579 100644
--- a/test/functional/class_members_py30.txt
+++ b/test/functional/class_members_py30.txt
@@ -1,6 +1,6 @@
-no-member:14:MyClass.test:Instance of 'MyClass' has no 'incorrect' member
-no-member:15:MyClass.test:Instance of 'MyClass' has no 'havenot' member
-no-member:16:MyClass.test:Instance of 'MyClass' has no 'nonexistent1' member
-no-member:17:MyClass.test:Instance of 'MyClass' has no 'nonexistent2' member
-no-member:48::Instance of 'TestMetaclass' has no 'register' member
-no-member:49::Instance of 'UsingMetaclass' has no 'test' member
+no-member:14:MyClass.test:Instance of 'MyClass' has no 'incorrect' member:INFERENCE
+no-member:15:MyClass.test:Instance of 'MyClass' has no 'havenot' member:INFERENCE
+no-member:16:MyClass.test:Instance of 'MyClass' has no 'nonexistent1' member:INFERENCE
+no-member:17:MyClass.test:Instance of 'MyClass' has no 'nonexistent2' member:INFERENCE
+no-member:48::Instance of 'TestMetaclass' has no 'register' member:INFERENCE
+no-member:49::Instance of 'UsingMetaclass' has no 'test' member:INFERENCE
diff --git a/test/functional/confidence_filter.py b/test/functional/confidence_filter.py
new file mode 100644
index 0000000..64cc8c4
--- /dev/null
+++ b/test/functional/confidence_filter.py
@@ -0,0 +1,14 @@
+"""Test for the confidence filter."""
+
+class Client(object):
+ """use provider class"""
+
+ def __init__(self):
+ self.set_later = 0
+
+ def set_set_later(self, value):
+ """set set_later attribute (introduce an inference ambiguity)"""
+ self.set_later = value
+
+print Client().set_later.lower()
+print Client().foo # [no-member]
diff --git a/test/functional/confidence_filter.rc b/test/functional/confidence_filter.rc
new file mode 100644
index 0000000..5d21cb5
--- /dev/null
+++ b/test/functional/confidence_filter.rc
@@ -0,0 +1,3 @@
+[Messages Control]
+disable=no-init,too-few-public-methods,undefined-variable
+confidence=INFERENCE,HIGH,UNDEFINED
diff --git a/test/functional/confidence_filter.txt b/test/functional/confidence_filter.txt
new file mode 100644
index 0000000..308035d
--- /dev/null
+++ b/test/functional/confidence_filter.txt
@@ -0,0 +1 @@
+no-member:14::Instance of 'Client' has no 'foo' member:INFERENCE
diff --git a/test/functional/docstrings.txt b/test/functional/docstrings.txt
index 1ea5c7d..e30e462 100644
--- a/test/functional/docstrings.txt
+++ b/test/functional/docstrings.txt
@@ -1,8 +1,8 @@
-missing-docstring:1::Missing module docstring
-empty-docstring:6:function0:Empty function docstring
-missing-docstring:10:function1:Missing function docstring
-missing-docstring:23:AAAA:Missing class docstring
-missing-docstring:40:AAAA.method1:Missing method docstring
-empty-docstring:48:AAAA.method3:Empty method docstring
-empty-docstring:62:DDDD.method2:Empty method docstring
-missing-docstring:70:DDDD.method4:Missing method docstring
+missing-docstring:1::Missing module docstring
+empty-docstring:6:function0:Empty function docstring
+missing-docstring:10:function1:Missing function docstring
+missing-docstring:23:AAAA:Missing class docstring
+missing-docstring:40:AAAA.method1:Missing method docstring:INFERENCE
+empty-docstring:48:AAAA.method3:Empty method docstring:INFERENCE
+empty-docstring:62:DDDD.method2:Empty method docstring:INFERENCE
+missing-docstring:70:DDDD.method4:Missing method docstring:INFERENCE
diff --git a/test/functional/exception_is_binary_op.txt b/test/functional/exception_is_binary_op.txt
index 039bc3f..e6512c9 100644
--- a/test/functional/exception_is_binary_op.txt
+++ b/test/functional/exception_is_binary_op.txt
@@ -1,4 +1,4 @@
-binary-op-exception:5::Exception to catch is the result of a binary "or" operation
-binary-op-exception:7::Exception to catch is the result of a binary "and" operation
-binary-op-exception:9::Exception to catch is the result of a binary "or" operation
-binary-op-exception:11::Exception to catch is the result of a binary "or" operation
+binary-op-exception:5::"Exception to catch is the result of a binary ""or"" operation"
+binary-op-exception:7::"Exception to catch is the result of a binary ""and"" operation"
+binary-op-exception:9::"Exception to catch is the result of a binary ""or"" operation"
+binary-op-exception:11::"Exception to catch is the result of a binary ""or"" operation"
diff --git a/test/functional/future_unicode_literals.txt b/test/functional/future_unicode_literals.txt
index c490da5..60d291e 100644
--- a/test/functional/future_unicode_literals.txt
+++ b/test/functional/future_unicode_literals.txt
@@ -1 +1 @@
-anomalous-unicode-escape-in-string:5::Anomalous Unicode escape in byte string: '\u'. String constant might be missing an r or u prefix.
+anomalous-unicode-escape-in-string:5::"Anomalous Unicode escape in byte string: '\u'. String constant might be missing an r or u prefix."
diff --git a/test/functional/import_error.txt b/test/functional/import_error.txt
index af7f739..0602d2a 100644
--- a/test/functional/import_error.txt
+++ b/test/functional/import_error.txt
@@ -1,2 +1,2 @@
-import-error:3::Unable to import 'totally_missing'
-import-error:16::Unable to import 'maybe_missing_2'
+import-error:3::Unable to import 'totally_missing'
+import-error:16::Unable to import 'maybe_missing_2'
diff --git a/test/functional/invalid_encoded_data.txt b/test/functional/invalid_encoded_data.txt
index dc23869..c6e69dc 100644
--- a/test/functional/invalid_encoded_data.txt
+++ b/test/functional/invalid_encoded_data.txt
@@ -1 +1 @@
-invalid-encoded-data:5::Cannot decode using encoding "utf-8", unexpected byte at position 11
+invalid-encoded-data:5::"Cannot decode using encoding ""utf-8"", unexpected byte at position 11"
diff --git a/test/functional/invalid_exceptions_caught.py b/test/functional/invalid_exceptions_caught.py
index 99872d2..0bd0f7b 100644
--- a/test/functional/invalid_exceptions_caught.py
+++ b/test/functional/invalid_exceptions_caught.py
@@ -45,3 +45,21 @@ try:
1 + 3
except (SkipException, SecondSkipException):
print "caught"
+
+try:
+ 1 + 42
+# +1:[catching-non-exception,catching-non-exception]
+except (None, list()):
+ print "caught"
+
+try:
+ 1 + 24
+except None: # [catching-non-exception]
+ print "caught"
+
+EXCEPTION = None
+EXCEPTION = ZeroDivisionError
+try:
+ 1 + 46
+except EXCEPTION:
+ print "caught"
diff --git a/test/functional/invalid_exceptions_caught.txt b/test/functional/invalid_exceptions_caught.txt
index 530ef9a..ce9ab2a 100644
--- a/test/functional/invalid_exceptions_caught.txt
+++ b/test/functional/invalid_exceptions_caught.txt
@@ -1,3 +1,6 @@
-catching-non-exception:25::Catching an exception which doesn't inherit from BaseException: MyException
-catching-non-exception:31::Catching an exception which doesn't inherit from BaseException: MyException
-catching-non-exception:31::Catching an exception which doesn't inherit from BaseException: MySecondException
+catching-non-exception:25::"Catching an exception which doesn't inherit from BaseException: MyException"
+catching-non-exception:31::"Catching an exception which doesn't inherit from BaseException: MyException"
+catching-non-exception:31::"Catching an exception which doesn't inherit from BaseException: MySecondException"
+catching-non-exception:52::"Catching an exception which doesn't inherit from BaseException: None"
+catching-non-exception:52::"Catching an exception which doesn't inherit from BaseException: list()"
+catching-non-exception:57::"Catching an exception which doesn't inherit from BaseException: None"
diff --git a/test/functional/invalid_exceptions_raised.txt b/test/functional/invalid_exceptions_raised.txt
index 756d92c..54ea0f4 100644
--- a/test/functional/invalid_exceptions_raised.txt
+++ b/test/functional/invalid_exceptions_raised.txt
@@ -1,9 +1,9 @@
+nonstandard-exception:23:bad_case0:"Exception doesn't inherit from standard ""Exception"" class":INFERENCE
raising-non-exception:23:bad_case0:Raising a new style class which doesn't inherit from BaseException
-nonstandard-exception:23:bad_case0:Exception doesn't inherit from standard "Exception" class
raising-non-exception:27:bad_case1:Raising a new style class which doesn't inherit from BaseException
-raising-non-exception:33:bad_case2:Raising a new style class which doesn't inherit from BaseException
-nonstandard-exception:33:bad_case2:Exception doesn't inherit from standard "Exception" class
+nonstandard-exception:33:bad_case2:"Exception doesn't inherit from standard ""Exception"" class":INFERENCE
old-raise-syntax:33:bad_case2:Use raise ErrorClass(args) instead of raise ErrorClass, args.
+raising-non-exception:33:bad_case2:Raising a new style class which doesn't inherit from BaseException
raising-non-exception:37:bad_case3:Raising a new style class which doesn't inherit from BaseException
notimplemented-raised:42:bad_case4:NotImplemented raised - should raise NotImplementedError
old-raise-syntax:42:bad_case4:Use raise ErrorClass(args) instead of raise ErrorClass, args.
diff --git a/test/functional/invalid_name.txt b/test/functional/invalid_name.txt
index c722861..0c8eafe 100644
--- a/test/functional/invalid_name.txt
+++ b/test/functional/invalid_name.txt
@@ -1,2 +1,2 @@
-invalid-name:10::Invalid constant name "aaa"
-invalid-name:14::Invalid constant name "time"
+invalid-name:10::"Invalid constant name ""aaa"""
+invalid-name:14::"Invalid constant name ""time"""
diff --git a/test/input/func_typecheck_getattr.py b/test/functional/member_checks.py
index b120ca4..fccf160 100644
--- a/test/input/func_typecheck_getattr.py
+++ b/test/functional/member_checks.py
@@ -1,7 +1,6 @@
# pylint: disable=
"""check getattr if inference succeed"""
-__revision__ = None
class Provider(object):
"""provide some attributes and method"""
@@ -16,13 +15,13 @@ class Provider(object):
print 'hop hop hop', self
-class Client:
+class Client(object):
"""use provider class"""
def __init__(self):
self._prov = Provider()
self._prov_attr = Provider.cattr
- self._prov_attr2 = Provider.cattribute
+ self._prov_attr2 = Provider.cattribute # [no-member]
self.set_later = 0
def set_set_later(self, value):
@@ -32,12 +31,12 @@ class Client:
def use_method(self):
"""use provider's method"""
self._prov.hophop()
- self._prov.hophophop()
+ self._prov.hophophop() # [no-member]
def use_attr(self):
"""use provider's attr"""
print self._prov.attr
- print self._prov.attribute
+ print self._prov.attribute # [no-member]
def debug(self):
"""print debug information"""
@@ -49,21 +48,16 @@ class Client:
def test_bt_types(self):
"""test access to unexistant member of builtin types"""
lis = []
- lis.apppend(self)
+ lis.apppend(self) # [no-member]
dic = {}
- dic.set(self)
+ dic.set(self) # [no-member]
tup = ()
- tup.append(self)
+ tup.append(self) # [no-member]
string = 'toto'
- print string.loower()
- # unicode : moved to func_3k_removed_stuff_py_30.py
- #
+ print string.loower() # [no-member]
integer = 1
- print integer.whatever
+ print integer.whatever # [no-member]
print object.__init__
print property.__init__
-print Client().set_later.lower()
-
-# should detect mixing new style / old style classes
-Client.__bases__ += (object,)
+print Client().set_later.lower() # [no-member]
diff --git a/test/functional/member_checks.txt b/test/functional/member_checks.txt
new file mode 100644
index 0000000..12fe6ee
--- /dev/null
+++ b/test/functional/member_checks.txt
@@ -0,0 +1,9 @@
+no-member:24:Client.__init__:Class 'Provider' has no 'cattribute' member:INFERENCE
+no-member:34:Client.use_method:Instance of 'Provider' has no 'hophophop' member:INFERENCE
+no-member:39:Client.use_attr:Instance of 'Provider' has no 'attribute' member:INFERENCE
+no-member:51:Client.test_bt_types:Instance of 'list' has no 'apppend' member:INFERENCE
+no-member:53:Client.test_bt_types:Instance of 'dict' has no 'set' member:INFERENCE
+no-member:55:Client.test_bt_types:Instance of 'tuple' has no 'append' member:INFERENCE
+no-member:57:Client.test_bt_types:Instance of 'str' has no 'loower' member:INFERENCE
+no-member:59:Client.test_bt_types:Instance of 'int' has no 'whatever' member:INFERENCE
+no-member:63::Instance of 'int' has no 'lower' member:INFERENCE_FAILURE
diff --git a/test/functional/missing_self_argument.txt b/test/functional/missing_self_argument.txt
index 3f7f7f2..1deef18 100644
--- a/test/functional/missing_self_argument.txt
+++ b/test/functional/missing_self_argument.txt
@@ -1,4 +1,6 @@
no-method-argument:11:MyClass.method:Method has no argument
-no-method-argument:13:MyClass.met:Method has no argument
+no-method-argument:13:MyClass.met:"""Method has no argument
+""
+"
no-method-argument:14:MyClass.setup:Method has no argument
undefined-variable:16:MyClass.setup:Undefined variable 'self'
diff --git a/test/functional/name_styles.txt b/test/functional/name_styles.txt
index 4f0ae53..e81d27a 100644
--- a/test/functional/name_styles.txt
+++ b/test/functional/name_styles.txt
@@ -1,17 +1,17 @@
-invalid-name:6::Invalid constant name "bad_const_name"
-invalid-name:9:BADFUNCTION_name:Invalid function name "BADFUNCTION_name"
-invalid-name:11:BADFUNCTION_name:Invalid variable name "BAD_LOCAL_VAR"
-invalid-name:15:func_bad_argname:Invalid argument name "NOT_GOOD"
-invalid-name:25:bad_class_name:Invalid class name "bad_class_name"
-invalid-name:36:CorrectClassName.__init__:Invalid attribute name "_Bad_AtTR_name"
-invalid-name:37:CorrectClassName.__init__:Invalid attribute name "Bad_PUBLIC_name"
-invalid-name:39:CorrectClassName:Invalid class attribute name "zz"
-invalid-name:42:CorrectClassName.BadMethodName:Invalid method name "BadMethodName"
-invalid-name:48:CorrectClassName.__DunDER_IS_not_free_for_all__:Invalid method name "__DunDER_IS_not_free_for_all__"
-invalid-name:78::Invalid class name "BAD_NAME_FOR_CLASS"
-invalid-name:79::Invalid class name "NEXT_BAD_NAME_FOR_CLASS"
-invalid-name:86::Invalid class name "NOT_CORRECT"
-invalid-name:92:test_globals:Invalid constant name "AlsoCorrect"
-invalid-name:105:FooClass.PROPERTY_NAME:Invalid attribute name "PROPERTY_NAME"
-invalid-name:110:FooClass.ABSTRACT_PROPERTY_NAME:Invalid attribute name "ABSTRACT_PROPERTY_NAME"
-invalid-name:115:FooClass.PROPERTY_NAME_SETTER:Invalid attribute name "PROPERTY_NAME_SETTER"
+invalid-name:6::"Invalid constant name ""bad_const_name"""
+invalid-name:9:BADFUNCTION_name:"Invalid function name ""BADFUNCTION_name"""
+invalid-name:11:BADFUNCTION_name:"Invalid variable name ""BAD_LOCAL_VAR"""
+invalid-name:15:func_bad_argname:"Invalid argument name ""NOT_GOOD"""
+invalid-name:25:bad_class_name:"Invalid class name ""bad_class_name"""
+invalid-name:36:CorrectClassName.__init__:"Invalid attribute name ""_Bad_AtTR_name"""
+invalid-name:37:CorrectClassName.__init__:"Invalid attribute name ""Bad_PUBLIC_name"""
+invalid-name:39:CorrectClassName:"Invalid class attribute name ""zz"""
+invalid-name:42:CorrectClassName.BadMethodName:"Invalid method name ""BadMethodName""":INFERENCE
+invalid-name:48:CorrectClassName.__DunDER_IS_not_free_for_all__:"Invalid method name ""__DunDER_IS_not_free_for_all__""":INFERENCE
+invalid-name:78::"Invalid class name ""BAD_NAME_FOR_CLASS"""
+invalid-name:79::"Invalid class name ""NEXT_BAD_NAME_FOR_CLASS"""
+invalid-name:86::"Invalid class name ""NOT_CORRECT"""
+invalid-name:92:test_globals:"Invalid constant name ""AlsoCorrect"""
+invalid-name:105:FooClass.PROPERTY_NAME:"Invalid attribute name ""PROPERTY_NAME""":INFERENCE
+invalid-name:110:FooClass.ABSTRACT_PROPERTY_NAME:"Invalid attribute name ""ABSTRACT_PROPERTY_NAME""":INFERENCE
+invalid-name:115:FooClass.PROPERTY_NAME_SETTER:"Invalid attribute name ""PROPERTY_NAME_SETTER""":INFERENCE
diff --git a/test/functional/namedtuple_member_inference.txt b/test/functional/namedtuple_member_inference.txt
index 87d9da4..308336f 100644
--- a/test/functional/namedtuple_member_inference.txt
+++ b/test/functional/namedtuple_member_inference.txt
@@ -1,3 +1,3 @@
-no-member:15:test:Class 'Thing' has no 'x' member
+no-member:15:test:Class 'Thing' has no 'x' member:INFERENCE
protected-access:19:test:Access to a protected member _replace of a client class
-no-member:21:test:Instance of 'Fantastic' has no 'foo' member
+no-member:21:test:Instance of 'Fantastic' has no 'foo' member:INFERENCE
diff --git a/test/functional/newstyle__slots__.txt b/test/functional/newstyle__slots__.txt
index afc9117..4320390 100644
--- a/test/functional/newstyle__slots__.txt
+++ b/test/functional/newstyle__slots__.txt
@@ -1,2 +1,2 @@
old-style-class:10:OldStyleClass:Old-style class defined.
-slots-on-old-class:10:OldStyleClass:Use of __slots__ on an old style class
+slots-on-old-class:10:OldStyleClass:Use of __slots__ on an old style class:INFERENCE
diff --git a/test/functional/newstyle_properties.txt b/test/functional/newstyle_properties.txt
index f3a8dbb..a16686b 100644
--- a/test/functional/newstyle_properties.txt
+++ b/test/functional/newstyle_properties.txt
@@ -1,2 +1,2 @@
old-style-class:13:OldStyleClass:Old-style class defined.
-property-on-old-class:15:OldStyleClass:Use of "property" on an old style class
+property-on-old-class:15:OldStyleClass:"Use of ""property"" on an old style class":INFERENCE
diff --git a/test/functional/old_style_class_py27.py b/test/functional/old_style_class_py27.py
new file mode 100644
index 0000000..2c3c126
--- /dev/null
+++ b/test/functional/old_style_class_py27.py
@@ -0,0 +1,17 @@
+""" Tests for old style classes. """
+# pylint: disable=no-init, too-few-public-methods, invalid-name
+
+class Old: # [old-style-class]
+ """ old style class """
+
+class Child(Old):
+ """ Old style class, but don't emit for it. """
+
+class NotOldStyle2:
+ """ Because I have a metaclass at class level. """
+ __metaclass__ = type
+
+__metaclass__ = type
+
+class NotOldStyle:
+ """ Because I have a metaclass at global level. """
diff --git a/test/functional/old_style_class_py27.rc b/test/functional/old_style_class_py27.rc
new file mode 100644
index 0000000..a650233
--- /dev/null
+++ b/test/functional/old_style_class_py27.rc
@@ -0,0 +1,2 @@
+[testoptions]
+max_pyver=3.0
diff --git a/test/functional/old_style_class_py27.txt b/test/functional/old_style_class_py27.txt
new file mode 100644
index 0000000..c46a7b6
--- /dev/null
+++ b/test/functional/old_style_class_py27.txt
@@ -0,0 +1,2 @@
+old-style-class:4:Old:Old-style class defined.
+old-style-class:7:Child:Old-style class defined.
diff --git a/test/functional/statement_without_effect.txt b/test/functional/statement_without_effect.txt
index 636a441..81fc51b 100644
--- a/test/functional/statement_without_effect.txt
+++ b/test/functional/statement_without_effect.txt
@@ -1,28 +1,60 @@
pointless-string-statement:5::String statement has no effect
-pointless-statement:6::Statement seems to have no effect
-pointless-statement:8::Statement seems to have no effect
+pointless-statement:6::"""Statement seems to have no effect
+""
+"
+pointless-statement:8::"""Statement seems to have no effect
+""
+"
pointless-statement:9::Statement seems to have no effect
pointless-statement:11::Statement seems to have no effect
-pointless-statement:12::Statement seems to have no effect
+pointless-statement:12::"""Statement seems to have no effect
+""
+"
pointless-statement:15::Statement seems to have no effect
-pointless-string-statement:15::String statement has no effect
-unnecessary-semicolon:17::Unnecessary semicolon
+pointless-string-statement:15::"""String statement has no effect
+""
+"
+unnecessary-semicolon:17::"""Unnecessary semicolon
+""
+"
pointless-string-statement:18::String statement has no effect
-unnecessary-semicolon:18::Unnecessary semicolon
-expression-not-assigned:19::Expression "(list()) and (tuple())" is assigned to nothing
-expression-not-assigned:20::Expression "(list()) and (tuple())" is assigned to nothing
+unnecessary-semicolon:18::"""Unnecessary semicolon
+""
+"
+expression-not-assigned:19::"""Expression """"(list()) and (tuple())"""" is assigned to nothing
+""
+"
+expression-not-assigned:20::"""Expression """"(list()) and (tuple())"""" is assigned to nothing
+""
+"
unnecessary-semicolon:21::Unnecessary semicolon
-expression-not-assigned:23::Expression "(list()) and (tuple())" is assigned to nothing
-expression-not-assigned:26::Expression "ANSWER == to_be()" is assigned to nothing
-expression-not-assigned:27::Expression "ANSWER == to_be()" is assigned to nothing
-expression-not-assigned:28::Expression "(to_be()) or (not to_be())" is assigned to nothing
-expression-not-assigned:29::Expression "(to_be()) or (not to_be())" is assigned to nothing
-expression-not-assigned:30::Expression "ANSWER == to_be()" is assigned to nothing
-expression-not-assigned:32::Expression "(to_be()) or (not to_be())" is assigned to nothing
-expression-not-assigned:33::Expression "to_be().title" is assigned to nothing
-pointless-string-statement:54:ClassLevelAttributeTest.__init__:String statement has no effect
-pointless-string-statement:55:ClassLevelAttributeTest.__init__:String statement has no effect
+expression-not-assigned:23::"Expression ""(list()) and (tuple())"" is assigned to nothing"
+expression-not-assigned:26::"""Expression """"ANSWER == to_be()"""" is assigned to nothing
+""
+"
+expression-not-assigned:27::"""Expression """"ANSWER == to_be()"""" is assigned to nothing
+""
+"
+expression-not-assigned:28::"""Expression """"(to_be()) or (not to_be())"""" is assigned to nothing
+""
+"
+expression-not-assigned:29::"""Expression """"(to_be()) or (not to_be())"""" is assigned to nothing
+""
+"
+expression-not-assigned:30::"Expression ""ANSWER == to_be()"" is assigned to nothing"
+expression-not-assigned:32::"Expression ""(to_be()) or (not to_be())"" is assigned to nothing"
+expression-not-assigned:33::"Expression ""to_be().title"" is assigned to nothing"
+pointless-string-statement:54:ClassLevelAttributeTest.__init__:"""String statement has no effect
+""
+"
+pointless-string-statement:55:ClassLevelAttributeTest.__init__:"""String statement has no effect
+""
+"
pointless-string-statement:58:ClassLevelAttributeTest.__init__:String statement has no effect
-pointless-string-statement:61:ClassLevelAttributeTest.test:String statement has no effect
-pointless-string-statement:62:ClassLevelAttributeTest.test:String statement has no effect
+pointless-string-statement:61:ClassLevelAttributeTest.test:"""String statement has no effect
+""
+"
+pointless-string-statement:62:ClassLevelAttributeTest.test:"""String statement has no effect
+""
+"
pointless-string-statement:65:ClassLevelAttributeTest.test:String statement has no effect
diff --git a/test/functional/string_formatting.txt b/test/functional/string_formatting.txt
index 5f27835..593c161 100644
--- a/test/functional/string_formatting.txt
+++ b/test/functional/string_formatting.txt
@@ -16,7 +16,7 @@ missing-format-argument-key:71:pprint_bad:Missing keyword argument 'b' for forma
missing-format-argument-key:72:pprint_bad:Missing keyword argument 'a' for format string
missing-format-attribute:74:pprint_bad:Missing format attribute 'length' in format specifier 'a.ids.__len__.length'
invalid-format-index:75:pprint_bad:Using invalid lookup key 400 in format specifier 'a.ids[3][400]'
-invalid-format-index:76:pprint_bad:Using invalid lookup key "'string'" in format specifier 'a.ids[3]["\'string\'"]'
+invalid-format-index:76:pprint_bad:"Using invalid lookup key ""'string'"" in format specifier 'a.ids[3][""\'string\'""]'"
invalid-format-index:77:pprint_bad:Using invalid lookup key 1 in format specifier '0[0][1]'
invalid-format-index:78:pprint_bad:Using invalid lookup key 0 in format specifier '0[0][0]'
missing-format-argument-key:80:pprint_bad:Missing keyword argument 'b' for format string
diff --git a/test/functional/super_checks.txt b/test/functional/super_checks.txt
index 2d69098..79f6ae1 100644
--- a/test/functional/super_checks.txt
+++ b/test/functional/super_checks.txt
@@ -1,8 +1,8 @@
old-style-class:6:Aaaa:Old-style class defined.
-super-on-old-class:8:Aaaa.hop:Use of super on an old style class
-super-on-old-class:12:Aaaa.__init__:Use of super on an old style class
-bad-super-call:22:NewAaaa.__init__:Bad first argument 'object' given to super()
-missing-super-argument:27:Py3kAaaa.__init__:Missing argument to super()
-bad-super-call:32:Py3kWrongSuper.__init__:Bad first argument 'NewAaaa' given to super()
-bad-super-call:37:WrongNameRegression.__init__:Bad first argument 'Missing' given to super()
-bad-super-call:46:CrashSuper.__init__:Bad first argument 'NewAaaa' given to super()
+super-on-old-class:8:Aaaa.hop:Use of super on an old style class:INFERENCE
+super-on-old-class:12:Aaaa.__init__:Use of super on an old style class:INFERENCE
+bad-super-call:22:NewAaaa.__init__:Bad first argument 'object' given to super():INFERENCE
+missing-super-argument:27:Py3kAaaa.__init__:Missing argument to super():INFERENCE
+bad-super-call:32:Py3kWrongSuper.__init__:Bad first argument 'NewAaaa' given to super():INFERENCE
+bad-super-call:37:WrongNameRegression.__init__:Bad first argument 'Missing' given to super():INFERENCE
+bad-super-call:46:CrashSuper.__init__:Bad first argument 'NewAaaa' given to super():INFERENCE
diff --git a/test/functional/too_many_branches.txt b/test/functional/too_many_branches.txt
index 434dc3e..fbc82cc 100644
--- a/test/functional/too_many_branches.txt
+++ b/test/functional/too_many_branches.txt
@@ -1 +1 @@
-too-many-branches:3:wrong:Too many branches (13/12)
+too-many-branches:3:wrong:Too many branches (13/12)
diff --git a/test/functional/undefined_variable.py b/test/functional/undefined_variable.py
index 25f0145..003708c 100644
--- a/test/functional/undefined_variable.py
+++ b/test/functional/undefined_variable.py
@@ -1,5 +1,5 @@
"""Test warnings about access to undefined variables."""
-# pylint: disable=too-few-public-methods, no-init, no-self-use
+# pylint: disable=too-few-public-methods, no-init, no-self-use, old-style-class
DEFINED = 1
diff --git a/test/input/func_noerror_access_attr_before_def_false_positive.py b/test/input/func_noerror_access_attr_before_def_false_positive.py
index 3bf966b..7f8bbb4 100644
--- a/test/input/func_noerror_access_attr_before_def_false_positive.py
+++ b/test/input/func_noerror_access_attr_before_def_false_positive.py
@@ -1,4 +1,4 @@
-#pylint: disable=C0103,R0904,R0903,W0201
+#pylint: disable=C0103,R0904,R0903,W0201,old-style-class
"""
This module demonstrates a possible problem of pyLint with calling __init__ s
from inherited classes.
diff --git a/test/input/func_noerror_classes_protected_member_access.py b/test/input/func_noerror_classes_protected_member_access.py
index bef1bff..eeff97d 100644
--- a/test/input/func_noerror_classes_protected_member_access.py
+++ b/test/input/func_noerror_classes_protected_member_access.py
@@ -22,3 +22,4 @@ class A3123(object):
smeth = staticmethod(smeth)
+ prop = property(lambda self: self._protected)
diff --git a/test/input/func_noerror_indirect_interface.py b/test/input/func_noerror_indirect_interface.py
index a0f245a..56d9ddd 100644
--- a/test/input/func_noerror_indirect_interface.py
+++ b/test/input/func_noerror_indirect_interface.py
@@ -1,7 +1,7 @@
"""shows a bug where pylint can't find interfaces when they are
used indirectly. See input/indirect[123].py for details on the
setup"""
-
+# pylint: disable=old-style-class
__revision__ = None
from input.indirect2 import AbstractToto
diff --git a/test/input/func_w0233.py b/test/input/func_w0233.py
index 857743a..ef5f205 100644
--- a/test/input/func_w0233.py
+++ b/test/input/func_w0233.py
@@ -34,3 +34,17 @@ class DDDD(AAAA):
AAAA.__init__(self)
else:
AAAA.__init__(self)
+
+class Super(dict):
+ """ test late binding super() call """
+ def __init__(self):
+ base = super()
+ base.__init__()
+
+class Super2(dict):
+ """ Using the same idiom as Super, but without calling
+ the __init__ method.
+ """
+ def __init__(self):
+ base = super()
+ base.__woohoo__()
diff --git a/test/messages/func_typecheck_getattr.txt b/test/messages/func_typecheck_getattr.txt
deleted file mode 100644
index bd91228..0000000
--- a/test/messages/func_typecheck_getattr.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-C: 19:Client: Old-style class defined.
-E: 25:Client.__init__: Class 'Provider' has no 'cattribute' member
-E: 35:Client.use_method: Instance of 'Provider' has no 'hophophop' member
-E: 40:Client.use_attr: Instance of 'Provider' has no 'attribute' member
-E: 52:Client.test_bt_types: Instance of 'list' has no 'apppend' member
-E: 54:Client.test_bt_types: Instance of 'dict' has no 'set' member
-E: 56:Client.test_bt_types: Instance of 'tuple' has no 'append' member
-E: 58:Client.test_bt_types: Instance of 'str' has no 'loower' member
-E: 62:Client.test_bt_types: Instance of 'int' has no 'whatever' member
-E: 66: Instance of 'int' has no 'lower' member (but some types could not be inferred)
diff --git a/test/messages/func_w0233.txt b/test/messages/func_w0233.txt
index 614ceac..157b270 100644
--- a/test/messages/func_w0233.txt
+++ b/test/messages/func_w0233.txt
@@ -2,3 +2,4 @@ E: 22:CCC: Module 'input.func_w0233' has no 'BBBB' member
E: 27:CCC.__init__: Module 'input.func_w0233' has no 'BBBB' member
F: 20: Unable to import 'nonexistant'
W: 12:AAAA.__init__: __init__ method from a non direct base class 'BBBBMixin' is called
+W: 48:Super2.__init__: __init__ method from base class 'dict' is not called \ No newline at end of file
diff --git a/test/test_functional.py b/test/test_functional.py
index fb495e0..c2467b3 100644
--- a/test/test_functional.py
+++ b/test/test_functional.py
@@ -1,5 +1,6 @@
"""Functional full-module tests for PyLint."""
from __future__ import unicode_literals
+import csv
import collections
import ConfigParser
import io
@@ -9,14 +10,31 @@ import re
import sys
import unittest
+from pylint import checkers
+from pylint import interfaces
from pylint import lint
from pylint import reporters
-from pylint import checkers
+from pylint import utils
+
+class test_dialect(csv.excel):
+ if sys.version_info[0] < 3:
+ delimiter = b':'
+ lineterminator = b'\n'
+ else:
+ delimiter = ':'
+ lineterminator = '\n'
+
+
+csv.register_dialect('test', test_dialect)
class NoFileError(Exception):
pass
+# Notes:
+# - for the purpose of this test, the confidence levels HIGH and UNDEFINED
+# are treated as the same.
+
# TODOs
# - implement exhaustivity tests
@@ -24,10 +42,24 @@ class NoFileError(Exception):
UPDATE = False
class OutputLine(collections.namedtuple('OutputLine',
- ['symbol', 'lineno', 'object', 'msg'])):
+ ['symbol', 'lineno', 'object', 'msg', 'confidence'])):
@classmethod
def from_msg(cls, msg):
- return cls(msg.symbol, msg.line, msg.obj or '', msg.msg + '\n')
+ return cls(
+ msg.symbol, msg.line, msg.obj or '', msg.msg,
+ msg.confidence.name
+ if msg.confidence != interfaces.UNDEFINED else interfaces.HIGH.name)
+
+ @classmethod
+ def from_csv(cls, row):
+ confidence = row[4] if len(row) == 5 else interfaces.HIGH.name
+ return cls(row[0], int(row[1]), row[2], row[3], confidence)
+
+ def to_csv(self):
+ if self.confidence == interfaces.HIGH.name:
+ return self[:-1]
+ else:
+ return self
# Common sub-expressions.
@@ -48,8 +80,8 @@ def parse_python_version(str):
class TestReporter(reporters.BaseReporter):
- def add_message(self, msg_id, location, msg):
- self.messages.append(reporters.Message(self, msg_id, location, msg))
+ def handle_message(self, msg):
+ self.messages.append(msg)
def on_set_current_module(self, module, filepath):
self.messages = []
@@ -123,16 +155,7 @@ _OPERATORS = {
}
def parse_expected_output(stream):
- lines = []
- for line in stream:
- parts = line.split(':', 3)
- if len(parts) != 4:
- symbol, lineno, obj, msg = lines.pop()
- lines.append(OutputLine(symbol, lineno, obj, msg + line))
- else:
- linenum = int(parts[1])
- lines.append(OutputLine(parts[0], linenum, parts[2], parts[3]))
- return lines
+ return [OutputLine.from_csv(row) for row in csv.reader(stream, 'test')]
def get_expected_messages(stream):
@@ -284,7 +307,7 @@ class LintModuleOutputUpdate(LintModuleTest):
try:
return super(LintModuleOutputUpdate, self)._open_expected_file()
except IOError:
- return contextlib.closing(cStringIO.StringIO())
+ return io.StringIO()
def _check_output_text(self, expected_messages, expected_lines,
received_lines):
@@ -295,9 +318,9 @@ class LintModuleOutputUpdate(LintModuleTest):
remaining.extend(received_lines)
remaining.sort(key=lambda m: (m[1], m[0], m[3]))
with open(self._test_file.expected_output, 'w') as fobj:
+ writer = csv.writer(fobj, dialect='test')
for line in remaining:
- fobj.write('{0}:{1}:{2}:{3}'.format(*line))
-
+ writer.writerow(line.to_csv())
def suite():
input_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)),
diff --git a/test/test_self.py b/test/test_self.py
index 4935f8a..7eb3261 100644
--- a/test/test_self.py
+++ b/test/test_self.py
@@ -33,9 +33,9 @@ class MultiReporter(BaseReporter):
for rep in self._reporters:
rep.on_set_current_module(*args, **kwargs)
- def add_message(self, *args, **kwargs):
+ def handle_message(self, msg):
for rep in self._reporters:
- rep.add_message(*args, **kwargs)
+ rep.handle_message(msg)
def display_results(self, layout):
pass
diff --git a/test/unittest_checker_base.py b/test/unittest_checker_base.py
index fe0dbca..adf50d8 100644
--- a/test/unittest_checker_base.py
+++ b/test/unittest_checker_base.py
@@ -82,8 +82,6 @@ class NameCheckerTest(CheckerTestCase):
@set_config(attr_rgx=re.compile('[A-Z]+'))
def test_property_names(self):
- if sys.version_info < (2, 6):
- self.skip('abc module does not exist on 2.5')
# 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.
@@ -161,18 +159,19 @@ class MultiNamingStyleTest(CheckerTestCase):
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):
+ def test_multi_name_detection_majority(self):
classes = test_utils.extract_node("""
- class CLASSA(object): #@
- pass
class classb(object): #@
pass
+ class CLASSA(object): #@
+ pass
class CLASSC(object): #@
pass
""")
- with self.assertAddsMessages(Message('invalid-name', node=classes[1], args=('class', 'classb', ''))):
+ with self.assertAddsMessages(Message('invalid-name', node=classes[0], args=('class', 'classb', ''))):
for cls in classes:
self.checker.visit_class(cls)
+ self.checker.leave_module(cls.root)
@set_config(class_rgx=MULTI_STYLE_RE)
def test_multi_name_detection_first_invalid(self):
@@ -188,6 +187,7 @@ class MultiNamingStyleTest(CheckerTestCase):
Message('invalid-name', node=classes[2], args=('class', 'CLASSC', ''))):
for cls in classes:
self.checker.visit_class(cls)
+ self.checker.leave_module(cls.root)
@set_config(method_rgx=MULTI_STYLE_RE,
function_rgx=MULTI_STYLE_RE,
@@ -204,6 +204,7 @@ class MultiNamingStyleTest(CheckerTestCase):
with self.assertAddsMessages(Message('invalid-name', node=function_defs[1], args=('function', 'FUNC', ''))):
for func in function_defs:
self.checker.visit_function(func)
+ self.checker.leave_module(func.root)
@set_config(function_rgx=re.compile('(?:(?P<ignore>FOO)|(?P<UP>[A-Z]+)|(?P<down>[a-z]+))$'))
def test_multi_name_detection_exempt(self):
@@ -220,6 +221,7 @@ class MultiNamingStyleTest(CheckerTestCase):
with self.assertAddsMessages(Message('invalid-name', node=function_defs[3], args=('function', 'UPPER', ''))):
for func in function_defs:
self.checker.visit_function(func)
+ self.checker.leave_module(func.root)
if __name__ == '__main__':
diff --git a/test/unittest_lint.py b/test/unittest_lint.py
index b2022cc..7dc7090 100644
--- a/test/unittest_lint.py
+++ b/test/unittest_lint.py
@@ -26,12 +26,13 @@ from logilab.common.compat import reload
from pylint import config
from pylint.lint import PyLinter, Run, UnknownMessage, preprocess_options, \
ArgumentPreprocessingError
-from pylint.utils import MSG_STATE_SCOPE_CONFIG, MSG_STATE_SCOPE_MODULE, \
+from pylint.utils import MSG_STATE_SCOPE_CONFIG, MSG_STATE_SCOPE_MODULE, MSG_STATE_CONFIDENCE, \
MessagesStore, PyLintASTWalker, MessageDefinition, FileState, \
build_message_def, tokenize_module
from pylint.testutils import TestReporter
from pylint.reporters import text
from pylint import checkers
+from pylint import interfaces
if sys.platform == 'win32':
HOME = 'USERPROFILE'
@@ -139,19 +140,26 @@ class PyLinterTC(unittest.TestCase):
self.assertTrue(linter.is_message_enabled('C0121', line=1))
def test_message_state_scope(self):
+ class FakeConfig(object):
+ confidence = ['HIGH']
+
linter = self.init_linter()
- fs = linter.file_state
linter.disable('C0121')
self.assertEqual(MSG_STATE_SCOPE_CONFIG,
- fs._message_state_scope('C0121'))
+ linter.get_message_state_scope('C0121'))
linter.disable('W0101', scope='module', line=3)
self.assertEqual(MSG_STATE_SCOPE_CONFIG,
- fs._message_state_scope('C0121'))
+ linter.get_message_state_scope('C0121'))
self.assertEqual(MSG_STATE_SCOPE_MODULE,
- fs._message_state_scope('W0101', 3))
+ linter.get_message_state_scope('W0101', 3))
linter.enable('W0102', scope='module', line=3)
self.assertEqual(MSG_STATE_SCOPE_MODULE,
- fs._message_state_scope('W0102', 3))
+ linter.get_message_state_scope('W0102', 3))
+ linter.config = FakeConfig()
+ self.assertEqual(
+ MSG_STATE_CONFIDENCE,
+ linter.get_message_state_scope('this-is-bad',
+ confidence=interfaces.INFERENCE))
def test_enable_message_block(self):
linter = self.init_linter()
diff --git a/test/unittest_utils.py b/test/unittest_utils.py
index ef0cda2..d631dbb 100644
--- a/test/unittest_utils.py
+++ b/test/unittest_utils.py
@@ -15,6 +15,7 @@ import unittest
from astroid import test_utils
from pylint import utils
+from pylint import interfaces
from pylint.checkers.utils import check_messages
@@ -59,4 +60,3 @@ class PyLintASTWalkerTest(unittest.TestCase):
if __name__ == '__main__':
unittest.main()
-
diff --git a/testutils.py b/testutils.py
index ef21211..86539ac 100644
--- a/testutils.py
+++ b/testutils.py
@@ -141,7 +141,7 @@ class UnittestLinter(object):
finally:
self._messages = []
- def add_message(self, msg_id, line=None, node=None, args=None):
+ def add_message(self, msg_id, line=None, node=None, args=None, confidence=None):
self._messages.append(Message(msg_id, line, node, args))
def is_message_enabled(self, *unused_args):
@@ -245,6 +245,7 @@ class LintTestUsingModule(unittest.TestCase):
depends = None
output = None
_TEST_TYPE = 'module'
+ maxDiff = None
def shortDescription(self):
values = {'mode' : self._TEST_TYPE,
diff --git a/utils.py b/utils.py
index b476dc1..4be8e20 100644
--- a/utils.py
+++ b/utils.py
@@ -16,12 +16,15 @@
"""some various utilities and helper classes, most of them used in the
main pylint class
"""
+from __future__ import print_function
+import collections
+import os
import re
import sys
import tokenize
-import os
-from warnings import warn
+import warnings
+
from os.path import dirname, basename, splitext, exists, isdir, join, normpath
from logilab.common.interface import implements
@@ -33,7 +36,7 @@ from astroid import nodes, Module
from astroid.modutils import modpath_from_file, get_module_files, \
file_from_modpath, load_module_from_file
-from pylint.interfaces import IRawChecker, ITokenChecker
+from pylint.interfaces import IRawChecker, ITokenChecker, UNDEFINED
class UnknownMessage(Exception):
@@ -51,7 +54,7 @@ MSG_TYPES = {
'E' : 'error',
'F' : 'fatal'
}
-MSG_TYPES_LONG = dict([(v, k) for k, v in MSG_TYPES.iteritems()])
+MSG_TYPES_LONG = {v: k for k, v in MSG_TYPES.iteritems()}
MSG_TYPES_STATUS = {
'I' : 0,
@@ -65,6 +68,7 @@ MSG_TYPES_STATUS = {
_MSG_ORDER = 'EWRCIF'
MSG_STATE_SCOPE_CONFIG = 0
MSG_STATE_SCOPE_MODULE = 1
+MSG_STATE_CONFIDENCE = 2
OPTION_RGX = re.compile(r'\s*#.*\bpylint:(.*)')
@@ -75,6 +79,29 @@ class WarningScope(object):
LINE = 'line-based-msg'
NODE = 'node-based-msg'
+_MsgBase = collections.namedtuple(
+ '_MsgBase',
+ ['msg_id', 'symbol', 'msg', 'C', 'category', 'confidence',
+ 'abspath', 'module', 'obj', 'line', 'column'])
+
+
+class Message(_MsgBase):
+ """This class represent a message to be issued by the reporters"""
+ def __new__(cls, msg_id, symbol, location, msg, confidence):
+ return _MsgBase.__new__(
+ cls, msg_id, symbol, msg, msg_id[0], MSG_TYPES[msg_id[0]],
+ confidence, *location)
+
+ def format(self, template):
+ """Format the message according to the given template.
+
+ The template format is the one of the format method :
+ cf. http://docs.python.org/2/library/string.html#formatstrings
+ """
+ # For some reason, _asdict on derived namedtuples does not work with
+ # Python 3.4. Needs some investigation.
+ return template.format(**dict(zip(self._fields, self)))
+
def get_module_and_frameid(node):
"""return the module name and the frame id in the module"""
@@ -124,8 +151,8 @@ def build_message_def(checker, msgid, msg_tuple):
# messages should have a symbol, but for backward compatibility
# they may not.
(msg, descr) = msg_tuple
- warn("[pylint 0.26] description of message %s doesn't include "
- "a symbolic name" % msgid, DeprecationWarning)
+ warnings.warn("[pylint 0.26] description of message %s doesn't include "
+ "a symbolic name" % msgid, DeprecationWarning)
symbol = None
options.setdefault('scope', default_scope)
return MessageDefinition(checker, msgid, msg, descr, symbol, **options)
@@ -278,12 +305,25 @@ class MessagesHandlerMixIn(object):
# sync configuration object
self.config.enable = [mid for mid, val in msgs.iteritems() if val]
- def is_message_enabled(self, msg_descr, line=None):
+ def get_message_state_scope(self, msgid, line=None, confidence=UNDEFINED):
+ """Returns the scope at which a message was enabled/disabled."""
+ if self.config.confidence and confidence.name not in self.config.confidence:
+ return MSG_STATE_CONFIDENCE
+ try:
+ if line in self.file_state._module_msgs_state[msgid]:
+ return MSG_STATE_SCOPE_MODULE
+ except (KeyError, TypeError):
+ return MSG_STATE_SCOPE_CONFIG
+
+ def is_message_enabled(self, msg_descr, line=None, confidence=None):
"""return true if the message associated to the given message id is
enabled
msgid may be either a numeric or symbolic message id.
"""
+ if self.config.confidence and confidence:
+ if confidence.name not in self.config.confidence:
+ return False
try:
msgid = self.msgs_store.check_message_id(msg_descr).msgid
except UnknownMessage:
@@ -298,7 +338,7 @@ class MessagesHandlerMixIn(object):
except KeyError:
return self._msgs_state.get(msgid, True)
- def add_message(self, msg_descr, line=None, node=None, args=None):
+ def add_message(self, msg_descr, line=None, node=None, args=None, confidence=UNDEFINED):
"""Adds a message given by ID or name.
If provided, the message string is expanded using args
@@ -328,8 +368,10 @@ class MessagesHandlerMixIn(object):
else:
col_offset = None
# should this message be displayed
- if not self.is_message_enabled(msgid, line):
- self.file_state.handle_ignored_message(msgid, line, node, args)
+ if not self.is_message_enabled(msgid, line, confidence):
+ self.file_state.handle_ignored_message(
+ self.get_message_state_scope(msgid, line, confidence),
+ msgid, line, node, args, confidence)
return
# update stats
msg_cat = MSG_TYPES[msgid[0]]
@@ -352,7 +394,9 @@ class MessagesHandlerMixIn(object):
module, obj = get_module_and_frameid(node)
path = node.root().file
# add the message
- self.reporter.add_message(msgid, (path, module, obj, line or 1, col_offset or 0), msg)
+ self.reporter.handle_message(
+ Message(msgid, symbol,
+ (path, module, obj, line or 1, col_offset or 0), msg, confidence))
def print_full_documentation(self):
"""output a full documentation in ReST format"""
@@ -360,18 +404,18 @@ class MessagesHandlerMixIn(object):
for checker in self.get_checkers():
if checker.name == 'master':
prefix = 'Main '
- print "Options"
- print '-------\n'
+ print("Options")
+ print('-------\n')
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)
+ print(title)
+ print('~' * len(title))
rest_format_section(sys.stdout, None, options)
- print
+ print()
else:
try:
by_checker[checker.name][0] += checker.options_and_values()
@@ -384,32 +428,32 @@ class MessagesHandlerMixIn(object):
for checker, (options, msgs, reports) in by_checker.iteritems():
prefix = ''
title = '%s checker' % checker
- print title
- print '-' * len(title)
- print
+ print(title)
+ print('-' * len(title))
+ print()
if options:
title = 'Options'
- print title
- print '~' * len(title)
+ print(title)
+ print('~' * len(title))
rest_format_section(sys.stdout, None, options)
- print
+ print()
if msgs:
title = ('%smessages' % prefix).capitalize()
- print title
- print '~' * len(title)
+ print(title)
+ print('~' * len(title))
for msgid, msg in sorted(msgs.iteritems(),
- key=lambda (k, v): (_MSG_ORDER.index(k[0]), k)):
+ key=lambda kv: (_MSG_ORDER.index(kv[0][0]), kv[1])):
msg = build_message_def(checker, msgid, msg)
- print msg.format_help(checkerref=False)
- print
+ print(msg.format_help(checkerref=False))
+ print()
if reports:
title = ('%sreports' % prefix).capitalize()
- print title
- print '~' * len(title)
+ print(title)
+ print('~' * len(title))
for report in reports:
- print ':%s: %s' % report[:2]
- print
- print
+ print(':%s: %s' % report[:2])
+ print()
+ print()
class FileState(object):
@@ -493,14 +537,13 @@ class FileState(object):
except KeyError:
self._module_msgs_state[msg.msgid] = {line: status}
- def handle_ignored_message(self, msgid, line, node, args):
+ def handle_ignored_message(self, state_scope, msgid, line, node, args, confidence):
"""Report an ignored message.
state_scope is either MSG_STATE_SCOPE_MODULE or MSG_STATE_SCOPE_CONFIG,
depending on whether the message was disabled locally in the module,
or globally. The other arguments are the same as for add_message.
"""
- state_scope = self._message_state_scope(msgid, line)
if state_scope == MSG_STATE_SCOPE_MODULE:
try:
orig_line = self._suppression_mapping[(msgid, line)]
@@ -508,14 +551,6 @@ class FileState(object):
except KeyError:
pass
- def _message_state_scope(self, msgid, line=None):
- """Returns the scope at which a message was enabled/disabled."""
- try:
- if line in self._module_msgs_state[msgid]:
- return MSG_STATE_SCOPE_MODULE
- except KeyError:
- return MSG_STATE_SCOPE_CONFIG
-
def iter_spurious_suppression_messages(self, msgs_store):
for warning, lines in self._raw_module_msgs_state.iteritems():
for line, enable in lines.iteritems():
@@ -615,11 +650,11 @@ class MessagesStore(object):
"""display help messages for the given message identifiers"""
for msgid in msgids:
try:
- print self.check_message_id(msgid).format_help(checkerref=True)
- print
- except UnknownMessage, ex:
- print ex
- print
+ print(self.check_message_id(msgid).format_help(checkerref=True))
+ print()
+ except UnknownMessage as ex:
+ print(ex)
+ print()
continue
def list_messages(self):
@@ -628,8 +663,8 @@ class MessagesStore(object):
for msg in msgs:
if not msg.may_be_emitted():
continue
- print msg.format_help(checkerref=False)
- print
+ print(msg.format_help(checkerref=False))
+ print()
class ReportsHandlerMixIn(object):
@@ -640,6 +675,22 @@ class ReportsHandlerMixIn(object):
self._reports = {}
self._reports_state = {}
+ @property
+ def _sorted_reports(self):
+ """ Return a list of reports, sorted in the order
+ of their reporting priority.
+ """
+ reports = sorted(self._reports, key=lambda x: getattr(x, 'name', ''))
+ try:
+ # Remove the current reporter and add it
+ # at the end of the list.
+ reports.pop(reports.index(self))
+ except ValueError:
+ pass
+ else:
+ reports.append(self)
+ return reports
+
def register_report(self, reportid, r_title, r_cb, checker):
"""register a report
@@ -671,7 +722,7 @@ class ReportsHandlerMixIn(object):
"""render registered reports"""
sect = Section('Report',
'%s statements analysed.'% (self.stats['statement']))
- for checker in self._reports:
+ for checker in self._sorted_reports:
for reportid, r_title, r_cb in self._reports[checker]:
if not self.report_is_enabled(reportid):
continue
@@ -721,7 +772,7 @@ def expand_modules(files_or_modules, black_list):
if filepath is None:
errors.append({'key' : 'ignored-builtin-module', 'mod': modname})
continue
- except (ImportError, SyntaxError), ex:
+ except (ImportError, SyntaxError) as ex:
# FIXME p3k : the SyntaxError is a Python bug and should be
# removed as soon as possible http://bugs.python.org/issue10588
errors.append({'key': 'fatal', 'mod': modname, 'ex': ex})
@@ -824,7 +875,7 @@ def register_plugins(linter, directory):
except ValueError:
# empty module name (usually emacs auto-save files)
continue
- except ImportError, exc:
+ except ImportError as exc:
print >> sys.stderr, "Problem importing module %s: %s" % (filename, exc)
else:
if hasattr(module, 'register'):