| 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 | 
|---|