diff --git a/ChangeLog b/ChangeLog index 5c389a399b..20e1cf4d57 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 1cf0edc610..d92813f695 100644 --- a/checkers/format.py +++ b/checkers/format.py @@ -193,6 +193,10 @@ class FormatChecker(BaseTokenChecker): 'default': r'^\s*(# )??$', 'help': ('Regexp for a line that is allowed to be longer than ' 'the limit.')}), + ('single-line-if-stmt', + {'default': False, 'type' : 'yn', 'metavar' : '', + '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' : '', 'help': 'Maximum number of lines in a module'} @@ -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 @@ -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) diff --git a/test/input/func_format.py b/test/input/func_format.py index a4eed23cce..828a180259 100644 --- a/test/input/func_format.py +++ b/test/input/func_format.py @@ -85,3 +85,24 @@ def hop(context): Drapeau vert\ %s' % 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 27b810774a..5ee36cda46 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 f14b4763d0..f74a65b07e 100644 --- a/test/test_format.py +++ b/test/test_format.py @@ -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() @@ -164,5 +165,42 @@ def test_known_values_tqstring(self): def test_known_values_tastring(self): self.assertEqual(check_line("print '''