summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTorsten Marek <tmarek@google.com>2013-11-05 22:26:57 -0800
committerTorsten Marek <tmarek@google.com>2013-11-05 22:26:57 -0800
commite65e963195bcaa56aa4f87ca2de99538fc412310 (patch)
treeb6f4abaa20c4218d2fd2dc4ac21d9bd6f9840740
parentc3a874766f86e7ded87a2e386be3b0f3ab8dc638 (diff)
downloadpylint-e65e963195bcaa56aa4f87ca2de99538fc412310.tar.gz
Added a new option to the multiple-statement-per-line warning to allow
if statements with single-line bodies on the same line. The option is off by default.
-rw-r--r--ChangeLog3
-rw-r--r--checkers/format.py38
-rw-r--r--test/input/func_format.py21
-rw-r--r--test/messages/func_format.txt3
-rw-r--r--test/test_format.py40
5 files changed, 97 insertions, 8 deletions
diff --git a/ChangeLog b/ChangeLog
index 5c389a3..20e1cf4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -5,6 +5,9 @@ ChangeLog for Pylint
* Avoid false used-before-assignment for except handler defined
identifier used on the same line (#111)
+ * Add a new option for the multi-statement warning to
+ allow single-line if statements.
+
* Add 'bad-context-manager' error, checking that '__exit__'
special method accepts the right number of arguments.
diff --git a/checkers/format.py b/checkers/format.py
index 1cf0edc..d92813f 100644
--- a/checkers/format.py
+++ b/checkers/format.py
@@ -193,6 +193,10 @@ class FormatChecker(BaseTokenChecker):
'default': r'^\s*(# )?<?https?://\S+>?$',
'help': ('Regexp for a line that is allowed to be longer than '
'the limit.')}),
+ ('single-line-if-stmt',
+ {'default': False, 'type' : 'yn', 'metavar' : '<y_or_n>',
+ 'help' : ('Allow the body of an if to be on the same '
+ 'line as the test if there is no else.')}),
('max-module-lines',
{'default' : 1000, 'type' : 'int', 'metavar' : '<int>',
'help': 'Maximum number of lines in a module'}
@@ -307,16 +311,19 @@ class FormatChecker(BaseTokenChecker):
if prev_sibl is not None:
prev_line = prev_sibl.fromlineno
else:
- prev_line = node.parent.statement().fromlineno
+ # The line on which a finally: occurs in a try/finally
+ # is not directly represented in the AST. We infer it
+ # by taking the last line of the body and adding 1, which
+ # should be the line of finally:
+ if (isinstance(node.parent, nodes.TryFinally)
+ and node in node.parent.finalbody):
+ prev_line = node.parent.body[0].tolineno + 1
+ else:
+ prev_line = node.parent.statement().fromlineno
line = node.fromlineno
assert line, node
if prev_line == line and self._visited_lines.get(line) != 2:
- # py2.5 try: except: finally:
- if not (isinstance(node, nodes.TryExcept)
- and isinstance(node.parent, nodes.TryFinally)
- and node.fromlineno == node.parent.fromlineno):
- self.add_message('C0321', node=node)
- self._visited_lines[line] = 2
+ self._check_multi_statement_line(node, line)
return
if line in self._visited_lines:
return
@@ -340,6 +347,23 @@ class FormatChecker(BaseTokenChecker):
# FIXME: internal error !
pass
+ def _check_multi_statement_line(self, node, line):
+ """Check for lines containing multiple statements."""
+ # Do not warn about multiple nested context managers
+ # in with statements.
+ if isinstance(node, nodes.With):
+ return
+ # For try... except... finally..., the two nodes
+ # appear to be on the same line due to how the AST is built.
+ if (isinstance(node, nodes.TryExcept) and
+ isinstance(node.parent, nodes.TryFinally)):
+ return
+ if (isinstance(node.parent, nodes.If) and not node.parent.orelse
+ and self.config.single_line_if_stmt):
+ return
+ self.add_message('C0321', node=node)
+ self._visited_lines[line] = 2
+
@check_messages('W0333')
def visit_backquote(self, node):
self.add_message('W0333', node=node)
diff --git a/test/input/func_format.py b/test/input/func_format.py
index a4eed23..828a180 100644
--- a/test/input/func_format.py
+++ b/test/input/func_format.py
@@ -85,3 +85,24 @@ titreprojet = '<tr><td colspan="10">\
<img src="images/drapeau_vert.png" alt="Drapeau vert" />\
<strong>%s</strong></td></tr>' % aaaa
+with open('a') as a, open('b') as b:
+ pass
+
+with open('a') as a, open('b') as b: pass # multiple-statements
+
+# Well-formatted try-except-finally block.
+try:
+ pass
+except IOError, e:
+ print e
+finally:
+ pass
+
+try:
+ pass
+except IOError, e:
+ print e
+finally: pass # multiple-statements
+
+# This is not allowed by the default configuration.
+if True: print False
diff --git a/test/messages/func_format.txt b/test/messages/func_format.txt
index 27b8107..5ee36cd 100644
--- a/test/messages/func_format.txt
+++ b/test/messages/func_format.txt
@@ -29,3 +29,6 @@ C: 71:_gc_debug: Operator not preceded by a space
C: 73:_gc_debug: Operator not preceded by a space
ocount[obj.__class__]=1
^
+C: 91: More than one statement on a single line
+C:105: More than one statement on a single line
+C:108: More than one statement on a single line
diff --git a/test/test_format.py b/test/test_format.py
index f14b476..f74a65b 100644
--- a/test/test_format.py
+++ b/test/test_format.py
@@ -21,10 +21,11 @@ import re
from os import linesep
from logilab.common.testlib import TestCase, unittest_main
+from astroid import test_utils
from pylint.checkers.format import *
-from pylint.testutils import TestReporter
+from pylint.testutils import TestReporter, CheckerTestCase, Message
REPORTER = TestReporter()
@@ -164,5 +165,42 @@ class ChecklineFunctionTest(TestCase):
def test_known_values_tastring(self):
self.assertEqual(check_line("print '''<a='=')\n'''"), None)
+
+class MultiStatementLineTest(CheckerTestCase):
+ CHECKER_CLASS = FormatChecker
+
+ def testSingleLineIfStmts(self):
+ stmt = test_utils.extract_node("""
+ if True: pass #@
+ """)
+ with self.assertAddsMessages(Message('C0321', node=stmt.body[0])):
+ self.checker.process_tokens([])
+ self.checker.visit_default(stmt.body[0])
+ self.checker.config.single_line_if_stmt = True
+ with self.assertNoMessages():
+ self.checker.process_tokens([])
+ self.checker.visit_default(stmt.body[0])
+ stmt = test_utils.extract_node("""
+ if True: pass #@
+ else:
+ pass
+ """)
+ with self.assertAddsMessages(Message('C0321', node=stmt.body[0])):
+ self.checker.process_tokens([])
+ self.checker.visit_default(stmt.body[0])
+
+ def testTryExceptFinallyNoMultipleStatement(self):
+ tree = test_utils.extract_node("""
+ try: #@
+ pass
+ except:
+ pass
+ finally:
+ pass""")
+ with self.assertNoMessages():
+ self.checker.process_tokens([])
+ self.checker.visit_default(tree.body[0])
+
+
if __name__ == '__main__':
unittest_main()