Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: flake8-eyeo/flake8_eyeo.py

Issue 29602819: Issue 5844 - Detect (more) redundant parentheses (Closed) Base URL: https://hg.adblockplus.org/codingtools/
Patch Set: Updated flake8-eyeo/README.md and applied patches to existing codebase Created Jan. 25, 2018, 7:34 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « flake8-eyeo/README.md ('k') | flake8-eyeo/tests/A111.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # This file is part of Adblock Plus <https://adblockplus.org/>, 1 # This file is part of Adblock Plus <https://adblockplus.org/>,
2 # Copyright (C) 2006-present eyeo GmbH 2 # Copyright (C) 2006-present eyeo GmbH
3 # 3 #
4 # Adblock Plus is free software: you can redistribute it and/or modify 4 # Adblock Plus is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License version 3 as 5 # it under the terms of the GNU General Public License version 3 as
6 # published by the Free Software Foundation. 6 # published by the Free Software Foundation.
7 # 7 #
8 # Adblock Plus is distributed in the hope that it will be useful, 8 # Adblock Plus is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after
61 if isinstance(node, ast.Name): 61 if isinstance(node, ast.Name):
62 return node.id 62 return node.id
63 if isinstance(node, ast.Attribute) and isinstance(node.value, ast.Name): 63 if isinstance(node, ast.Attribute) and isinstance(node.value, ast.Name):
64 return '{}.{}'.format(node.value.id, node.attr) 64 return '{}.{}'.format(node.value.id, node.attr)
65 65
66 66
67 def get_statement(node): 67 def get_statement(node):
68 return type(node).__name__.lower() 68 return type(node).__name__.lower()
69 69
70 70
71 def get_descendant_nodes(node):
72 for child in ast.iter_child_nodes(node):
73 yield (child, node)
74 for nodes in get_descendant_nodes(child):
75 yield nodes
76
77
71 class TreeVisitor(ast.NodeVisitor): 78 class TreeVisitor(ast.NodeVisitor):
72 Scope = collections.namedtuple('Scope', ['node', 'names', 'globals']) 79 Scope = collections.namedtuple('Scope', ['node', 'names', 'globals'])
73 80
74 def __init__(self): 81 def __init__(self):
75 self.errors = [] 82 self.errors = []
76 self.scope_stack = [] 83 self.scope_stack = []
77 84
78 def _visit_block(self, nodes, block_required=False, 85 def _visit_block(self, nodes, block_required=False,
79 nodes_required=True, docstring=False, 86 nodes_required=True, docstring=False,
80 can_have_unused_expr=False): 87 can_have_unused_expr=False):
(...skipping 336 matching lines...) Expand 10 before | Expand all | Expand 10 after
417 prefix = 'u' 424 prefix = 'u'
418 425
419 literal = '{0}{1}{2}{1}'.format(prefix, quote, text) 426 literal = '{0}{1}{2}{1}'.format(prefix, quote, text)
420 if ascii(eval(literal)) != literal: 427 if ascii(eval(literal)) != literal:
421 yield (start, "A110 string literal doesn't match " 428 yield (start, "A110 string literal doesn't match "
422 '{}()'.format(ascii.__name__)) 429 '{}()'.format(ascii.__name__))
423 430
424 first_token = False 431 first_token = False
425 432
426 433
427 def check_redundant_parenthesis(logical_line, tokens): 434 def check_redundant_parenthesis(tree, lines, file_tokens):
428 start_line = tokens[0][2][0] 435 orig = ast.dump(tree)
429 level = 0 436 nodes = get_descendant_nodes(tree)
430 statement = None 437 stack = []
431 438
432 for i, (kind, token, _, end, _) in enumerate(tokens): 439 for i, (kind, token, _, _, _) in enumerate(file_tokens):
433 if kind == tokenize.INDENT or kind == tokenize.DEDENT: 440 if kind != tokenize.OP:
434 continue 441 continue
435 442
436 if statement is None: 443 if token == '(':
437 # logical line doesn't start with an if, elif or while statement 444 stack.append(i)
438 if kind != tokenize.NAME or token not in {'if', 'elif', 'while'}: 445 elif token == ')':
439 break 446 start = stack.pop()
447 sample = lines[:]
440 448
441 # expression doesn't start with parenthesis 449 for pos in [i, start]:
442 next_token = tokens[i + 1] 450 _, _, (lineno, col1), (_, col2), _ = file_tokens[pos]
443 if next_token[:2] != (tokenize.OP, '('): 451 lineno -= 1
444 break 452 sample[lineno] = (sample[lineno][:col1] +
453 sample[lineno][col2:])
445 454
446 # expression is empty tuple 455 try:
447 if tokens[i + 2][:2] == (tokenize.OP, ')'): 456 modified = ast.parse(''.join(sample))
448 break 457 except SyntaxError:
458 # Parentheses syntactically required.
459 continue
449 460
450 statement = token 461 # Parentheses logically required.
451 pos = next_token[2] 462 if orig != ast.dump(modified):
452 continue 463 continue
453 464
454 # expression ends on a different line, parenthesis are necessary 465 pos = file_tokens[start][2]
455 if end[0] > start_line: 466 while True:
456 break 467 node, parent = next(nodes)
468 if pos < (getattr(node, 'lineno', -1),
469 getattr(node, 'col_offset', -1)):
470 break
457 471
458 if kind == tokenize.OP: 472 # Allow redundant parentheses for readability,
459 if token == ',': 473 # when creating tuples (but not when unpacking variables),
460 # expression is non-empty tuple 474 # nested operations and comparisons inside assignments.
461 if level == 1: 475 is_tuple = (
462 break 476 isinstance(node, ast.Tuple) and not (
463 elif token == '(': 477 isinstance(parent, (ast.For, ast.comprehension)) and
464 level += 1 478 node == parent.target or
465 elif token == ')': 479 isinstance(parent, ast.Assign) and
466 level -= 1 480 node in parent.targets
467 if level == 0: 481 )
468 # outer parenthesis closed before end of expression 482 )
469 if tokens[i + 1][:2] != (tokenize.OP, ':'): 483 is_nested_op = (
470 break 484 isinstance(node, (ast.BinOp, ast.BoolOp)) and
485 isinstance(parent, (ast.BinOp, ast.BoolOp))
486 )
487 is_compare_in_assign = (
488 isinstance(parent, (ast.Assign, ast.keyword)) and
489 any(isinstance(x, ast.Compare) for x in ast.walk(node))
490 )
491 if is_tuple or is_nested_op or is_compare_in_assign:
492 continue
471 493
472 return [(pos, 'A111 redundant parenthesis for {} ' 494 yield (pos[0], pos[1], 'A111 redundant parenthesis', None)
473 'statement'.format(statement))]
474
475 return []
476 495
477 496
478 for checker in [check_ast, check_non_default_encoding, 497 for checker in [check_ast, check_non_default_encoding,
479 check_quotes, check_redundant_parenthesis]: 498 check_quotes, check_redundant_parenthesis]:
480 checker.name = 'eyeo' 499 checker.name = 'eyeo'
481 checker.version = __version__ 500 checker.version = __version__
OLDNEW
« no previous file with comments | « flake8-eyeo/README.md ('k') | flake8-eyeo/tests/A111.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld