summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRadu Ciorba <radu@devrandom.ro>2015-03-23 13:56:57 +0200
committerRadu Ciorba <radu@devrandom.ro>2015-03-23 13:56:57 +0200
commit415b50df1a0eeeca6e15d9f4573b618dae35d065 (patch)
treee8b6d2fe42c48516154a73bcc1ad682b921bfccc
parentdb95658fb9309ed26cbf4a0b8b7653bcb2325b30 (diff)
parentaab6eb1b69b153b1175f1f0c4bb0601a6fd798ff (diff)
downloadpylint-not-context-manager.tar.gz
merged default from logilab/pylint and resolve conflictnot-context-manager
-rw-r--r--ChangeLog5
-rw-r--r--pylint/checkers/base.py37
-rw-r--r--pylint/test/functional/confusing_with_statement.py27
-rw-r--r--pylint/test/functional/confusing_with_statement.txt1
4 files changed, 70 insertions, 0 deletions
diff --git a/ChangeLog b/ChangeLog
index 3d1e069..199033a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -6,6 +6,11 @@ ChangeLog for Pylint
that doesn't implement __enter__ and __exit__ is used in a with
statement.
+ * Add a new warning, 'confusing-with-statement', emitted by the
+ base checker, when an ambiguous looking with statement is used.
+ For example `with open() as first, second` which looks like a
+ tuple assignment but is actually 2 context managers.
+
* Add a new warning, 'duplicate-except', emitted when there is an
exception handler which handles an exception type that was handled
before. Closes issue #485.
diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py
index 6ce8825..7659fa4 100644
--- a/pylint/checkers/base.py
+++ b/pylint/checkers/base.py
@@ -508,6 +508,13 @@ functions, methods
'A call of assert on a tuple will always evaluate to true if '
'the tuple is not empty, and will always evaluate to false if '
'it is.'),
+ 'W0124': ('Following "as" with another context manager looks like a tuple.',
+ 'confusing-with-statement',
+ 'Emitted when a `with` statement component returns multiple values '
+ 'and uses name binding with `as` only for a part of those values, '
+ 'as in with ctx() as a, b. This can be misleading, since it\'s not '
+ 'clear if the context manager returns a tuple or if the node without '
+ 'a name binding is another context manager.'),
'C0121': ('Missing required attribute "%s"', # W0103
'missing-module-attribute',
'Used when an attribute required for modules is missing.'),
@@ -872,6 +879,36 @@ functions, methods
# everything else is not a proper sequence for reversed()
self.add_message('bad-reversed-sequence', node=node)
+ @check_messages('confusing-with-statement')
+ def visit_with(self, node):
+ if not PY3K:
+ # in Python 2 a "with" statement with multiple managers coresponds
+ # to multiple nested AST "With" nodes
+ pairs = []
+ parent_node = node.parent
+ if isinstance(parent_node, astroid.With):
+ # we only care about the direct parent, since this method
+ # gets called for each with node anyway
+ pairs.extend(parent_node.items)
+ pairs.extend(node.items)
+ else:
+ # in PY3K a "with" statement with multiple managers coresponds
+ # to one AST "With" node with multiple items
+ pairs = node.items
+ prev_pair = None
+ for pair in pairs:
+ if prev_pair is not None:
+ if (isinstance(prev_pair[1], astroid.AssName) and
+ (pair[1] is None and not isinstance(pair[0], astroid.CallFunc))):
+ # don't emit a message if the second is a function call
+ # there's no way that can be mistaken for a name assignment
+ if PY3K or node.lineno == node.parent.lineno:
+ # if the line number doesn't match
+ # we assume it's a nested "with"
+ self.add_message('confusing-with-statement', node=node)
+ prev_pair = pair
+
+
_NAME_TYPES = {
'module': (MOD_NAME_RGX, 'module'),
'const': (CONST_NAME_RGX, 'constant'),
diff --git a/pylint/test/functional/confusing_with_statement.py b/pylint/test/functional/confusing_with_statement.py
new file mode 100644
index 0000000..1444a97
--- /dev/null
+++ b/pylint/test/functional/confusing_with_statement.py
@@ -0,0 +1,27 @@
+# pylint: disable=undefined-variable,not-context-manager,missing-docstring
+
+# both context managers are named
+with one as first, two as second:
+ pass
+
+# first matched as tuple; this is ok
+with one as (first, second), third:
+ pass
+
+# the first context manager doesn't have as name
+with one, two as second:
+ pass
+
+# the second is a function call; this is ok
+with one as first, two():
+ pass
+
+# nested with statements; make sure no message is emited
+# this could be a false positive on Py2
+with one as first:
+ with two:
+ pass
+
+# ambiguous looking with statement
+with one as first, two: # [confusing-with-statement]
+ pass
diff --git a/pylint/test/functional/confusing_with_statement.txt b/pylint/test/functional/confusing_with_statement.txt
new file mode 100644
index 0000000..750ad71
--- /dev/null
+++ b/pylint/test/functional/confusing_with_statement.txt
@@ -0,0 +1 @@
+confusing-with-statement:26::Following "as" with another context manager looks like a tuple.