diff --git a/pycodestyle.py b/pycodestyle.py index 17fac7a8..5062cb59 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -58,6 +58,8 @@ import tokenize import warnings import bisect +from collections import defaultdict +from copy import deepcopy try: from functools import lru_cache @@ -637,12 +639,13 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, # for each depth, memorize the hanging indentation hangs = [None] # visual indents - indent_chances = {} - last_indent = tokens[0][2] + indent_chances_stack = [] + indent_chances = defaultdict(lambda: set()) + last_indent = [tokens[0][2][1]] visual_indent = None last_token_multiline = False # for each depth, memorize the visual indent column - indent = [last_indent[1]] + indent = [last_indent[-1]] if verbose >= 3: print(">>> " + tokens[0][4].rstrip()) @@ -655,7 +658,7 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, if newline: # this is the beginning of a continuation line. - last_indent = start + last_indent.append(start[1]) if verbose >= 3: print("... " + line.rstrip()) @@ -676,7 +679,8 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, # is there any chance of visual indent? visual_indent = (not close_bracket and hang > 0 and indent_chances.get(start[1])) - + visual_indent = visual_indent or set() + assert isinstance(visual_indent, set) if close_bracket and indent[depth]: # closing bracket for visual indent if start[1] != indent[depth]: @@ -688,7 +692,7 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, if hang_closing: yield start, "E133 closing bracket is missing indentation" elif indent[depth] and start[1] < indent[depth]: - if visual_indent is not True: + if not ({str, True} & visual_indent): # visual indent is broken yield (start, "E128 continuation line " "under-indented for visual indent") @@ -699,10 +703,10 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, yield (start, "E123 closing bracket does not match " "indentation of opening bracket's line") hangs[depth] = hang - elif visual_indent is True: + elif True in visual_indent: # visual indent is verified indent[depth] = start[1] - elif visual_indent in (text, str): + elif visual_indent & {text, str}: # ignore token lined up with matching one from a # previous line pass @@ -727,19 +731,19 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, token_type not in (tokenize.NL, tokenize.COMMENT) and not indent[depth]): indent[depth] = start[1] - indent_chances[start[1]] = True + indent_chances[start[1]].add(True) if verbose >= 4: print("bracket depth %s indent to %s" % (depth, start[1])) # deal with implicit string concatenation elif (token_type in (tokenize.STRING, tokenize.COMMENT) or text in ('u', 'ur', 'b', 'br')): - indent_chances[start[1]] = str + indent_chances[start[1]].add(str) # visual indent after assert/raise/with elif not row and not depth and text in ["assert", "raise", "with"]: - indent_chances[end[1] + 1] = True + indent_chances[end[1] + 1].add(True) # special case for the "if" statement because len("if (") == 4 elif not indent_chances and not row and not depth and text == 'if': - indent_chances[end[1] + 1] = True + indent_chances[end[1] + 1].add(True) elif text == ':' and line[end[1]:].isspace(): open_rows[depth].append(row) @@ -747,6 +751,7 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, if token_type == tokenize.OP: if text in '([{': depth += 1 + indent_chances_stack.append(deepcopy(indent_chances)) indent.append(0) hangs.append(None) if len(open_rows) == depth: @@ -758,26 +763,31 @@ def continued_indentation(logical_line, tokens, indent_level, hang_closing, (depth, start[1], indent[depth])) elif text in ')]}' and depth > 0: # parent indents should not be more than this one - prev_indent = indent.pop() or last_indent[1] + prev_indent = indent.pop() or last_indent[-1] + if row > open_rows[depth][0]: + pass hangs.pop() for d in range(depth): if indent[d] > prev_indent: indent[d] = 0 + prev_indent_chances = indent_chances + indent_chances = indent_chances_stack.pop() for ind in list(indent_chances): - if ind >= prev_indent: + if ind > prev_indent: del indent_chances[ind] + for ind in list(prev_indent_chances): + if ind < prev_indent and ind not in indent_chances: + indent_chances[ind] = prev_indent_chances[ind] del open_rows[depth + 1:] depth -= 1 if depth: - indent_chances[indent[depth]] = True + indent_chances[indent[depth]].add(True) for idx in range(row, -1, -1): if parens[idx]: parens[idx] -= 1 break assert len(indent) == depth + 1 - if start[1] not in indent_chances: - # allow lining up tokens - indent_chances[start[1]] = text + indent_chances[start[1]].add(text) last_token_multiline = (start[0] != end[0]) if last_token_multiline: diff --git a/testsuite/E12not.py b/testsuite/E12not.py index 2e2366c2..436c8fa0 100644 --- a/testsuite/E12not.py +++ b/testsuite/E12not.py @@ -659,3 +659,25 @@ def f1(): open('/path/to/some/file/being/written', 'w') as file_2, \ open('just-making-sure-more-continuations-also-work'): file_2.write(file_1.read()) + + +aaaa = ( + sqla.session + .query(table) +) + + +aaaa5 = ( + sqla.session + .query(table) +) + +verify = ( + sqla.session + .query(table) + .filter( + table.col1 == col1, + table.col2 == col2, + ) + .first() +)