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: Created Nov. 10, 2017, 12:26 a.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
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 330 matching lines...) Expand 10 before | Expand all | Expand 10 after
411 prefix = 'u' 418 prefix = 'u'
412 419
413 literal = '{0}{1}{2}{1}'.format(prefix, quote, text) 420 literal = '{0}{1}{2}{1}'.format(prefix, quote, text)
414 if ascii(eval(literal)) != literal: 421 if ascii(eval(literal)) != literal:
415 yield (start, "A110 string literal doesn't match " 422 yield (start, "A110 string literal doesn't match "
416 '{}()'.format(ascii.__name__)) 423 '{}()'.format(ascii.__name__))
417 424
418 first_token = False 425 first_token = False
419 426
420 427
421 def check_redundant_parenthesis(logical_line, tokens): 428 def check_redundant_parenthesis(tree, lines, file_tokens):
422 start_line = tokens[0][2][0] 429 orig = ast.dump(tree)
423 level = 0 430 nodes = get_descendant_nodes(tree)
424 statement = None 431 stack = []
425 432
426 for i, (kind, token, _, end, _) in enumerate(tokens): 433 for i, (kind, token, _, _, _) in enumerate(file_tokens):
427 if kind == tokenize.INDENT or kind == tokenize.DEDENT: 434 if kind != tokenize.OP:
428 continue 435 continue
429 436
430 if statement is None: 437 if token == '(':
431 # logical line doesn't start with an if, elif or while statement 438 stack.append(i)
432 if kind != tokenize.NAME or token not in {'if', 'elif', 'while'}: 439 elif token == ')':
433 break 440 start = stack.pop()
441 sample = lines[:]
434 442
435 # expression doesn't start with parenthesis 443 for pos in [i, start]:
436 next_token = tokens[i + 1] 444 _, _, (lineno, col1), (_, col2), _ = file_tokens[pos]
437 if next_token[:2] != (tokenize.OP, '('): 445 lineno -= 1
438 break 446 sample[lineno] = (sample[lineno][:col1] +
447 sample[lineno][col2:])
439 448
440 # expression is empty tuple 449 try:
441 if tokens[i + 2][:2] == (tokenize.OP, ')'): 450 modified = ast.parse(''.join(sample))
442 break 451 except SyntaxError:
452 # Parentheses syntactically required.
453 continue
443 454
444 statement = token 455 # Parentheses logically required.
445 pos = next_token[2] 456 if orig != ast.dump(modified):
446 continue 457 continue
447 458
448 # expression ends on a different line, parenthesis are necessary 459 pos = file_tokens[start][2]
449 if end[0] > start_line: 460 while True:
450 break 461 node, parent = next(nodes)
462 if pos < (getattr(node, 'lineno', -1),
463 getattr(node, 'col_offset', -1)):
464 break
451 465
452 if kind == tokenize.OP: 466 # Allow redundant parentheses for readability,
453 if token == ',': 467 # when creating tuples (but not when unpacking variables),
454 # expression is non-empty tuple 468 # nested operations and comparisons inside assignments.
455 if level == 1: 469 is_tuple = (
456 break 470 isinstance(node, ast.Tuple) and not (
457 elif token == '(': 471 isinstance(parent, (ast.For, ast.comprehension)) and
458 level += 1 472 node == parent.target or
459 elif token == ')': 473 isinstance(parent, ast.Assign) and
460 level -= 1 474 node in parent.targets
461 if level == 0: 475 )
462 # outer parenthesis closed before end of expression 476 )
463 if tokens[i + 1][:2] != (tokenize.OP, ':'): 477 is_nested_op = (
464 break 478 isinstance(node, (ast.BinOp, ast.BoolOp)) and
479 isinstance(parent, (ast.BinOp, ast.BoolOp))
480 )
481 is_compare_in_assign = (
482 isinstance(parent, (ast.Assign, ast.keyword)) and
483 any(isinstance(x, ast.Compare) for x in ast.walk(node))
484 )
485 if is_tuple or is_nested_op or is_compare_in_assign:
486 continue
465 487
466 return [(pos, 'A111 redundant parenthesis for {} ' 488 yield (pos[0], pos[1], 'A111 redundant parenthesis', None)
467 'statement'.format(statement))]
468
469 return []
470 489
471 490
472 for checker in [check_ast, check_non_default_encoding, 491 for checker in [check_ast, check_non_default_encoding,
473 check_quotes, check_redundant_parenthesis]: 492 check_quotes, check_redundant_parenthesis]:
474 checker.name = 'eyeo' 493 checker.name = 'eyeo'
475 checker.version = __version__ 494 checker.version = __version__
OLDNEW
« no previous file with comments | « flake8-eyeo/README.md ('k') | flake8-eyeo/tests/A111.py » ('j') | flake8-eyeo/tests/A111.py » ('J')

Powered by Google App Engine
This is Rietveld