summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTorsten Marek <shlomme@gmail.com>2014-04-17 14:00:21 +0200
committerTorsten Marek <shlomme@gmail.com>2014-04-17 14:00:21 +0200
commit4ec1bf99b1dabed62cbaa981a94f69df0d78613a (patch)
treea4fc5562a5201893acc55f228daf542f19d5c09a
parent3a8f94025d14cbd2d9547cdc75611ad6f105e67d (diff)
downloadpylint-4ec1bf99b1dabed62cbaa981a94f69df0d78613a.tar.gz
Add a check for indentation of continued lines, enforcing PEP8 style of continued
and hanging indents.
-rw-r--r--ChangeLog2
-rw-r--r--__pkginfo__.py4
-rw-r--r--checkers/format.py362
-rw-r--r--test/input/func_bad_continuation.py222
-rw-r--r--test/input/func_format_py27.py3
-rw-r--r--test/input/func_format_py_27.py3
-rw-r--r--test/input/func_noerror_new_style_class_py_30.py2
-rw-r--r--test/messages/func_bad_continuation.txt70
-rw-r--r--test/messages/func_format_py27.txt6
-rw-r--r--test/unittest_checker_format.py2
10 files changed, 647 insertions, 29 deletions
diff --git a/ChangeLog b/ChangeLog
index 93ba5a7..679a7ba 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,6 +2,8 @@ ChangeLog for Pylint
====================
--
+ * Add a new warning [bad-continuation] for badly indentend continued
+ lines.
* Extend the checking for unbalanced-tuple-unpacking and
unpacking-non-sequence to instance attribute unpacking as well.
diff --git a/__pkginfo__.py b/__pkginfo__.py
index ea67fe6..d0a9aac 100644
--- a/__pkginfo__.py
+++ b/__pkginfo__.py
@@ -45,8 +45,8 @@ classifiers = ['Development Status :: 4 - Beta',
'Programming Language :: Python :: 3',
'Topic :: Software Development :: Debuggers',
'Topic :: Software Development :: Quality Assurance',
- 'Topic :: Software Development :: Testing',
- ]
+ 'Topic :: Software Development :: Testing'
+ ]
long_desc = """\
diff --git a/checkers/format.py b/checkers/format.py
index 7ab6c7e..c957816 100644
--- a/checkers/format.py
+++ b/checkers/format.py
@@ -35,6 +35,7 @@ from pylint.checkers import BaseTokenChecker
from pylint.checkers.utils import check_messages
from pylint.utils import WarningScope, OPTION_RGX
+_CONTINUATION_BLOCK_OPENERS = ['elif', 'except', 'for', 'if', 'while', 'def', 'class']
_KEYWORD_TOKENS = ['assert', 'del', 'elif', 'except', 'for', 'if', 'in', 'not',
'raise', 'return', 'while', 'yield']
if sys.version_info < (3, 0):
@@ -45,6 +46,7 @@ _SPACED_OPERATORS = ['==', '<', '>', '!=', '<>', '<=', '>=',
'%=', '>>=', '<<=']
_OPENING_BRACKETS = ['(', '[', '{']
_CLOSING_BRACKETS = [')', ']', '}']
+_TAB_LENGTH = 8
_EOL = frozenset([tokenize.NEWLINE, tokenize.NL, tokenize.COMMENT])
@@ -77,6 +79,9 @@ MSGS = {
'bad-indentation',
'Used when an unexpected number of indentation\'s tabulations or '
'spaces has been found.'),
+ 'C0330': ('Wrong %s indentation%s.\n%s%s',
+ 'bad-continuation',
+ 'TODO'),
'W0312': ('Found indentation with %ss instead of %ss',
'mixed-indentation',
'Used when there are some mixed tabs and spaces in a module.'),
@@ -138,6 +143,254 @@ def _column_distance(token1, token2):
return token2[2][1] - token1[3][1]
+def _token_followed_by_eol(tokens, position):
+ return (tokens.type(position+1) == tokenize.NL or
+ tokens.type(position+1) == tokenize.COMMENT and
+ tokens.type(position+2) == tokenize.NL)
+
+
+def _get_indent_length(line):
+ """Return the length of the indentation on the given token's line."""
+ result = 0
+ for char in line:
+ if char == ' ':
+ result += 1
+ elif char == '\t':
+ result += _TAB_LENGTH
+ else:
+ break
+ return result
+
+
+def _get_indent_hint_line(bar_positions, bad_position):
+ """Return a line with |s for each of the positions in the given lists."""
+ if not bar_positions:
+ return ''
+ markers = [(pos, '|') for pos in bar_positions]
+ markers.append((bad_position, '^'))
+ markers.sort()
+ line = [' '] * (markers[-1][0] + 1)
+ for position, marker in markers:
+ line[position] = marker
+ return ''.join(line)
+
+
+class _ContinuedIndent(object):
+ __slots__ = ('valid_outdent_offsets',
+ 'valid_continuation_offsets',
+ 'context_type',
+ 'token',
+ 'position')
+
+ def __init__(self,
+ context_type,
+ token,
+ position,
+ valid_outdent_offsets,
+ valid_continuation_offsets):
+ self.valid_outdent_offsets = valid_outdent_offsets
+ self.valid_continuation_offsets = valid_continuation_offsets
+ self.context_type = context_type
+ self.position = position
+ self.token = token
+
+
+# The contexts for hanging indents.
+# A hanging indented dictionary value after :
+HANGING_DICT_VALUE = 'dict-value'
+# Hanging indentation in an expression.
+HANGING = 'hanging'
+# Hanging indentation in a block header.
+HANGING_BLOCK = 'hanging-block'
+# Continued indentation inside an expression.
+CONTINUED = 'continued'
+# Continued indentation in a block header.
+CONTINUED_BLOCK = 'continued-block'
+
+SINGLE_LINE = 'single'
+WITH_BODY = 'multi'
+
+_CONTINUATION_MSG_PARTS = {
+ HANGING_DICT_VALUE: ('hanging', ' in dict value'),
+ HANGING: ('hanging', ''),
+ HANGING_BLOCK: ('hanging', ' before block'),
+ CONTINUED: ('continued', ''),
+ CONTINUED_BLOCK: ('continued', ' before block'),
+}
+
+
+def _Offsets(*args):
+ """Valid indentation offsets for a continued line."""
+ return {a: None for a in args}
+
+
+def _BeforeBlockOffsets(single, with_body):
+ """Valid alternative indent offsets for continued lines before blocks.
+
+ :param single: Valid offset for statements on a single logical line.
+ :param with_body: Valid offset for statements on several lines.
+ """
+ return {single: SINGLE_LINE, with_body: WITH_BODY}
+
+
+class TokenWrapper(object):
+ """A wrapper for readable access to token information."""
+
+ def __init__(self, tokens):
+ self._tokens = tokens
+
+ def token(self, idx):
+ return self._tokens[idx][1]
+
+ def type(self, idx):
+ return self._tokens[idx][0]
+
+ def start_line(self, idx):
+ return self._tokens[idx][2][0]
+
+ def start_col(self, idx):
+ return self._tokens[idx][2][1]
+
+ def line(self, idx):
+ return self._tokens[idx][4]
+
+
+class ContinuedLineState(object):
+ """Tracker for continued indentation inside a logical line."""
+
+ def __init__(self, tokens, config):
+ self._line_start = -1
+ self._cont_stack = []
+ self._is_block_opener = False
+ self.retained_warnings = []
+ self._config = config
+ self._tokens = TokenWrapper(tokens)
+
+ @property
+ def has_content(self):
+ return bool(self._cont_stack)
+
+ @property
+ def _block_indent_size(self):
+ return len(self._config.indent_string.replace('\t', ' ' * _TAB_LENGTH))
+
+ @property
+ def _continuation_size(self):
+ return self._config.indent_after_paren
+
+ def handle_line_start(self, pos):
+ """Record the first non-junk token at the start of a line."""
+ if self._line_start > -1:
+ return
+ self._is_block_opener = self._tokens.token(pos) in _CONTINUATION_BLOCK_OPENERS
+ self._line_start = pos
+
+ def next_physical_line(self):
+ """Prepares the tracker for a new physical line (NL)."""
+ self._line_start = -1
+ self._is_block_opener = False
+
+ def next_logical_line(self):
+ """Prepares the tracker for a new logical line (NEWLINE).
+
+ A new logical line only starts with block indentation.
+ """
+ self.next_physical_line()
+ self.retained_warnings = []
+ self._cont_stack = []
+
+ def add_block_warning(self, token_position, state, valid_offsets):
+ self.retained_warnings.append((token_position, state, valid_offsets))
+
+ def get_valid_offsets(self, idx):
+ """"Returns the valid offsets for the token at the given position."""
+ # The closing brace on a dict or the 'for' in a dict comprehension may
+ # reset two indent levels because the dict value is ended implicitly
+ stack_top = -1
+ if self._tokens.token(idx) in ('}', 'for') and self._cont_stack[-1].token == ':':
+ stack_top = -2
+ indent = self._cont_stack[stack_top]
+ if self._tokens.token(idx) in _CLOSING_BRACKETS:
+ valid_offsets = indent.valid_outdent_offsets
+ else:
+ valid_offsets = indent.valid_continuation_offsets
+ return indent, valid_offsets.copy()
+
+ def _hanging_indent_after_bracket(self, bracket, position):
+ """Extracts indentation information for a hanging indent."""
+ indentation = _get_indent_length(self._tokens.line(position))
+ if self._is_block_opener and self._continuation_size == self._block_indent_size:
+ return _ContinuedIndent(
+ HANGING_BLOCK,
+ bracket,
+ position,
+ _Offsets(indentation + self._continuation_size, indentation),
+ _BeforeBlockOffsets(indentation + self._continuation_size,
+ indentation + self._continuation_size * 2))
+ elif bracket == ':':
+ if self._cont_stack[-1].context_type == CONTINUED:
+ # If the dict key was on the same line as the open brace, the new
+ # correct indent should be relative to the key instead of the
+ # current indent level
+ paren_align = self._cont_stack[-1].valid_outdent_offsets
+ next_align = self._cont_stack[-1].valid_continuation_offsets.copy()
+ next_align[next_align.keys()[0] + self._continuation_size] = True
+ else:
+ next_align = _Offsets(indentation + self._continuation_size, indentation)
+ paren_align = _Offsets(indentation + self._continuation_size, indentation)
+ return _ContinuedIndent(HANGING_DICT_VALUE, bracket, position, paren_align, next_align)
+ else:
+ return _ContinuedIndent(
+ HANGING,
+ bracket,
+ position,
+ _Offsets(indentation, indentation + self._continuation_size),
+ _Offsets(indentation + self._continuation_size))
+
+ def _continuation_inside_bracket(self, bracket, pos):
+ """Extracts indentation information for a continued indent."""
+ indentation = _get_indent_length(self._tokens.line(pos))
+ if self._is_block_opener and self._tokens.start_col(pos+1) - indentation == self._block_indent_size:
+ return _ContinuedIndent(
+ CONTINUED_BLOCK,
+ bracket,
+ pos,
+ _Offsets(self._tokens.start_col(pos)),
+ _BeforeBlockOffsets(self._tokens.start_col(pos+1),
+ self._tokens.start_col(pos+1) + self._continuation_size))
+ else:
+ return _ContinuedIndent(
+ CONTINUED,
+ bracket,
+ pos,
+ _Offsets(self._tokens.start_col(pos)),
+ _Offsets(self._tokens.start_col(pos+1)))
+
+ def pop_token(self):
+ self._cont_stack.pop()
+
+ def push_token(self, token, position):
+ """Pushes a new token for continued indentation on the stack.
+
+ Tokens that can modify continued indentation offsets are:
+ * opening brackets
+ * 'lambda'
+ * : inside dictionaries
+
+ push_token relies on the caller to filter out those
+ interesting tokens.
+
+ :param token: The concrete token
+ :param position: The position of the token in the stream.
+ """
+ if _token_followed_by_eol(self._tokens, position):
+ self._cont_stack.append(
+ self._hanging_indent_after_bracket(token, position))
+ else:
+ self._cont_stack.append(
+ self._continuation_inside_bracket(token, position))
+
+
class FormatChecker(BaseTokenChecker):
"""checks for :
* unauthorized constructions
@@ -180,11 +433,25 @@ class FormatChecker(BaseTokenChecker):
{'default' : ' ', 'type' : "string", 'metavar' : '<string>',
'help' : 'String used as indentation unit. This is usually \
" " (4 spaces) or "\\t" (1 tab).'}),
+ ('indent-after-paren',
+ {'type': 'int', 'metavar': '<int>', 'default': 4,
+ 'help': 'Number of spaces of indent required inside a hanging '
+ ' or continued line.'}),
)
+
def __init__(self, linter=None):
BaseTokenChecker.__init__(self, linter)
self._lines = None
self._visited_lines = None
+ self._bracket_stack = [None]
+
+ def _pop_token(self):
+ self._bracket_stack.pop()
+ self._current_line.pop_token()
+
+ def _push_token(self, token, idx):
+ self._bracket_stack.append(token)
+ self._current_line.push_token(token, idx)
def new_line(self, tok_type, line, line_num, junk):
"""a new line has been encountered, process it if necessary"""
@@ -211,6 +478,8 @@ class FormatChecker(BaseTokenChecker):
start: int; the position of the keyword in the token list.
"""
# If the next token is not a paren, we're fine.
+ if self._inside_brackets(':') and tokens[start][1] == 'for':
+ self._pop_token()
if tokens[start+1][1] != '(':
return
@@ -270,7 +539,7 @@ class FormatChecker(BaseTokenChecker):
return
def _opening_bracket(self, tokens, i):
- self._bracket_stack.append(tokens[i][1])
+ self._push_token(tokens[i][1], i)
# Special case: ignore slices
if tokens[i][1] == '[' and tokens[i+1][1] == ':':
return
@@ -283,7 +552,9 @@ class FormatChecker(BaseTokenChecker):
self._check_space(tokens, i, (_IGNORE, _MUST_NOT))
def _closing_bracket(self, tokens, i):
- self._bracket_stack.pop()
+ if self._inside_brackets(':'):
+ self._pop_token()
+ self._pop_token()
# Special case: ignore slices
if tokens[i-1][1] == ':' and tokens[i][1] == ']':
return
@@ -302,7 +573,7 @@ class FormatChecker(BaseTokenChecker):
self._check_space(tokens, i, (_MUST, _MUST))
def _open_lambda(self, tokens, i): # pylint:disable=unused-argument
- self._bracket_stack.append('lambda')
+ self._push_token('lambda', i)
def _handle_colon(self, tokens, i):
# Special case: ignore slices
@@ -316,7 +587,9 @@ class FormatChecker(BaseTokenChecker):
self._check_space(tokens, i, policy)
if self._inside_brackets('lambda'):
- self._bracket_stack.pop()
+ self._pop_token()
+ elif self._inside_brackets('{'):
+ self._push_token(':', i)
def _handle_comma(self, tokens, i):
# Only require a following whitespace if this is
@@ -325,6 +598,8 @@ class FormatChecker(BaseTokenChecker):
self._check_space(tokens, i, (_MUST_NOT, _IGNORE))
else:
self._check_space(tokens, i, (_MUST_NOT, _MUST))
+ if self._inside_brackets(':'):
+ self._pop_token()
def _check_surrounded_by_space(self, tokens, i):
"""Check that a binary operator is surrounded by exactly one space."""
@@ -419,10 +694,10 @@ class FormatChecker(BaseTokenChecker):
regular expression).
"""
self._bracket_stack = [None]
- indent = tokenize.INDENT
- dedent = tokenize.DEDENT
- newline = tokenize.NEWLINE
+ # TODO(tmarek): extra to toplevel
junk = (tokenize.COMMENT, tokenize.NL)
+ indent_junk = set(junk) | set([tokenize.NEWLINE, tokenize.INDENT, tokenize.DEDENT])
+
indents = [0]
check_equal = 0
line_num = 0
@@ -431,6 +706,8 @@ class FormatChecker(BaseTokenChecker):
self._visited_lines = {}
new_line_delay = False
token_handlers = self._prepare_token_dispatcher()
+
+ self._current_line = ContinuedLineState(tokens, self.config)
for idx, (tok_type, token, start, _, line) in enumerate(tokens):
if new_line_delay:
new_line_delay = False
@@ -448,9 +725,9 @@ class FormatChecker(BaseTokenChecker):
new_line_delay = True
else:
self.new_line(tok_type, line, line_num, junk)
- if tok_type not in (indent, dedent, newline) + junk:
+ if tok_type not in indent_junk:
previous = tok_type, token, start[0]
-
+ self._current_line.handle_line_start(idx)
if tok_type == tokenize.OP:
if token == '<>':
self.add_message('old-ne-operator', line=line_num)
@@ -458,28 +735,31 @@ class FormatChecker(BaseTokenChecker):
if token.endswith('l'):
self.add_message('lowercase-l-suffix', line=line_num)
- elif tok_type == newline:
+ elif tok_type == tokenize.NEWLINE:
# a program statement, or ENDMARKER, will eventually follow,
# after some (possibly empty) run of tokens of the form
# (NL | COMMENT)* (INDENT | DEDENT+)?
# If an INDENT appears, setting check_equal is wrong, and will
# be undone when we see the INDENT.
- check_equal = 1
+ check_equal = True
- elif tok_type == indent:
- check_equal = 0
+ self._process_retained_warnings(TokenWrapper(tokens), idx)
+ self._current_line.next_logical_line()
+ elif tok_type == tokenize.INDENT:
+ check_equal = False
self.check_indent_level(token, indents[-1]+1, line_num)
indents.append(indents[-1]+1)
-
- elif tok_type == dedent:
+ elif tok_type == tokenize.DEDENT:
# there's nothing we need to check here! what's important is
# that when the run of DEDENTs ends, the indentation of the
# program statement (or ENDMARKER) that triggered the run is
# equal to what's left at the top of the indents stack
- check_equal = 1
+ check_equal = True
if len(indents) > 1:
del indents[-1]
-
+ elif tok_type == tokenize.NL:
+ self._check_continued_indentation(TokenWrapper(tokens), idx+1)
+ self._current_line.next_physical_line()
elif check_equal and tok_type not in junk:
# this is the first "real token" following a NEWLINE, so it
# must be the first token of the next program statement, or an
@@ -487,7 +767,7 @@ class FormatChecker(BaseTokenChecker):
# for this statement; in the case of ENDMARKER, line is an empty
# string, so will properly match the empty string with which the
# "indents" stack was seeded
- check_equal = 0
+ check_equal = False
self.check_indent_level(line, indents[-1], line_num)
try:
@@ -501,6 +781,52 @@ class FormatChecker(BaseTokenChecker):
if line_num > self.config.max_module_lines:
self.add_message('too-many-lines', args=line_num, line=1)
+ def _process_retained_warnings(self, tokens, current_pos):
+ single_line_block_stmt = not (
+ tokens.token(current_pos-1) == ':' or
+ current_pos > 1 and tokens.token(current_pos-2) == ':'
+ and tokens.type(current_pos-1) == tokenize.COMMENT)
+
+ for indent_pos, state, offsets in self._current_line.retained_warnings:
+ block_type = offsets[tokens.start_col(indent_pos)]
+ hints = dict((k, v) for k, v in offsets.iteritems()
+ if v != block_type)
+ if single_line_block_stmt and block_type == WITH_BODY:
+ self._add_continuation_message(state, hints, tokens, indent_pos)
+ elif not single_line_block_stmt and block_type == SINGLE_LINE:
+ self._add_continuation_message(state, hints, tokens, indent_pos)
+
+ def _check_continued_indentation(self, tokens, next_idx):
+ # Do not issue any warnings if the next line is empty.
+ if not self._current_line.has_content or tokens.type(next_idx) == tokenize.NL:
+ return
+
+ state, valid_offsets = self._current_line.get_valid_offsets(next_idx)
+ # Special handling for hanging comments. If the last line ended with a
+ # comment and the new line contains only a comment, the line may also be
+ # indented to the start of the previous comment.
+ if (tokens.type(next_idx) == tokenize.COMMENT and
+ tokens.type(next_idx-2) == tokenize.COMMENT):
+ valid_offsets[tokens.start_col(next_idx-2)] = True
+
+ # We can only decide if the indentation of a continued line before opening
+ # a new block is valid once we know of the body of the block is on the
+ # same line as the block opener. Since the token processing is single-pass,
+ # emitting those warnings is delayed until the block opener is processed.
+ if (state.context_type in (HANGING_BLOCK, CONTINUED_BLOCK)
+ and tokens.start_col(next_idx) in valid_offsets):
+ self._current_line.add_block_warning(next_idx, state, valid_offsets)
+ elif tokens.start_col(next_idx) not in valid_offsets:
+ self._add_continuation_message(state, valid_offsets, tokens, next_idx)
+
+ def _add_continuation_message(self, state, offsets, tokens, position):
+ readable_type, readable_position = _CONTINUATION_MSG_PARTS[state.context_type]
+ hint_line = _get_indent_hint_line(offsets, tokens.start_col(position))
+ self.add_message(
+ 'bad-continuation',
+ line=tokens.start_line(position),
+ args=(readable_type, readable_position, tokens.line(position), hint_line))
+
@check_messages('multiple-statements')
def visit_default(self, node):
"""check the node line number and check it if not yet done"""
diff --git a/test/input/func_bad_continuation.py b/test/input/func_bad_continuation.py
new file mode 100644
index 0000000..f0787de
--- /dev/null
+++ b/test/input/func_bad_continuation.py
@@ -0,0 +1,222 @@
+"""Regression test case for bad-continuation."""
+
+__revision__ = 1
+
+# Various alignment for brackets
+print [
+ 1, 2, 3
+]
+print [
+ 1, 2, 3
+ ]
+print [
+ 1, 2, 3
+ ] # [bad-continuation]
+
+# Alignment inside literals
+W0 = [1, 2, 3,
+ 4, 5, 6,
+ 7, # [bad-continuation]
+ 8, 9, 10,
+ 11, 12, 13,
+ # and a comment
+ 14, 15, 16]
+
+W1 = {
+ 'a': 1,
+ 'b': 2, # [bad-continuation]
+ 'c': 3,
+ }
+
+W2 = {
+ 'a': 1,
+ 'b': 2, # [bad-continuation]
+ 'c': 3,
+ }
+
+W2 = ['some', 'contents' # with a continued comment that may be aligned
+ # under the previous comment (optionally)
+ 'and',
+ 'more', # but this
+ # [bad-continuation] is not accepted
+ 'contents', # nor this. [bad-continuation]
+ ]
+
+# Values in dictionaries should be indented 4 spaces further if they are on a
+# different line than their key
+W4 = {
+ 'key1':
+ 'value1', # Grandfather in the old style
+ 'key2':
+ 'value2', # [bad-continuation]
+ 'key3':
+ 'value3', # Comma here
+ }
+
+# And should follow the same rules as continuations within parens
+W5 = {
+ 'key1': 'long value'
+ 'long continuation',
+ 'key2': 'breaking'
+ 'wrong', # [bad-continuation]
+ 'key3': 2*(
+ 2+2),
+ 'key4': ('parenthesis',
+ 'continuation') # No comma here
+ }
+
+# Allow values to line up with their keys when the key is next to the brace
+W6 = {'key1':
+ 'value1',
+ 'key2':
+ 'value2',
+ }
+
+# Or allow them to be indented
+W7 = {'key1':
+ 'value1',
+ 'key2':
+ 'value2'
+ }
+
+# Bug that caused a warning on the previous two cases permitted these odd
+# incorrect indentations
+W8 = {'key1':
+'value1', # [bad-continuation]
+ }
+
+W9 = {'key1':
+ 'value1', # [bad-continuation]
+ }
+
+# Dictionary comprehensions should not require extra indentation when breaking
+# before the 'for', which is not part of the value
+C1 = {'key{}'.format(x): 'value{}'.format(x)
+ for x in range(3)}
+
+C2 = {'key{}'.format(x): 'value{}'.format(x) for x in
+ range(3)}
+
+# Dictionary comprehensions with multiple loops broken in different places
+C3 = {x*y: (x, y) for x in range(3) for y in range(3)}
+
+C4 = {x*y: (x, y)
+ for x in range(3) for y in range(3)}
+
+C5 = {x*y: (x, y) for x
+ in range(3) for y in range(3)}
+
+C6 = {x*y: (x, y) for x in range(3)
+ for y in range(3)}
+
+C7 = {key:
+ key ** 2
+ for key in range(10)}
+
+C8 = {
+ key: key ** 2
+ for key in range(10)}
+
+# Misaligned cases for dict comprehensions
+C9 = {'key{}'.format(x): 'value{}'.format(x)
+ for x in range(3)} # [bad-continuation]
+
+C9 = {'key{}'.format(x): 'value{}'.format(x)
+ for x in range(3)} # [bad-continuation]
+
+# Alignment of arguments in function definitions
+def continue1(some_arg,
+ some_other_arg):
+ """A function with well-aligned arguments."""
+ print some_arg, some_other_arg
+
+
+def continue2(
+ some_arg,
+ some_other_arg):
+ """A function with well-aligned arguments."""
+ print some_arg, some_other_arg
+
+def continue3(
+ some_arg, # [bad-continuation]
+ some_other_arg): # [bad-continuation]
+ """A function with misaligned arguments"""
+ print some_arg, some_other_arg
+
+def continue4(
+ arg1,
+ arg2): print arg1, arg2
+
+
+def callee(*args):
+ """noop"""
+ print args
+
+
+callee(
+ "a",
+ "b"
+ )
+
+callee("a",
+ "b") # [bad-continuation]
+
+callee(5, {'a': 'b',
+ 'c': 'd'})
+
+if (
+ 1
+ ): pass
+
+if (
+ 1
+): pass
+if (
+ 1
+ ): pass # [bad-continuation]
+
+if (1 and
+ 2): # [bad-continuation]
+ pass
+
+while (1 and
+ 2):
+ pass
+
+while (1 and
+ 2 and # [bad-continuation]
+ 3):
+ pass
+
+if (
+ 2): pass # [bad-continuation]
+
+if (1 or
+ 2 or
+ 3): pass
+
+if (1 or
+ 2 or # [bad-continuation]
+ 3): print 1, 2
+
+if (1 and
+ 2): pass # [bad-continuation]
+
+if (
+ 2): pass
+
+if (
+ 2): # [bad-continuation]
+ pass
+
+L1 = (lambda a,
+ b: a + b)
+
+if not (1 and
+ 2):
+ print 3
+
+if not (1 and
+ 2): # [bad-continuation]
+ print 3
+
diff --git a/test/input/func_format_py27.py b/test/input/func_format_py27.py
index 828a180..46d5cfe 100644
--- a/test/input/func_format_py27.py
+++ b/test/input/func_format_py27.py
@@ -79,8 +79,7 @@ def hop(context):
return ['''<a id="sendbutton" href="javascript: $('%(domid)s').submit()">
<img src="%(sendimgpath)s" alt="%(send)s"/>%(send)s</a>''' % context,
'''<a id="cancelbutton" href="javascript: history.back()">
-<img src="%(cancelimgpath)s" alt="%(cancel)s"/>%(cancel)s</a>''' % context,
- ]
+<img src="%(cancelimgpath)s" alt="%(cancel)s"/>%(cancel)s</a>''' % context]
titreprojet = '<tr><td colspan="10">\
<img src="images/drapeau_vert.png" alt="Drapeau vert" />\
<strong>%s</strong></td></tr>' % aaaa
diff --git a/test/input/func_format_py_27.py b/test/input/func_format_py_27.py
index f062f27..95f7fde 100644
--- a/test/input/func_format_py_27.py
+++ b/test/input/func_format_py_27.py
@@ -79,8 +79,7 @@ def hop(context):
return ['''<a id="sendbutton" href="javascript: $('%(domid)s').submit()">
<img src="%(sendimgpath)s" alt="%(send)s"/>%(send)s</a>''' % context,
'''<a id="cancelbutton" href="javascript: history.back()">
-<img src="%(cancelimgpath)s" alt="%(cancel)s"/>%(cancel)s</a>''' % context,
- ]
+<img src="%(cancelimgpath)s" alt="%(cancel)s"/>%(cancel)s</a>''' % context]
titreprojet = '<tr><td colspan="10">\
<img src="images/drapeau_vert.png" alt="Drapeau vert" />\
<strong>%s</strong></td></tr>' % aaaa
diff --git a/test/input/func_noerror_new_style_class_py_30.py b/test/input/func_noerror_new_style_class_py_30.py
index 87bfb9e..9e55277 100644
--- a/test/input/func_noerror_new_style_class_py_30.py
+++ b/test/input/func_noerror_new_style_class_py_30.py
@@ -18,7 +18,7 @@ class File(file):
super(File, self).__init__(name, mode, buffering)
if self.verbose:
print "File %s is opened. The mode is: %s" % (self.name,
-self.mode)
+ self.mode)
#
def write(self, a_string):
diff --git a/test/messages/func_bad_continuation.txt b/test/messages/func_bad_continuation.txt
new file mode 100644
index 0000000..ba6516d
--- /dev/null
+++ b/test/messages/func_bad_continuation.txt
@@ -0,0 +1,70 @@
+C: 14: Wrong hanging indentation.
+ ] # [bad-continuation]
+| ^|
+C: 19: Wrong continued indentation.
+ 7, # [bad-continuation]
+ | ^
+C: 27: Wrong hanging indentation.
+ 'b': 2, # [bad-continuation]
+ ^|
+C: 33: Wrong hanging indentation.
+ 'b': 2, # [bad-continuation]
+ ^|
+C: 41: Wrong continued indentation.
+ # [bad-continuation] is not accepted
+ | | ^
+C: 42: Wrong continued indentation.
+ 'contents', # nor this. [bad-continuation]
+ | ^
+C: 51: Wrong hanging indentation in dict value.
+ 'value2', # [bad-continuation]
+ | ^ |
+C: 61: Wrong continued indentation.
+ 'wrong', # [bad-continuation]
+ ^ |
+C: 85: Wrong hanging indentation in dict value.
+'value1', # [bad-continuation]
+^ | |
+C: 89: Wrong hanging indentation in dict value.
+ 'value1', # [bad-continuation]
+ ^ | |
+C:122: Wrong continued indentation.
+ for x in range(3)} # [bad-continuation]
+ ^ |
+C:125: Wrong continued indentation.
+ for x in range(3)} # [bad-continuation]
+ | ^
+C:141: Wrong hanging indentation before block.
+ some_arg, # [bad-continuation]
+ ^ |
+C:142: Wrong hanging indentation before block.
+ some_other_arg): # [bad-continuation]
+ ^ |
+C:146:continue4: Missing function docstring
+C:162: Wrong continued indentation.
+ "b") # [bad-continuation]
+ ^ |
+C:176: Wrong hanging indentation before block.
+ ): pass # [bad-continuation]
+| ^|
+C:179: Wrong continued indentation before block.
+ 2): # [bad-continuation]
+ ^ |
+C:187: Wrong continued indentation.
+ 2 and # [bad-continuation]
+ | ^
+C:192: Wrong hanging indentation before block.
+ 2): pass # [bad-continuation]
+ ^ | |
+C:199: Wrong continued indentation before block.
+ 2 or # [bad-continuation]
+ |^ |
+C:203: Wrong continued indentation before block.
+ 2): pass # [bad-continuation]
+ ^ | |
+C:209: Wrong hanging indentation before block.
+ 2): # [bad-continuation]
+ ^ | |
+C:220: Wrong continued indentation.
+ 2): # [bad-continuation]
+ ^ |
diff --git a/test/messages/func_format_py27.txt b/test/messages/func_format_py27.txt
index ce5df81..f71cee8 100644
--- a/test/messages/func_format_py27.txt
+++ b/test/messages/func_format_py27.txt
@@ -41,6 +41,6 @@ C: 71: Exactly one space required before assignment
C: 73: Exactly one space required around assignment
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
+C: 90: More than one statement on a single line
+C:104: More than one statement on a single line
+C:107: More than one statement on a single line
diff --git a/test/unittest_checker_format.py b/test/unittest_checker_format.py
index 8f1be78..b9fba5c 100644
--- a/test/unittest_checker_format.py
+++ b/test/unittest_checker_format.py
@@ -135,7 +135,7 @@ class CheckSpaceTest(CheckerTestCase):
good_cases = [
'(a)\n',
'(a * (b + c))\n',
- '( #\na)\n',
+ '(#\n a)\n',
]
with self.assertNoMessages():
for code in good_cases: