summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcpopa <devnull@localhost>2014-01-11 20:38:14 +0200
committercpopa <devnull@localhost>2014-01-11 20:38:14 +0200
commit3a48d6623f43ef174dd13dab976e6564ab267654 (patch)
tree2562b0b43c3306c6a161c45ba4210c7f27399611
parent54bba4b019a2da504351469ee3cef7c1c8665817 (diff)
parentedbb10fb6ebe63f4bcf03fbfea7393eac9fb90ed (diff)
downloadpylint-3a48d6623f43ef174dd13dab976e6564ab267654.tar.gz
Merge with default.
-rw-r--r--ChangeLog13
-rw-r--r--README4
-rw-r--r--checkers/base.py68
-rw-r--r--checkers/exceptions.py30
-rw-r--r--checkers/utils.py8
-rw-r--r--test/input/func_bad_reversed_sequence.py59
-rw-r--r--test/input/func_catching_non_exception.py15
-rw-r--r--test/input/func_with_used_before_assignment.py12
-rw-r--r--test/messages/func_bad_reversed_sequence.txt9
-rw-r--r--test/messages/func_catching_non_exception.txt6
-rw-r--r--test/messages/func_with_used_before_assignment.txt2
11 files changed, 211 insertions, 15 deletions
diff --git a/ChangeLog b/ChangeLog
index c3c05e7..f74df6e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,9 +2,19 @@ ChangeLog for Pylint
====================
--
+ * bitbucket #128: pylint doesn't crash when looking
+ for used-before-assignment in context manager
+ assignments.
+
+ * Add new warning, 'bad-reversed-sequence', for checking that the
+ reversed() builtin receive a sequence (implements __getitem__ and __len__,
+ without being a dict or a dict subclass) or an instance which implements
+ __reversed__.
+
* Enhance the check for 'used-before-assignment' to look
for 'nonlocal' uses.
+
2013-12-22 -- 1.1.0
* Add new check for use of deprecated pragma directives "pylint:disable-msg"
or "pylint:enable-msg" (I0022, deprecated-pragma) which was previously
@@ -61,12 +71,9 @@ ChangeLog for Pylint
* Various documentation fixes and enhancements
- * Added a __main__.py file so you can run "python -m pylint"
-
* Fix issue #55 (false-positive trailing-whitespace on Windows)
-
2013-08-06 -- 1.0.0
* Add check for the use of 'exec' function
diff --git a/README b/README
index 25a82ea..ec6bfde 100644
--- a/README
+++ b/README
@@ -15,8 +15,8 @@ It's a free software distributed under the GNU Public Licence.
Development is hosted on bitbucket: https://bitbucket.org/logilab/pylint/
You can use the code-quality@python.org mailing list to discuss about
-Pylint. Subscribe at http://lists.python.org/mailman/listinfo/code-quality
-or read the archives at http://lists.python.org/pipermail/code-quality/
+Pylint. Subscribe at https://mail.python.org/mailman/listinfo/code-quality/
+or read the archives at https://mail.python.org/pipermail/code-quality/
Install
-------
diff --git a/checkers/base.py b/checkers/base.py
index d2be803..cf4304c 100644
--- a/checkers/base.py
+++ b/checkers/base.py
@@ -33,6 +33,8 @@ from pylint.checkers.utils import (
is_inside_except,
overrides_a_method,
safe_infer,
+ get_argument_from_call,
+ NoSuchArgumentError,
)
@@ -47,6 +49,8 @@ DEFAULT_NAME_RGX = re.compile('[a-z_][a-z0-9_]{2,30}$')
CLASS_ATTRIBUTE_RGX = re.compile(r'([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$')
# do not require a doc string on system methods
NO_REQUIRED_DOC_RGX = re.compile('__.*__')
+REVERSED_METHODS = (('__getitem__', '__len__'),
+ ('__reversed__', ))
BAD_FUNCTIONS = ['map', 'filter', 'apply']
if sys.version_info < (3, 0):
@@ -419,6 +423,16 @@ functions, methods
'C0121': ('Missing required attribute "%s"', # W0103
'missing-module-attribute',
'Used when an attribute required for modules is missing.'),
+
+ 'E0109': ('Missing argument to reversed()',
+ 'missing-reversed-argument',
+ 'Used when reversed() builtin didn\'t receive an argument.'),
+ 'E0111': ('The first reversed() argument is not a sequence',
+ 'bad-reversed-sequence',
+ 'Used when the first argument to reversed() builtin '
+ 'isn\'t a sequence (does not implement __reversed__, '
+ 'nor __getitem__ and __len__'),
+
}
options = (('required-attributes',
@@ -608,7 +622,9 @@ functions, methods
"""just print a warning on exec statements"""
self.add_message('exec-used', node=node)
- @check_messages('bad-builtin', 'star-args', 'exec-used')
+ @check_messages('bad-builtin', 'star-args',
+ 'exec-used', 'missing-reversed-argument',
+ 'bad-reversed-sequence')
def visit_callfunc(self, node):
"""visit a CallFunc node -> check if this is not a blacklisted builtin
call and check for * or ** use
@@ -621,6 +637,8 @@ functions, methods
name in node.root()):
if name == 'exec':
self.add_message('exec-used', node=node)
+ elif name == 'reversed':
+ self._check_reversed(node)
if name in self.config.bad_functions:
self.add_message('bad-builtin', node=node, args=name)
if node.starargs or node.kwargs:
@@ -685,7 +703,55 @@ functions, methods
return
_node = _parent
_parent = _node.parent
+
+ def _check_reversed(self, node):
+ """ check that the argument to `reversed` is a sequence """
+ try:
+ argument = safe_infer(get_argument_from_call(node, position=0))
+ except NoSuchArgumentError:
+ self.add_message('missing-reversed-argument', node=node)
+ else:
+ if argument is astroid.YES:
+ return
+ if argument is None:
+ # nothing was infered
+ # try to see if we have iter()
+ if (isinstance(node.args[0], astroid.CallFunc) and
+ node.args[0].func.name == 'iter'):
+ func = node.args[0].func.infer().next()
+ if is_builtin_object(func):
+ self.add_message('bad-reversed-sequence', node=node)
+ return
+ if isinstance(argument, astroid.Instance):
+ if (argument._proxied.name == 'dict' and
+ is_builtin_object(argument._proxied)):
+ self.add_message('bad-reversed-sequence', node=node)
+ return
+ elif any(ancestor.name == 'dict' and is_builtin_object(ancestor)
+ for ancestor in argument._proxied.ancestors()):
+ # mappings aren't accepted by reversed()
+ self.add_message('bad-reversed-sequence', node=node)
+ return
+
+ for methods in REVERSED_METHODS:
+ for meth in methods:
+ try:
+ argument.getattr(meth)
+ except astroid.NotFoundError:
+ break
+ else:
+ break
+ else:
+ # check if it is a .deque. It doesn't seem that
+ # we can retrieve special methods
+ # from C implemented constructs
+ if argument._proxied.qname().endswith(".deque"):
+ return
+ self.add_message('bad-reversed-sequence', node=node)
+ elif not isinstance(argument, (astroid.List, astroid.Tuple)):
+ # everything else is not a proper sequence for reversed()
+ self.add_message('bad-reversed-sequence', node=node)
class NameChecker(_BasicChecker):
msgs = {
diff --git a/checkers/exceptions.py b/checkers/exceptions.py
index 9843001..5bb07ac 100644
--- a/checkers/exceptions.py
+++ b/checkers/exceptions.py
@@ -25,6 +25,23 @@ from pylint.checkers import BaseChecker
from pylint.checkers.utils import is_empty, is_raising, check_messages
from pylint.interfaces import IAstroidChecker
+def infer_bases(klass):
+ """ Fully infer the bases of the klass node.
+
+ This doesn't use .ancestors(), because we need
+ the non-inferable nodes (YES nodes),
+ which can't be retrieved from .ancestors()
+ """
+ for base in klass.bases:
+ try:
+ inferit = base.infer().next()
+ except astroid.InferenceError:
+ continue
+ if inferit is YES:
+ yield inferit
+ else:
+ for base in infer_bases(inferit):
+ yield base
OVERGENERAL_EXCEPTIONS = ('Exception',)
@@ -214,9 +231,16 @@ class ExceptionsChecker(BaseChecker):
if (not inherit_from_std_ex(exc) and
exc.root().name != BUILTINS_NAME):
- self.add_message('catching-non-exception',
- node=handler.type,
- args=(exc.name, ))
+ # try to see if the exception is based on a C based
+ # exception, by infering all the base classes and
+ # looking for inference errors
+ bases = infer_bases(exc)
+ fully_infered = all(inferit is not YES
+ for inferit in bases)
+ if fully_infered:
+ self.add_message('catching-non-exception',
+ node=handler.type,
+ args=(exc.name, ))
exceptions_classes += excs
diff --git a/checkers/utils.py b/checkers/utils.py
index 53b40a6..728893e 100644
--- a/checkers/utils.py
+++ b/checkers/utils.py
@@ -124,7 +124,7 @@ SPECIAL_BUILTINS = ('__builtins__',) # '__path__', '__file__')
def is_builtin_object(node):
"""Returns True if the given node is an object from the __builtin__ module."""
- return node and node.root().name == '__builtin__'
+ return node and node.root().name == BUILTINS_NAME
def is_builtin(name): # was is_native_builtin
"""return true if <name> could be considered as a builtin defined by python
@@ -154,8 +154,10 @@ def is_defined_before(var_node):
elif isinstance(_node, astroid.With):
for expr, vars in _node.items:
if expr.parent_of(var_node):
- break
- if vars and vars.name == varname:
+ break
+ if (vars and
+ isinstance(vars, astroid.AssName) and
+ vars.name == varname):
return True
elif isinstance(_node, (astroid.Lambda, astroid.Function)):
if _node.args.is_argument(varname):
diff --git a/test/input/func_bad_reversed_sequence.py b/test/input/func_bad_reversed_sequence.py
new file mode 100644
index 0000000..a394e53
--- /dev/null
+++ b/test/input/func_bad_reversed_sequence.py
@@ -0,0 +1,59 @@
+""" Checks that reversed() receive proper argument """
+
+# pylint: disable=too-few-public-methods,no-self-use
+from collections import deque
+
+__revision__ = 0
+
+class GoodReversed(object):
+ """ Implements __reversed__ """
+ def __reversed__(self):
+ return [1, 2, 3]
+
+class SecondGoodReversed(object):
+ """ Implements __len__ and __getitem__ """
+ def __len__(self):
+ return 3
+
+ def __getitem__(self, index):
+ return index
+
+class BadReversed(object):
+ """ implements only len() """
+ def __len__(self):
+ return 3
+
+class SecondBadReversed(object):
+ """ implements only __getitem__ """
+ def __getitem__(self, index):
+ return index
+
+class ThirdBadReversed(dict):
+ """ dict subclass """
+
+def uninferable(seq):
+ """ This can't be infered at this moment,
+ make sure we don't have a false positive.
+ """
+ return reversed(seq)
+
+def test():
+ """ test function """
+ seq = reversed()
+ seq = reversed(None)
+ seq = reversed([1, 2, 3])
+ seq = reversed((1, 2, 3))
+ seq = reversed(set())
+ seq = reversed({'a': 1, 'b': 2})
+ seq = reversed(iter([1, 2, 3]))
+ seq = reversed(GoodReversed())
+ seq = reversed(SecondGoodReversed())
+ seq = reversed(BadReversed())
+ seq = reversed(SecondBadReversed())
+ seq = reversed(range(100))
+ seq = reversed(ThirdBadReversed())
+ seq = reversed(lambda: None)
+ seq = reversed(deque([]))
+ seq = reversed("123")
+ seq = uninferable([1, 2, 3])
+ return seq
diff --git a/test/input/func_catching_non_exception.py b/test/input/func_catching_non_exception.py
index e6bfc57..d8e0e2d 100644
--- a/test/input/func_catching_non_exception.py
+++ b/test/input/func_catching_non_exception.py
@@ -1,5 +1,6 @@
"""test non-exceptions catched
"""
+import socket
__revision__ = 1
@@ -19,6 +20,16 @@ class MySecondGoodException(MyGoodException):
""" custom 'exception' """
pass
+class SkipException(socket.error):
+ """ This gave false positives for Python 2,
+ but not for Python 3, due to exception
+ hierarchy rewrite.
+ """
+
+class SecondSkipException(SkipException):
+ """ This too shouldn't give false
+ positives. """
+
try:
1 + 1
except MyException:
@@ -39,3 +50,7 @@ try:
except (MyGoodException, MySecondGoodException):
print "should work"
+try:
+ 1 + 3
+except (SkipException, SecondSkipException):
+ print "should work"
diff --git a/test/input/func_with_used_before_assignment.py b/test/input/func_with_used_before_assignment.py
new file mode 100644
index 0000000..c79815c
--- /dev/null
+++ b/test/input/func_with_used_before_assignment.py
@@ -0,0 +1,12 @@
+'''
+Regression test for
+https://bitbucket.org/logilab/pylint/issue/128/attributeerror-when-parsing
+'''
+from __future__ import with_statement
+__revision__ = 1
+
+def do_nothing():
+ """ empty """
+ with open("") as ctx.obj:
+ context.do()
+ context = None
diff --git a/test/messages/func_bad_reversed_sequence.txt b/test/messages/func_bad_reversed_sequence.txt
new file mode 100644
index 0000000..6e85197
--- /dev/null
+++ b/test/messages/func_bad_reversed_sequence.txt
@@ -0,0 +1,9 @@
+E: 42:test: Missing argument to reversed()
+E: 43:test: The first reversed() argument is not a sequence
+E: 46:test: The first reversed() argument is not a sequence
+E: 47:test: The first reversed() argument is not a sequence
+E: 48:test: The first reversed() argument is not a sequence
+E: 51:test: The first reversed() argument is not a sequence
+E: 52:test: The first reversed() argument is not a sequence
+E: 54:test: The first reversed() argument is not a sequence
+E: 55:test: The first reversed() argument is not a sequence \ No newline at end of file
diff --git a/test/messages/func_catching_non_exception.txt b/test/messages/func_catching_non_exception.txt
index 9dc23eb..19f22ab 100644
--- a/test/messages/func_catching_non_exception.txt
+++ b/test/messages/func_catching_non_exception.txt
@@ -1,3 +1,3 @@
-E: 24: Catching an exception which doesn't inherit from BaseException: MyException
-E: 29: Catching an exception which doesn't inherit from BaseException: MyException
-E: 29: Catching an exception which doesn't inherit from BaseException: MySecondException \ No newline at end of file
+E: 35: Catching an exception which doesn't inherit from BaseException: MyException
+E: 40: Catching an exception which doesn't inherit from BaseException: MyException
+E: 40: Catching an exception which doesn't inherit from BaseException: MySecondException \ No newline at end of file
diff --git a/test/messages/func_with_used_before_assignment.txt b/test/messages/func_with_used_before_assignment.txt
new file mode 100644
index 0000000..dc6e386
--- /dev/null
+++ b/test/messages/func_with_used_before_assignment.txt
@@ -0,0 +1,2 @@
+E: 10:do_nothing: Undefined variable 'ctx'
+E: 11:do_nothing: Using variable 'context' before assignment \ No newline at end of file