Skip to content

Commit

Permalink
Added a new option to the multiple-statement-per-line warning to allow
Browse files Browse the repository at this point in the history
if statements with single-line bodies on the same line. The option is off
by default.
  • Loading branch information
tmarek-google committed Nov 6, 2013
1 parent 9744f15 commit f1209f5
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 8 deletions.
3 changes: 3 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
38 changes: 31 additions & 7 deletions checkers/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'}
Expand Down Expand Up @@ -307,16 +311,19 @@ def visit_default(self, node):
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
Expand All @@ -340,6 +347,23 @@ def visit_default(self, node):
# 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)
Expand Down
21 changes: 21 additions & 0 deletions test/input/func_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,24 @@ def hop(context):
<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
3 changes: 3 additions & 0 deletions test/messages/func_format.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
40 changes: 39 additions & 1 deletion test/test_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@
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()

Expand Down Expand Up @@ -164,5 +165,42 @@ def test_known_values_tqstring(self):
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()

0 comments on commit f1209f5

Please sign in to comment.