From 661eb896e31f6ac4c1ff26858daaf3ee59d7013c Mon Sep 17 00:00:00 2001 From: Marco Forte Date: Thu, 14 Aug 2014 14:13:46 +0000 Subject: tiny typo fix --- doc/installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: -- cgit v1.2.1 -- cgit v1.2.1 From 2619e069558471d6600cf691f2673e520c087668 Mon Sep 17 00:00:00 2001 From: cpopa Date: Thu, 14 Aug 2014 18:20:13 +0300 Subject: Don't emit 'protected-access' if the attribute is accessed using a property defined at the class level. --- ChangeLog | 3 +++ checkers/classes.py | 19 ++++++++++++++++++- .../func_noerror_classes_protected_member_access.py | 1 + 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 7b70fa7..f7c3b90 100644 --- a/ChangeLog +++ b/ChangeLog @@ -63,6 +63,9 @@ 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. + 2014-07-26 -- 1.3.0 * Allow hanging continued indentation for implicitly concatenated diff --git a/checkers/classes.py b/checkers/classes.py index 90b2fbe..ebca3f2 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): 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) -- cgit v1.2.1 From 91af7c933718f518c819fd9eb9f3aa3dd967f43b Mon Sep 17 00:00:00 2001 From: cpopa Date: Sat, 16 Aug 2014 00:25:57 +0300 Subject: Detect calls of the parent's __init__, through a binded super() call. --- ChangeLog | 3 +++ checkers/classes.py | 11 +++++++++++ test/input/func_w0233.py | 14 ++++++++++++++ test/messages/func_w0233.txt | 1 + 4 files changed, 29 insertions(+) diff --git a/ChangeLog b/ChangeLog index f7c3b90..c6b9b03 100644 --- a/ChangeLog +++ b/ChangeLog @@ -66,6 +66,9 @@ ChangeLog for Pylint * 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. + + 2014-07-26 -- 1.3.0 * Allow hanging continued indentation for implicitly concatenated diff --git a/checkers/classes.py b/checkers/classes.py index ebca3f2..d9ebd8d 100644 --- a/checkers/classes.py +++ b/checkers/classes.py @@ -818,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/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_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 -- cgit v1.2.1 From 7c50e98ba61dc650e995eb0c106ba2487e6e359f Mon Sep 17 00:00:00 2001 From: cpopa Date: Sun, 17 Aug 2014 22:35:28 +0300 Subject: Check that a class has an explicitly defined metaclass before emitting 'old-style-class' for Python 2. This can catch metaclasses defined at module level, using `__metaclass__ = ...` syntax, which transforms all old style classes to newstyle. Also, look for the .newstyle attribute instead of bases. --- ChangeLog | 3 +++ checkers/newstyle.py | 10 +++++----- test/functional/old_style_class_py27.py | 17 +++++++++++++++++ test/functional/old_style_class_py27.rc | 2 ++ test/functional/old_style_class_py27.txt | 2 ++ test/functional/undefined_variable.py | 2 +- 6 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 test/functional/old_style_class_py27.py create mode 100644 test/functional/old_style_class_py27.rc create mode 100644 test/functional/old_style_class_py27.txt diff --git a/ChangeLog b/ChangeLog index c6b9b03..dc85ee4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -68,6 +68,9 @@ ChangeLog for Pylint * 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. + 2014-07-26 -- 1.3.0 diff --git a/checkers/newstyle.py b/checkers/newstyle.py index cc8f640..532f3f1 100644 --- a/checkers/newstyle.py +++ b/checkers/newstyle.py @@ -74,14 +74,14 @@ 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) - # 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': + # If the class node is not marked as newstyle and it has no explicitly + # defined metaclass, then it's an old style class. + if not node.newstyle and node.type == 'class' and not node.metaclass(): self.add_message('old-style-class', node=node) @check_messages('property-on-old-class') diff --git a/test/functional/old_style_class_py27.py b/test/functional/old_style_class_py27.py new file mode 100644 index 0000000..4073c73 --- /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] + """ still an old style class """ + +__metaclass__ = type + +class NotOldStyle: + """ Because I have a metaclass at global level. """ + +class NotOldStyle2: + """ Because I have a metaclass at class level. """ + __metaclass__ = type 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..223233e --- /dev/null +++ b/test/functional/old_style_class_py27.txt @@ -0,0 +1,2 @@ +old-style-class:4::Old-style class defined. +old-style-class:7::Old-style class defined. 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 -- cgit v1.2.1 From 98d800bdab4f9d7209b0466ee62036638c511abd Mon Sep 17 00:00:00 2001 From: cpopa Date: Sun, 17 Aug 2014 22:42:13 +0300 Subject: Fix test failures. --- test/functional/old_style_class_py27.txt | 4 ++-- test/input/func_noerror_access_attr_before_def_false_positive.py | 2 +- test/input/func_noerror_indirect_interface.py | 2 +- test/messages/func_w0231.txt | 1 + 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/test/functional/old_style_class_py27.txt b/test/functional/old_style_class_py27.txt index 223233e..c46a7b6 100644 --- a/test/functional/old_style_class_py27.txt +++ b/test/functional/old_style_class_py27.txt @@ -1,2 +1,2 @@ -old-style-class:4::Old-style class defined. -old-style-class:7::Old-style class defined. +old-style-class:4:Old:Old-style class defined. +old-style-class:7:Child:Old-style class defined. 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_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/messages/func_w0231.txt b/test/messages/func_w0231.txt index 8cbf0b7..2f97fba 100644 --- a/test/messages/func_w0231.txt +++ b/test/messages/func_w0231.txt @@ -1,6 +1,7 @@ C: 7:AAAA: Old-style class defined. C: 13:BBBB: Old-style class defined. C: 19:CCCC: Old-style class defined. +C: 23:ZZZZ: Old-style class defined. W: 19:CCCC: Class has no __init__ method W: 26:ZZZZ.__init__: __init__ method from base class 'BBBB' is not called W: 59:AssignedInit.__init__: __init__ method from base class 'NewStyleC' is not called -- cgit v1.2.1 From 19ea91dd27f970d9bd30cf9551b2770ab0fbb16b Mon Sep 17 00:00:00 2001 From: cpopa Date: Mon, 18 Aug 2014 10:55:46 +0300 Subject: Revert change from 3b748b4: emit old-style-classes only for classes without bases and without explicitly defined metaclasses. --- checkers/newstyle.py | 7 ++++--- test/functional/old_style_class_py27.py | 12 ++++++------ test/messages/func_w0231.txt | 1 - 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/checkers/newstyle.py b/checkers/newstyle.py index 532f3f1..cf50229 100644 --- a/checkers/newstyle.py +++ b/checkers/newstyle.py @@ -79,9 +79,10 @@ class NewStyleConflictChecker(BaseChecker): """ if '__slots__' in node and not node.newstyle: self.add_message('slots-on-old-class', node=node) - # If the class node is not marked as newstyle and it has no explicitly - # defined metaclass, then it's an old style class. - if not node.newstyle and node.type == 'class' and not node.metaclass(): + # 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' and not node.metaclass(): self.add_message('old-style-class', node=node) @check_messages('property-on-old-class') diff --git a/test/functional/old_style_class_py27.py b/test/functional/old_style_class_py27.py index 4073c73..2c3c126 100644 --- a/test/functional/old_style_class_py27.py +++ b/test/functional/old_style_class_py27.py @@ -4,14 +4,14 @@ class Old: # [old-style-class] """ old style class """ -class Child(Old): # [old-style-class] - """ still an 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. """ - -class NotOldStyle2: - """ Because I have a metaclass at class level. """ - __metaclass__ = type diff --git a/test/messages/func_w0231.txt b/test/messages/func_w0231.txt index 2f97fba..8cbf0b7 100644 --- a/test/messages/func_w0231.txt +++ b/test/messages/func_w0231.txt @@ -1,7 +1,6 @@ C: 7:AAAA: Old-style class defined. C: 13:BBBB: Old-style class defined. C: 19:CCCC: Old-style class defined. -C: 23:ZZZZ: Old-style class defined. W: 19:CCCC: Class has no __init__ method W: 26:ZZZZ.__init__: __init__ method from base class 'BBBB' is not called W: 59:AssignedInit.__init__: __init__ method from base class 'NewStyleC' is not called -- cgit v1.2.1 From f72c2e1a0e0943594740475c1e6071a111562eeb Mon Sep 17 00:00:00 2001 From: cpopa Date: Mon, 18 Aug 2014 14:34:23 +0300 Subject: Emit 'catching-non-exception' for non-class nodes. Closes issue #303. --- ChangeLog | 2 + checkers/exceptions.py | 63 ++++++++++++++++++++++++--- test/functional/invalid_exceptions_caught.py | 18 ++++++++ test/functional/invalid_exceptions_caught.txt | 3 ++ 4 files changed, 80 insertions(+), 6 deletions(-) diff --git a/ChangeLog b/ChangeLog index dc85ee4..cd25ca8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -71,6 +71,8 @@ ChangeLog for Pylint * 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. + 2014-07-26 -- 1.3.0 diff --git a/checkers/exceptions.py b/checkers/exceptions.py index 81520ce..ecbf4a4 100644 --- a/checkers/exceptions.py +++ b/checkers/exceptions.py @@ -19,7 +19,7 @@ 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 ( @@ -28,6 +28,36 @@ from pylint.checkers.utils import ( EXCEPTIONS_MODULE) from pylint.interfaces import IAstroidChecker +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. @@ -255,13 +285,34 @@ 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 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 +340,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/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..55b653b 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: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 -- cgit v1.2.1 From 41a05fd331428b7805a806d37bccbc69a887910f Mon Sep 17 00:00:00 2001 From: cpopa Date: Mon, 18 Aug 2014 14:52:14 +0300 Subject: Add debug code for drone.io. --- testutils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/testutils.py b/testutils.py index ef21211..e53ec70 100644 --- a/testutils.py +++ b/testutils.py @@ -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, -- cgit v1.2.1 From 762ba61a3e8329ce5036f6a3dbde8173d0902846 Mon Sep 17 00:00:00 2001 From: cpopa Date: Mon, 18 Aug 2014 15:02:14 +0300 Subject: Add debug code for drone.io. --- checkers/exceptions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/checkers/exceptions.py b/checkers/exceptions.py index ecbf4a4..7af0aed 100644 --- a/checkers/exceptions.py +++ b/checkers/exceptions.py @@ -336,6 +336,7 @@ class ExceptionsChecker(BaseChecker): fully_infered = all(inferit is not YES for inferit in bases) if fully_infered: + print("Infered", fully_infered, bases) self.add_message('catching-non-exception', node=handler.type, args=(exc.name, )) -- cgit v1.2.1 From 61b31662f6119e9031557f42837b8288b293541c Mon Sep 17 00:00:00 2001 From: cpopa Date: Mon, 18 Aug 2014 15:04:42 +0300 Subject: Uh, drone.io. --- checkers/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/checkers/exceptions.py b/checkers/exceptions.py index 7af0aed..8c9bea9 100644 --- a/checkers/exceptions.py +++ b/checkers/exceptions.py @@ -336,7 +336,7 @@ class ExceptionsChecker(BaseChecker): fully_infered = all(inferit is not YES for inferit in bases) if fully_infered: - print("Infered", fully_infered, bases) + print("Infered", fully_infered, bases, exc) self.add_message('catching-non-exception', node=handler.type, args=(exc.name, )) -- cgit v1.2.1 From ce4ba79604eef9316106e29adf4ca425450d7263 Mon Sep 17 00:00:00 2001 From: cpopa Date: Mon, 18 Aug 2014 15:06:38 +0300 Subject: Debug. --- checkers/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/checkers/exceptions.py b/checkers/exceptions.py index 8c9bea9..3dbf81f 100644 --- a/checkers/exceptions.py +++ b/checkers/exceptions.py @@ -295,6 +295,7 @@ class ExceptionsChecker(BaseChecker): # Don't emit the warning if the infered stmt # is None, but the exception handler is something else, # maybe it was redefined. + print("not a class", exc, type(exc), exc.as_string()) if (isinstance(exc, astroid.Const) and exc.value is None): if ((isinstance(handler.type, astroid.Const) and @@ -336,7 +337,6 @@ class ExceptionsChecker(BaseChecker): fully_infered = all(inferit is not YES for inferit in bases) if fully_infered: - print("Infered", fully_infered, bases, exc) self.add_message('catching-non-exception', node=handler.type, args=(exc.name, )) -- cgit v1.2.1 From 67752720f07f1d62dfc8ed38e3d2e2a96ebccfa3 Mon Sep 17 00:00:00 2001 From: cpopa Date: Mon, 18 Aug 2014 15:09:35 +0300 Subject: Backed out changeset ff26eefc7e0c --- checkers/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/checkers/exceptions.py b/checkers/exceptions.py index 3dbf81f..8c9bea9 100644 --- a/checkers/exceptions.py +++ b/checkers/exceptions.py @@ -295,7 +295,6 @@ class ExceptionsChecker(BaseChecker): # Don't emit the warning if the infered stmt # is None, but the exception handler is something else, # maybe it was redefined. - print("not a class", exc, type(exc), exc.as_string()) if (isinstance(exc, astroid.Const) and exc.value is None): if ((isinstance(handler.type, astroid.Const) and @@ -337,6 +336,7 @@ class ExceptionsChecker(BaseChecker): fully_infered = all(inferit is not YES for inferit in bases) if fully_infered: + print("Infered", fully_infered, bases, exc) self.add_message('catching-non-exception', node=handler.type, args=(exc.name, )) -- cgit v1.2.1 From 5e4d1597d8260de4836e2652b424310e74690c4c Mon Sep 17 00:00:00 2001 From: cpopa Date: Mon, 18 Aug 2014 15:09:57 +0300 Subject: Backout changeset. --- checkers/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/checkers/exceptions.py b/checkers/exceptions.py index 8c9bea9..7af0aed 100644 --- a/checkers/exceptions.py +++ b/checkers/exceptions.py @@ -336,7 +336,7 @@ class ExceptionsChecker(BaseChecker): fully_infered = all(inferit is not YES for inferit in bases) if fully_infered: - print("Infered", fully_infered, bases, exc) + print("Infered", fully_infered, bases) self.add_message('catching-non-exception', node=handler.type, args=(exc.name, )) -- cgit v1.2.1 From 766333545bc67f42aa4710fd08bebba5f55eb7e4 Mon Sep 17 00:00:00 2001 From: cpopa Date: Mon, 18 Aug 2014 15:10:27 +0300 Subject: Backout changeset. --- checkers/exceptions.py | 1 - 1 file changed, 1 deletion(-) diff --git a/checkers/exceptions.py b/checkers/exceptions.py index 7af0aed..ecbf4a4 100644 --- a/checkers/exceptions.py +++ b/checkers/exceptions.py @@ -336,7 +336,6 @@ class ExceptionsChecker(BaseChecker): fully_infered = all(inferit is not YES for inferit in bases) if fully_infered: - print("Infered", fully_infered, bases) self.add_message('catching-non-exception', node=handler.type, args=(exc.name, )) -- cgit v1.2.1 From fc3d7edd86a6ca68791fa53c08a94e39406bf7f8 Mon Sep 17 00:00:00 2001 From: cpopa Date: Mon, 18 Aug 2014 15:13:34 +0300 Subject: Unproxy the infered instances. --- checkers/exceptions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/checkers/exceptions.py b/checkers/exceptions.py index ecbf4a4..0142d9a 100644 --- a/checkers/exceptions.py +++ b/checkers/exceptions.py @@ -291,6 +291,8 @@ class ExceptionsChecker(BaseChecker): for part, exc in excs: if exc is YES: continue + if isinstance(exc, astroid.Instance): + 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, -- cgit v1.2.1 From 93e71976377c537fc87a4ca1fbcedd4e6f597096 Mon Sep 17 00:00:00 2001 From: cpopa Date: Mon, 18 Aug 2014 15:23:11 +0300 Subject: Check if the instance inherits from a standard exception before unproxying. --- checkers/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/checkers/exceptions.py b/checkers/exceptions.py index 0142d9a..cd78719 100644 --- a/checkers/exceptions.py +++ b/checkers/exceptions.py @@ -291,7 +291,7 @@ class ExceptionsChecker(BaseChecker): for part, exc in excs: if exc is YES: continue - if isinstance(exc, astroid.Instance): + 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 -- cgit v1.2.1 From 168db0ec2772579d2dae24f1971a84546f8675f3 Mon Sep 17 00:00:00 2001 From: cpopa Date: Mon, 18 Aug 2014 17:54:07 +0300 Subject: Order of reporting is consistent. --- ChangeLog | 2 ++ utils.py | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index cd25ca8..3572212 100644 --- a/ChangeLog +++ b/ChangeLog @@ -73,6 +73,8 @@ ChangeLog for Pylint * Emit 'catching-non-exception' for non-class nodes. Closes issue #303. + * Order of reporting is consistent. + 2014-07-26 -- 1.3.0 diff --git a/utils.py b/utils.py index b476dc1..deef486 100644 --- a/utils.py +++ b/utils.py @@ -640,6 +640,21 @@ 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 + reports.append(self) + return reports + def register_report(self, reportid, r_title, r_cb, checker): """register a report @@ -671,7 +686,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 -- cgit v1.2.1 From 54e8ee1d68f66b5a9f89d48209c24494a56d68d9 Mon Sep 17 00:00:00 2001 From: cpopa Date: Mon, 18 Aug 2014 17:57:37 +0300 Subject: Add the current reporter only if it was removed. --- utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils.py b/utils.py index deef486..9a7189e 100644 --- a/utils.py +++ b/utils.py @@ -652,7 +652,8 @@ class ReportsHandlerMixIn(object): reports.pop(reports.index(self)) except ValueError: pass - reports.append(self) + else: + reports.append(self) return reports def register_report(self, reportid, r_title, r_cb, checker): -- cgit v1.2.1 From faae5267f9541ffbce013f3505e26295698a43b9 Mon Sep 17 00:00:00 2001 From: Torsten Marek Date: Thu, 24 Jul 2014 14:13:09 +0200 Subject: Change the multi-style name checker from first-style-wins to majority-style-wins. --- checkers/base.py | 44 +++++++++++++++++++++++++++++++++---------- doc/options.rst | 13 +++++++------ test/unittest_checker_base.py | 12 ++++++++---- 3 files changed, 49 insertions(+), 20 deletions(-) diff --git a/checkers/base.py b/checkers/base.py index 6e5804f..d6553ed 100644 --- a/checkers/base.py +++ b/checkers/base.py @@ -16,6 +16,8 @@ # 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 @@ -944,6 +946,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 +964,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): @@ -1030,6 +1052,14 @@ class NameChecker(_BasicChecker): match.lastgroup is not None and match.lastgroup not in EXEMPT_NAME_CATEGORIES) + def _raise_name_warning(self, node, node_type, name): + 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 + def _check_name(self, node_type, name, node): """check for a name using the type's regexp""" if is_inside_except(node): @@ -1047,18 +1077,12 @@ class NameChecker(_BasicChecker): if self._is_multi_naming_match(match): name_group = self._find_name_group(node_type) - if name_group not in self._name_category: - self._name_category[name_group] = match.lastgroup - elif self._name_category[name_group] != match.lastgroup: - match = None + bad_name_group = self._bad_names.setdefault(name_group, {}) + warnings = bad_name_group.setdefault(match.lastgroup, []) + warnings.append((node, node_type, name)) 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) class DocStringChecker(_BasicChecker): 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/test/unittest_checker_base.py b/test/unittest_checker_base.py index fe0dbca..4f16a77 100644 --- a/test/unittest_checker_base.py +++ b/test/unittest_checker_base.py @@ -161,18 +161,19 @@ class MultiNamingStyleTest(CheckerTestCase): MULTI_STYLE_RE = re.compile('(?:(?P[A-Z]+)|(?P[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 +189,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 +206,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('(?:(?PFOO)|(?P[A-Z]+)|(?P[a-z]+))$')) def test_multi_name_detection_exempt(self): @@ -220,6 +223,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__': -- cgit v1.2.1 From d4695d4fd8b50df7840c6e7e4b4a9103c37388dc Mon Sep 17 00:00:00 2001 From: Torsten Marek Date: Sat, 16 Aug 2014 17:40:44 -0700 Subject: Move definition of Message class out of reporters module, and make it a namedtuple. --- reporters/__init__.py | 22 ---------------------- reporters/guireporter.py | 5 +++-- reporters/html.py | 6 ++++-- reporters/text.py | 14 +++++++++----- test/test_functional.py | 6 +++--- utils.py | 32 ++++++++++++++++++++++++++++---- 6 files changed, 47 insertions(+), 38 deletions(-) diff --git a/reporters/__init__.py b/reporters/__init__.py index 12d193f..28d7071 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 @@ -41,27 +40,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 diff --git a/reporters/guireporter.py b/reporters/guireporter.py index 331eb17..8ebc270 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 @@ -20,7 +21,7 @@ class GUIReporter(BaseReporter): def add_message(self, msg_id, location, msg): """manage message of different type and in the context of path""" - message = Message(self, msg_id, location, msg) + message = utils.Message(msg_id, self.linter.msgs_store.check_message_id(msg_id).symbol, location, msg) self.gui.msg_queue.put(message) def _display(self, layout): diff --git a/reporters/html.py b/reporters/html.py index 71d46eb..441f68a 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): @@ -35,7 +36,8 @@ class HTMLReporter(BaseReporter): def add_message(self, msg_id, location, msg): """manage message of different type and in the context of path""" - msg = Message(self, msg_id, location, msg) + msg = utils.Message(msg_id, self.linter.msgs_store.check_message_id(msg_id).symbol, + 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..83bfe60 100644 --- a/reporters/text.py +++ b/reporters/text.py @@ -23,7 +23,8 @@ 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 +from pylint import utils TITLE_UNDERLINES = ['', '=', '-', '.'] @@ -50,7 +51,8 @@ class TextReporter(BaseReporter): def add_message(self, msg_id, location, msg): """manage message of different type and in the context of path""" - m = Message(self, msg_id, location, msg) + m = utils.Message(msg_id, self.linter.msgs_store.check_message_id(msg_id).symbol, + location, msg) if m.module not in self._modules: if m.module: self.writeln('************* Module %s' % m.module) @@ -118,7 +120,7 @@ class ColorizedTextReporter(TextReporter): """manage message of different types, and colorize output using ansi escape codes """ - msg = Message(self, msg_id, location, msg) + msg = utils.Message(msg_id, self.linter.msgs_store.check_message_id(msg_id).symbol, location, msg) if msg.module not in self._modules: color, style = self._get_decoration('S') if msg.module: @@ -130,8 +132,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/test_functional.py b/test/test_functional.py index fb495e0..a6c6780 100644 --- a/test/test_functional.py +++ b/test/test_functional.py @@ -9,10 +9,10 @@ import re import sys import unittest +from pylint import checkers from pylint import lint from pylint import reporters -from pylint import checkers - +from pylint import utils class NoFileError(Exception): pass @@ -49,7 +49,7 @@ 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)) + self.messages.append(utils.Message(msg_id, self.linter.msgs_store.check_message_id(msg_id).symbol, location, msg)) def on_set_current_module(self, module, filepath): self.messages = [] diff --git a/utils.py b/utils.py index 9a7189e..59a957a 100644 --- a/utils.py +++ b/utils.py @@ -17,11 +17,13 @@ main pylint class """ +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 @@ -75,6 +77,28 @@ class WarningScope(object): LINE = 'line-based-msg' NODE = 'node-based-msg' +_MsgBase = collections.namedtuple( + '_MsgBase', + ['msg_id', 'symbol', 'msg', 'C', 'category', '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): + return _MsgBase.__new__( + cls, msg_id, symbol, msg, msg_id[0], MSG_TYPES[msg_id[0]], *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 +148,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) -- cgit v1.2.1 From 20ffb646e24da344602c705be938f2f6baac0494 Mon Sep 17 00:00:00 2001 From: Torsten Marek Date: Sat, 16 Aug 2014 17:57:50 -0700 Subject: Extend the reporter interface with a method that takes a message object, for better extendability. --- reporters/__init__.py | 11 +++++++++-- reporters/guireporter.py | 5 ++--- reporters/html.py | 4 +--- reporters/text.py | 18 +++++++----------- test/test_functional.py | 4 ++-- test/test_self.py | 4 ++-- utils.py | 4 +++- 7 files changed, 26 insertions(+), 24 deletions(-) diff --git a/reporters/__init__.py b/reporters/__init__.py index 28d7071..f22653e 100644 --- a/reporters/__init__.py +++ b/reporters/__init__.py @@ -60,9 +60,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 8ebc270..f908f76 100644 --- a/reporters/guireporter.py +++ b/reporters/guireporter.py @@ -19,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 = utils.Message(msg_id, self.linter.msgs_store.check_message_id(msg_id).symbol, 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 441f68a..3e5a1a7 100644 --- a/reporters/html.py +++ b/reporters/html.py @@ -34,10 +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 = utils.Message(msg_id, self.linter.msgs_store.check_message_id(msg_id).symbol, - 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 83bfe60..48b5155 100644 --- a/reporters/text.py +++ b/reporters/text.py @@ -24,7 +24,6 @@ from logilab.common.textutils import colorize_ansi from pylint.interfaces import IReporter from pylint.reporters import BaseReporter -from pylint import utils TITLE_UNDERLINES = ['', '=', '-', '.'] @@ -49,17 +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 = utils.Message(msg_id, self.linter.msgs_store.check_message_id(msg_id).symbol, - 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""" @@ -116,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 = utils.Message(msg_id, self.linter.msgs_store.check_message_id(msg_id).symbol, location, msg) if msg.module not in self._modules: color, style = self._get_decoration('S') if msg.module: diff --git a/test/test_functional.py b/test/test_functional.py index a6c6780..daf699f 100644 --- a/test/test_functional.py +++ b/test/test_functional.py @@ -48,8 +48,8 @@ def parse_python_version(str): class TestReporter(reporters.BaseReporter): - def add_message(self, msg_id, location, msg): - self.messages.append(utils.Message(msg_id, self.linter.msgs_store.check_message_id(msg_id).symbol, location, msg)) + def handle_message(self, msg): + self.messages.append(msg) def on_set_current_module(self, module, filepath): self.messages = [] 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/utils.py b/utils.py index 59a957a..0d2cd3c 100644 --- a/utils.py +++ b/utils.py @@ -376,7 +376,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)) def print_full_documentation(self): """output a full documentation in ReST format""" -- cgit v1.2.1 From a7d1515daef1234b3bf3e3991299771bea3c56bc Mon Sep 17 00:00:00 2001 From: Torsten Marek Date: Sat, 16 Aug 2014 20:16:08 -0700 Subject: Expected output files for the new test framework are now CSV files. CSV files are easier to parse (messages with multi-line output were parsed with a heuristic so far), and are easier to extend with new fields. --- test/functional/anomalous_unicode_escape.txt | 6 +- test/functional/bad_continuation.txt | 86 +++++++++++++-------------- test/functional/bad_open_mode.txt | 14 ++--- test/functional/class_members_py27.txt | 12 ++-- test/functional/docstrings.txt | 16 ++--- test/functional/exception_is_binary_op.txt | 8 +-- test/functional/future_unicode_literals.txt | 2 +- test/functional/import_error.txt | 4 +- test/functional/invalid_encoded_data.txt | 2 +- test/functional/invalid_exceptions_caught.txt | 6 +- test/functional/invalid_exceptions_raised.txt | 12 ++-- test/functional/invalid_name.txt | 4 +- test/functional/missing_self_argument.txt | 4 +- test/functional/name_styles.txt | 34 +++++------ test/functional/newstyle_properties.txt | 2 +- test/functional/statement_without_effect.txt | 72 +++++++++++++++------- test/functional/string_formatting.txt | 2 +- test/functional/too_many_branches.txt | 2 +- test/test_functional.py | 35 +++++++---- 19 files changed, 185 insertions(+), 138 deletions(-) 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..c7b017e 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 +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 diff --git a/test/functional/docstrings.txt b/test/functional/docstrings.txt index 1ea5c7d..177d02a 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 +empty-docstring:48:AAAA.method3:Empty method docstring +empty-docstring:62:DDDD.method2:Empty method docstring +missing-docstring:70:DDDD.method4:Missing method docstring 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.txt b/test/functional/invalid_exceptions_caught.txt index 55b653b..3156a0b 100644 --- a/test/functional/invalid_exceptions_caught.txt +++ b/test/functional/invalid_exceptions_caught.txt @@ -1,6 +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..c40eca7 100644 --- a/test/functional/invalid_exceptions_raised.txt +++ b/test/functional/invalid_exceptions_raised.txt @@ -1,9 +1,13 @@ -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 +nonstandard-exception:23:bad_case0:"Exception doesn't inherit from standard ""Exception"" class" +raising-non-exception:23:bad_case0:"""Raising a new style class which doesn't inherit from BaseException +"" +" 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" 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/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..985e9a2 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""" +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""" diff --git a/test/functional/newstyle_properties.txt b/test/functional/newstyle_properties.txt index f3a8dbb..9f060c0 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" 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/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/test_functional.py b/test/test_functional.py index daf699f..759a447 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 @@ -14,6 +15,18 @@ from pylint import lint from pylint import reporters 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 @@ -27,7 +40,11 @@ class OutputLine(collections.namedtuple('OutputLine', ['symbol', 'lineno', 'object', 'msg'])): @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) + + @classmethod + def from_csv(cls, row): + return cls(row[0], int(row[1]), row[2], row[3]) # Common sub-expressions. @@ -123,16 +140,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): @@ -295,9 +303,10 @@ 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)) - + #fobj.write('{0}:{1}:{2}:{3}'.format(*line)) + writer.writerow(line) def suite(): input_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), -- cgit v1.2.1 From 7aa57622583caab7dc14d0458bc3c4082036b766 Mon Sep 17 00:00:00 2001 From: Torsten Marek Date: Thu, 24 Jul 2014 15:07:51 +0200 Subject: Implement confidence levels. - attach confidence levels to a number of messages - include confidence levels in the message object - if the confidence of a message is not HIGH or UNDEFINED, include it in the test output. --- ChangeLog | 5 ++ checkers/__init__.py | 6 ++- checkers/base.py | 46 +++++++++++------ checkers/exceptions.py | 8 +-- checkers/newstyle.py | 29 ++++++++--- checkers/typecheck.py | 22 +++----- checkers/utils.py | 19 +++++++ checkers/variables.py | 11 ++-- interfaces.py | 12 +++++ lint.py | 22 +++++++- test/functional/access_to__name__.txt | 4 +- test/functional/class_members_py27.txt | 12 ++--- test/functional/class_members_py30.txt | 12 ++--- test/functional/confidence_filter.py | 14 +++++ test/functional/confidence_filter.rc | 3 ++ test/functional/confidence_filter.txt | 1 + test/functional/docstrings.txt | 8 +-- test/functional/invalid_exceptions_caught.txt | 6 +-- test/functional/invalid_exceptions_raised.txt | 12 ++--- test/functional/member_checks.py | 63 ++++++++++++++++++++++ test/functional/member_checks.txt | 9 ++++ test/functional/name_styles.txt | 10 ++-- test/functional/namedtuple_member_inference.txt | 4 +- test/functional/newstyle__slots__.txt | 2 +- test/functional/newstyle_properties.txt | 2 +- test/functional/super_checks.txt | 14 ++--- test/input/func_typecheck_getattr.py | 69 ------------------------- test/messages/func_typecheck_getattr.txt | 10 ---- test/test_functional.py | 26 +++++++--- test/unittest_lint.py | 20 ++++--- test/unittest_utils.py | 2 +- testutils.py | 2 +- utils.py | 50 ++++++++++-------- 33 files changed, 329 insertions(+), 206 deletions(-) create mode 100644 test/functional/confidence_filter.py create mode 100644 test/functional/confidence_filter.rc create mode 100644 test/functional/confidence_filter.txt create mode 100644 test/functional/member_checks.py create mode 100644 test/functional/member_checks.txt delete mode 100644 test/input/func_typecheck_getattr.py delete mode 100644 test/messages/func_typecheck_getattr.txt diff --git a/ChangeLog b/ChangeLog index 3572212..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: 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 from and , @@ -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 d6553ed..298ac5f 100644 --- a/checkers/base.py +++ b/checkers/base.py @@ -24,7 +24,7 @@ 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 @@ -36,6 +36,7 @@ from pylint.checkers.utils import ( overrides_a_method, safe_infer, get_argument_from_call, + has_known_bases, NoSuchArgumentError, is_import_error, ) @@ -995,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: @@ -1047,20 +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) + match.lastgroup not in EXEMPT_NAME_CATEGORIES + and (node_type != 'method' or confidence != INFERENCE_FAILURE)) - def _raise_name_warning(self, node, node_type, name): + 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)) + 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): + 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) @@ -1075,14 +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) bad_name_group = self._bad_names.setdefault(name_group, {}) warnings = bad_name_group.setdefault(match.lastgroup, []) - warnings.append((node, node_type, name)) + warnings.append((node, node_type, name, confidence)) if match is None: - self._raise_name_warning(node, node_type, name) + self._raise_name_warning(node, node_type, name, confidence) class DocStringChecker(_BasicChecker): @@ -1133,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 \ @@ -1140,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: @@ -1170,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/exceptions.py b/checkers/exceptions.py index cd78719..9b1a071 100644 --- a/checkers/exceptions.py +++ b/checkers/exceptions.py @@ -25,8 +25,8 @@ 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): """ @@ -234,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: diff --git a/checkers/newstyle.py b/checkers/newstyle.py index cf50229..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', @@ -78,12 +78,17 @@ class NewStyleConflictChecker(BaseChecker): 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' and not node.metaclass(): - self.add_message('old-style-class', node=node) + # 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): @@ -92,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): @@ -112,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: @@ -128,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: @@ -144,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..b3ba51b 100644 --- a/checkers/utils.py +++ b/checkers/utils.py @@ -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/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..0f5aa6f 100644 --- a/lint.py +++ b/lint.py @@ -51,7 +51,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 +238,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': '', + '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': '', 'short': 'e', @@ -879,6 +888,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': '', 'callback' : self.cb_full_documentation, @@ -1037,6 +1052,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/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/class_members_py27.txt b/test/functional/class_members_py27.txt index c7b017e..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 177d02a..e30e462 100644 --- a/test/functional/docstrings.txt +++ b/test/functional/docstrings.txt @@ -2,7 +2,7 @@ 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: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/invalid_exceptions_caught.txt b/test/functional/invalid_exceptions_caught.txt index 3156a0b..ce9ab2a 100644 --- a/test/functional/invalid_exceptions_caught.txt +++ b/test/functional/invalid_exceptions_caught.txt @@ -1,6 +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: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 +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 c40eca7..54ea0f4 100644 --- a/test/functional/invalid_exceptions_raised.txt +++ b/test/functional/invalid_exceptions_raised.txt @@ -1,13 +1,9 @@ -nonstandard-exception:23:bad_case0:"Exception doesn't inherit from standard ""Exception"" class" -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":INFERENCE +raising-non-exception:23:bad_case0:Raising a new style class which doesn't inherit from BaseException raising-non-exception:27:bad_case1: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: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/member_checks.py b/test/functional/member_checks.py new file mode 100644 index 0000000..fccf160 --- /dev/null +++ b/test/functional/member_checks.py @@ -0,0 +1,63 @@ +# pylint: disable= +"""check getattr if inference succeed""" + + +class Provider(object): + """provide some attributes and method""" + cattr = 4 + def __init__(self): + self.attr = 4 + def method(self, val): + """impressive method""" + return self.attr * val + def hophop(self): + """hop method""" + print 'hop hop hop', self + + +class Client(object): + """use provider class""" + + def __init__(self): + self._prov = Provider() + self._prov_attr = Provider.cattr + self._prov_attr2 = Provider.cattribute # [no-member] + self.set_later = 0 + + def set_set_later(self, value): + """set set_later attribute (introduce an inference ambiguity)""" + self.set_later = value + + def use_method(self): + """use provider's method""" + self._prov.hophop() + self._prov.hophophop() # [no-member] + + def use_attr(self): + """use provider's attr""" + print self._prov.attr + print self._prov.attribute # [no-member] + + def debug(self): + """print debug information""" + print self.__class__.__name__ + print self.__doc__ + print self.__dict__ + print self.__module__ + + def test_bt_types(self): + """test access to unexistant member of builtin types""" + lis = [] + lis.apppend(self) # [no-member] + dic = {} + dic.set(self) # [no-member] + tup = () + tup.append(self) # [no-member] + string = 'toto' + print string.loower() # [no-member] + integer = 1 + print integer.whatever # [no-member] + +print object.__init__ +print property.__init__ +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/name_styles.txt b/test/functional/name_styles.txt index 985e9a2..e81d27a 100644 --- a/test/functional/name_styles.txt +++ b/test/functional/name_styles.txt @@ -6,12 +6,12 @@ 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: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""" -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: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 9f060c0..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/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/input/func_typecheck_getattr.py b/test/input/func_typecheck_getattr.py deleted file mode 100644 index b120ca4..0000000 --- a/test/input/func_typecheck_getattr.py +++ /dev/null @@ -1,69 +0,0 @@ -# pylint: disable= -"""check getattr if inference succeed""" - -__revision__ = None - -class Provider(object): - """provide some attributes and method""" - cattr = 4 - def __init__(self): - self.attr = 4 - def method(self, val): - """impressive method""" - return self.attr * val - def hophop(self): - """hop method""" - print 'hop hop hop', self - - -class Client: - """use provider class""" - - def __init__(self): - self._prov = Provider() - self._prov_attr = Provider.cattr - self._prov_attr2 = Provider.cattribute - self.set_later = 0 - - def set_set_later(self, value): - """set set_later attribute (introduce an inference ambiguity)""" - self.set_later = value - - def use_method(self): - """use provider's method""" - self._prov.hophop() - self._prov.hophophop() - - def use_attr(self): - """use provider's attr""" - print self._prov.attr - print self._prov.attribute - - def debug(self): - """print debug information""" - print self.__class__.__name__ - print self.__doc__ - print self.__dict__ - print self.__module__ - - def test_bt_types(self): - """test access to unexistant member of builtin types""" - lis = [] - lis.apppend(self) - dic = {} - dic.set(self) - tup = () - tup.append(self) - string = 'toto' - print string.loower() - # unicode : moved to func_3k_removed_stuff_py_30.py - # - integer = 1 - print integer.whatever - -print object.__init__ -print property.__init__ -print Client().set_later.lower() - -# should detect mixing new style / old style classes -Client.__bases__ += (object,) 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/test_functional.py b/test/test_functional.py index 759a447..c2467b3 100644 --- a/test/test_functional.py +++ b/test/test_functional.py @@ -11,6 +11,7 @@ import sys import unittest from pylint import checkers +from pylint import interfaces from pylint import lint from pylint import reporters from pylint import utils @@ -30,6 +31,10 @@ 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 @@ -37,14 +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) + 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): - return cls(row[0], int(row[1]), row[2], row[3]) + 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. @@ -292,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): @@ -305,8 +320,7 @@ class LintModuleOutputUpdate(LintModuleTest): 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) + writer.writerow(line.to_csv()) def suite(): input_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 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 e53ec70..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): diff --git a/utils.py b/utils.py index 0d2cd3c..4e65a56 100644 --- a/utils.py +++ b/utils.py @@ -35,7 +35,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): @@ -67,6 +67,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:(.*)') @@ -79,15 +80,16 @@ class WarningScope(object): _MsgBase = collections.namedtuple( '_MsgBase', - ['msg_id', 'symbol', 'msg', 'C', 'category', 'abspath', 'module', 'obj', - 'line', 'column']) + ['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): + 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]], *location) + 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. @@ -302,12 +304,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: @@ -322,7 +337,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 @@ -352,8 +367,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]] @@ -377,8 +394,8 @@ class MessagesHandlerMixIn(object): path = node.root().file # add the message self.reporter.handle_message( - Message(msgid, symbol, - (path, module, obj, line or 1, col_offset or 0), msg)) + 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""" @@ -519,14 +536,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)] @@ -534,14 +550,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(): -- cgit v1.2.1 From 724f386d65fd5ffa9023cce056ed687096028729 Mon Sep 17 00:00:00 2001 From: Torsten Marek Date: Sat, 16 Aug 2014 21:07:04 -0700 Subject: Some work on making pylint work on Python 3 without 2to3. --- checkers/utils.py | 2 +- lint.py | 25 ++++++++--------- reporters/__init__.py | 4 --- test/unittest_checker_base.py | 2 -- utils.py | 63 ++++++++++++++++++++++--------------------- 5 files changed, 46 insertions(+), 50 deletions(-) diff --git a/checkers/utils.py b/checkers/utils.py index b3ba51b..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: diff --git a/lint.py b/lint.py index 0f5aa6f..39409d7 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 @@ -413,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 @@ -633,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)) @@ -647,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 @@ -718,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 @@ -858,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(( @@ -983,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') diff --git a/reporters/__init__.py b/reporters/__init__.py index f22653e..429155a 100644 --- a/reporters/__init__.py +++ b/reporters/__init__.py @@ -27,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 diff --git a/test/unittest_checker_base.py b/test/unittest_checker_base.py index 4f16a77..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. diff --git a/utils.py b/utils.py index 4e65a56..4be8e20 100644 --- a/utils.py +++ b/utils.py @@ -16,6 +16,7 @@ """some various utilities and helper classes, most of them used in the main pylint class """ +from __future__ import print_function import collections import os @@ -53,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, @@ -403,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() @@ -427,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): @@ -649,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): @@ -662,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): @@ -771,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}) @@ -874,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'): -- cgit v1.2.1 From 073c0c5140776a1f530f3a83bc3f1c3629776723 Mon Sep 17 00:00:00 2001 From: Torsten Marek Date: Mon, 18 Aug 2014 21:57:53 +0200 Subject: Fix print statement. --- lint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lint.py b/lint.py index 39409d7..a1ce1e0 100644 --- a/lint.py +++ b/lint.py @@ -1055,7 +1055,7 @@ group are mutually exclusive.'), def cb_list_confidence_levels(self, option, optname, value, parser): for level in CONFIDENCE_LEVELS: - print '%-18s: %s' % level + print('%-18s: %s' % level) sys.exit(0) def cb_init_hook(optname, value): -- cgit v1.2.1