diff --git a/pycodestyle.py b/pycodestyle.py index b7e0b2fb..b331889c 100755 --- a/pycodestyle.py +++ b/pycodestyle.py @@ -413,6 +413,12 @@ def extraneous_whitespace(logical_line): - Immediately inside parentheses, brackets or braces. - Immediately before a comma, semicolon, or colon. + Exceptions: + - When the colon acts as a slice, the rule of binary operators + applies and we should have the same amount of space on either side + - When the colon acts as a slice but a parameter is omitted, then + the space is omitted + Okay: spam(ham[1], {eggs: 2}) E201: spam( ham[1], {eggs: 2}) E201: spam(ham[ 1], {eggs: 2}) @@ -421,10 +427,45 @@ def extraneous_whitespace(logical_line): E202: spam(ham[1 ], {eggs: 2}) E202: spam(ham[1], {eggs: 2 }) + Okay: ham[8 : 2] + Okay: ham[: 2] E203: if x == 4: print x, y; x, y = y , x E203: if x == 4: print x, y ; x, y = y, x E203: if x == 4 : print x, y; x, y = y, x """ + + def is_a_slice(line, space_pos): + """Check if the colon after an extra space acts as a slice + + Return True if the colon is directly contained within + square brackets. + """ + # list of found '[({' as tuples "(char, position)" + parentheses_brackets_braces = list() + for i in range(space_pos): + c = line[i] + if c in '[({': + # add it to the stack + parentheses_brackets_braces.append((c, i)) + elif c in '])}': + # unstack last item and check consistency + last_opened = parentheses_brackets_braces.pop()[0] + expected_close = {'{': '}', '(': ')', '[': ']'}[last_opened] + if c != expected_close: # invalid Python code + return False + is_within_brackets = (len(parentheses_brackets_braces) > 0 and + parentheses_brackets_braces[-1][0] == '[') + + is_lambda_expr = False + if is_within_brackets: + # check for a lambda expression nested in brackets + if space_pos > 6: + last_opened = parentheses_brackets_braces[-1] + between_bracket_colon = line[last_opened[1] + 1: space_pos] + is_lambda_expr = 'lambda' in between_bracket_colon + + return (is_within_brackets and not is_lambda_expr) + line = logical_line for match in EXTRANEOUS_WHITESPACE_REGEX.finditer(line): text = match.group() @@ -433,7 +474,8 @@ def extraneous_whitespace(logical_line): if text == char + ' ': # assert char in '([{' yield found + 1, "E201 whitespace after '%s'" % char - elif line[found - 1] != ',': + elif (line[found - 1] != ',' and + not (char == ':' and is_a_slice(line, found))): code = ('E202' if char in '}])' else 'E203') # if char in ',;:' yield found, "%s whitespace before '%s'" % (code, char) diff --git a/testsuite/E20.py b/testsuite/E20.py index 2f1ecc28..33c6ae11 100644 --- a/testsuite/E20.py +++ b/testsuite/E20.py @@ -46,10 +46,22 @@ if x == 4: print x, y x, y = y , x +#: E203:1:37 +foo[idxs[2 : 6] : spam(ham[1], {eggs : a[2 : 4]})] +#: E203:1:8 +[lambda : foo] +#: E203:1:13 +ham[lambda x : foo] #: Okay if x == 4: print x, y x, y = y, x a[b1, :] == a[b1, ...] b = a[:, b1] +ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:] +ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)] +ham[lower + offset : upper + offset] +foo[idxs[2 : 6] : spam(ham[1], {eggs: a[2 : 4]})] +[lambda: foo] +ham[lambda x: foo] #: