| OLD | NEW |
| 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 Loading... |
| 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 Loading... |
| 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__ |
| OLD | NEW |