summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcpopa <devnull@localhost>2014-07-22 13:29:02 +0200
committercpopa <devnull@localhost>2014-07-22 13:29:02 +0200
commit5359ed4f09b8667907280b49c84831a1c1dfe8bf (patch)
treea5f44cd333582ed59302f881bde5ddb5cbbdd9fb
parent7f5869347f70a4af11587a8975e262e9c813386b (diff)
parentd2dac2c5d2ba2f8a06fe43d815fd3ff63a089000 (diff)
downloadpylint-5359ed4f09b8667907280b49c84831a1c1dfe8bf.tar.gz
Merge with default.
-rw-r--r--CONTRIBUTORS.txt2
-rw-r--r--ChangeLog39
-rw-r--r--checkers/classes.py77
-rw-r--r--checkers/exceptions.py10
-rw-r--r--checkers/misc.py29
-rw-r--r--checkers/strings.py277
-rw-r--r--checkers/typecheck.py207
-rw-r--r--checkers/utils.py10
-rw-r--r--checkers/variables.py125
-rw-r--r--doc/run.rst8
-rw-r--r--lint.py6
-rw-r--r--pyreverse/diagrams.py7
-rw-r--r--test/input/func_arguments.py17
-rw-r--r--test/input/func_assigning_non_slot.py56
-rw-r--r--test/input/func_bad_exception_context_py30.py5
-rw-r--r--test/input/func_block_disable_msg.py2
-rw-r--r--test/input/func_defining-attr-methods_order.py8
-rw-r--r--test/input/func_e12xx.py2
-rw-r--r--test/input/func_e13xx.py1
-rw-r--r--test/input/func_fixme.py7
-rw-r--r--test/input/func_format_py27.py2
-rw-r--r--test/input/func_format_py_27.py2
-rw-r--r--test/input/func_invalid_sequence_index.py210
-rw-r--r--test/input/func_invalid_slice_index.py61
-rw-r--r--test/input/func_noerror_unbalanced_tuple_unpacking_py30.py11
-rw-r--r--test/input/func_string_format_py27.py78
-rw-r--r--test/input/func_typecheck_non_callable_call.py36
-rw-r--r--test/input/func_undefined_var.py36
-rw-r--r--test/input/func_used_before_assignment_py30.py22
-rw-r--r--test/input/func_w0612.py18
-rw-r--r--test/input/func_w0623_py_30.py2
-rw-r--r--test/messages/func_assigning_non_slot.txt3
-rw-r--r--test/messages/func_defining-attr-methods_order.txt2
-rw-r--r--test/messages/func_e12xx.txt2
-rw-r--r--test/messages/func_e13xx.txt1
-rw-r--r--test/messages/func_e13xx_py30.txt11
-rw-r--r--test/messages/func_fixme.txt4
-rw-r--r--test/messages/func_invalid_sequence_index.txt19
-rw-r--r--test/messages/func_invalid_slice_index.txt5
-rw-r--r--test/messages/func_string_format_py27.txt26
-rw-r--r--test/messages/func_typecheck_non_callable_call.txt2
-rw-r--r--test/messages/func_undefined_var.txt4
-rw-r--r--test/messages/func_used_before_assignment_py30.txt6
-rw-r--r--test/messages/func_w0612.txt8
-rw-r--r--test/messages/func_w0702.txt1
-rw-r--r--test/messages/func_w0705.txt10
-rw-r--r--test/unittest_checker_variables.py15
-rw-r--r--testutils.py3
-rw-r--r--utils.py20
49 files changed, 1429 insertions, 86 deletions
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 8b28ae3..eeb582d 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -32,6 +32,8 @@ Order doesn't matter (not that much, at least ;)
* Nathaniel Manista: suspicious lambda checking
+* David Shea: invalid sequence and slice index
+
* Wolfgang Grafen, Axel Muller, Fabio Zadrozny, Pierre Rouleau,
Maarten ter Huurne, Mirko Friedenhagen and all the Logilab's team (among others):
bug reports, feedback, feature requests... Many other people have contributed
diff --git a/ChangeLog b/ChangeLog
index 92faa54..35e4753 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,11 +2,50 @@ ChangeLog for Pylint
====================
--
+
* Emit 'undefined-variable' for undefined names when using the
Python 3 `metaclass=` argument.
+ * Checkers respect priority now. Close issue #229.
+
+ * Fix a false positive regarding W0511. Closes issue #149.
+
* Fix unused-import false positive with Python 3 metaclasses (#143).
+ * Don't warn with 'bad-format-character' when encountering
+ the 'a' format on Python 3.
+
+ * Add multiple checks for PEP 3101 advanced string formatting:
+ 'bad-format-string', 'missing-format-argument-key',
+ 'unused-format-string-argument', 'format-combined-specification',
+ 'missing-format-attribute' and 'invalid-format-index'.
+
+ * Issue broad-except and bare-except even if the number
+ of except handlers is different than 1. Fixes issue #113.
+
+ * Issue attribute-defined-outside-init for all cases, not just
+ for the last assignment. Closes issue #262.
+
+ * Emit 'not-callable' when calling properties. Closes issue #268.
+
+ * Fix a false positive with unbalanced iterable unpacking,
+ when encountering starred nodes. Closes issue #273.
+
+ * Add new checks, 'invalid-slice-index' and 'invalid-sequence-index'
+ for invalid sequence and slice indices.
+
+ * Add 'assigning-non-slot' warning, which detects assignments to
+ attributes not defined in slots.
+
+ * Don't emit 'no-name-in-module' for ignored modules.
+ Closes issue #223.
+
+ * Fix an 'unused-variable' false positive, where the variable is
+ assigned through an import. Closes issue #196.
+
+ * Definition order is considered for classes, function arguments
+ and annotations. Closes issue #257.
+
2014-04-30 -- 1.2.1
* Restore the ability to specify the init-hook option via the
configuration file, which was accidentally broken in 1.2.0.
diff --git a/checkers/classes.py b/checkers/classes.py
index f5e2783..570f7ac 100644
--- a/checkers/classes.py
+++ b/checkers/classes.py
@@ -26,7 +26,8 @@ from astroid.bases import Generator
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)
+ check_messages, is_attr_private, is_attr_protected, node_frame_class,
+ safe_infer)
if sys.version_info >= (3, 0):
NEXT_METHOD = '__next__'
@@ -164,6 +165,10 @@ MSGS = {
'only non empty strings',
'invalid-slots-object',
'Used when an invalid (non-string) object occurs in __slots__.'),
+ 'E0237': ('Assigning to attribute %r not defined in class slots',
+ 'assigning-non-slot',
+ 'Used when assigning to an attribute not defined '
+ 'in the class slots.'),
'E0238': ('Invalid __slots__ object',
'invalid-slots',
'Used when an invalid __slots__ is found in class. '
@@ -272,28 +277,30 @@ a metaclass class method.'}
isinstance(n.statement(), (astroid.Delete, astroid.AugAssign))]
if not nodes:
continue # error detected by typechecking
- attr_defined = False
# check if any method attr is defined in is a defining method
- for node in nodes:
- if node.frame().name in defining_methods:
- attr_defined = True
- if not attr_defined:
- # check attribute is defined in a parent's __init__
- for parent in cnode.instance_attr_ancestors(attr):
- attr_defined = False
- # check if any parent method attr is defined in is a defining method
- for node in parent.instance_attrs[attr]:
- if node.frame().name in defining_methods:
- attr_defined = True
- if attr_defined:
- # we're done :)
- break
- else:
- # check attribute is defined as a class attribute
- try:
- cnode.local_attr(attr)
- except astroid.NotFoundError:
- self.add_message('attribute-defined-outside-init', args=attr, node=node)
+ if any(node.frame().name in defining_methods
+ for node in nodes):
+ continue
+
+ # check attribute is defined in a parent's __init__
+ for parent in cnode.instance_attr_ancestors(attr):
+ attr_defined = False
+ # check if any parent method attr is defined in is a defining method
+ for node in parent.instance_attrs[attr]:
+ if node.frame().name in defining_methods:
+ attr_defined = True
+ if attr_defined:
+ # we're done :)
+ break
+ else:
+ # check attribute is defined as a class attribute
+ try:
+ cnode.local_attr(attr)
+ except astroid.NotFoundError:
+ for node in nodes:
+ if node.frame().name not in defining_methods:
+ self.add_message('attribute-defined-outside-init',
+ args=attr, node=node)
def visit_function(self, node):
"""check method arguments, overriding"""
@@ -461,6 +468,32 @@ a metaclass class method.'}
def visit_assattr(self, node):
if isinstance(node.ass_type(), astroid.AugAssign) and self.is_first_attr(node):
self._accessed[-1].setdefault(node.attrname, []).append(node)
+ self._check_in_slots(node)
+
+ def _check_in_slots(self, node):
+ """ Check that the given assattr node
+ is defined in the class slots.
+ """
+ infered = safe_infer(node.expr)
+ if infered and isinstance(infered, Instance):
+ klass = infered._proxied
+ if '__slots__' not in klass.locals or not klass.newstyle:
+ return
+
+ slots = klass.slots()
+ # If any ancestor doesn't use slots, the slots
+ # defined for this class are superfluous.
+ if any('__slots__' not in ancestor.locals and
+ ancestor.name != 'object'
+ for ancestor in klass.ancestors()):
+ return
+
+ if not any(slot.value == node.attrname for slot in slots):
+ # If we have a '__dict__' in slots, then
+ # assigning any name is valid.
+ if not any(slot.value == '__dict__' for slot in slots):
+ self.add_message('assigning-non-slot',
+ args=(node.attrname, ), node=node)
@check_messages('protected-access')
def visit_assign(self, assign_node):
diff --git a/checkers/exceptions.py b/checkers/exceptions.py
index 84f92ea..c91c95d 100644
--- a/checkers/exceptions.py
+++ b/checkers/exceptions.py
@@ -153,6 +153,8 @@ class ExceptionsChecker(BaseChecker):
except astroid.InferenceError:
pass
else:
+ if cause is YES:
+ return
if isinstance(cause, astroid.Const):
if cause.value is not None:
self.add_message('bad-exception-context',
@@ -237,14 +239,14 @@ class ExceptionsChecker(BaseChecker):
nb_handlers = len(node.handlers)
for index, handler in enumerate(node.handlers):
# single except doing nothing but "pass" without else clause
- if nb_handlers == 1 and is_empty(handler.body) and not node.orelse:
+ if is_empty(handler.body) and not node.orelse:
self.add_message('pointless-except', node=handler.type or handler.body[0])
if handler.type is None:
- if nb_handlers == 1 and not is_raising(handler.body):
+ if not is_raising(handler.body):
self.add_message('bare-except', node=handler)
# check if a "except:" is followed by some other
# except
- elif index < (nb_handlers - 1):
+ if index < (nb_handlers - 1):
msg = 'empty except clause should always appear last'
self.add_message('bad-except-order', node=node, args=msg)
@@ -268,7 +270,7 @@ class ExceptionsChecker(BaseChecker):
self.add_message('bad-except-order', node=handler.type, args=msg)
if (exc.name in self.config.overgeneral_exceptions
and exc.root().name == EXCEPTIONS_MODULE
- and nb_handlers == 1 and not is_raising(handler.body)):
+ and not is_raising(handler.body)):
self.add_message('broad-except', args=exc.name, node=handler.type)
if (not inherit_from_std_ex(exc) and
diff --git a/checkers/misc.py b/checkers/misc.py
index d1b7c21..b4738b0 100644
--- a/checkers/misc.py
+++ b/checkers/misc.py
@@ -32,10 +32,11 @@ MSGS = {
'Used when a source line cannot be decoded using the specified '
'source file encoding.',
{'maxversion': (3, 0)}),
- }
+}
class EncodingChecker(BaseChecker):
+
"""checks for:
* warning notes in the code like FIXME, XXX
* encoding issues.
@@ -47,17 +48,16 @@ class EncodingChecker(BaseChecker):
msgs = MSGS
options = (('notes',
- {'type' : 'csv', 'metavar' : '<comma separated values>',
- 'default' : ('FIXME', 'XXX', 'TODO'),
- 'help' : 'List of note tags to take in consideration, \
-separated by a comma.'
- }),
- )
+ {'type': 'csv', 'metavar': '<comma separated values>',
+ 'default': ('FIXME', 'XXX', 'TODO'),
+ 'help': ('List of note tags to take in consideration, '
+ 'separated by a comma.')}),)
def _check_note(self, notes, lineno, line):
match = notes.search(line)
- if match:
- self.add_message('fixme', args=line[match.start():-1], line=lineno)
+ if not match:
+ return
+ self.add_message('fixme', args=line[match.start(1):-1], line=lineno)
def _check_encoding(self, lineno, line, file_encoding):
try:
@@ -71,19 +71,22 @@ separated by a comma.'
notes
"""
stream = module.file_stream
- stream.seek(0) # XXX may be removed with astroid > 0.23
+ stream.seek(0) # XXX may be removed with astroid > 0.23
if self.config.notes:
- notes = re.compile('|'.join(self.config.notes))
+ notes = re.compile(
+ r'.*?#\s+(%s)(:*\s*.+)' % "|".join(self.config.notes))
else:
notes = None
if module.file_encoding:
encoding = module.file_encoding
else:
encoding = 'ascii'
+
for lineno, line in enumerate(stream):
- line = self._check_encoding(lineno+1, line, encoding)
+ line = self._check_encoding(lineno + 1, line, encoding)
if line is not None and notes:
- self._check_note(notes, lineno+1, line)
+ self._check_note(notes, lineno + 1, line)
+
def register(linter):
"""required method to auto register this checker"""
diff --git a/checkers/strings.py b/checkers/strings.py
index 04cf1bc..4fe16dd 100644
--- a/checkers/strings.py
+++ b/checkers/strings.py
@@ -20,6 +20,7 @@
import sys
import tokenize
+import string
import astroid
@@ -28,7 +29,8 @@ from pylint.checkers import BaseChecker, BaseTokenChecker
from pylint.checkers import utils
from pylint.checkers.utils import check_messages
-_PY3K = sys.version_info >= (3, 0)
+_PY3K = sys.version_info[:2] >= (3, 0)
+_PY27 = sys.version_info[:2] == (2, 7)
MSGS = {
'E1300': ("Unsupported format character %r (%#02x) at index %d",
@@ -71,12 +73,121 @@ MSGS = {
"too-few-format-args",
"Used when a format string that uses unnamed conversion \
specifiers is given too few arguments"),
+
+ 'W1302': ("Invalid format string",
+ "bad-format-string",
+ "Used when a PEP 3101 format string is invalid."),
+ 'W1303': ("Missing keyword argument %r for format string",
+ "missing-format-argument-key",
+ "Used when a PEP 3101 format string that uses named fields "
+ "doesn't receive one or more required keywords."),
+ 'W1304': ("Unused format argument %r",
+ "unused-format-string-argument",
+ "Used when a PEP 3101 format string that uses named "
+ "fields is used with an argument that "
+ "is not required by the format string."),
+ 'W1305': ("Format string contains both automatic field numbering "
+ "and manual field specification",
+ "format-combined-specification",
+ "Usen when a PEP 3101 format string contains both automatic "
+ "field numbering (e.g. '{}') and manual field "
+ "specification (e.g. '{0}')."),
+ 'W1306': ("Missing format attribute %r in format specifier %r",
+ "missing-format-attribute",
+ "Used when a PEP 3101 format string uses an "
+ "attribute specifier ({0.length}), but the argument "
+ "passed for formatting doesn't have that attribute."),
+ 'W1307': ("Using invalid lookup key %r in format specifier %r",
+ "invalid-format-index",
+ "Used when a PEP 3101 format string uses a lookup specifier "
+ "({a[1]}), but the argument passed for formatting "
+ "doesn't contain or doesn't have that key as an attribute.")
}
OTHER_NODES = (astroid.Const, astroid.List, astroid.Backquote,
astroid.Lambda, astroid.Function,
astroid.ListComp, astroid.SetComp, astroid.GenExpr)
+if _PY3K:
+ import _string
+
+ def split_format_field_names(format_string):
+ return _string.formatter_field_name_split(format_string)
+else:
+ def _field_iterator_convertor(iterator):
+ for is_attr, key in iterator:
+ if not isinstance(key, str):
+ yield is_attr, int(key)
+ else:
+ yield is_attr, key
+
+ def split_format_field_names(format_string):
+ keyname, fielditerator = format_string._formatter_field_name_split()
+ # it will return longs, instead of ints, which will complicate
+ # the output
+ return keyname, _field_iterator_convertor(fielditerator)
+
+def parse_format_method_string(format_string):
+ """
+ Parses a PEP 3101 format string, returning a tuple of
+ (keys, num_args),
+ where keys is the set of mapping keys in the format string and num_args
+ is the number of arguments required by the format string.
+ """
+ keys = []
+ num_args = 0
+ formatter = string.Formatter()
+ parseiterator = formatter.parse(format_string)
+ try:
+ for result in parseiterator:
+ if all(item is None for item in result[1:]):
+ # not a replacement format
+ continue
+ name = result[1]
+ if name:
+ keyname, fielditerator = split_format_field_names(name)
+ if not isinstance(keyname, str):
+ # In Python 2 it will return long which will lead
+ # to different output between 2 and 3
+ keyname = int(keyname)
+ keys.append((keyname, list(fielditerator)))
+ else:
+ num_args += 1
+ except ValueError:
+ # probably the format string is invalid
+ # should we check the argument of the ValueError?
+ raise utils.IncompleteFormatString(format_string)
+ return keys, num_args
+
+def get_args(callfunc):
+ """ Get the arguments from the given `CallFunc` node.
+ Return a tuple, where the first element is the
+ number of positional arguments and the second element
+ is the keyword arguments in a dict.
+ """
+ positional = 0
+ named = {}
+
+ for arg in callfunc.args:
+ if isinstance(arg, astroid.Keyword):
+ named[arg.arg] = utils.safe_infer(arg.value)
+ else:
+ positional += 1
+ return positional, named
+
+def get_access_path(key, parts):
+ """ Given a list of format specifiers, returns
+ the final access path (e.g. a.b.c[0][1]).
+ """
+ path = []
+ for is_attribute, specifier in parts:
+ if is_attribute:
+ path.append(".{}".format(specifier))
+ else:
+ path.append("[{!r}]".format(specifier))
+ return str(key) + "".join(path)
+
+
class StringFormatChecker(BaseChecker):
"""Checks string formatting operations to ensure that the format string
is valid and the arguments match the format string.
@@ -182,15 +293,157 @@ class StringMethodsChecker(BaseChecker):
func = utils.safe_infer(node.func)
if (isinstance(func, astroid.BoundMethod)
and isinstance(func.bound, astroid.Instance)
- and func.bound.name in ('str', 'unicode', 'bytes')
- and func.name in ('strip', 'lstrip', 'rstrip')
- and node.args):
- arg = utils.safe_infer(node.args[0])
- if not isinstance(arg, astroid.Const):
- return
- if len(arg.value) != len(set(arg.value)):
- self.add_message('bad-str-strip-call', node=node,
- args=(func.bound.name, func.name))
+ and func.bound.name in ('str', 'unicode', 'bytes')):
+
+ if func.name in ('strip', 'lstrip', 'rstrip') and node.args:
+ arg = utils.safe_infer(node.args[0])
+ if not isinstance(arg, astroid.Const):
+ return
+ if len(arg.value) != len(set(arg.value)):
+ self.add_message('bad-str-strip-call', node=node,
+ args=(func.bound.name, func.name))
+ elif func.name == 'format':
+ if _PY27 or _PY3K:
+ self._check_new_format(node, func)
+
+ def _check_new_format(self, node, func):
+ """ Check the new string formatting. """
+ try:
+ strnode = func.bound.infer().next()
+ except astroid.InferenceError:
+ return
+ if not isinstance(strnode, astroid.Const):
+ return
+ if node.starargs or node.kwargs:
+ # TODO: Don't complicate the logic, skip these for now.
+ return
+ try:
+ positional, named = get_args(node)
+ except astroid.InferenceError:
+ return
+ try:
+ fields, num_args = parse_format_method_string(strnode.value)
+ except utils.IncompleteFormatString:
+ self.add_message('bad-format-string', node=node)
+ return
+
+ manual_fields = {field[0] for field in fields
+ if isinstance(field[0], int)}
+ named_fields = {field[0] for field in fields
+ if isinstance(field[0], str)}
+ if manual_fields and num_args:
+ self.add_message('format-combined-specification',
+ node=node)
+ return
+
+ if named_fields:
+ for field in named_fields:
+ if field not in named and field:
+ self.add_message('missing-format-argument-key',
+ node=node,
+ args=(field, ))
+ for field in named:
+ if field not in named_fields:
+ self.add_message('unused-format-string-argument',
+ node=node,
+ args=(field, ))
+ else:
+ if positional > num_args:
+ # We can have two possibilities:
+ # * "{0} {1}".format(a, b)
+ # * "{} {} {}".format(a, b, c, d)
+ # We can check the manual keys for the first one.
+ if len(manual_fields) != positional:
+ self.add_message('too-many-format-args', node=node)
+ elif positional < num_args:
+ self.add_message('too-few-format-args', node=node)
+
+ if manual_fields and positional < len(manual_fields):
+ self.add_message('too-few-format-args', node=node)
+
+ self._check_new_format_specifiers(node, fields, named)
+
+ def _check_new_format_specifiers(self, node, fields, named):
+ """
+ Check attribute and index access in the format
+ string ("{0.a}" and "{0[a]}").
+ """
+ for key, specifiers in fields:
+ # Obtain the argument. If it can't be obtained
+ # or infered, skip this check.
+ if key == '':
+ # {[0]} will have an unnamed argument, defaulting
+ # to 0. It will not be present in `named`, so use the value
+ # 0 for it.
+ key = 0
+ if isinstance(key, int):
+ try:
+ argument = utils.get_argument_from_call(node, key)
+ except utils.NoSuchArgumentError:
+ continue
+ else:
+ if key not in named:
+ continue
+ argument = named[key]
+ if argument in (astroid.YES, None):
+ continue
+ try:
+ argument = argument.infer().next()
+ except astroid.InferenceError:
+ continue
+ if not specifiers or argument is astroid.YES:
+ # No need to check this key if it doesn't
+ # use attribute / item access
+ continue
+
+ previous = argument
+ parsed = []
+ for is_attribute, specifier in specifiers:
+ if previous is astroid.YES:
+ break
+ parsed.append((is_attribute, specifier))
+ if is_attribute:
+ try:
+ previous = previous.getattr(specifier)[0]
+ except astroid.NotFoundError:
+ if (hasattr(previous, 'has_dynamic_getattr') and
+ previous.has_dynamic_getattr()):
+ # Don't warn if the object has a custom __getattr__
+ break
+ path = get_access_path(key, parsed)
+ self.add_message('missing-format-attribute',
+ args=(specifier, path),
+ node=node)
+ break
+ else:
+ warn_error = False
+ if hasattr(previous, 'getitem'):
+ try:
+ previous = previous.getitem(specifier)
+ except (IndexError, TypeError):
+ warn_error = True
+ else:
+ try:
+ # Lookup __getitem__ in the current node,
+ # but skip further checks, because we can't
+ # retrieve the looked object
+ previous.getattr('__getitem__')
+ break
+ except astroid.NotFoundError:
+ warn_error = True
+ if warn_error:
+ path = get_access_path(key, parsed)
+ self.add_message('invalid-format-index',
+ args=(specifier, path),
+ node=node)
+ break
+
+ try:
+ previous = previous.infer().next()
+ except astroid.InferenceError:
+ # can't check further if we can't infer it
+ break
+
class StringConstantChecker(BaseTokenChecker):
@@ -285,10 +538,10 @@ class StringConstantChecker(BaseTokenChecker):
elif (_PY3K or self._unicode_literals) and 'b' not in prefix:
pass # unicode by default
else:
- self.add_message('anomalous-unicode-escape-in-string',
+ self.add_message('anomalous-unicode-escape-in-string',
line=start_row, args=(match, ))
elif next_char not in self.ESCAPE_CHARACTERS:
- self.add_message('anomalous-backslash-in-string',
+ self.add_message('anomalous-backslash-in-string',
line=start_row, args=(match, ))
# Whether it was a valid escape or not, backslash followed by
# another character can always be consumed whole: the second
diff --git a/checkers/typecheck.py b/checkers/typecheck.py
index 4cb406f..3bcb099 100644
--- a/checkers/typecheck.py
+++ b/checkers/typecheck.py
@@ -18,9 +18,11 @@
import re
import shlex
+import sys
import astroid
from astroid import InferenceError, NotFoundError, YES, Instance
+from astroid.bases import BUILTINS
from pylint.interfaces import IAstroidChecker
from pylint.checkers import BaseChecker
@@ -74,8 +76,20 @@ MSGS = {
('Used when a function call does not pass a mandatory'
' keyword-only argument.'),
{'minversion': (3, 0)}),
+ 'E1126': ('Sequence index is not an int, slice, or instance with __index__',
+ 'invalid-sequence-index',
+ 'Used when a sequence type is indexed with an invalid type. Valid \
+ types are ints, slices, and objects with an __index__ method.'),
+ 'E1127': ('Slice index is not an int, None, or instance with __index__',
+ 'invalid-slice-index',
+ 'Used when a slice index is not an integer, None, or an object \
+ with an __index__ method.'),
}
+# builtin sequence types in Python 2 and 3.
+sequence_types = set(['str', 'unicode', 'list', 'tuple', 'bytearray',
+ 'xrange', 'range', 'bytes', 'memoryview'])
+
def _determine_callable(callable_obj):
# Ordering is important, since BoundMethod is a subclass of UnboundMethod,
# and Function inherits Lambda.
@@ -98,7 +112,7 @@ def _determine_callable(callable_obj):
except astroid.NotFoundError:
new = None
- if not new or new.parent.name == 'object':
+ if not new or new.parent.scope().name == 'object':
try:
# Use the last definition of __init__.
callable_obj = callable_obj.local_attr('__init__')[-1]
@@ -139,7 +153,7 @@ class should be ignored. A mixin class is detected if its name ends with \
'metavar': '<module names>',
'help': 'List of module names for which member attributes \
should not be checked (useful for modules/projects where namespaces are \
-manipulated during runtime and thus extisting member attributes cannot be \
+manipulated during runtime and thus existing member attributes cannot be \
deduced by static analysis'},
),
('ignored-classes',
@@ -293,7 +307,72 @@ accessed. Python regular expressions are accepted.'}
else:
self.add_message('assignment-from-none', node=node)
- @check_messages(*(MSGS.keys()))
+ def _check_uninferable_callfunc(self, node):
+ """
+ Check that the given uninferable CallFunc node does not
+ call an actual function.
+ """
+ if not isinstance(node.func, astroid.Getattr):
+ return
+
+ # Look for properties. First, obtain
+ # the lhs of the Getattr node and search the attribute
+ # there. If that attribute is a property or a subclass of properties,
+ # then most likely it's not callable.
+
+ # TODO: since astroid doesn't understand descriptors very well
+ # we will not handle them here, right now.
+
+ expr = node.func.expr
+ klass = safe_infer(expr)
+ if (klass is None or klass is astroid.YES or
+ not isinstance(klass, astroid.Instance)):
+ return
+
+ try:
+ attrs = klass._proxied.getattr(node.func.attrname)
+ except astroid.NotFoundError:
+ return
+
+ stop_checking = False
+ for attr in attrs:
+ if attr is astroid.YES:
+ continue
+ if stop_checking:
+ break
+ if not isinstance(attr, astroid.Function):
+ continue
+
+ # Decorated, see if it is decorated with a property
+ if not attr.decorators:
+ continue
+ for decorator in attr.decorators.nodes:
+ if not isinstance(decorator, astroid.Name):
+ continue
+ try:
+ for infered in decorator.infer():
+ property_like = False
+ if isinstance(infered, astroid.Class):
+ if (infered.root().name == BUILTINS and
+ infered.name == 'property'):
+ property_like = True
+ else:
+ for ancestor in infered.ancestors():
+ if (ancestor.name == 'property' and
+ ancestor.root().name == BUILTINS):
+ property_like = True
+ break
+ if property_like:
+ self.add_message('not-callable', node=node,
+ args=node.func.as_string())
+ stop_checking = True
+ break
+ except InferenceError:
+ pass
+ if stop_checking:
+ break
+
+ @check_messages(*(list(MSGS.keys())))
def visit_callfunc(self, node):
"""check that called functions/methods are inferred to callable objects,
and that the arguments passed to the function match the parameters in
@@ -315,12 +394,15 @@ accessed. Python regular expressions are accepted.'}
called = safe_infer(node.func)
# only function, generator and object defining __call__ are allowed
if called is not None and not called.callable():
- self.add_message('not-callable', node=node, args=node.func.as_string())
+ self.add_message('not-callable', node=node,
+ args=node.func.as_string())
+
+ self._check_uninferable_callfunc(node)
try:
called, implicit_args, callable_name = _determine_callable(called)
except ValueError:
- # Any error occurred during determining the function type, most of
+ # Any error occurred during determining the function type, most of
# those errors are handled by different warnings.
return
num_positional_args += implicit_args
@@ -445,6 +527,121 @@ accessed. Python regular expressions are accepted.'}
if defval is None and not assigned:
self.add_message('missing-kwoa', node=node, args=(name, callable_name))
+ @check_messages('invalid-sequence-index')
+ def visit_extslice(self, node):
+ # Check extended slice objects as if they were used as a sequence
+ # index to check if the object being sliced can support them
+ return self.visit_index(node)
+
+ @check_messages('invalid-sequence-index')
+ def visit_index(self, node):
+ if not node.parent or not hasattr(node.parent, "value"):
+ return
+
+ # Look for index operations where the parent is a sequence type.
+ # If the types can be determined, only allow indices to be int,
+ # slice or instances with __index__.
+
+ parent_type = safe_infer(node.parent.value)
+
+ if not isinstance(parent_type, (astroid.Class, astroid.Instance)):
+ return
+
+ # Determine what method on the parent this index will use
+ # The parent of this node will be a Subscript, and the parent of that
+ # node determines if the Subscript is a get, set, or delete operation.
+ operation = node.parent.parent
+ if isinstance(operation, astroid.Assign):
+ methodname = '__setitem__'
+ elif isinstance(operation, astroid.Delete):
+ methodname = '__delitem__'
+ else:
+ methodname = '__getitem__'
+
+ # Check if this instance's __getitem__, __setitem__, or __delitem__, as
+ # appropriate to the statement, is implemented in a builtin sequence
+ # type. This way we catch subclasses of sequence types but skip classes
+ # that override __getitem__ and which may allow non-integer indices.
+ try:
+ methods = parent_type.getattr(methodname)
+ if methods is astroid.YES:
+ return
+ itemmethod = methods[0]
+ except (astroid.NotFoundError, IndexError):
+ return
+
+ if not isinstance(itemmethod, astroid.Function):
+ return
+
+ if itemmethod.root().name != BUILTINS:
+ return
+
+ if not itemmethod.parent:
+ return
+
+ if itemmethod.parent.name not in sequence_types:
+ return
+
+ # For ExtSlice objects coming from visit_extslice, no further
+ # inference is necessary, since if we got this far the ExtSlice
+ # is an error.
+ if isinstance(node, astroid.ExtSlice):
+ index_type = node
+ else:
+ index_type = safe_infer(node)
+
+ if index_type is None or index_type is astroid.YES:
+ return
+
+ # Constants must be of type int
+ if isinstance(index_type, astroid.Const):
+ if isinstance(index_type.value, int):
+ return
+ # Instance values must be int, slice, or have an __index__ method
+ elif isinstance(index_type, astroid.Instance):
+ if index_type.pytype() in (BUILTINS + '.int', BUILTINS + '.slice'):
+ return
+
+ try:
+ index_type.getattr('__index__')
+ return
+ except astroid.NotFoundError:
+ pass
+
+ # Anything else is an error
+ self.add_message('invalid-sequence-index', node=node)
+
+ @check_messages('invalid-slice-index')
+ def visit_slice(self, node):
+ # Check the type of each part of the slice
+ for index in (node.lower, node.upper, node.step):
+ if index is None:
+ continue
+
+ index_type = safe_infer(index)
+
+ if index_type is None or index_type is astroid.YES:
+ continue
+
+ # Constants must of type int or None
+ if isinstance(index_type, astroid.Const):
+ if isinstance(index_type.value, (int, type(None))):
+ continue
+ # Instance values must be of type int, None or an object
+ # with __index__
+ elif isinstance(index_type, astroid.Instance):
+ if index_type.pytype() in (BUILTINS + '.int',
+ BUILTINS + '.NoneType'):
+ continue
+
+ try:
+ index_type.getattr('__index__')
+ return
+ except astroid.NotFoundError:
+ pass
+
+ # Anything else is an error
+ self.add_message('invalid-slice-index', node=node)
def register(linter):
"""required method to auto register this checker """
diff --git a/checkers/utils.py b/checkers/utils.py
index e7d85d4..01989be 100644
--- a/checkers/utils.py
+++ b/checkers/utils.py
@@ -19,6 +19,7 @@
"""
import re
+import sys
import string
import astroid
@@ -26,8 +27,8 @@ from astroid import scoped_nodes
from logilab.common.compat import builtins
BUILTINS_NAME = builtins.__name__
-
COMP_NODE_TYPES = astroid.ListComp, astroid.SetComp, astroid.DictComp, astroid.GenExpr
+PY3K = sys.version_info[0] == 3
class NoSuchArgumentError(Exception):
@@ -345,7 +346,11 @@ def parse_format_string(format_string):
if char in 'hlL':
i, char = next_char(i)
# Parse the conversion type (mandatory).
- if char not in 'diouxXeEfFgGcrs%':
+ if PY3K:
+ flags = 'diouxXeEfFgGcrs%a'
+ else:
+ flags = 'diouxXeEfFgGcrs%'
+ if char not in flags:
raise UnsupportedFormatCharacter(i)
if key:
keys.add(key)
@@ -354,6 +359,7 @@ def parse_format_string(format_string):
i += 1
return keys, num_args
+
def is_attr_protected(attrname):
"""return True if attribute name is protected (start with _ and some other
details), False otherwise.
diff --git a/checkers/variables.py b/checkers/variables.py
index ebe520a..6fba97d 100644
--- a/checkers/variables.py
+++ b/checkers/variables.py
@@ -25,6 +25,7 @@ from astroid import are_exclusive, builtin_lookup, AstroidBuildingException
from logilab.common.modutils import file_from_modpath
from pylint.interfaces import IAstroidChecker
+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,
@@ -68,6 +69,71 @@ def _get_unpacking_extra_info(node, infered):
more = ' defined at line %s of %s' % (infered.lineno, infered_module)
return more
+def _detect_global_scope(node, frame, defframe):
+ """ Detect that the given frames shares a global
+ scope.
+
+ Two frames shares a global scope when neither
+ of them are hidden under a function scope, as well
+ as any of parent scope of them, until the root scope.
+ In this case, depending from something defined later on
+ will not work, because it is still undefined.
+
+ Example:
+ class A:
+ # B has the same global scope as `C`, leading to a NameError.
+ class B(C): ...
+ class C: ...
+
+ """
+ def_scope = scope = None
+ if frame and frame.parent:
+ scope = frame.parent.scope()
+ if defframe and defframe.parent:
+ def_scope = defframe.parent.scope()
+ if isinstance(frame, astroid.Function):
+ # If the parent of the current node is a
+ # function, then it can be under its scope
+ # (defined in, which doesn't concern us) or
+ # the `->` part of annotations. The same goes
+ # for annotations of function arguments, they'll have
+ # their parent the Arguments node.
+ if not isinstance(node.parent,
+ (astroid.Function, astroid.Arguments)):
+ return False
+ elif any(not isinstance(f, (astroid.Class, astroid.Module))
+ for f in (frame, defframe)):
+ # Not interested in other frames, since they are already
+ # not in a global scope.
+ return False
+
+ break_scopes = []
+ for s in (scope, def_scope):
+ # Look for parent scopes. If there is anything different
+ # than a module or a class scope, then they frames don't
+ # share a global scope.
+ parent_scope = s
+ while parent_scope:
+ if not isinstance(parent_scope, (astroid.Class, astroid.Module)):
+ break_scopes.append(parent_scope)
+ break
+ if parent_scope.parent:
+ parent_scope = parent_scope.parent.scope()
+ else:
+ break
+ if break_scopes and len(set(break_scopes)) != 1:
+ # Store different scopes than expected.
+ # If the stored scopes are, in fact, the very same, then it means
+ # that the two frames (frame and defframe) shares the same scope,
+ # and we could apply our lineno analysis over them.
+ # For instance, this works when they are inside a function, the node
+ # that uses a definition and the definition itself.
+ return False
+ # At this point, we are certain that frame and defframe shares a scope
+ # and the definition of the first depends on the second.
+ return frame.lineno < defframe.lineno
+
+
MSGS = {
'E0601': ('Using variable %r before assignment',
'used-before-assignment',
@@ -147,7 +213,7 @@ MSGS = {
'a sequence is used in an unpack assignment'),
'W0640': ('Cell variable %s defined in loop',
- 'cell-var-from-loop',
+ 'cell-var-from-loop',
'A variable used in a closure is defined in a loop. '
'This will result in all closures using the same value for '
'the closed-over variable.'),
@@ -355,6 +421,10 @@ builtins. Remember that you should avoid to define new builtins when possible.'
authorized_rgx = self.config.dummy_variables_rgx
called_overridden = False
argnames = node.argnames()
+ global_names = set()
+ for global_stmt in node.nodes_of_class(astroid.Global):
+ global_names.update(set(global_stmt.names))
+
for name, stmts in not_consumed.iteritems():
# ignore some special names specified by user configuration
if authorized_rgx.match(name):
@@ -364,6 +434,23 @@ builtins. Remember that you should avoid to define new builtins when possible.'
stmt = stmts[0]
if isinstance(stmt, astroid.Global):
continue
+ if isinstance(stmt, (astroid.Import, astroid.From)):
+ # Detect imports, assigned to global statements.
+ if global_names:
+ skip = False
+ for import_name, import_alias in stmt.names:
+ # If the import uses an alias, check only that.
+ # Otherwise, check only the import name.
+ if import_alias:
+ if import_alias in global_names:
+ skip = True
+ break
+ elif import_name in global_names:
+ skip = True
+ break
+ if skip:
+ continue
+
# care about functions with unknown argument (builtins)
if name in argnames:
if is_method:
@@ -411,7 +498,26 @@ builtins. Remember that you should avoid to define new builtins when possible.'
break
else:
# global but no assignment
- self.add_message('global-variable-not-assigned', args=name, node=node)
+ # Detect imports in the current frame, with the required
+ # name. Such imports can be considered assignments.
+ imports = frame.nodes_of_class((astroid.Import, astroid.From))
+ for import_node in imports:
+ found = False
+ for import_name, import_alias in import_node.names:
+ # If the import uses an alias, check only that.
+ # Otherwise, check only the import name.
+ if import_alias:
+ if import_alias == name:
+ found = True
+ break
+ elif import_name and import_name == name:
+ found = True
+ break
+ if found:
+ break
+ else:
+ self.add_message('global-variable-not-assigned',
+ args=name, node=node)
default_message = False
if not assign_nodes:
continue
@@ -447,7 +553,7 @@ builtins. Remember that you should avoid to define new builtins when possible.'
else:
if maybe_for.parent_of(node_scope) and not isinstance(node_scope.statement(), astroid.Return):
self.add_message('cell-var-from-loop', node=node, args=node.name)
-
+
def _loopvar_name(self, node, name):
# filter variables according to node's scope
# XXX used to filter parents but don't remember why, and removing this
@@ -552,7 +658,7 @@ builtins. Remember that you should avoid to define new builtins when possible.'
defframe = defstmt.frame()
maybee0601 = True
if not frame is defframe:
- maybee0601 = False
+ maybee0601 = _detect_global_scope(node, frame, defframe)
elif defframe.parent is None:
# we are at the module level, check the name is not
# defined in builtins
@@ -649,6 +755,10 @@ builtins. Remember that you should avoid to define new builtins when possible.'
# attempt to check unpacking is properly balanced
values = infered.itered()
if len(targets) != len(values):
+ # Check if we have starred nodes.
+ if any(isinstance(target, astroid.Starred)
+ for target in targets):
+ return
self.add_message('unbalanced-tuple-unpacking', node=node,
args=(_get_unpacking_extra_info(node, infered),
len(targets),
@@ -675,6 +785,8 @@ builtins. Remember that you should avoid to define new builtins when possible.'
if the latest access name corresponds to a module, return it
"""
assert isinstance(module, astroid.Module), module
+ ignored_modules = get_global_option(self, 'ignored-modules',
+ default=[])
while module_names:
name = module_names.pop(0)
if name == '__dict__':
@@ -685,7 +797,10 @@ builtins. Remember that you should avoid to define new builtins when possible.'
if module is astroid.YES:
return None
except astroid.NotFoundError:
- self.add_message('no-name-in-module', args=(name, module.name), node=node)
+ if module.name in ignored_modules:
+ return None
+ self.add_message('no-name-in-module',
+ args=(name, module.name), node=node)
return None
except astroid.InferenceError:
return None
diff --git a/doc/run.rst b/doc/run.rst
index 4e752b0..d4a2aa9 100644
--- a/doc/run.rst
+++ b/doc/run.rst
@@ -10,9 +10,11 @@ Pylint is meant to be called from the command line. The usage is ::
pylint [options] module_or_package
You should give Pylint the name of a python package or module. Pylint
-will ``import`` this package or module, so you should pay attention to
-your ``PYTHONPATH``, since it is a common error to analyze an
-installed version of a module instead of the development version.
+``will not import`` this package or module, though uses Python internals
+to locate them and as such is subject to the same rules and configuration.
+You should pay attention to your ``PYTHONPATH``, since it is a common error
+to analyze an installed version of a module instead of the
+development version.
It is also possible to analyze python files, with a few
restrictions. The thing to keep in mind is that Pylint will try to
diff --git a/lint.py b/lint.py
index 529fbd4..26e099d 100644
--- a/lint.py
+++ b/lint.py
@@ -33,6 +33,7 @@ import functools
import sys
import os
import tokenize
+from operator import attrgetter
from warnings import warn
from logilab.common.configuration import UnsupportedAction, OptionsManagerMixIn
@@ -569,7 +570,10 @@ warning, statement which respectively contain the number of errors / warnings\
if msg[0] != 'F' and self.is_message_enabled(msg))
if (messages or
any(self.report_is_enabled(r[0]) for r in checker.reports)):
- neededcheckers.append(checker)
+ neededcheckers.append(checker)
+ # Sort checkers by priority
+ neededcheckers = sorted(neededcheckers, key=attrgetter('priority'),
+ reverse=True)
return neededcheckers
def should_analyze_file(self, modname, path): # pylint: disable=unused-argument
diff --git a/pyreverse/diagrams.py b/pyreverse/diagrams.py
index 6dde9a1..28cc500 100644
--- a/pyreverse/diagrams.py
+++ b/pyreverse/diagrams.py
@@ -89,8 +89,11 @@ class ClassDiagram(Figure, FilterMixIn):
def get_methods(self, node):
"""return visible methods"""
- return [m for m in sorted(node.values(), key=lambda n: n.name)
- if isinstance(m, astroid.Function) and self.show_attr(m.name)]
+ methods = [
+ m for m in node.values()
+ if isinstance(m, astroid.Function) and self.show_attr(m.name)
+ ]
+ return sorted(methods, key=lambda n: n.name)
def add_object(self, title, node):
"""create a diagram object
diff --git a/test/input/func_arguments.py b/test/input/func_arguments.py
index 1c27ef2..b607699 100644
--- a/test/input/func_arguments.py
+++ b/test/input/func_arguments.py
@@ -81,3 +81,20 @@ def method_tests():
demo.decorated_method()
DemoClass.decorated_method(demo)
+# Test a regression (issue #234)
+import sys
+
+# pylint: disable=too-few-public-methods
+class Text(object):
+ """ Regression """
+
+ if sys.version_info > (3,):
+ def __new__(cls):
+ """ empty """
+ return object.__new__(cls)
+ else:
+ def __new__(cls):
+ """ empty """
+ return object.__new__(cls)
+
+Text()
diff --git a/test/input/func_assigning_non_slot.py b/test/input/func_assigning_non_slot.py
new file mode 100644
index 0000000..9c91dd2
--- /dev/null
+++ b/test/input/func_assigning_non_slot.py
@@ -0,0 +1,56 @@
+""" Checks assigning attributes not found in class slots
+will trigger assigning-non-slot warning.
+"""
+# pylint: disable=too-few-public-methods, no-init
+from collections import deque
+
+__revision__ = 0
+
+class Empty(object):
+ """ empty """
+
+class Bad(object):
+ """ missing not in slots. """
+
+ __slots__ = ['member']
+
+ def __init__(self):
+ self.missing = 42
+
+class Bad2(object):
+ """ missing not in slots """
+ __slots__ = [deque.__name__, 'member']
+
+ def __init__(self):
+ self.deque = 42
+ self.missing = 42
+
+class Bad3(Bad):
+ """ missing not found in slots """
+
+ __slots__ = ['component']
+
+ def __init__(self):
+ self.component = 42
+ self.member = 24
+ self.missing = 42
+ super(Bad3, self).__init__()
+
+class Good(Empty):
+ """ missing not in slots, but Empty doesn't
+ specify __slots__.
+ """
+ __slots__ = ['a']
+
+ def __init__(self):
+ self.missing = 42
+
+class Good2(object):
+ """ Using __dict__ in slots will be safe. """
+
+ __slots__ = ['__dict__', 'comp']
+
+ def __init__(self):
+ self.comp = 4
+ self.missing = 5
+ \ No newline at end of file
diff --git a/test/input/func_bad_exception_context_py30.py b/test/input/func_bad_exception_context_py30.py
index d3ab127..98b44ee 100644
--- a/test/input/func_bad_exception_context_py30.py
+++ b/test/input/func_bad_exception_context_py30.py
@@ -1,8 +1,8 @@
"""Check that raise ... from .. uses a proper exception context """
-# pylint: disable=unreachable
+# pylint: disable=unreachable, import-error
-import socket
+import socket, unknown
__revision__ = 0
@@ -21,3 +21,4 @@ def test():
raise IndexError() from ZeroDivisionError
raise IndexError() from ZeroDivisionError()
raise IndexError() from object()
+ raise IndexError() from unknown
diff --git a/test/input/func_block_disable_msg.py b/test/input/func_block_disable_msg.py
index 2551823..480eac0 100644
--- a/test/input/func_block_disable_msg.py
+++ b/test/input/func_block_disable_msg.py
@@ -1,4 +1,4 @@
-# pylint: disable=C0302
+# pylint: disable=C0302,bare-except
"""pylint option block-disable"""
__revision__ = None
diff --git a/test/input/func_defining-attr-methods_order.py b/test/input/func_defining-attr-methods_order.py
index d64217d..888b192 100644
--- a/test/input/func_defining-attr-methods_order.py
+++ b/test/input/func_defining-attr-methods_order.py
@@ -26,8 +26,16 @@ class A(object):
def set_z(self, z):
''' set_z docstring filler '''
self.z = z
+ self.z = z
def setUp(self):
''' setUp docstring filler '''
self.x = 0
self.y = 0
+
+class B(A):
+ ''' class B '''
+
+ def test(self):
+ """ test """
+ self.z = 44
diff --git a/test/input/func_e12xx.py b/test/input/func_e12xx.py
index 6482c92..83df7ab 100644
--- a/test/input/func_e12xx.py
+++ b/test/input/func_e12xx.py
@@ -14,7 +14,7 @@ def pprint():
logging.info('', '') # 1205
logging.info('%s%', '') # 1201
logging.info('%s%s', '') # 1206
- logging.info('%s%a', '', '') # 1200
+ logging.info('%s%y', '', '') # 1200
logging.info('%s%s', '', '', '') # 1205
# These should be okay:
diff --git a/test/input/func_e13xx.py b/test/input/func_e13xx.py
index a0d39ef..1eaf598 100644
--- a/test/input/func_e13xx.py
+++ b/test/input/func_e13xx.py
@@ -18,4 +18,5 @@ def pprint():
print "%(PARG_1)d %(PARG_2)d" % [2, 3] # 1303
print "%2z" % PARG_1
print "strange format %2" % PARG_2
+ print "works in 3 %a" % 1
diff --git a/test/input/func_fixme.py b/test/input/func_fixme.py
index b6371f1..66e86db 100644
--- a/test/input/func_fixme.py
+++ b/test/input/func_fixme.py
@@ -1,9 +1,14 @@
"""docstring"""
-
+# pylint: disable=W0612
__revision__ = ''
# FIXME: beep
+
def function():
'''XXX:bop'''
+ variable = "FIXME: Ignore me!"
+ test = "text" # FIXME: Valid test
+ # TODO: Do something with the variables
+ xxx = "n/a" # XXX: Fix this later
diff --git a/test/input/func_format_py27.py b/test/input/func_format_py27.py
index 46d5cfe..eba178d 100644
--- a/test/input/func_format_py27.py
+++ b/test/input/func_format_py27.py
@@ -1,4 +1,4 @@
-# pylint:disable=C0103,W0104,W0105
+# pylint:disable=C0103,W0104,W0105,pointless-except
"""Check format
"""
__revision__ = ''
diff --git a/test/input/func_format_py_27.py b/test/input/func_format_py_27.py
index 95f7fde..be5033c 100644
--- a/test/input/func_format_py_27.py
+++ b/test/input/func_format_py_27.py
@@ -1,4 +1,4 @@
-# pylint:disable=C0103,W0104,W0105
+# pylint:disable=C0103,W0104,W0105,pointless-except
"""Check format
"""
__revision__ = ''
diff --git a/test/input/func_invalid_sequence_index.py b/test/input/func_invalid_sequence_index.py
new file mode 100644
index 0000000..b60e0b5
--- /dev/null
+++ b/test/input/func_invalid_sequence_index.py
@@ -0,0 +1,210 @@
+"""Errors for invalid sequence indices"""
+# pylint: disable=too-few-public-methods, no-self-use
+
+__revision__ = 0
+
+TESTLIST = [1, 2, 3]
+TESTTUPLE = (1, 2, 3)
+TESTSTR = '123'
+
+# getitem tests with bad indices
+def function1():
+ """list index is a function"""
+ return TESTLIST[id]
+
+def function2():
+ """list index is None"""
+ return TESTLIST[None]
+
+def function3():
+ """list index is a float expression"""
+ return TESTLIST[float(0)]
+
+def function4():
+ """list index is a str constant"""
+ return TESTLIST['0']
+
+def function5():
+ """list index does not implement __index__"""
+ class NonIndexType(object):
+ """Class without __index__ method"""
+ pass
+
+ return TESTLIST[NonIndexType()]
+
+def function6():
+ """Tuple index is None"""
+ return TESTTUPLE[None]
+
+def function7():
+ """String index is None"""
+ return TESTSTR[None]
+
+def function8():
+ """Index of subclass of tuple is None"""
+ class TupleTest(tuple):
+ """Subclass of tuple"""
+ pass
+ return TupleTest()[None]
+
+# getitem tests with good indices
+def function9():
+ """list index is an int constant"""
+ return TESTLIST[0] # no error
+
+def function10():
+ """list index is a integer expression"""
+ return TESTLIST[int(0.0)] # no error
+
+def function11():
+ """list index is a slice"""
+ return TESTLIST[slice(1, 2, 3)] # no error
+
+def function12():
+ """list index implements __index__"""
+ class IndexType(object):
+ """Class with __index__ method"""
+ def __index__(self):
+ """Allow objects of this class to be used as slice indices"""
+ return 0
+
+ return TESTLIST[IndexType()] # no error
+
+def function13():
+ """list index implements __index__ in a superclass"""
+ class IndexType(object):
+ """Class with __index__ method"""
+ def __index__(self):
+ """Allow objects of this class to be used as slice indices"""
+ return 0
+
+ class IndexSubType(IndexType):
+ """Class with __index__ in parent"""
+ pass
+
+ return TESTLIST[IndexSubType()] # no error
+
+def function14():
+ """Tuple index is an int constant"""
+ return TESTTUPLE[0]
+
+def function15():
+ """String index is an int constant"""
+ return TESTSTR[0]
+
+def function16():
+ """Index of subclass of tuple is an int constant"""
+ class TupleTest(tuple):
+ """Subclass of tuple"""
+ pass
+ return TupleTest()[0] # no error
+
+def function17():
+ """Index of subclass of tuple with custom __getitem__ is None"""
+ class TupleTest(tuple):
+ """Subclass of tuple with custom __getitem__"""
+ def __getitem__(self, index):
+ """Allow non-integer indices"""
+ return 0
+ return TupleTest()[None] # no error
+
+def function18():
+ """Index of subclass of tuple with __getitem__ in superclass is None"""
+ class TupleTest(tuple):
+ """Subclass of tuple with custom __getitem__"""
+ def __getitem__(self, index):
+ """Allow non-integer indices"""
+ return 0
+
+ class SubTupleTest(TupleTest):
+ """Subclass of a subclass of tuple"""
+ pass
+
+ return SubTupleTest()[None] # no error
+
+# Test with set and delete statements
+def function19():
+ """Set with None and integer indices"""
+ TESTLIST[None] = 0
+ TESTLIST[0] = 0 # no error
+
+def function20():
+ """Delete with None and integer indicies"""
+ del TESTLIST[None]
+ del TESTLIST[0] # no error
+
+def function21():
+ """Set and delete on a subclass of list"""
+ class ListTest(list):
+ """Inherit all list get/set/del handlers"""
+ pass
+ test = ListTest()
+
+ # Set and delete with invalid indices
+ test[None] = 0
+ del test[None]
+
+ # Set and delete with valid indices
+ test[0] = 0 # no error
+ del test[0] # no error
+
+def function22():
+ """Get, set, and delete on a subclass of list that overrides __setitem__"""
+ class ListTest(list):
+ """Override setitem but not get or del"""
+ def __setitem__(self, key, value):
+ pass
+ test = ListTest()
+
+ test[None][0] = 0 # failure on the getitem with None
+ del test[None]
+
+ test[0][0] = 0 # getitem with int and setitem with int, no error
+ test[None] = 0 # setitem overridden, no error
+ test[0] = 0 # setitem with int, no error
+ del test[0] # delitem with int, no error
+
+def function23():
+ """Get, set, and delete on a subclass of list that overrides __delitem__"""
+ class ListTest(list):
+ """Override delitem but not get or set"""
+ def __delitem__(self, key):
+ pass
+ test = ListTest()
+
+ test[None][0] = 0 # failure on the getitem with None
+ test[None] = 0 # setitem with invalid index
+
+ test[0][0] = 0 # getitem with int and setitem with int, no error
+ test[0] = 0 # setitem with int, no error
+ del test[None] # delitem overriden, no error
+ del test[0] # delitem with int, no error
+
+def function24():
+ """Get, set, and delete on a subclass of list that overrides __getitem__"""
+ class ListTest(list):
+ """Override gelitem but not del or set"""
+ def __getitem__(self, key):
+ pass
+ test = ListTest()
+
+ test[None] = 0 # setitem with invalid index
+ del test[None] # delitem with invalid index
+
+ test[None][0] = 0 # getitem overriden, no error
+ test[0][0] = 0 # getitem with int and setitem with int, no error
+ test[0] = 0 # setitem with int, no error
+ del test[0] # delitem with int, no error
+
+# Teest ExtSlice usage
+def function25():
+ """Extended slice used with a list"""
+ return TESTLIST[..., 0]
+
+def function26():
+ """Extended slice used with an object that implements __getitem__"""
+ class ExtSliceTest(object):
+ """Permit extslice syntax by implementing __getitem__"""
+ def __getitem__(self, index):
+ return 0
+ return ExtSliceTest[..., 0] # no error
diff --git a/test/input/func_invalid_slice_index.py b/test/input/func_invalid_slice_index.py
new file mode 100644
index 0000000..32f2f2d
--- /dev/null
+++ b/test/input/func_invalid_slice_index.py
@@ -0,0 +1,61 @@
+"""Errors for invalid slice indices"""
+# pylint: disable=too-few-public-methods, no-self-use
+
+__revision__ = 0
+
+TESTLIST = [1, 2, 3]
+
+# Invalid indices
+def function1():
+ """functions used as indices"""
+ return TESTLIST[id:id:]
+
+def function2():
+ """strings used as indices"""
+ return TESTLIST['0':'1':]
+
+def function3():
+ """class without __index__ used as index"""
+
+ class NoIndexTest(object):
+ """Class with no __index__ method"""
+ pass
+
+ return TESTLIST[NoIndexTest()::]
+
+# Valid indices
+def function4():
+ """integers used as indices"""
+ return TESTLIST[0:0:0] # no error
+
+def function5():
+ """None used as indices"""
+ return TESTLIST[None:None:None] # no error
+
+def function6():
+ """class with __index__ used as index"""
+ class IndexTest(object):
+ """Class with __index__ method"""
+ def __index__(self):
+ """Allow objects of this class to be used as slice indices"""
+ return 0
+
+ return TESTLIST[IndexTest():None:None] # no error
+
+def function7():
+ """class with __index__ in superclass used as index"""
+ class IndexType(object):
+ """Class with __index__ method"""
+ def __index__(self):
+ """Allow objects of this class to be used as slice indices"""
+ return 0
+
+ class IndexSubType(IndexType):
+ """Class with __index__ in parent"""
+ pass
+
+ return TESTLIST[IndexSubType():None:None] # no error
+
+def function8():
+ """slice object used as index"""
+ return TESTLIST[slice(1, 2, 3)] # no error
diff --git a/test/input/func_noerror_unbalanced_tuple_unpacking_py30.py b/test/input/func_noerror_unbalanced_tuple_unpacking_py30.py
new file mode 100644
index 0000000..68f5fb7
--- /dev/null
+++ b/test/input/func_noerror_unbalanced_tuple_unpacking_py30.py
@@ -0,0 +1,11 @@
+""" Test that using starred nodes in unpacking
+does not trigger a false positive on Python 3.
+"""
+
+__revision__ = 1
+
+def test():
+ """ Test that starred expressions don't give false positives. """
+ first, second, *last = (1, 2, 3, 4)
+ *last, = (1, 2)
+ return (first, second, last)
diff --git a/test/input/func_string_format_py27.py b/test/input/func_string_format_py27.py
new file mode 100644
index 0000000..d2f5599
--- /dev/null
+++ b/test/input/func_string_format_py27.py
@@ -0,0 +1,78 @@
+"""test for Python 3 string formatting error
+"""
+# pylint: disable=too-few-public-methods, import-error, unused-argument, star-args
+import os
+from missing import Missing
+
+__revision__ = 1
+
+class Custom(object):
+ """ Has a __getattr__ """
+ def __getattr__(self):
+ return self
+
+class Test(object):
+ """ test format attribute access """
+ custom = Custom()
+ ids = [1, 2, 3, [4, 5, 6]]
+
+class Getitem(object):
+ """ test custom getitem for lookup access """
+ def __getitem__(self, index):
+ return 42
+
+class ReturnYes(object):
+ """ can't be properly infered """
+ missing = Missing()
+
+def log(message, message_type="error"):
+ """ Test """
+ return message
+
+def print_good():
+ """ Good format strings """
+ print "{0} {1}".format(1, 2)
+ print "{0!r:20}".format("Hello")
+ print "{!r:20}".format("Hello")
+ print "{a!r:20}".format(a="Hello")
+ print "{pid}".format(pid=os.getpid())
+ print str("{}").format(2)
+ print "{0.missing.length}".format(ReturnYes())
+ print "{1.missing.length}".format(ReturnYes())
+ print "{a.ids[3][1]}".format(a=Test())
+ print "{a[0][0]}".format(a=[[1]])
+ print "{[0][0]}".format({0: {0: 1}})
+ print "{a.test}".format(a=Custom())
+ print "{a.__len__}".format(a=[])
+ print "{a.ids.__len__}".format(a=Test())
+ print "{a[0]}".format(a=Getitem())
+ print "{a[0][0]}".format(a=[Getitem()])
+ print "{[0][0]}".format(["test"])
+ # these are skipped
+ print "{0} {1}".format(*[1, 2])
+ print "{a} {b}".format(**{'a': 1, 'b': 2})
+ print "{a}".format(a=Missing())
+
+def pprint_bad():
+ """Test string format """
+ print "{{}}".format(1)
+ print "{} {".format()
+ print "{} }".format()
+ print "{0} {}".format(1, 2)
+ print "{a} {b}".format(a=1, c=2)
+ print "{} {a}".format(1, 2)
+ print "{} {}".format(1)
+ print "{} {}".format(1, 2, 3)
+ print "{a} {b} {c}".format()
+ print "{} {}".format(a=1, b=2)
+ print "{a} {b}".format(1, 2)
+ print "{0} {1} {a}".format(1, 2, 3)
+ print "{a.ids.__len__.length}".format(a=Test())
+ print "{a.ids[3][400]}".format(a=Test())
+ print "{a.ids[3]['string']}".format(a=Test())
+ print "{[0][1]}".format(["a"])
+ print "{[0][0]}".format(((1, )))
+ print "{b[0]}".format(a=23)
+ print "{a[0]}".format(a=object)
+ print log("{}".format(2, "info"))
+ print "{0.missing}".format(2)
diff --git a/test/input/func_typecheck_non_callable_call.py b/test/input/func_typecheck_non_callable_call.py
index 8d8a6c2..6d56b90 100644
--- a/test/input/func_typecheck_non_callable_call.py
+++ b/test/input/func_typecheck_non_callable_call.py
@@ -35,3 +35,39 @@ TUPLE = ()
INCORRECT = TUPLE()
INT = 1
INCORRECT = INT()
+
+# Test calling properties. Pylint can detect when using only the
+# getter, but it doesn't infer properly when having a getter
+# and a setter.
+class MyProperty(property):
+ """ test subclasses """
+
+class PropertyTest(object):
+ """ class """
+
+ def __init__(self):
+ self.attr = 4
+
+ @property
+ def test(self):
+ """ Get the attribute """
+ return self.attr
+
+ @test.setter
+ def test(self, value):
+ """ Set the attribute """
+ self.attr = value
+
+ @MyProperty
+ def custom(self):
+ """ Get the attribute """
+ return self.attr
+
+ @custom.setter
+ def custom(self, value):
+ """ Set the attribute """
+ self.attr = value
+
+PROP = PropertyTest()
+PROP.test(40)
+PROP.custom()
diff --git a/test/input/func_undefined_var.py b/test/input/func_undefined_var.py
index 407f3f6..fb5fc30 100644
--- a/test/input/func_undefined_var.py
+++ b/test/input/func_undefined_var.py
@@ -1,5 +1,5 @@
"""test access to undefined variables"""
-
+# pylint: disable=too-few-public-methods, no-init, no-self-use
__revision__ = '$Id:'
DEFINED = 1
@@ -83,3 +83,37 @@ def func1():
def func2():
"""A function with a decorator that contains a genexpr."""
pass
+
+# Test shared scope.
+
+def test_arguments(arg=TestClass):
+ """ TestClass isn't defined yet. """
+ return arg
+
+class TestClass(Ancestor):
+ """ contains another class, which uses an undefined ancestor. """
+
+ class MissingAncestor(Ancestor1):
+ """ no op """
+
+ def test1(self):
+ """ It should trigger here, because the two classes
+ have the same scope.
+ """
+ class UsingBeforeDefinition(Empty):
+ """ uses Empty before definition """
+ class Empty(object):
+ """ no op """
+ return UsingBeforeDefinition
+
+ def test(self):
+ """ Ancestor isn't defined yet, but we don't care. """
+ class MissingAncestor1(Ancestor):
+ """ no op """
+ return MissingAncestor1
+
+class Ancestor(object):
+ """ No op """
+
+class Ancestor1(object):
+ """ No op """
diff --git a/test/input/func_used_before_assignment_py30.py b/test/input/func_used_before_assignment_py30.py
index b5d0bf3..ae979a1 100644
--- a/test/input/func_used_before_assignment_py30.py
+++ b/test/input/func_used_before_assignment_py30.py
@@ -1,5 +1,5 @@
"""Check for nonlocal and used-before-assignment"""
-# pylint: disable=missing-docstring, unused-variable
+# pylint: disable=missing-docstring, unused-variable, no-init, too-few-public-methods
__revision__ = 0
@@ -26,3 +26,23 @@ def test_fail2():
nonlocal count
cnt = cnt + 1
wrap()
+
+def test_fail3(arg: test_fail4):
+ """ Depends on `test_fail4`, in argument annotation. """
+ return arg
+
+def test_fail4(*args: test_fail5, **kwargs: undefined):
+ """ Depends on `test_fail5` and `undefined` in
+ variable and named arguments annotations.
+ """
+ return args, kwargs
+
+def test_fail5()->undefined1:
+ """ Depends on `undefined1` in function return annotation. """
+
+def undefined():
+ """ no op """
+
+def undefined1():
+ """ no op """
+
diff --git a/test/input/func_w0612.py b/test/input/func_w0612.py
index 57e139c..e871bb2 100644
--- a/test/input/func_w0612.py
+++ b/test/input/func_w0612.py
@@ -1,7 +1,8 @@
"""test unused variable
"""
-
+# pylint: disable=invalid-name, redefined-outer-name
__revision__ = 0
+PATH = OS = collections = deque = None
def function(matches):
""""yo"""
@@ -20,3 +21,18 @@ def visit_if(self, node):
self.inc_branch(branches)
self.stmts += branches
+def test_global():
+ """ Test various assignments of global
+ variables through imports.
+ """
+ global PATH, OS, collections, deque
+ from os import path as PATH
+ import os as OS
+ import collections
+ from collections import deque
+ # make sure that these triggers unused-variable
+ from sys import platform
+ from sys import version as VERSION
+ import this
+ import re as RE
+
diff --git a/test/input/func_w0623_py_30.py b/test/input/func_w0623_py_30.py
index 9bccbc6..8f1f34c 100644
--- a/test/input/func_w0623_py_30.py
+++ b/test/input/func_w0623_py_30.py
@@ -1,5 +1,5 @@
"""Test for W0623, overwriting names in exception handlers."""
-
+# pylint: disable=broad-except,bare-except,pointless-except
__revision__ = ''
import exceptions
diff --git a/test/messages/func_assigning_non_slot.txt b/test/messages/func_assigning_non_slot.txt
new file mode 100644
index 0000000..5a06fc6
--- /dev/null
+++ b/test/messages/func_assigning_non_slot.txt
@@ -0,0 +1,3 @@
+E: 18:Bad.__init__: Assigning to attribute 'missing' not defined in class slots
+E: 26:Bad2.__init__: Assigning to attribute 'missing' not defined in class slots
+E: 36:Bad3.__init__: Assigning to attribute 'missing' not defined in class slots \ No newline at end of file
diff --git a/test/messages/func_defining-attr-methods_order.txt b/test/messages/func_defining-attr-methods_order.txt
index 3594754..5588319 100644
--- a/test/messages/func_defining-attr-methods_order.txt
+++ b/test/messages/func_defining-attr-methods_order.txt
@@ -1 +1,3 @@
W: 28:A.set_z: Attribute 'z' defined outside __init__
+W: 29:A.set_z: Attribute 'z' defined outside __init__
+W: 41:B.test: Attribute 'z' defined outside __init__
diff --git a/test/messages/func_e12xx.txt b/test/messages/func_e12xx.txt
index 690e6f4..d0a8b9c 100644
--- a/test/messages/func_e12xx.txt
+++ b/test/messages/func_e12xx.txt
@@ -2,5 +2,5 @@ E: 13:pprint: Too many arguments for logging format string
E: 14:pprint: Too many arguments for logging format string
E: 15:pprint: Logging format string ends in middle of conversion specifier
E: 16:pprint: Not enough arguments for logging format string
-E: 17:pprint: Unsupported logging format character 'a' (0x61) at index 3
+E: 17:pprint: Unsupported logging format character 'y' (0x79) at index 3
E: 18:pprint: Too many arguments for logging format string
diff --git a/test/messages/func_e13xx.txt b/test/messages/func_e13xx.txt
index c130949..f2d0d36 100644
--- a/test/messages/func_e13xx.txt
+++ b/test/messages/func_e13xx.txt
@@ -7,6 +7,7 @@ E: 17:pprint: Expected mapping for format string, not Tuple
E: 18:pprint: Expected mapping for format string, not List
E: 19:pprint: Unsupported format character 'z' (0x7a) at index 2
E: 20:pprint: Format string ends in middle of conversion specifier
+E: 21:pprint: Unsupported format character 'a' (0x61) at index 12
W: 15:pprint: Unused key 'PARG_3' in format string dictionary
W: 16:pprint: Format string dictionary key should be a string, not 2
diff --git a/test/messages/func_e13xx_py30.txt b/test/messages/func_e13xx_py30.txt
new file mode 100644
index 0000000..7ac9fb1
--- /dev/null
+++ b/test/messages/func_e13xx_py30.txt
@@ -0,0 +1,11 @@
+E: 11:pprint: Not enough arguments for format string
+E: 12:pprint: Too many arguments for format string
+E: 13:pprint: Mixing named and unnamed conversion specifiers in format string
+E: 14:pprint: Missing key 'PARG_2' in format string dictionary
+E: 16:pprint: Missing key 'PARG_2' in format string dictionary
+E: 17:pprint: Expected mapping for format string, not Tuple
+E: 18:pprint: Expected mapping for format string, not List
+E: 19:pprint: Unsupported format character 'z' (0x7a) at index 2
+E: 20:pprint: Format string ends in middle of conversion specifier
+W: 15:pprint: Unused key 'PARG_3' in format string dictionary
+W: 16:pprint: Format string dictionary key should be a string, not 2 \ No newline at end of file
diff --git a/test/messages/func_fixme.txt b/test/messages/func_fixme.txt
index 2544ce8..88199a7 100644
--- a/test/messages/func_fixme.txt
+++ b/test/messages/func_fixme.txt
@@ -1,2 +1,4 @@
W: 5: FIXME: beep
-W: 8: XXX:bop'''
+W: 11: FIXME: Valid test
+W: 13: TODO: Do something with the variables
+W: 14: XXX: Fix this later \ No newline at end of file
diff --git a/test/messages/func_invalid_sequence_index.txt b/test/messages/func_invalid_sequence_index.txt
new file mode 100644
index 0000000..db9edab
--- /dev/null
+++ b/test/messages/func_invalid_sequence_index.txt
@@ -0,0 +1,19 @@
+E: 13:function1: Sequence index is not an int, slice, or instance with __index__
+E: 17:function2: Sequence index is not an int, slice, or instance with __index__
+E: 21:function3: Sequence index is not an int, slice, or instance with __index__
+E: 25:function4: Sequence index is not an int, slice, or instance with __index__
+E: 33:function5: Sequence index is not an int, slice, or instance with __index__
+E: 37:function6: Sequence index is not an int, slice, or instance with __index__
+E: 41:function7: Sequence index is not an int, slice, or instance with __index__
+E: 48:function8: Sequence index is not an int, slice, or instance with __index__
+E:128:function19: Sequence index is not an int, slice, or instance with __index__
+E:133:function20: Sequence index is not an int, slice, or instance with __index__
+E:144:function21: Sequence index is not an int, slice, or instance with __index__
+E:145:function21: Sequence index is not an int, slice, or instance with __index__
+E:159:function22: Sequence index is not an int, slice, or instance with __index__
+E:160:function22: Sequence index is not an int, slice, or instance with __index__
+E:175:function23: Sequence index is not an int, slice, or instance with __index__
+E:176:function23: Sequence index is not an int, slice, or instance with __index__
+E:191:function24: Sequence index is not an int, slice, or instance with __index__
+E:192:function24: Sequence index is not an int, slice, or instance with __index__
+E:202:function25: Sequence index is not an int, slice, or instance with __index__
diff --git a/test/messages/func_invalid_slice_index.txt b/test/messages/func_invalid_slice_index.txt
new file mode 100644
index 0000000..d5b9e86
--- /dev/null
+++ b/test/messages/func_invalid_slice_index.txt
@@ -0,0 +1,5 @@
+E: 11:function1: Slice index is not an int, None, or instance with __index__
+E: 11:function1: Slice index is not an int, None, or instance with __index__
+E: 15:function2: Slice index is not an int, None, or instance with __index__
+E: 15:function2: Slice index is not an int, None, or instance with __index__
+E: 24:function3: Slice index is not an int, None, or instance with __index__
diff --git a/test/messages/func_string_format_py27.txt b/test/messages/func_string_format_py27.txt
new file mode 100644
index 0000000..56f8c41
--- /dev/null
+++ b/test/messages/func_string_format_py27.txt
@@ -0,0 +1,26 @@
+E: 58:pprint_bad: Too many arguments for format string
+E: 64:pprint_bad: Not enough arguments for format string
+E: 65:pprint_bad: Too many arguments for format string
+E: 67:pprint_bad: Not enough arguments for format string
+E: 77:pprint_bad: Too many arguments for format string
+W: 59:pprint_bad: Invalid format string
+W: 60:pprint_bad: Invalid format string
+W: 61:pprint_bad: Format string contains both automatic field numbering and manual field specification
+W: 62:pprint_bad: Missing keyword argument 'b' for format string
+W: 62:pprint_bad: Unused format argument 'c'
+W: 63:pprint_bad: Missing keyword argument 'a' for format string
+W: 66:pprint_bad: Missing keyword argument 'a' for format string
+W: 66:pprint_bad: Missing keyword argument 'b' for format string
+W: 66:pprint_bad: Missing keyword argument 'c' for format string
+W: 68:pprint_bad: Missing keyword argument 'a' for format string
+W: 68:pprint_bad: Missing keyword argument 'b' for format string
+W: 69:pprint_bad: Missing keyword argument 'a' for format string
+W: 70:pprint_bad: Missing format attribute 'length' in format specifier 'a.ids.__len__.length'
+W: 71:pprint_bad: Using invalid lookup key 400 in format specifier 'a.ids[3][400]'
+W: 72:pprint_bad: Using invalid lookup key "'string'" in format specifier 'a.ids[3]["\'string\'"]'
+W: 73:pprint_bad: Using invalid lookup key 1 in format specifier '0[0][1]'
+W: 74:pprint_bad: Using invalid lookup key 0 in format specifier '0[0][0]'
+W: 75:pprint_bad: Missing keyword argument 'b' for format string
+W: 75:pprint_bad: Unused format argument 'a'
+W: 76:pprint_bad: Using invalid lookup key 0 in format specifier 'a[0]'
+W: 78:pprint_bad: Missing format attribute 'missing' in format specifier '0.missing' \ No newline at end of file
diff --git a/test/messages/func_typecheck_non_callable_call.txt b/test/messages/func_typecheck_non_callable_call.txt
index 0218074..8baa237 100644
--- a/test/messages/func_typecheck_non_callable_call.txt
+++ b/test/messages/func_typecheck_non_callable_call.txt
@@ -4,3 +4,5 @@ E: 31: LIST is not callable
E: 33: DICT is not callable
E: 35: TUPLE is not callable
E: 37: INT is not callable
+E: 72: PROP.test is not callable
+E: 73: PROP.custom is not callable \ No newline at end of file
diff --git a/test/messages/func_undefined_var.txt b/test/messages/func_undefined_var.txt
index 25fb2c3..5505156 100644
--- a/test/messages/func_undefined_var.txt
+++ b/test/messages/func_undefined_var.txt
@@ -7,4 +7,8 @@ E: 27:bad_default: Undefined variable 'augvar'
E: 28:bad_default: Undefined variable 'vardel'
E: 56: Using variable 'PLOUF' before assignment
E: 65:if_branch_test: Using variable 'xxx' before assignment
+E: 89:test_arguments: Using variable 'TestClass' before assignment
+E: 93:TestClass: Using variable 'Ancestor' before assignment
+E: 96:TestClass.MissingAncestor: Using variable 'Ancestor1' before assignment
+E:103:TestClass.test1.UsingBeforeDefinition: Using variable 'Empty' before assignment
W: 27:bad_default: Unused variable 'augvar'
diff --git a/test/messages/func_used_before_assignment_py30.txt b/test/messages/func_used_before_assignment_py30.txt
index 5b6080f..8bb131d 100644
--- a/test/messages/func_used_before_assignment_py30.txt
+++ b/test/messages/func_used_before_assignment_py30.txt
@@ -1,2 +1,6 @@
E: 18:test_fail.wrap: Using variable 'cnt' before assignment
-E: 27:test_fail2.wrap: Using variable 'cnt' before assignment \ No newline at end of file
+E: 27:test_fail2.wrap: Using variable 'cnt' before assignment
+E: 30:test_fail3: Using variable 'test_fail4' before assignment
+E: 34:test_fail4: Using variable 'test_fail5' before assignment
+E: 34:test_fail4: Using variable 'undefined' before assignment
+E: 40:test_fail5: Using variable 'undefined1' before assignment \ No newline at end of file
diff --git a/test/messages/func_w0612.txt b/test/messages/func_w0612.txt
index f1647a3..c81b4f9 100644
--- a/test/messages/func_w0612.txt
+++ b/test/messages/func_w0612.txt
@@ -1,2 +1,6 @@
-W: 8:function: Unused variable 'aaaa'
-W: 9:function: Unused variable 'index'
+W: 9:function: Unused variable 'aaaa'
+W: 10:function: Unused variable 'index'
+W: 34:test_global: Unused variable 'platform'
+W: 35:test_global: Unused variable 'VERSION'
+W: 36:test_global: Unused variable 'this'
+W: 37:test_global: Unused variable 'RE'
diff --git a/test/messages/func_w0702.txt b/test/messages/func_w0702.txt
index 7e0ea84..d40a837 100644
--- a/test/messages/func_w0702.txt
+++ b/test/messages/func_w0702.txt
@@ -1 +1,2 @@
W: 9: No exception type(s) specified
+W: 16: Catching too general exception Exception
diff --git a/test/messages/func_w0705.txt b/test/messages/func_w0705.txt
index fbc200f..0393a88 100644
--- a/test/messages/func_w0705.txt
+++ b/test/messages/func_w0705.txt
@@ -3,3 +3,13 @@ E: 17: Bad except clauses order (LookupError is an ancestor class of IndexError)
E: 24: Bad except clauses order (LookupError is an ancestor class of IndexError)
E: 24: Bad except clauses order (NameError is an ancestor class of UnboundLocalError)
E: 27: Bad except clauses order (empty except clause should always appear last)
+W: 8: Catching too general exception Exception
+W: 29: No exception type(s) specified
+W: 30: Except doesn't do anything
+W: 31: Catching too general exception Exception
+W: 31: Except doesn't do anything
+W: 38: No exception type(s) specified
+W: 43: Catching too general exception Exception
+W: 43: Except doesn't do anything
+W: 45: No exception type(s) specified
+W: 46: Except doesn't do anything \ No newline at end of file
diff --git a/test/unittest_checker_variables.py b/test/unittest_checker_variables.py
index 30bc5e6..20a0d9e 100644
--- a/test/unittest_checker_variables.py
+++ b/test/unittest_checker_variables.py
@@ -4,7 +4,7 @@ import os
from astroid import test_utils
from pylint.checkers import variables
-from pylint.testutils import CheckerTestCase, linter
+from pylint.testutils import CheckerTestCase, linter, set_config
class VariablesCheckerTC(CheckerTestCase):
@@ -22,6 +22,19 @@ class VariablesCheckerTC(CheckerTestCase):
with self.assertNoMessages():
self.walk(module)
+ @set_config(ignored_modules=('argparse',))
+ def test_no_name_in_module_skipped(self):
+ """Make sure that 'from ... import ...' does not emit a
+ 'no-name-in-module' with a module that is configured
+ to be ignored.
+ """
+
+ node = test_utils.extract_node("""
+ from argparse import THIS_does_not_EXIST
+ """)
+ with self.assertNoMessages():
+ self.checker.visit_from(node)
+
class MissingSubmoduleTest(CheckerTestCase):
CHECKER_CLASS = variables.VariablesChecker
diff --git a/testutils.py b/testutils.py
index fb8170a..daf7477 100644
--- a/testutils.py
+++ b/testutils.py
@@ -152,6 +152,9 @@ class UnittestLinter(object):
self.stats[name] = value
return self.stats
+ @property
+ def options_providers(self):
+ return linter.options_providers
def set_config(**kwargs):
"""Decorator for setting config values on a checker."""
diff --git a/utils.py b/utils.py
index c6a7371..c2f1704 100644
--- a/utils.py
+++ b/utils.py
@@ -742,3 +742,23 @@ def register_plugins(linter, directory):
module.register(linter)
imported[base] = 1
+def get_global_option(checker, option, default=None):
+ """ Retrieve an option defined by the given *checker* or
+ by all known option providers.
+
+ It will look in the list of all options providers
+ until the given *option* will be found.
+ If the option wasn't found, the *default* value will be returned.
+ """
+ # First, try in the given checker's config.
+ # After that, look in the options providers.
+
+ try:
+ return getattr(checker.config, option.replace("-", "_"))
+ except AttributeError:
+ pass
+ for provider in checker.linter.options_providers:
+ for options in provider.options:
+ if options[0] == option:
+ return getattr(provider.config, option.replace("-", "_"))
+ return default