summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTORS.txt2
-rw-r--r--ChangeLog39
-rw-r--r--doc/ide-integration.rst68
-rw-r--r--doc/run.rst2
-rw-r--r--pylint/checkers/base.py13
-rw-r--r--pylint/checkers/classes.py49
-rw-r--r--pylint/checkers/similar.py2
-rw-r--r--pylint/checkers/spelling.py6
-rw-r--r--pylint/checkers/typecheck.py208
-rw-r--r--pylint/checkers/variables.py2
-rw-r--r--pylint/config.py3
-rw-r--r--pylint/lint.py16
-rw-r--r--pylint/reporters/__init__.py19
-rw-r--r--pylint/reporters/html.py6
-rw-r--r--pylint/reporters/ureports/__init__.py42
-rw-r--r--pylint/reporters/ureports/html_writer.py38
-rw-r--r--pylint/reporters/ureports/nodes.py82
-rw-r--r--pylint/reporters/ureports/text_writer.py58
-rw-r--r--pylint/reporters/ureports/tree.py235
-rw-r--r--pylint/test/functional/abstract_class_instantiated_in_class.py20
-rw-r--r--pylint/test/functional/arguments.py2
-rw-r--r--pylint/test/functional/iterable_context.py141
-rw-r--r--pylint/test/functional/iterable_context.txt10
-rw-r--r--pylint/test/functional/iterable_context_py2.py18
-rw-r--r--pylint/test/functional/iterable_context_py2.rc3
-rw-r--r--pylint/test/functional/iterable_context_py2.txt1
-rw-r--r--pylint/test/functional/iterable_context_py3.py18
-rw-r--r--pylint/test/functional/iterable_context_py3.rc3
-rw-r--r--pylint/test/functional/iterable_context_py3.txt1
-rw-r--r--pylint/test/functional/mapping_context.py59
-rw-r--r--pylint/test/functional/mapping_context.txt2
-rw-r--r--pylint/test/functional/mapping_context_py2.py19
-rw-r--r--pylint/test/functional/mapping_context_py2.rc3
-rw-r--r--pylint/test/functional/mapping_context_py2.txt1
-rw-r--r--pylint/test/functional/mapping_context_py3.py19
-rw-r--r--pylint/test/functional/mapping_context_py3.rc3
-rw-r--r--pylint/test/functional/mapping_context_py3.txt1
-rw-r--r--pylint/test/functional/member_checks.py6
-rw-r--r--pylint/test/functional/member_checks.txt1
-rw-r--r--pylint/test/functional/membership_protocol.py85
-rw-r--r--pylint/test/functional/membership_protocol.txt7
-rw-r--r--pylint/test/functional/membership_protocol_py2.py36
-rw-r--r--pylint/test/functional/membership_protocol_py2.rc3
-rw-r--r--pylint/test/functional/membership_protocol_py2.txt3
-rw-r--r--pylint/test/functional/membership_protocol_py3.py36
-rw-r--r--pylint/test/functional/membership_protocol_py3.rc3
-rw-r--r--pylint/test/functional/membership_protocol_py3.txt3
-rw-r--r--pylint/test/functional/no_self_use_py3.py12
-rw-r--r--pylint/test/functional/no_self_use_py3.rc2
-rw-r--r--pylint/test/functional/no_self_use_py3.txt1
-rw-r--r--pylint/test/functional/non_iterator_returned.py42
-rw-r--r--pylint/test/functional/non_iterator_returned.txt8
-rw-r--r--pylint/test/functional/unbalanced_tuple_unpacking.py7
-rw-r--r--pylint/test/functional/yield_from_iterable_py33.py7
-rw-r--r--pylint/test/functional/yield_from_iterable_py33.rc2
-rw-r--r--pylint/test/functional/yield_from_iterable_py33.txt1
-rw-r--r--pylint/test/regrtest_data/html_crash_420.py5
-rw-r--r--pylint/test/test_self.py11
-rw-r--r--pylint/test/unittest_checker_base.py1
-rw-r--r--pylint/test/unittest_checker_typecheck.py1
-rw-r--r--pylint/testutils.py2
-rw-r--r--pylint/utils.py11
62 files changed, 1056 insertions, 454 deletions
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 5b21f01..5409b8f 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -71,6 +71,6 @@ Order doesn't matter (not that much, at least ;)
* Stéphane Wirtel: nonlocal-without-binding
-* Dmitry Pribysh: multiple-imports.
+* Dmitry Pribysh: multiple-imports, not-iterable, not-a-mapping, various patches.
* Laura Medioni (Logilab): misplaced-comparison-constant
diff --git a/ChangeLog b/ChangeLog
index 964ab1a..62a973f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,6 +2,29 @@ ChangeLog for Pylint
--------------------
--
+
+ * Don't warn about abstract classes instantiated in their own
+ body. Closes issue #627.
+
+ * Obsolete options are not present by default in the generated
+ configuration file. Closes issue #632.
+
+ * non-iterator-returned can detect classes with iterator-metaclasses.
+ Closes issue #679.
+
+ * Add a new error, 'unsupported-membership-test', emitted when value
+ to the right of the 'in' operator doesn't support membership test
+ protocol (i.e. doesn't define __contains__/__iter__/__getitem__)
+
+ * Add new errors, 'not-an-iterable', emitted when non-iterable value
+ is used in an iterating context (starargs, for-statement,
+ comprehensions, etc), and 'not-a-mapping', emitted when non-mapping
+ value is used in a mapping context. Closes issue #563.
+
+ * Make 'no-self-use' checker not emit a warning if there is a 'super()'
+ call inside the method.
+ Closes issue #667.
+
* Add checker to identify multiple imports on one line.
Closes issue #598.
@@ -70,7 +93,7 @@ ChangeLog for Pylint
* When checking for invalid arguments to a callable, in typecheck.py,
look up for the __init__ in case the found __new__ comes from builtins.
-
+
Since the __new__ comes from builtins, it will not have attached any
information regarding what parameters it expects, so the check
will be useless. Retrieving __init__ in that case will at least
@@ -79,7 +102,7 @@ ChangeLog for Pylint
* Don't emit no-member for classes with unknown bases.
Since we don't know what those bases might add, we simply ignore
- the error in this case.
+ the error in this case.
* Lookup in the implicit metaclass when checking for no-member,
if the class in question has an implicit metaclass, which is
@@ -143,7 +166,7 @@ ChangeLog for Pylint
* Don't emit undefined-variable if the node is guarded by a NameError, Exception
or bare except clause.
-
+
* Add a new warning, 'using-constant-test', which is emitted when a conditional
statement (If, IfExp) uses a test which is always constant, such as numbers,
classes, functions etc. This is most likely an error from the user's part.
@@ -181,11 +204,11 @@ ChangeLog for Pylint
* yield-outside-func is also emitted for `yield from`.
* Add a new error, 'too-many-star-expressions', emitted when
- there are more than one starred expression (*x) in an assignment.
+ there are more than one starred expression (`*x`) in an assignment.
The warning is emitted only on Python 3.
* Add a new error, 'invalid-star-assignment-target', emitted when
- a starred expression (*x) is used as the lhs side of an assignment,
+ a starred expression (`*x`) is used as the lhs side of an assignment,
as in `*x = [1, 2]`. This is not a SyntaxError on Python 3 though.
* Detect a couple of objects which can't be base classes (bool,
@@ -197,7 +220,7 @@ ChangeLog for Pylint
SyntaxWarning on Python 2.
* Add a new error, 'star-needs-assignment-target', emitted on Python 3 when
- a Starred expression (*x) is not used in an assignment target. This is not
+ a Starred expression (`*x`) is not used in an assignment target. This is not
caught when parsing the AST on Python 3, so it needs to be a separate check.
* Add a new error, 'unsupported-binary-operation', emitted when
@@ -322,7 +345,7 @@ ChangeLog for Pylint
an async context manager block is used with an object which doesn't
support this protocol (PEP 492).
- * Add a new convention warning, 'singleton-comparison', emitted when
+ * Add a new convention warning, 'singleton-comparison', emitted when
comparison to True, False or None is found.
* Don't emit 'assigning-non-slot' for descriptors. Closes issue #652.
@@ -352,7 +375,7 @@ ChangeLog for Pylint
depth exceeded error, due to its visitor architecture. The peephole
just transforms such calls, if it can, into the final resulting string
and this exhibit a problem, because the visit_binop method stops being
- called (in the optimized AST it will be a Const node).
+ called (in the optimized AST it will be a Const node).
2015-03-11 -- 1.4.2
diff --git a/doc/ide-integration.rst b/doc/ide-integration.rst
index 30b48e5..ed0c1be 100644
--- a/doc/ide-integration.rst
+++ b/doc/ide-integration.rst
@@ -1,7 +1,7 @@
-=================
- IDE integration
-=================
+============================
+ Editor and IDE integration
+============================
To use Pylint with:
@@ -12,6 +12,7 @@ To use Pylint with:
- gedit_, see https://launchpad.net/gedit-pylint-2 or https://wiki.gnome.org/Apps/Gedit/PylintPlugin,
- WingIDE_, see http://www.wingware.com/doc/edit/pylint,
- PyCharm_, see http://blog.saturnlaboratories.co.za/archive/2012/09/10/running-pylint-pycharm.
+ - TextMate_,
Pylint is integrated in:
@@ -30,6 +31,7 @@ Pylint is integrated in:
.. _WingIDE: http://www.wingware.com/
.. _spyder: http://code.google.com/p/spyderlib/
.. _PyCharm: http://www.jetbrains.com/pycharm/
+.. _TextMate: http://macromates.com
Using Pylint thru flymake in Emacs
==================================
@@ -53,8 +55,8 @@ To enable flymake for Python, insert the following into your .emacs:
;; Set as a minor mode for Python
(add-hook 'python-mode-hook '(lambda () (flymake-mode)))
-Above stuff is in pylint/elisp/pylint-flymake.el, which should be automatically
-installed on Debian systems, in which cases you don't have to put it in your .emacs file.
+Above stuff is in ``pylint/elisp/pylint-flymake.el``, which should be automatically
+installed on Debian systems, in which cases you don't have to put it in your ``.emacs`` file.
Other things you may find useful to set:
@@ -108,3 +110,59 @@ Setup the MS Visual Studio .NET 2003 editor to call Pylint
.. image:: _static/vs2003_config.jpeg
The output of Pylint is then shown in the "Output" pane of the editor.
+
+
+.. _pylint_in_textmate:
+
+Integrate Pylint with TextMate
+==============================
+
+Install Pylint in the usual way::
+
+ pip install pylint
+
+Install the `Python bundle for TextMate <https://github.com/textmate/python.tmbundle>`_:
+
+#. select *TextMate* > *Preferences*
+#. select the *Bundles* tab
+#. find and tick the *Python* bundle in the list
+
+You should now see it in *Bundles* > *Python*.
+
+In *Preferences*, select the *Variables* tab. If a ``TM_PYCHECKER`` variable is not already listed, add
+it, with the value ``pylint``.
+
+The default keyboard shortcut to run the syntax checker is *Control-Shift-V* - open a ``.py`` file
+in Textmate, and try it.
+
+You should see the output in a new window:
+
+ PyCheckMate 1.2 – Pylint 1.4.4
+
+ No config file found, using default configuration
+
+Then all is well, and most likely Pylint will have expressed some opinions about your Python code
+(or will exit with ``0`` if your code already conforms to its expectations).
+
+If you receive a message:
+
+ Please install PyChecker, PyFlakes, Pylint, PEP 8 or flake8 for more extensive code checking.
+
+That means that Pylint wasn't found, which is likely an issue with command paths - TextMate needs
+be looking for Pylint on the right paths.
+
+Check where Pylint has been installed, using ``which``::
+
+ $ which pylint
+ /usr/local/bin/pylint
+
+The output will tell you where Pylint can be found; in this case, in ``/usr/local/bin``.
+
+#. select *TextMate* > *Preferences*
+#. select the *Variables* tab
+#. find and check that a ``PATH`` variable exists, and that it contains the appropriate path (if
+ the path to Pylint were ``/usr/local/bin/pylint`` as above, then the variable would need to
+ contain ``/usr/local/bin``). An actual example in this case might be
+ ``$PATH:/opt/local/bin:/usr/local/bin:/usr/texbin``, which includes other paths.
+
+... and try running Pylint again.
diff --git a/doc/run.rst b/doc/run.rst
index 68bc8fd..42f8831 100644
--- a/doc/run.rst
+++ b/doc/run.rst
@@ -95,7 +95,7 @@ command line using the ``--rcfile`` option. Otherwise, Pylint searches for a
configuration file in the following order and uses the first one it finds:
#. ``pylintrc`` in the current working directory
-# ``.pylintrc`` in the current working directory
+#. ``.pylintrc`` in the current working directory
#. If the current working directory is in a Python module, Pylint searches \
up the hierarchy of Python modules until it finds a ``pylintrc`` file. \
This allows you to specify coding standards on a module-by-module \
diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py
index ac86d44..234737e 100644
--- a/pylint/checkers/base.py
+++ b/pylint/checkers/base.py
@@ -41,6 +41,7 @@ from pylint.checkers.utils import (
is_inside_except,
overrides_a_method,
get_argument_from_call,
+ node_frame_class,
NoSuchArgumentError,
error_of_type,
unimplemented_abstract_methods,
@@ -309,7 +310,7 @@ class BasicErrorChecker(_BasicChecker):
'E0112': ('More than one starred expression in assignment',
'too-many-star-expressions',
'Emitted when there are more than one starred '
- 'expressions (*x) in an assignment. This is a SyntaxError.',
+ 'expressions (`*x`) in an assignment. This is a SyntaxError.',
{'minversion': (3, 0)}),
'E0113': ('Starred assignment target must be in a list or tuple',
'invalid-star-assignment-target',
@@ -497,8 +498,18 @@ class BasicErrorChecker(_BasicChecker):
infered = next(node.func.infer())
except astroid.InferenceError:
return
+
if not isinstance(infered, astroid.ClassDef):
return
+
+ klass = node_frame_class(node)
+ if klass is infered:
+ # Don't emit the warning if the class is instantiated
+ # in its own body or if the call is not an instance
+ # creation. If the class is instantiated into its own
+ # body, we're expecting that it knows what it is doing.
+ return
+
# __init__ was called
metaclass = infered.metaclass()
abstract_methods = _has_abstract_methods(infered)
diff --git a/pylint/checkers/classes.py b/pylint/checkers/classes.py
index 0fc0fae..86ccb19 100644
--- a/pylint/checkers/classes.py
+++ b/pylint/checkers/classes.py
@@ -140,6 +140,14 @@ def _is_attribute_property(name, klass):
return True
return False
+def _has_bare_super_call(fundef_node):
+ for call in fundef_node.nodes_of_class(astroid.Call):
+ func = call.func
+ if (isinstance(func, astroid.Name) and
+ func.name == 'super' and
+ not call.args):
+ return True
+ return False
MSGS = {
'F0202': ('Unable to check methods signature (%s / %s)',
@@ -545,7 +553,8 @@ a metaclass class method.'}
and not node.name in PYMETHODS
and not (node.is_abstract() or
overrides_a_method(class_node, node.name) or
- decorated_with_property(node))):
+ decorated_with_property(node) or
+ (six.PY3 and _has_bare_super_call(node)))):
self.add_message('no-self-use', node=node)
def visit_attribute(self, node):
@@ -997,23 +1006,39 @@ class SpecialMethodsChecker(BaseChecker):
args=(node.name, expected_params, current_params, verb),
node=node)
+ @staticmethod
+ def _is_iterator(node):
+ if node is astroid.YES:
+ # Just ignore YES objects.
+ return True
+ if isinstance(node, Generator):
+ # Generators can be itered.
+ return True
+
+ if isinstance(node, astroid.Instance):
+ try:
+ node.local_attr(NEXT_METHOD)
+ return True
+ except astroid.NotFoundError:
+ pass
+ elif isinstance(node, astroid.ClassDef):
+ metaclass = node.metaclass()
+ if metaclass and isinstance(metaclass, astroid.ClassDef):
+ try:
+ metaclass.local_attr(NEXT_METHOD)
+ return True
+ except astroid.NotFoundError:
+ pass
+ return False
+
def _check_iter(self, node):
try:
infered = node.infer_call_result(node)
except astroid.InferenceError:
return
- for infered_node in infered:
- if (infered_node is astroid.YES
- or isinstance(infered_node, Generator)):
- continue
- if isinstance(infered_node, astroid.Instance):
- try:
- infered_node.local_attr(NEXT_METHOD)
- except astroid.NotFoundError:
- self.add_message('non-iterator-returned',
- node=node)
- break
+ if not all(map(self._is_iterator, infered)):
+ self.add_message('non-iterator-returned', node=node)
def _ancestors_to_call(klass_node, method='__init__'):
diff --git a/pylint/checkers/similar.py b/pylint/checkers/similar.py
index e500d2c..859a04b 100644
--- a/pylint/checkers/similar.py
+++ b/pylint/checkers/similar.py
@@ -305,7 +305,7 @@ class SimilarChecker(BaseChecker, Similar):
def close(self):
"""compute and display similarities on closing (i.e. end of parsing)"""
- total = sum([len(lineset) for lineset in self.linesets])
+ total = sum(len(lineset) for lineset in self.linesets)
duplicated = 0
stats = self.stats
for num, couples in self._compute_sims():
diff --git a/pylint/checkers/spelling.py b/pylint/checkers/spelling.py
index a5b7857..725f25a 100644
--- a/pylint/checkers/spelling.py
+++ b/pylint/checkers/spelling.py
@@ -21,6 +21,8 @@ import tokenize
import string
import re
+import six
+
if sys.version_info[0] >= 3:
maketrans = str.maketrans
else:
@@ -244,6 +246,10 @@ class SpellingChecker(BaseTokenChecker):
return
start_line = node.lineno + 1
+ if six.PY2:
+ encoding = node.root().file_encoding
+ docstring = docstring.decode(encoding or sys.getdefaultencoding(),
+ 'replace')
# Go through lines of docstring
for idx, line in enumerate(docstring.splitlines()):
diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py
index 66ac05b..e9fddbd 100644
--- a/pylint/checkers/typecheck.py
+++ b/pylint/checkers/typecheck.py
@@ -44,6 +44,11 @@ _ZOPE_DEPRECATED = (
)
BUILTINS = six.moves.builtins.__name__
STR_FORMAT = "%s.str.format" % BUILTINS
+ITER_METHOD = '__iter__'
+NEXT_METHOD = 'next' if six.PY2 else '__next__'
+GETITEM_METHOD = '__getitem__'
+CONTAINS_METHOD = '__contains__'
+KEYS_METHOD = 'keys'
def _unflatten(iterable):
@@ -84,6 +89,47 @@ def _is_owner_ignored(owner, name, ignored_classes, ignored_modules):
return any(name == ignore or qname == ignore for ignore in ignored_classes)
+def _hasattr(value, attr):
+ try:
+ value.getattr(attr)
+ return True
+ except astroid.NotFoundError:
+ return False
+
+def _is_comprehension(node):
+ comprehensions = (astroid.ListComp,
+ astroid.SetComp,
+ astroid.DictComp)
+ return isinstance(node, comprehensions)
+
+
+def _is_iterable(value):
+ # '__iter__' is for standard iterables
+ # '__getitem__' is for strings and other old-style iterables
+ return _hasattr(value, ITER_METHOD) or _hasattr(value, GETITEM_METHOD)
+
+
+def _is_iterator(value):
+ return _hasattr(value, NEXT_METHOD) and _hasattr(value, ITER_METHOD)
+
+
+def _is_mapping(value):
+ return _hasattr(value, GETITEM_METHOD) and _hasattr(value, KEYS_METHOD)
+
+def _supports_membership_test(value):
+ return _hasattr(value, CONTAINS_METHOD)
+
+
+def _is_inside_mixin_declaration(node):
+ while node is not None:
+ if isinstance(node, astroid.ClassDef):
+ name = getattr(node, 'name', None)
+ if name is not None and name.lower().endswith("mixin"):
+ return True
+ node = node.parent
+ return False
+
+
MSGS = {
'E1101': ('%s %r has no %r member',
'no-member',
@@ -147,6 +193,10 @@ MSGS = {
'E1132': ('Got multiple values for keyword argument %r in function call',
'repeated-keyword',
'Emitted when a function call got multiple values for a keyword.'),
+ 'E1135': ("Value '%s' doesn't support membership test",
+ 'unsupported-membership-test',
+ 'Emitted when an instance in membership test expression doesn\'t'
+ 'implement membership protocol (__contains__/__iter__/__getitem__)'),
}
# builtin sequence types in Python 2 and 3.
@@ -790,7 +840,165 @@ accessed. Python regular expressions are accepted.'}
self.add_message('unsupported-binary-operation',
args=str(error), node=node)
+ def _check_membership_test(self, node):
+ # instance supports membership test in either of those cases:
+ # 1. instance defines __contains__ method
+ # 2. instance is iterable (defines __iter__ or __getitem__)
+ if _is_comprehension(node) or _is_inside_mixin_declaration(node):
+ return
+
+ infered = helpers.safe_infer(node)
+ if infered is None or infered is astroid.YES:
+ return
+
+ # classes can be iterables/containers too
+ if isinstance(infered, astroid.ClassDef):
+ if not helpers.has_known_bases(infered):
+ return
+ meta = infered.metaclass()
+ if meta is not None:
+ if _supports_membership_test(meta):
+ return
+ if _is_iterable(meta):
+ return
+
+ if isinstance(infered, astroid.Instance):
+ if not helpers.has_known_bases(infered):
+ return
+ if _supports_membership_test(infered) or _is_iterable(infered):
+ return
+
+ self.add_message('unsupported-membership-test',
+ args=node.as_string(),
+ node=node)
+
+ @check_messages('unsupported-membership-test')
+ def visit_compare(self, node):
+ if len(node.ops) != 1:
+ return
+ operator, right = node.ops[0]
+ if operator in ['in', 'not in']:
+ self._check_membership_test(right)
+
+
+class IterableChecker(BaseChecker):
+ """
+ Checks for non-iterables used in an iterable context.
+ Contexts include:
+ - for-statement
+ - starargs in function call
+ - `yield from`-statement
+ - list, dict and set comprehensions
+ - generator expressions
+ Also checks for non-mappings in function call kwargs.
+ """
+
+ __implements__ = (IAstroidChecker,)
+ name = 'iterable_check'
+
+ msgs = {'E1133': ('Non-iterable value %s is used in an iterating context',
+ 'not-an-iterable',
+ 'Used when a non-iterable value is used in place where'
+ 'iterable is expected'),
+ 'E1134': ('Non-mapping value %s is used in a mapping context',
+ 'not-a-mapping',
+ 'Used when a non-mapping value is used in place where'
+ 'mapping is expected'),
+ }
+
+ def _check_iterable(self, node, root_node):
+ # for/set/dict-comprehensions can't be infered with astroid
+ # so we have to check for them explicitly
+ if _is_comprehension(node) or _is_inside_mixin_declaration(node):
+ return
+
+ infered = helpers.safe_infer(node)
+ if infered is None or infered is astroid.YES:
+ return
+
+ if isinstance(infered, astroid.ClassDef):
+ if not helpers.has_known_bases(infered):
+ return
+ # classobj can only be iterable if it has an iterable metaclass
+ meta = infered.metaclass()
+ if meta is not None:
+ if _is_iterable(meta):
+ return
+ if _is_iterator(meta):
+ return
+
+ if isinstance(infered, astroid.Instance):
+ if not helpers.has_known_bases(infered):
+ return
+ if _is_iterable(infered) or _is_iterator(infered):
+ return
+
+ self.add_message('not-an-iterable',
+ args=node.as_string(),
+ node=root_node)
+
+ def _check_mapping(self, node, root_node):
+ if isinstance(node, astroid.DictComp) or _is_inside_mixin_declaration(node):
+ return
+
+ infered = helpers.safe_infer(node)
+ if infered is None or infered is astroid.YES:
+ return
+
+ if isinstance(infered, astroid.ClassDef):
+ if not helpers.has_known_bases(infered):
+ return
+ meta = infered.metaclass()
+ if meta is not None and _is_mapping(meta):
+ return
+
+ if isinstance(infered, astroid.Instance):
+ if not helpers.has_known_bases(infered):
+ return
+ if _is_mapping(infered):
+ return
+
+ self.add_message('not-a-mapping',
+ args=node.as_string(),
+ node=root_node)
+
+ @check_messages('not-an-iterable')
+ def visit_for(self, node):
+ self._check_iterable(node.iter, node)
+
+ @check_messages('not-an-iterable')
+ def visit_yieldfrom(self, node):
+ self._check_iterable(node.value, node)
+
+ @check_messages('not-an-iterable', 'not-a-mapping')
+ def visit_call(self, node):
+ for stararg in node.starargs:
+ self._check_iterable(stararg.value, node)
+ for kwarg in node.kwargs:
+ self._check_mapping(kwarg.value, node)
+
+ @check_messages('not-an-iterable')
+ def visit_listcomp(self, node):
+ for gen in node.generators:
+ self._check_iterable(gen.iter, node)
+
+ @check_messages('not-an-iterable')
+ def visit_dictcomp(self, node):
+ for gen in node.generators:
+ self._check_iterable(gen.iter, node)
+
+ @check_messages('not-an-iterable')
+ def visit_setcomp(self, node):
+ for gen in node.generators:
+ self._check_iterable(gen.iter, node)
+
+ @check_messages('not-an-iterable')
+ def visit_generatorexp(self, node):
+ for gen in node.generators:
+ self._check_iterable(gen.iter, node)
+
def register(linter):
"""required method to auto register this checker """
linter.register_checker(TypeChecker(linter))
+ linter.register_checker(IterableChecker(linter))
diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py
index 2ce12e8..79f6d05 100644
--- a/pylint/checkers/variables.py
+++ b/pylint/checkers/variables.py
@@ -248,7 +248,7 @@ MSGS = {
'W0614': ('Unused import %s from wildcard import',
'unused-wildcard-import',
'Used when an imported module or variable is not used from a \
- \'from X import *\' style import.'),
+ `\'from X import *\'` style import.'),
'W0621': ('Redefining name %r from outer scope (line %s)',
'redefined-outer-name',
diff --git a/pylint/config.py b/pylint/config.py
index fb5c2da..490f932 100644
--- a/pylint/config.py
+++ b/pylint/config.py
@@ -554,7 +554,8 @@ class OptionsManagerMixIn(object):
if section in skipsections:
continue
options = [(n, d, v) for (n, d, v) in options
- if d.get('type') is not None]
+ if d.get('type') is not None
+ and not d.get('deprecated')]
if not options:
continue
if section not in sections:
diff --git a/pylint/lint.py b/pylint/lint.py
index 698f3e6..d1e39b2 100644
--- a/pylint/lint.py
+++ b/pylint/lint.py
@@ -736,19 +736,25 @@ class PyLinter(config.OptionsManagerMixIn,
with _patch_sysmodules():
self._parallel_check(files_or_modules)
-
- def _parallel_task(self, files_or_modules):
- # Prepare configuration for child linters.
- filter_options = {'symbols', 'include-ids', 'long-help'}
- filter_options.update([opt_name for opt_name, _ in self._external_opts])
+ def _get_jobs_config(self):
child_config = {}
+ filter_options = {'symbols', 'include-ids', 'long-help'}
+ filter_options.update((opt_name for opt_name, _ in self._external_opts))
for opt_providers in six.itervalues(self._all_options):
for optname, optdict, val in opt_providers.options_and_values():
+ if optdict.get('deprecated'):
+ continue
+
if optname not in filter_options:
child_config[optname] = utils._format_option_value(
optdict, val)
child_config['python3_porting_mode'] = self._python3_porting_mode
child_config['plugins'] = self._dynamic_plugins
+ return child_config
+
+ def _parallel_task(self, files_or_modules):
+ # Prepare configuration for child linters.
+ child_config = self._get_jobs_config()
children = []
manager = multiprocessing.Manager()
diff --git a/pylint/reporters/__init__.py b/pylint/reporters/__init__.py
index 5c4437a..664c62a 100644
--- a/pylint/reporters/__init__.py
+++ b/pylint/reporters/__init__.py
@@ -47,12 +47,9 @@ class BaseReporter(object):
def __init__(self, output=None):
self.linter = None
- # self.include_ids = None # Deprecated
- # self.symbols = None # Deprecated
self.section = 0
self.out = None
self.out_encoding = None
- self.encode = None
self.set_output(output)
# Build the path prefix to strip to get relative paths
self.path_strip_prefix = os.getcwd() + os.sep
@@ -75,12 +72,11 @@ class BaseReporter(object):
def set_output(self, output=None):
"""set output stream"""
self.out = output or sys.stdout
- # py3k streams handle their encoding :
- if sys.version_info >= (3, 0):
- self.encode = lambda x: x
- return
- def encode(string):
+ if six.PY3:
+ encode = lambda self, string: string
+ else:
+ def encode(self, string):
if not isinstance(string, six.text_type):
return string
encoding = (getattr(self.out, 'encoding', None) or
@@ -90,7 +86,6 @@ class BaseReporter(object):
# source code line that can't be encoded with the current locale
# settings
return string.encode(encoding, 'replace')
- self.encode = encode
def writeln(self, string=''):
"""write a line in the output buffer"""
@@ -110,12 +105,10 @@ class BaseReporter(object):
# Event callbacks
def on_set_current_module(self, module, filepath):
- """starting analyzis of a module"""
- pass
+ """Hook called when a module starts to be analysed."""
def on_close(self, stats, previous_stats):
- """global end of analyzis"""
- pass
+ """Hook called when a module finished analyzing."""
class CollectingReporter(BaseReporter):
diff --git a/pylint/reporters/html.py b/pylint/reporters/html.py
index b2214b1..b06ee16 100644
--- a/pylint/reporters/html.py
+++ b/pylint/reporters/html.py
@@ -17,6 +17,8 @@ import itertools
import string
import sys
+import six
+
from pylint.interfaces import IReporter
from pylint.reporters import BaseReporter
from pylint.reporters.ureports.html_writer import HTMLWriter
@@ -67,7 +69,9 @@ class HTMLReporter(BaseReporter):
self._parse_template()
# We want to add the lines given by the template
- self.msgs += [str(getattr(msg, field)) for field in self.msgargs]
+ values = [getattr(msg, field) for field in self.msgargs]
+ self.msgs += [value if isinstance(value, six.text_type) else str(value)
+ for value in values]
def set_output(self, output=None):
"""set output stream
diff --git a/pylint/reporters/ureports/__init__.py b/pylint/reporters/ureports/__init__.py
index 0da4051..02322db 100644
--- a/pylint/reporters/ureports/__init__.py
+++ b/pylint/reporters/ureports/__init__.py
@@ -26,8 +26,6 @@ import sys
import six
-# pylint: disable=method-hidden; Weird API in compute_content.
-
class BaseWriter(object):
"""base class for ureport writers"""
@@ -43,7 +41,6 @@ class BaseWriter(object):
if not encoding:
encoding = getattr(stream, 'encoding', 'UTF-8')
self.encoding = encoding or 'UTF-8'
- self.__compute_funcs = []
self.out = stream
self.begin_format()
layout.accept(self)
@@ -62,10 +59,7 @@ class BaseWriter(object):
def write(self, string):
"""write a string in the output buffer"""
- try:
- self.out.write(string)
- except UnicodeEncodeError:
- self.out.write(string.encode(self.encoding))
+ self.out.write(string)
def begin_format(self):
"""begin to format a layout"""
@@ -98,27 +92,15 @@ class BaseWriter(object):
return an iterator on strings (one for each child element)
"""
- # use cells !
- def write(data):
- try:
- stream.write(data)
- except UnicodeEncodeError:
- stream.write(data.encode(self.encoding))
- def writeln(data=u''):
- try:
- stream.write(data + os.linesep)
- except UnicodeEncodeError:
- stream.write(data.encode(self.encoding) + os.linesep)
- self.write = write
- self.writeln = writeln
- self.__compute_funcs.append((write, writeln))
- for child in layout.children:
- stream = six.StringIO()
- child.accept(self)
- yield stream.getvalue()
- self.__compute_funcs.pop()
+ # Patch the underlying output stream with a fresh-generated stream,
+ # which is used to store a temporary representation of a child
+ # node.
+ out = self.out
try:
- self.write, self.writeln = self.__compute_funcs[-1]
- except IndexError:
- del self.write
- del self.writeln
+ for child in layout.children:
+ stream = six.StringIO()
+ self.out = stream
+ child.accept(self)
+ yield stream.getvalue()
+ finally:
+ self.out = out
diff --git a/pylint/reporters/ureports/html_writer.py b/pylint/reporters/ureports/html_writer.py
index 005ac62..c5f74d3 100644
--- a/pylint/reporters/ureports/html_writer.py
+++ b/pylint/reporters/ureports/html_writer.py
@@ -27,18 +27,6 @@ class HTMLWriter(BaseWriter):
super(HTMLWriter, self).__init__()
self.snippet = snippet
- @staticmethod
- def handle_attrs(layout):
- """get an attribute string from layout member attributes"""
- attrs = u''
- klass = getattr(layout, 'klass', None)
- if klass:
- attrs += u' class="%s"' % klass
- nid = getattr(layout, 'id', None)
- if nid:
- attrs += u' id="%s"' % nid
- return attrs
-
def begin_format(self):
"""begin to format a layout"""
super(HTMLWriter, self).begin_format()
@@ -55,20 +43,20 @@ class HTMLWriter(BaseWriter):
def visit_section(self, layout):
"""display a section as html, using div + h[section level]"""
self.section += 1
- self.writeln(u'<div%s>' % self.handle_attrs(layout))
+ self.writeln(u'<div>')
self.format_children(layout)
self.writeln(u'</div>')
self.section -= 1
def visit_title(self, layout):
"""display a title using <hX>"""
- self.write(u'<h%s%s>' % (self.section, self.handle_attrs(layout)))
+ self.write(u'<h%s>' % self.section)
self.format_children(layout)
self.writeln(u'</h%s>' % self.section)
def visit_table(self, layout):
"""display a table as html"""
- self.writeln(u'<table%s>' % self.handle_attrs(layout))
+ self.writeln(u'<table>')
table_content = self.get_table_content(layout)
for i, row in enumerate(table_content):
if i == 0 and layout.rheaders:
@@ -76,7 +64,7 @@ class HTMLWriter(BaseWriter):
elif i+1 == len(table_content) and layout.rrheaders:
self.writeln(u'<tr class="header">')
else:
- self.writeln(u'<tr class="%s">' % (i%2 and 'even' or 'odd'))
+ self.writeln(u'<tr class="%s">' % (u'even' if i % 2 else u'odd'))
for j, cell in enumerate(row):
cell = cell or u'&#160;'
if (layout.rheaders and i == 0) or \
@@ -89,30 +77,12 @@ class HTMLWriter(BaseWriter):
self.writeln(u'</tr>')
self.writeln(u'</table>')
- def visit_list(self, layout):
- """display a list as html"""
- self.writeln(u'<ul%s>' % self.handle_attrs(layout))
- for row in list(self.compute_content(layout)):
- self.writeln(u'<li>%s</li>' % row)
- self.writeln(u'</ul>')
-
def visit_paragraph(self, layout):
"""display links (using <p>)"""
self.write(u'<p>')
self.format_children(layout)
self.write(u'</p>')
- def visit_span(self, layout):
- """display links (using <p>)"""
- self.write(u'<span%s>' % self.handle_attrs(layout))
- self.format_children(layout)
- self.write(u'</span>')
-
- def visit_link(self, layout):
- """display links (using <a>)"""
- self.write(u' <a href="%s"%s>%s</a>' % (layout.url,
- self.handle_attrs(layout),
- layout.label))
def visit_verbatimtext(self, layout):
"""display verbatim text (using <pre>)"""
self.write(u'<pre>')
diff --git a/pylint/reporters/ureports/nodes.py b/pylint/reporters/ureports/nodes.py
index 01cbcb7..104ba83 100644
--- a/pylint/reporters/ureports/nodes.py
+++ b/pylint/reporters/ureports/nodes.py
@@ -22,32 +22,58 @@ A micro report is a tree of layout and content objects.
from six import string_types
-from pylint.reporters.ureports.tree import VNode
+class VNode(object):
-class BaseComponent(VNode):
- """base report component
+ def __init__(self, nid=None):
+ self.id = nid
+ # navigation
+ self.parent = None
+ self.children = []
- attributes
- * id : the component's optional id
- * klass : the component's optional klass
- """
- def __init__(self, id=None, klass=None):
- super(BaseComponent, self).__init__(id)
- self.klass = klass
+ def __iter__(self):
+ return iter(self.children)
-
-class BaseLayout(BaseComponent):
+ def append(self, child):
+ """add a node to children"""
+ self.children.append(child)
+ child.parent = self
+
+ def insert(self, index, child):
+ """insert a child node"""
+ self.children.insert(index, child)
+ child.parent = self
+
+ def _get_visit_name(self):
+ """
+ return the visit name for the mixed class. When calling 'accept', the
+ method <'visit_' + name returned by this method> will be called on the
+ visitor
+ """
+ try:
+ return self.TYPE.replace('-', '_')
+ except Exception:
+ return self.__class__.__name__.lower()
+
+ def accept(self, visitor, *args, **kwargs):
+ func = getattr(visitor, 'visit_%s' % self._get_visit_name())
+ return func(self, *args, **kwargs)
+
+ def leave(self, visitor, *args, **kwargs):
+ func = getattr(visitor, 'leave_%s' % self._get_visit_name())
+ return func(self, *args, **kwargs)
+
+
+class BaseLayout(VNode):
"""base container node
attributes
- * BaseComponent attributes
* children : components in this table (i.e. the table's cells)
"""
def __init__(self, children=(), **kwargs):
super(BaseLayout, self).__init__(**kwargs)
for child in children:
- if isinstance(child, BaseComponent):
+ if isinstance(child, VNode):
self.append(child)
else:
self.add_text(child)
@@ -71,11 +97,10 @@ class BaseLayout(BaseComponent):
# non container nodes #########################################################
-class Text(BaseComponent):
+class Text(VNode):
"""a text portion
attributes :
- * BaseComponent attributes
* data : the text value as an encoded or unicode string
"""
def __init__(self, data, escaped=True, **kwargs):
@@ -91,26 +116,9 @@ class VerbatimText(Text):
"""a verbatim text, display the raw data
attributes :
- * BaseComponent attributes
* data : the text value as an encoded or unicode string
"""
-
-class Link(BaseComponent):
- """a labelled link
-
- attributes :
- * BaseComponent attributes
- * url : the link's target (REQUIRED)
- * label : the link's label as a string (use the url by default)
- """
- def __init__(self, url, label=None, **kwargs):
- super(Link, self).__init__(**kwargs)
- assert url
- self.url = url
- self.label = label or url
-
-
# container nodes #############################################################
class Section(BaseLayout):
@@ -173,11 +181,3 @@ class Table(BaseLayout):
self.cheaders = cheaders
self.rrheaders = rrheaders
self.rcheaders = rcheaders
-
-
-class List(BaseLayout):
- """some list data
-
- attributes :
- * BaseLayout attributes
- """
diff --git a/pylint/reporters/ureports/text_writer.py b/pylint/reporters/ureports/text_writer.py
index 545f999..6109b95 100644
--- a/pylint/reporters/ureports/text_writer.py
+++ b/pylint/reporters/ureports/text_writer.py
@@ -19,10 +19,6 @@
from __future__ import print_function
-import os
-
-from six.moves import range
-
from pylint.reporters.ureports import BaseWriter
@@ -36,7 +32,6 @@ class TextWriter(BaseWriter):
def begin_format(self):
super(TextWriter, self).begin_format()
self.list_level = 0
- self.pending_urls = []
def visit_section(self, layout):
"""display a section as text
@@ -44,11 +39,6 @@ class TextWriter(BaseWriter):
self.section += 1
self.writeln()
self.format_children(layout)
- if self.pending_urls:
- self.writeln()
- for label, url in self.pending_urls:
- self.writeln(u'.. _`%s`: %s' % (label, url))
- self.pending_urls = []
self.section -= 1
self.writeln()
@@ -65,23 +55,15 @@ class TextWriter(BaseWriter):
self.format_children(layout)
self.writeln()
- def visit_span(self, layout):
- """enter a span"""
- self.format_children(layout)
-
def visit_table(self, layout):
"""display a table as text"""
table_content = self.get_table_content(layout)
# get columns width
cols_width = [0]*len(table_content[0])
for row in table_content:
- for index in range(len(row)):
- col = row[index]
+ for index, col in enumerate(row):
cols_width[index] = max(cols_width[index], len(col))
- if layout.klass == 'field':
- self.field_table(layout, table_content, cols_width)
- else:
- self.default_table(layout, table_content, cols_width)
+ self.default_table(layout, table_content, cols_width)
self.writeln()
def default_table(self, layout, table_content, cols_width):
@@ -89,47 +71,21 @@ class TextWriter(BaseWriter):
cols_width = [size+1 for size in cols_width]
format_strings = u' '.join([u'%%-%ss'] * len(cols_width))
format_strings = format_strings % tuple(cols_width)
- format_strings = format_strings.split(' ')
+ format_strings = format_strings.split(u' ')
table_linesep = u'\n+' + u'+'.join([u'-'*w for w in cols_width]) + u'+\n'
headsep = u'\n+' + u'+'.join([u'='*w for w in cols_width]) + u'+\n'
# FIXME: layout.cheaders
self.write(table_linesep)
- for i in range(len(table_content)):
+ for index, line in enumerate(table_content):
self.write(u'|')
- line = table_content[i]
- for j in range(len(line)):
- self.write(format_strings[j] % line[j])
+ for line_index, at_index in enumerate(line):
+ self.write(format_strings[line_index] % at_index)
self.write(u'|')
- if i == 0 and layout.rheaders:
+ if index == 0 and layout.rheaders:
self.write(headsep)
else:
self.write(table_linesep)
- def field_table(self, layout, table_content, cols_width):
- """special case for field table"""
- assert layout.cols == 2
- format_string = u'%s%%-%ss: %%s' % (os.linesep, cols_width[0])
- for field, value in table_content:
- self.write(format_string % (field, value))
-
- def visit_list(self, layout):
- """display a list layout as text"""
- bullet = BULLETS[self.list_level % len(BULLETS)]
- indent = ' ' * self.list_level
- self.list_level += 1
- for child in layout.children:
- self.write(u'%s%s%s ' % (os.linesep, indent, bullet))
- child.accept(self)
- self.list_level -= 1
-
- def visit_link(self, layout):
- """add a hyperlink"""
- if layout.label != layout.url:
- self.write(u'`%s`_' % layout.label)
- self.pending_urls.append((layout.label, layout.url))
- else:
- self.write(layout.url)
-
def visit_verbatimtext(self, layout):
"""display a verbatim layout as text (so difficult ;)
"""
diff --git a/pylint/reporters/ureports/tree.py b/pylint/reporters/ureports/tree.py
deleted file mode 100644
index 99965f2..0000000
--- a/pylint/reporters/ureports/tree.py
+++ /dev/null
@@ -1,235 +0,0 @@
-# copyright 2003-2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
-#
-# This file is part of pylint.
-#
-# logilab-common is free software: you can redistribute it and/or modify it under
-# the terms of the GNU Lesser General Public License as published by the Free
-# Software Foundation, either version 2.1 of the License, or (at your option) any
-# later version.
-#
-# pylint is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
-# details.
-#
-# You should have received a copy of the GNU Lesser General Public License along
-# with pylint. If not, see <http://www.gnu.org/licenses/>.
-
-
-class NodeNotFound(Exception):
- """raised when a node has not been found"""
-
-EX_SIBLING_NOT_FOUND = "No such sibling as '%s'"
-EX_CHILD_NOT_FOUND = "No such child as '%s'"
-EX_NODE_NOT_FOUND = "No such node as '%s'"
-
-
-# Base node ###################################################################
-
-class Node(object):
- """a basic tree node, characterized by an id"""
-
- def __init__(self, nid=None):
- self.id = nid
- # navigation
- self.parent = None
- self.children = []
-
- def __iter__(self):
- return iter(self.children)
-
- def __str__(self, indent=0):
- s = ['%s%s %s' % (' '*indent, self.__class__.__name__, self.id)]
- indent += 2
- for child in self.children:
- try:
- s.append(child.__str__(indent))
- except TypeError:
- s.append(child.__str__())
- return '\n'.join(s)
-
- def is_leaf(self):
- return not self.children
-
- def append(self, child):
- """add a node to children"""
- self.children.append(child)
- child.parent = self
-
- def remove(self, child):
- """remove a child node"""
- self.children.remove(child)
- child.parent = None
-
- def insert(self, index, child):
- """insert a child node"""
- self.children.insert(index, child)
- child.parent = self
-
- def replace(self, old_child, new_child):
- """replace a child node with another"""
- i = self.children.index(old_child)
- self.children.pop(i)
- self.children.insert(i, new_child)
- new_child.parent = self
-
- def get_sibling(self, nid):
- """return the sibling node that has given id"""
- try:
- return self.parent.get_child_by_id(nid)
- except NodeNotFound:
- raise NodeNotFound(EX_SIBLING_NOT_FOUND % nid)
-
- def next_sibling(self):
- """
- return the next sibling for this node if any
- """
- parent = self.parent
- if parent is None:
- # root node has no sibling
- return None
- index = parent.children.index(self)
- try:
- return parent.children[index+1]
- except IndexError:
- return None
-
- def previous_sibling(self):
- """
- return the previous sibling for this node if any
- """
- parent = self.parent
- if parent is None:
- # root node has no sibling
- return None
- index = parent.children.index(self)
- if index > 0:
- return parent.children[index-1]
- return None
-
- def get_node_by_id(self, nid):
- """
- return node in whole hierarchy that has given id
- """
- root = self.root()
- try:
- return root.get_child_by_id(nid, 1)
- except NodeNotFound:
- raise NodeNotFound(EX_NODE_NOT_FOUND % nid)
-
- def get_child_by_id(self, nid, recurse=None):
- """
- return child of given id
- """
- if self.id == nid:
- return self
- for c in self.children:
- if recurse:
- try:
- return c.get_child_by_id(nid, 1)
- except NodeNotFound:
- continue
- if c.id == nid:
- return c
- raise NodeNotFound(EX_CHILD_NOT_FOUND % nid)
-
- def get_child_by_path(self, path):
- """
- return child of given path (path is a list of ids)
- """
- if len(path) > 0 and path[0] == self.id:
- if len(path) == 1:
- return self
- else:
- for c in self.children:
- try:
- return c.get_child_by_path(path[1:])
- except NodeNotFound:
- pass
- raise NodeNotFound(EX_CHILD_NOT_FOUND % path)
-
- def depth(self):
- """
- return depth of this node in the tree
- """
- if self.parent is not None:
- return 1 + self.parent.depth()
- else:
- return 0
-
- def depth_down(self):
- """
- return depth of the tree from this node
- """
- if self.children:
- return 1 + max([c.depth_down() for c in self.children])
- return 1
-
- def width(self):
- """
- return the width of the tree from this node
- """
- return len(self.leaves())
-
- def root(self):
- """
- return the root node of the tree
- """
- if self.parent is not None:
- return self.parent.root()
- return self
-
- def leaves(self):
- """
- return a list with all the leaves nodes descendant from this node
- """
- leaves = []
- if self.children:
- for child in self.children:
- leaves += child.leaves()
- return leaves
- else:
- return [self]
-
- def flatten(self, _list=None):
- """
- return a list with all the nodes descendant from this node
- """
- if _list is None:
- _list = []
- _list.append(self)
- for c in self.children:
- c.flatten(_list)
- return _list
-
- def lineage(self):
- """
- return list of parents up to root node
- """
- lst = [self]
- if self.parent is not None:
- lst.extend(self.parent.lineage())
- return lst
-
-
-class VNode(Node):
-
- def get_visit_name(self):
- """
- return the visit name for the mixed class. When calling 'accept', the
- method <'visit_' + name returned by this method> will be called on the
- visitor
- """
- try:
- return self.TYPE.replace('-', '_')
- except Exception:
- return self.__class__.__name__.lower()
-
- def accept(self, visitor, *args, **kwargs):
- func = getattr(visitor, 'visit_%s' % self.get_visit_name())
- return func(self, *args, **kwargs)
-
- def leave(self, visitor, *args, **kwargs):
- func = getattr(visitor, 'leave_%s' % self.get_visit_name())
- return func(self, *args, **kwargs)
diff --git a/pylint/test/functional/abstract_class_instantiated_in_class.py b/pylint/test/functional/abstract_class_instantiated_in_class.py
new file mode 100644
index 0000000..9402c12
--- /dev/null
+++ b/pylint/test/functional/abstract_class_instantiated_in_class.py
@@ -0,0 +1,20 @@
+"""Don't warn if the class is instantiated in its own body."""
+# pylint: disable=missing-docstring
+
+
+import abc
+
+import six
+
+
+@six.add_metaclass(abc.ABCMeta)
+class Ala(object):
+
+ @abc.abstractmethod
+ def bala(self):
+ pass
+
+ @classmethod
+ def portocala(cls):
+ instance = cls()
+ return instance
diff --git a/pylint/test/functional/arguments.py b/pylint/test/functional/arguments.py
index 9f0dc63..8ae008d 100644
--- a/pylint/test/functional/arguments.py
+++ b/pylint/test/functional/arguments.py
@@ -147,6 +147,8 @@ class Issue642(object):
# since they have something invalid.
from ala_bala_portocola import unknown
+# pylint: disable=not-a-mapping,not-an-iterable
+
function_1_arg(*unknown)
function_1_arg(1, *2)
function_1_arg(1, 2, 3, **unknown)
diff --git a/pylint/test/functional/iterable_context.py b/pylint/test/functional/iterable_context.py
new file mode 100644
index 0000000..8dfcbbe
--- /dev/null
+++ b/pylint/test/functional/iterable_context.py
@@ -0,0 +1,141 @@
+"""
+Checks that primitive values are not used in an
+iterating/mapping context.
+"""
+# pylint: disable=missing-docstring,invalid-name,too-few-public-methods,no-init,no-self-use,import-error,unused-argument,bad-mcs-method-argument
+from __future__ import print_function
+
+# primitives
+numbers = [1, 2, 3]
+
+for i in numbers:
+ pass
+
+for i in iter(numbers):
+ pass
+
+for i in "123":
+ pass
+
+for i in u"123":
+ pass
+
+for i in b"123":
+ pass
+
+for i in bytearray(b"123"):
+ pass
+
+for i in set(numbers):
+ pass
+
+for i in frozenset(numbers):
+ pass
+
+for i in dict(a=1, b=2):
+ pass
+
+# comprehensions
+for i in [x for x in range(10)]:
+ pass
+
+for i in {x for x in range(1, 100, 2)}:
+ pass
+
+for i in {x: 10 - x for x in range(10)}:
+ pass
+
+# generators
+def powers_of_two():
+ k = 0
+ while k < 10:
+ yield 2 ** k
+ k += 1
+
+for i in powers_of_two():
+ pass
+
+for i in powers_of_two: # [not-an-iterable]
+ pass
+
+# check for custom iterators
+class A(object):
+ pass
+
+class B(object):
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ return 1
+
+ def next(self):
+ return 1
+
+class C(object):
+ "old-style iterator"
+ def __getitem__(self, k):
+ if k > 10:
+ raise IndexError
+ return k + 1
+
+ def __len__(self):
+ return 10
+
+for i in C():
+ print(i)
+
+
+def test(*args):
+ print(args)
+
+
+test(*A()) # [not-an-iterable]
+test(*B())
+test(*B) # [not-an-iterable]
+for i in A(): # [not-an-iterable]
+ pass
+for i in B():
+ pass
+for i in B: # [not-an-iterable]
+ pass
+
+for i in range: # [not-an-iterable]
+ pass
+
+# check that primitive non-iterable types are catched
+for i in True: # [not-an-iterable]
+ pass
+
+for i in None: # [not-an-iterable]
+ pass
+
+for i in 8.5: # [not-an-iterable]
+ pass
+
+for i in 10: # [not-an-iterable]
+ pass
+
+
+# skip uninferable instances
+from some_missing_module import Iterable
+
+class MyClass(Iterable):
+ pass
+
+m = MyClass()
+for i in m:
+ print(i)
+
+# skip checks if statement is inside mixin class
+class ManagedAccessViewMixin(object):
+ access_requirements = None
+
+ def get_access_requirements(self):
+ return self.access_requirements
+
+ def dispatch(self, *_args, **_kwargs):
+ klasses = self.get_access_requirements()
+
+ for requirement in klasses:
+ print(requirement)
diff --git a/pylint/test/functional/iterable_context.txt b/pylint/test/functional/iterable_context.txt
new file mode 100644
index 0000000..fbe1433
--- /dev/null
+++ b/pylint/test/functional/iterable_context.txt
@@ -0,0 +1,10 @@
+not-an-iterable:58::Non-iterable value powers_of_two is used in an iterating context
+not-an-iterable:93::Non-iterable value A() is used in an iterating context
+not-an-iterable:95::Non-iterable value B is used in an iterating context
+not-an-iterable:96::Non-iterable value A() is used in an iterating context
+not-an-iterable:100::Non-iterable value B is used in an iterating context
+not-an-iterable:103::Non-iterable value range is used in an iterating context
+not-an-iterable:107::Non-iterable value True is used in an iterating context
+not-an-iterable:110::Non-iterable value None is used in an iterating context
+not-an-iterable:113::Non-iterable value 8.5 is used in an iterating context
+not-an-iterable:116::Non-iterable value 10 is used in an iterating context
diff --git a/pylint/test/functional/iterable_context_py2.py b/pylint/test/functional/iterable_context_py2.py
new file mode 100644
index 0000000..8687f84
--- /dev/null
+++ b/pylint/test/functional/iterable_context_py2.py
@@ -0,0 +1,18 @@
+"""
+Checks that iterable metaclasses are recognized by pylint.
+"""
+# pylint: disable=missing-docstring,too-few-public-methods,no-init,no-self-use,unused-argument,bad-mcs-method-argument
+
+# metaclasses as iterables
+class Meta(type):
+ def __iter__(self):
+ return iter((1, 2, 3))
+
+class SomeClass(object):
+ __metaclass__ = Meta
+
+
+for i in SomeClass:
+ print i
+for i in SomeClass(): # [not-an-iterable]
+ print i
diff --git a/pylint/test/functional/iterable_context_py2.rc b/pylint/test/functional/iterable_context_py2.rc
new file mode 100644
index 0000000..61e01ea
--- /dev/null
+++ b/pylint/test/functional/iterable_context_py2.rc
@@ -0,0 +1,3 @@
+[testoptions]
+max_pyver=2.7
+
diff --git a/pylint/test/functional/iterable_context_py2.txt b/pylint/test/functional/iterable_context_py2.txt
new file mode 100644
index 0000000..8de579a
--- /dev/null
+++ b/pylint/test/functional/iterable_context_py2.txt
@@ -0,0 +1 @@
+not-an-iterable:17::Non-iterable value SomeClass() is used in an iterating context
diff --git a/pylint/test/functional/iterable_context_py3.py b/pylint/test/functional/iterable_context_py3.py
new file mode 100644
index 0000000..cb2a505
--- /dev/null
+++ b/pylint/test/functional/iterable_context_py3.py
@@ -0,0 +1,18 @@
+"""
+Checks that iterable metaclasses are recognized by pylint.
+"""
+# pylint: disable=missing-docstring,too-few-public-methods,no-init,no-self-use,unused-argument,bad-mcs-method-argument
+
+# metaclasses as iterables
+class Meta(type):
+ def __iter__(self):
+ return iter((1, 2, 3))
+
+class SomeClass(metaclass=Meta):
+ pass
+
+
+for i in SomeClass:
+ print(i)
+for i in SomeClass(): # [not-an-iterable]
+ print(i)
diff --git a/pylint/test/functional/iterable_context_py3.rc b/pylint/test/functional/iterable_context_py3.rc
new file mode 100644
index 0000000..9bf6df0
--- /dev/null
+++ b/pylint/test/functional/iterable_context_py3.rc
@@ -0,0 +1,3 @@
+[testoptions]
+min_pyver=3.0
+
diff --git a/pylint/test/functional/iterable_context_py3.txt b/pylint/test/functional/iterable_context_py3.txt
new file mode 100644
index 0000000..8de579a
--- /dev/null
+++ b/pylint/test/functional/iterable_context_py3.txt
@@ -0,0 +1 @@
+not-an-iterable:17::Non-iterable value SomeClass() is used in an iterating context
diff --git a/pylint/test/functional/mapping_context.py b/pylint/test/functional/mapping_context.py
new file mode 100644
index 0000000..cfab8dc
--- /dev/null
+++ b/pylint/test/functional/mapping_context.py
@@ -0,0 +1,59 @@
+"""
+Checks that only valid values are used in a mapping context.
+"""
+# pylint: disable=missing-docstring,invalid-name,too-few-public-methods,no-self-use,import-error
+from __future__ import print_function
+
+
+def test(**kwargs):
+ print(kwargs)
+
+
+# dictionary value/comprehension
+dict_value = dict(a=1, b=2, c=3)
+dict_comp = {chr(x): x for x in range(256)}
+test(**dict_value)
+test(**dict_comp)
+
+
+# in order to be used in kwargs custom mapping class should define
+# __iter__(), __getitem__(key) and keys().
+class CustomMapping(object):
+ def __init__(self):
+ self.data = dict(a=1, b=2, c=3, d=4, e=5)
+
+ def __getitem__(self, key):
+ return self.data[key]
+
+ def keys(self):
+ return self.data.keys()
+
+test(**CustomMapping())
+test(**CustomMapping) # [not-a-mapping]
+
+class NotMapping(object):
+ pass
+
+test(**NotMapping()) # [not-a-mapping]
+
+# skip checks if statement is inside mixin class
+class SomeMixin(object):
+ kwargs = None
+
+ def get_kwargs(self):
+ return self.kwargs
+
+ def run(self, **kwargs):
+ print(kwargs)
+
+ def dispatch(self):
+ kws = self.get_kwargs()
+ self.run(**kws)
+
+# skip uninferable instances
+from some_missing_module import Mapping
+
+class MyClass(Mapping):
+ pass
+
+test(**MyClass())
diff --git a/pylint/test/functional/mapping_context.txt b/pylint/test/functional/mapping_context.txt
new file mode 100644
index 0000000..201da1a
--- /dev/null
+++ b/pylint/test/functional/mapping_context.txt
@@ -0,0 +1,2 @@
+not-a-mapping:32::Non-mapping value CustomMapping is used in a mapping context
+not-a-mapping:37::Non-mapping value NotMapping() is used in a mapping context
diff --git a/pylint/test/functional/mapping_context_py2.py b/pylint/test/functional/mapping_context_py2.py
new file mode 100644
index 0000000..afe4400
--- /dev/null
+++ b/pylint/test/functional/mapping_context_py2.py
@@ -0,0 +1,19 @@
+# pylint: disable=missing-docstring,invalid-name,too-few-public-methods
+from __future__ import print_function
+
+
+def test(**kwargs):
+ print(kwargs)
+
+# metaclasses as mappings
+class Meta(type):
+ def __getitem__(self, key):
+ return ord(key)
+ def keys(self):
+ return ['a', 'b', 'c']
+
+class SomeClass(object):
+ __metaclass__ = Meta
+
+test(**SomeClass)
+test(**SomeClass()) # [not-a-mapping]
diff --git a/pylint/test/functional/mapping_context_py2.rc b/pylint/test/functional/mapping_context_py2.rc
new file mode 100644
index 0000000..61e01ea
--- /dev/null
+++ b/pylint/test/functional/mapping_context_py2.rc
@@ -0,0 +1,3 @@
+[testoptions]
+max_pyver=2.7
+
diff --git a/pylint/test/functional/mapping_context_py2.txt b/pylint/test/functional/mapping_context_py2.txt
new file mode 100644
index 0000000..59cca6c
--- /dev/null
+++ b/pylint/test/functional/mapping_context_py2.txt
@@ -0,0 +1 @@
+not-a-mapping:19::Non-mapping value SomeClass() is used in a mapping context
diff --git a/pylint/test/functional/mapping_context_py3.py b/pylint/test/functional/mapping_context_py3.py
new file mode 100644
index 0000000..042d4d0
--- /dev/null
+++ b/pylint/test/functional/mapping_context_py3.py
@@ -0,0 +1,19 @@
+# pylint: disable=missing-docstring,invalid-name,too-few-public-methods,no-self-use
+from __future__ import print_function
+
+def test(**kwargs):
+ print(kwargs)
+
+# metaclasses as mappings
+class Meta(type):
+ def __getitem__(cls, key):
+ return ord(key)
+
+ def keys(cls):
+ return ['a', 'b', 'c']
+
+class SomeClass(metaclass=Meta):
+ pass
+
+test(**SomeClass)
+test(**SomeClass()) # [not-a-mapping]
diff --git a/pylint/test/functional/mapping_context_py3.rc b/pylint/test/functional/mapping_context_py3.rc
new file mode 100644
index 0000000..9bf6df0
--- /dev/null
+++ b/pylint/test/functional/mapping_context_py3.rc
@@ -0,0 +1,3 @@
+[testoptions]
+min_pyver=3.0
+
diff --git a/pylint/test/functional/mapping_context_py3.txt b/pylint/test/functional/mapping_context_py3.txt
new file mode 100644
index 0000000..59cca6c
--- /dev/null
+++ b/pylint/test/functional/mapping_context_py3.txt
@@ -0,0 +1 @@
+not-a-mapping:19::Non-mapping value SomeClass() is used in a mapping context
diff --git a/pylint/test/functional/member_checks.py b/pylint/test/functional/member_checks.py
index 8838fae..50a0d97 100644
--- a/pylint/test/functional/member_checks.py
+++ b/pylint/test/functional/member_checks.py
@@ -173,3 +173,9 @@ def no_conjugate_member(magic_flag):
if isinstance(something, float):
return something
return something.conjugate()
+
+
+class NoDunderNameInInstance(object):
+ """Emit a warning when accessing __name__ from an instance."""
+ def __init__(self):
+ self.var = self.__name__ # [no-member]
diff --git a/pylint/test/functional/member_checks.txt b/pylint/test/functional/member_checks.txt
index 3d50a25..6e527bc 100644
--- a/pylint/test/functional/member_checks.txt
+++ b/pylint/test/functional/member_checks.txt
@@ -15,3 +15,4 @@ no-member:120::Class 'Client' has no 'missing' member:INFERENCE
no-member:144::Class 'Client' has no 'ala' member:INFERENCE
no-member:145::Class 'dict' has no 'bala' member:INFERENCE
no-member:146::Class 'str' has no 'portocala' member:INFERENCE
+no-member:181:NoDunderNameInInstance.__init__:Instance of 'NoDunderNameInInstance' has no '__name__' member:INFERENCE \ No newline at end of file
diff --git a/pylint/test/functional/membership_protocol.py b/pylint/test/functional/membership_protocol.py
new file mode 100644
index 0000000..7b3a46f
--- /dev/null
+++ b/pylint/test/functional/membership_protocol.py
@@ -0,0 +1,85 @@
+# pylint: disable=missing-docstring,pointless-statement,expression-not-assigned,too-few-public-methods,import-error,no-init
+
+# standard types
+1 in [1, 2, 3]
+1 in {'a': 1, 'b': 2}
+1 in {1, 2, 3}
+1 in (1, 2, 3)
+1 in "123"
+1 in u"123"
+1 in bytearray(b"123")
+1 in frozenset([1, 2, 3])
+
+# comprehensions
+1 in [x ** 2 % 10 for x in range(10)]
+1 in {x ** 2 % 10 for x in range(10)}
+1 in {x: x ** 2 % 10 for x in range(10)}
+
+# iterators
+1 in iter([1, 2, 3])
+
+# generator
+def count(upto=float("inf")):
+ i = 0
+ while True:
+ if i > upto:
+ break
+ yield i
+ i += 1
+
+10 in count(upto=10)
+
+# custom instance
+class UniversalContainer(object):
+ def __contains__(self, key):
+ return True
+
+42 in UniversalContainer()
+
+# custom iterable
+class CustomIterable(object):
+ def __iter__(self):
+ return iter((1, 2, 3))
+3 in CustomIterable()
+
+# old-style iterable
+class OldStyleIterable(object):
+ def __getitem__(self, key):
+ if key < 10:
+ return 2 ** key
+ else:
+ raise IndexError("bad index")
+64 in OldStyleIterable()
+
+# do not emit warning if class has unknown bases
+from some_missing_module import ImportedClass
+
+class MaybeIterable(ImportedClass):
+ pass
+
+10 in MaybeIterable()
+
+# do not emit warning inside mixins
+class UsefulMixin(object):
+ stuff = None
+
+ def get_stuff(self):
+ return self.stuff
+
+ def act(self, thing):
+ stuff = self.get_stuff()
+ if thing in stuff:
+ pass
+
+# error cases
+42 in 42 # [unsupported-membership-test]
+42 not in None # [unsupported-membership-test]
+42 in 8.5 # [unsupported-membership-test]
+
+class EmptyClass(object):
+ pass
+
+42 not in EmptyClass() # [unsupported-membership-test]
+42 in EmptyClass # [unsupported-membership-test]
+42 not in count # [unsupported-membership-test]
+42 in range # [unsupported-membership-test]
diff --git a/pylint/test/functional/membership_protocol.txt b/pylint/test/functional/membership_protocol.txt
new file mode 100644
index 0000000..6e9bd8e
--- /dev/null
+++ b/pylint/test/functional/membership_protocol.txt
@@ -0,0 +1,7 @@
+unsupported-membership-test:75::Value '42' doesn't support membership test
+unsupported-membership-test:76::Value 'None' doesn't support membership test
+unsupported-membership-test:77::Value '8.5' doesn't support membership test
+unsupported-membership-test:82::Value 'EmptyClass()' doesn't support membership test
+unsupported-membership-test:83::Value 'EmptyClass' doesn't support membership test
+unsupported-membership-test:84::Value 'count' doesn't support membership test
+unsupported-membership-test:85::Value 'range' doesn't support membership test
diff --git a/pylint/test/functional/membership_protocol_py2.py b/pylint/test/functional/membership_protocol_py2.py
new file mode 100644
index 0000000..1a01637
--- /dev/null
+++ b/pylint/test/functional/membership_protocol_py2.py
@@ -0,0 +1,36 @@
+# pylint: disable=missing-docstring,too-few-public-methods,no-init,no-self-use,unused-argument,pointless-statement,expression-not-assigned,undefined-variable
+
+# metaclasses that support membership test protocol
+class MetaIterable(type):
+ def __iter__(cls):
+ return iter((1, 2, 3))
+
+class MetaOldIterable(type):
+ def __getitem__(cls, key):
+ if key < 10:
+ return key ** 2
+ else:
+ raise IndexError("bad index")
+
+class MetaContainer(type):
+ def __contains__(cls, key):
+ return False
+
+
+class IterableClass(object):
+ __metaclass__ = MetaIterable
+
+class OldIterableClass(object):
+ __metaclass__ = MetaOldIterable
+
+class ContainerClass(object):
+ __metaclass__ = MetaContainer
+
+
+def test():
+ 1 in IterableClass
+ 1 in OldIterableClass
+ 1 in ContainerClass
+ 1 in IterableClass() # [unsupported-membership-test]
+ 1 in OldIterableClass() # [unsupported-membership-test]
+ 1 in ContainerClass() # [unsupported-membership-test]
diff --git a/pylint/test/functional/membership_protocol_py2.rc b/pylint/test/functional/membership_protocol_py2.rc
new file mode 100644
index 0000000..c78f32f
--- /dev/null
+++ b/pylint/test/functional/membership_protocol_py2.rc
@@ -0,0 +1,3 @@
+[testoptions]
+max_pyver=3.0
+
diff --git a/pylint/test/functional/membership_protocol_py2.txt b/pylint/test/functional/membership_protocol_py2.txt
new file mode 100644
index 0000000..4ba7575
--- /dev/null
+++ b/pylint/test/functional/membership_protocol_py2.txt
@@ -0,0 +1,3 @@
+unsupported-membership-test:34:test:Value 'IterableClass()' doesn't support membership test
+unsupported-membership-test:35:test:Value 'OldIterableClass()' doesn't support membership test
+unsupported-membership-test:36:test:Value 'ContainerClass()' doesn't support membership test
diff --git a/pylint/test/functional/membership_protocol_py3.py b/pylint/test/functional/membership_protocol_py3.py
new file mode 100644
index 0000000..6a77f20
--- /dev/null
+++ b/pylint/test/functional/membership_protocol_py3.py
@@ -0,0 +1,36 @@
+# pylint: disable=missing-docstring,too-few-public-methods,no-init,no-self-use,unused-argument,pointless-statement,expression-not-assigned
+
+# metaclasses that support membership test protocol
+class MetaIterable(type):
+ def __iter__(cls):
+ return iter((1, 2, 3))
+
+class MetaOldIterable(type):
+ def __getitem__(cls, key):
+ if key < 10:
+ return key ** 2
+ else:
+ raise IndexError("bad index")
+
+class MetaContainer(type):
+ def __contains__(cls, key):
+ return False
+
+
+class IterableClass(metaclass=MetaOldIterable):
+ pass
+
+class OldIterableClass(metaclass=MetaOldIterable):
+ pass
+
+class ContainerClass(metaclass=MetaContainer):
+ pass
+
+
+def test():
+ 1 in IterableClass
+ 1 in OldIterableClass
+ 1 in ContainerClass
+ 1 in IterableClass() # [unsupported-membership-test]
+ 1 in OldIterableClass() # [unsupported-membership-test]
+ 1 in ContainerClass() # [unsupported-membership-test]
diff --git a/pylint/test/functional/membership_protocol_py3.rc b/pylint/test/functional/membership_protocol_py3.rc
new file mode 100644
index 0000000..9bf6df0
--- /dev/null
+++ b/pylint/test/functional/membership_protocol_py3.rc
@@ -0,0 +1,3 @@
+[testoptions]
+min_pyver=3.0
+
diff --git a/pylint/test/functional/membership_protocol_py3.txt b/pylint/test/functional/membership_protocol_py3.txt
new file mode 100644
index 0000000..4ba7575
--- /dev/null
+++ b/pylint/test/functional/membership_protocol_py3.txt
@@ -0,0 +1,3 @@
+unsupported-membership-test:34:test:Value 'IterableClass()' doesn't support membership test
+unsupported-membership-test:35:test:Value 'OldIterableClass()' doesn't support membership test
+unsupported-membership-test:36:test:Value 'ContainerClass()' doesn't support membership test
diff --git a/pylint/test/functional/no_self_use_py3.py b/pylint/test/functional/no_self_use_py3.py
new file mode 100644
index 0000000..f401508
--- /dev/null
+++ b/pylint/test/functional/no_self_use_py3.py
@@ -0,0 +1,12 @@
+# pylint: disable=missing-docstring,no-init,unused-argument,invalid-name,too-few-public-methods
+
+class A:
+ def __init__(self):
+ self.store = {}
+
+ def get(self, key, default=None):
+ return self.store.get(key, default)
+
+class B(A):
+ def get_memo(self, obj):
+ return super().get(obj)
diff --git a/pylint/test/functional/no_self_use_py3.rc b/pylint/test/functional/no_self_use_py3.rc
new file mode 100644
index 0000000..a2ab06c
--- /dev/null
+++ b/pylint/test/functional/no_self_use_py3.rc
@@ -0,0 +1,2 @@
+[testoptions]
+min_pyver=3.0 \ No newline at end of file
diff --git a/pylint/test/functional/no_self_use_py3.txt b/pylint/test/functional/no_self_use_py3.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pylint/test/functional/no_self_use_py3.txt
@@ -0,0 +1 @@
+
diff --git a/pylint/test/functional/non_iterator_returned.py b/pylint/test/functional/non_iterator_returned.py
index 845500b..804ceee 100644
--- a/pylint/test/functional/non_iterator_returned.py
+++ b/pylint/test/functional/non_iterator_returned.py
@@ -1,8 +1,8 @@
"""Check non-iterators returned by __iter__ """
-# pylint: disable=too-few-public-methods
+# pylint: disable=too-few-public-methods, missing-docstring, no-self-use
-__revision__ = 0
+import six
class FirstGoodIterator(object):
""" yields in iterator. """
@@ -17,11 +17,11 @@ class SecondGoodIterator(object):
def __iter__(self):
return self
- def __next__(self): # pylint: disable=no-self-use
+ def __next__(self):
""" Infinite iterator, but still an iterator """
return 1
- def next(self): # pylint: disable=no-self-use
+ def next(self):
"""Same as __next__, but for Python 2."""
return 1
@@ -37,6 +37,26 @@ class FourthGoodIterator(object):
def __iter__(self):
return iter(range(10))
+
+class IteratorMetaclass(type):
+ def __next__(cls):
+ return 1
+
+ def next(cls):
+ return 2
+
+
+@six.add_metaclass(IteratorMetaclass)
+class IteratorClass(object):
+ """Iterable through the metaclass."""
+
+
+class FifthGoodIterator(object):
+ """__iter__ returns a class which uses an iterator-metaclass."""
+ def __iter__(self):
+ return IteratorClass
+
+
class FirstBadIterator(object):
""" __iter__ returns a list """
@@ -54,3 +74,17 @@ class ThirdBadIterator(object):
def __iter__(self): # [non-iterator-returned]
return SecondBadIterator()
+
+class FourthBadIterator(object):
+ """__iter__ returns a class."""
+
+ def __iter__(self): # [non-iterator-returned]
+ return ThirdBadIterator
+
+class FifthBadIterator(object):
+ """All branches should return an iterator."""
+
+ def __iter__(self): # [non-iterator-returned]
+ if self:
+ return 1
+ return SecondGoodIterator()
diff --git a/pylint/test/functional/non_iterator_returned.txt b/pylint/test/functional/non_iterator_returned.txt
index f2881eb..fe3db10 100644
--- a/pylint/test/functional/non_iterator_returned.txt
+++ b/pylint/test/functional/non_iterator_returned.txt
@@ -1,3 +1,5 @@
-non-iterator-returned:43:FirstBadIterator.__iter__:__iter__ returns non-iterator
-non-iterator-returned:49:SecondBadIterator.__iter__:__iter__ returns non-iterator
-non-iterator-returned:55:ThirdBadIterator.__iter__:__iter__ returns non-iterator \ No newline at end of file
+non-iterator-returned:63:FirstBadIterator.__iter__:__iter__ returns non-iterator
+non-iterator-returned:69:SecondBadIterator.__iter__:__iter__ returns non-iterator
+non-iterator-returned:75:ThirdBadIterator.__iter__:__iter__ returns non-iterator
+non-iterator-returned:81:FourthBadIterator.__iter__:__iter__ returns non-iterator
+non-iterator-returned:87:FifthBadIterator.__iter__:__iter__ returns non-iterator \ No newline at end of file
diff --git a/pylint/test/functional/unbalanced_tuple_unpacking.py b/pylint/test/functional/unbalanced_tuple_unpacking.py
index 02722c7..bd21a05 100644
--- a/pylint/test/functional/unbalanced_tuple_unpacking.py
+++ b/pylint/test/functional/unbalanced_tuple_unpacking.py
@@ -97,3 +97,10 @@ def test_decimal():
dec = Decimal(2)
first, second, third = dec.as_tuple()
return first, second, third
+
+
+def test_issue_559():
+ """Test that we don't have a false positive wrt to issue #559."""
+ from ctypes import c_int
+ root_x, root_y, win_x, win_y = [c_int()] * 4
+ return root_x, root_y, win_x, win_y
diff --git a/pylint/test/functional/yield_from_iterable_py33.py b/pylint/test/functional/yield_from_iterable_py33.py
new file mode 100644
index 0000000..7803936
--- /dev/null
+++ b/pylint/test/functional/yield_from_iterable_py33.py
@@ -0,0 +1,7 @@
+"""
+Check that `yield from`-statement takes an iterable.
+"""
+# pylint: disable=missing-docstring
+
+def to_ten():
+ yield from 10 # [not-an-iterable]
diff --git a/pylint/test/functional/yield_from_iterable_py33.rc b/pylint/test/functional/yield_from_iterable_py33.rc
new file mode 100644
index 0000000..3330edd
--- /dev/null
+++ b/pylint/test/functional/yield_from_iterable_py33.rc
@@ -0,0 +1,2 @@
+[testoptions]
+min_pyver=3.3 \ No newline at end of file
diff --git a/pylint/test/functional/yield_from_iterable_py33.txt b/pylint/test/functional/yield_from_iterable_py33.txt
new file mode 100644
index 0000000..906ee93
--- /dev/null
+++ b/pylint/test/functional/yield_from_iterable_py33.txt
@@ -0,0 +1 @@
+not-an-iterable:7:to_ten:Non-iterable value 10 is used in an iterating context
diff --git a/pylint/test/regrtest_data/html_crash_420.py b/pylint/test/regrtest_data/html_crash_420.py
new file mode 100644
index 0000000..a0edbb5
--- /dev/null
+++ b/pylint/test/regrtest_data/html_crash_420.py
@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+tag2struct = {u"#": "R_HEADER"
+ ,u"£": "RDR_HEADER"
+ ,u"µ": "RDR_DRAFT"
+ } \ No newline at end of file
diff --git a/pylint/test/test_self.py b/pylint/test/test_self.py
index ba4cdab..324cc16 100644
--- a/pylint/test/test_self.py
+++ b/pylint/test/test_self.py
@@ -145,6 +145,12 @@ class RunTC(unittest.TestCase):
messages = parser.get('MESSAGES CONTROL', 'disable').split(",")
self.assertIn('suppressed-message', messages)
+ def test_generate_rcfile_no_obsolete_methods(self):
+ out = six.StringIO()
+ self._run_pylint(["--generate-rcfile"], out=out)
+ output = out.getvalue()
+ self.assertNotIn("profile", output)
+
def _test_deprecated_options(self, option, expected):
out = six.StringIO()
self._run_pylint([option, "--rcfile=", "pylint.config"], out=out)
@@ -259,6 +265,11 @@ class RunTC(unittest.TestCase):
self._test_output([module, "--disable=all", "--enable=all", "-rn"],
expected_output=expected)
+ def test_html_crash_report(self):
+ out = six.StringIO()
+ module = join(HERE, 'regrtest_data', 'html_crash_420.py')
+ self._runtest([module], code=16, reporter=HTMLReporter(out))
+
if __name__ == '__main__':
unittest.main()
diff --git a/pylint/test/unittest_checker_base.py b/pylint/test/unittest_checker_base.py
index ec3ae2b..9901765 100644
--- a/pylint/test/unittest_checker_base.py
+++ b/pylint/test/unittest_checker_base.py
@@ -1,7 +1,6 @@
"""Unittest for the base checker."""
import re
-import sys
import unittest
import astroid
diff --git a/pylint/test/unittest_checker_typecheck.py b/pylint/test/unittest_checker_typecheck.py
index b7135ea..0aaa8a5 100644
--- a/pylint/test/unittest_checker_typecheck.py
+++ b/pylint/test/unittest_checker_typecheck.py
@@ -5,6 +5,7 @@ from astroid import test_utils
from pylint.checkers import typecheck
from pylint.testutils import CheckerTestCase, Message, set_config
+
class TypeCheckerTest(CheckerTestCase):
"Tests for pylint.checkers.typecheck"
CHECKER_CLASS = typecheck.TypeChecker
diff --git a/pylint/testutils.py b/pylint/testutils.py
index adc4e0c..ecb26ce 100644
--- a/pylint/testutils.py
+++ b/pylint/testutils.py
@@ -272,6 +272,7 @@ class LintTestUsingModule(unittest.TestCase):
def test_functionality(self):
tocheck = [self.package+'.'+self.module]
+ # pylint: disable=not-an-iterable; can't handle boolean checks for now
if self.depends:
tocheck += [self.package+'.%s' % name.replace('.py', '')
for name, _ in self.depends]
@@ -317,6 +318,7 @@ class LintTestUsingFile(LintTestUsingModule):
if not isdir(importable):
importable += '.py'
tocheck = [importable]
+ # pylint: disable=not-an-iterable; can't handle boolean checks for now
if self.depends:
tocheck += [join(self.INPUT_DIR, name) for name, _ in self.depends]
self._test(tocheck)
diff --git a/pylint/utils.py b/pylint/utils.py
index f303411..2f8de80 100644
--- a/pylint/utils.py
+++ b/pylint/utils.py
@@ -142,9 +142,8 @@ def category_id(cid):
return MSG_TYPES_LONG.get(cid)
-def _decoding_readline(stream, module):
- return lambda: stream.readline().decode(module.file_encoding,
- 'replace')
+def _decoding_readline(stream, encoding):
+ return lambda: stream.readline().decode(encoding, 'replace')
def tokenize_module(module):
@@ -152,7 +151,8 @@ def tokenize_module(module):
readline = stream.readline
if sys.version_info < (3, 0):
if module.file_encoding is not None:
- readline = _decoding_readline(stream, module)
+ readline = _decoding_readline(stream, module.file_encoding)
+
return list(tokenize.generate_tokens(readline))
return list(tokenize.tokenize(readline))
@@ -997,7 +997,8 @@ def deprecated_option(shortname=None, opt_type=None, help_msg=None):
'hide': True,
'type': opt_type,
'action': 'callback',
- 'callback': _warn_deprecated
+ 'callback': _warn_deprecated,
+ 'deprecated': True
}
if shortname:
option['shortname'] = shortname