diff options
author | Torsten Marek <shlomme@gmail.com> | 2014-04-17 14:00:21 +0200 |
---|---|---|
committer | Torsten Marek <shlomme@gmail.com> | 2014-04-17 14:00:21 +0200 |
commit | 4ec1bf99b1dabed62cbaa981a94f69df0d78613a (patch) | |
tree | a4fc5562a5201893acc55f228daf542f19d5c09a | |
parent | 3a8f94025d14cbd2d9547cdc75611ad6f105e67d (diff) | |
download | pylint-4ec1bf99b1dabed62cbaa981a94f69df0d78613a.tar.gz |
Add a check for indentation of continued lines, enforcing PEP8 style of continued
and hanging indents.
-rw-r--r-- | ChangeLog | 2 | ||||
-rw-r--r-- | __pkginfo__.py | 4 | ||||
-rw-r--r-- | checkers/format.py | 362 | ||||
-rw-r--r-- | test/input/func_bad_continuation.py | 222 | ||||
-rw-r--r-- | test/input/func_format_py27.py | 3 | ||||
-rw-r--r-- | test/input/func_format_py_27.py | 3 | ||||
-rw-r--r-- | test/input/func_noerror_new_style_class_py_30.py | 2 | ||||
-rw-r--r-- | test/messages/func_bad_continuation.txt | 70 | ||||
-rw-r--r-- | test/messages/func_format_py27.txt | 6 | ||||
-rw-r--r-- | test/unittest_checker_format.py | 2 |
10 files changed, 647 insertions, 29 deletions
@@ -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: |