| Index: flake8-eyeo/flake8_eyeo.py | 
| =================================================================== | 
| deleted file mode 100644 | 
| --- a/flake8-eyeo/flake8_eyeo.py | 
| +++ /dev/null | 
| @@ -1,519 +0,0 @@ | 
| -# This file is part of Adblock Plus <https://adblockplus.org/>, | 
| -# Copyright (C) 2006-present eyeo GmbH | 
| -# | 
| -# Adblock Plus is free software: you can redistribute it and/or modify | 
| -# it under the terms of the GNU General Public License version 3 as | 
| -# published by the Free Software Foundation. | 
| -# | 
| -# Adblock Plus is distributed in the hope that it will be useful, | 
| -# but WITHOUT ANY WARRANTY; without even the implied warranty of | 
| -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
| -# GNU General Public License for more details. | 
| -# | 
| -# You should have received a copy of the GNU General Public License | 
| -# along with Adblock Plus.  If not, see <http://www.gnu.org/licenses/>. | 
| - | 
| -import ast | 
| -import re | 
| -import tokenize | 
| -import sys | 
| -import collections | 
| - | 
| -try: | 
| -    import builtins | 
| -except ImportError: | 
| -    import __builtin__ as builtins | 
| - | 
| -import pkg_resources | 
| - | 
| -try: | 
| -    ascii | 
| -except NameError: | 
| -    ascii = repr | 
| - | 
| -__version__ = pkg_resources.get_distribution('flake8-eyeo').version | 
| - | 
| -DISCOURAGED_APIS = { | 
| -    're.match': 're.search', | 
| -    'codecs.open': 'io.open', | 
| -} | 
| - | 
| -ESSENTIAL_BUILTINS = set(dir(builtins)) - {'apply', 'buffer', 'coerce', | 
| -                                           'intern', 'file'} | 
| - | 
| -LEAVE_BLOCK = (ast.Return, ast.Raise, ast.Continue, ast.Break) | 
| -VOLATILE = object() | 
| - | 
| - | 
| -def evaluate(node, namespace): | 
| -    try: | 
| -        return eval(compile(ast.Expression(node), '', 'eval'), namespace) | 
| -    except Exception: | 
| -        return VOLATILE | 
| - | 
| - | 
| -def is_const(node): | 
| -    namespace = {'__builtins__': {'True': True, 'False': False, 'None': None}} | 
| -    return evaluate(node, namespace) is not VOLATILE | 
| - | 
| - | 
| -def get_identifier(node): | 
| -    if isinstance(node, ast.Name): | 
| -        return node.id | 
| -    if isinstance(node, ast.Attribute) and isinstance(node.value, ast.Name): | 
| -        return '{}.{}'.format(node.value.id, node.attr) | 
| - | 
| - | 
| -def get_statement(node): | 
| -    return type(node).__name__.lower() | 
| - | 
| - | 
| -def get_descendant_nodes(node): | 
| -    for child in ast.iter_child_nodes(node): | 
| -        yield (child, node) | 
| -        for nodes in get_descendant_nodes(child): | 
| -            yield nodes | 
| - | 
| - | 
| -class TreeVisitor(ast.NodeVisitor): | 
| -    Scope = collections.namedtuple('Scope', ['node', 'names', 'globals']) | 
| - | 
| -    def __init__(self): | 
| -        self.errors = [] | 
| -        self.scope_stack = [] | 
| - | 
| -    def _visit_block(self, nodes, block_required=False, | 
| -                     nodes_required=True, docstring=False, | 
| -                     can_have_unused_expr=False): | 
| -        pass_node = None | 
| -        has_non_pass = False | 
| -        leave_node = None | 
| -        dead_code = False | 
| - | 
| -        for i, node in enumerate(nodes): | 
| -            if isinstance(node, ast.Pass): | 
| -                pass_node = node | 
| -            else: | 
| -                has_non_pass = True | 
| - | 
| -            if leave_node and not dead_code: | 
| -                dead_code = True | 
| -                statement = get_statement(leave_node) | 
| -                self.errors.append((node, 'A202 dead code after ' | 
| -                                          '{}'.format(statement))) | 
| - | 
| -            if isinstance(node, LEAVE_BLOCK): | 
| -                leave_node = node | 
| - | 
| -            if can_have_unused_expr or not isinstance(node, ast.Expr): | 
| -                continue | 
| -            if docstring and i == 0 and isinstance(node.value, ast.Str): | 
| -                continue | 
| - | 
| -            non_literal_expr_nodes = (ast.Call, ast.Yield) | 
| -            try: | 
| -                non_literal_expr_nodes += (ast.YieldFrom,) | 
| -            except AttributeError: | 
| -                pass | 
| -            if isinstance(node.value, non_literal_expr_nodes): | 
| -                continue | 
| - | 
| -            self.errors.append((node, 'A203 unused expression')) | 
| - | 
| -        if pass_node: | 
| -            if not nodes_required or len(nodes) > 1: | 
| -                self.errors.append((pass_node, 'A204 redundant ' | 
| -                                               'pass statement')) | 
| - | 
| -            if not block_required and not has_non_pass: | 
| -                self.errors.append((pass_node, 'A205 empty block')) | 
| - | 
| -    def _check_redundant_else(self, node, handlers, clause): | 
| -        if not node.orelse: | 
| -            return | 
| - | 
| -        for handler in handlers: | 
| -            for child in handler.body: | 
| -                if isinstance(child, LEAVE_BLOCK): | 
| -                    leave_node = child | 
| -                    break | 
| -            else: | 
| -                return | 
| - | 
| -        statement = get_statement(leave_node) | 
| -        self.errors.append((node.orelse[0], | 
| -                            'A206 Extraneous else statement after {} ' | 
| -                            'in {}-clause'.format(statement, clause))) | 
| - | 
| -    def visit_If(self, node): | 
| -        self._visit_block(node.body, block_required=bool(node.orelse)) | 
| -        self._visit_block(node.orelse) | 
| -        self._check_redundant_else(node, [node], 'if') | 
| -        self.generic_visit(node) | 
| - | 
| -    def visit_Try(self, node): | 
| -        self._visit_block(node.body, can_have_unused_expr=bool(node.handlers)) | 
| -        self._visit_block(node.orelse) | 
| -        self._visit_block(node.finalbody) | 
| -        self._check_redundant_else(node, node.handlers, 'except') | 
| -        self.generic_visit(node) | 
| - | 
| -    def visit_TryExcept(self, node): | 
| -        self._visit_block(node.body, can_have_unused_expr=True) | 
| -        self._visit_block(node.orelse) | 
| -        self._check_redundant_else(node, node.handlers, 'except') | 
| -        self.generic_visit(node) | 
| - | 
| -    def visit_TryFinally(self, node): | 
| -        self._visit_block(node.body) | 
| -        self._visit_block(node.finalbody) | 
| -        self.generic_visit(node) | 
| - | 
| -    def visit_ExceptHandler(self, node): | 
| -        self._visit_block(node.body, block_required=True) | 
| -        self.generic_visit(node) | 
| - | 
| -    def _visit_stored_name(self, node, name): | 
| -        scope = self.scope_stack[-1] | 
| -        scope.names.add(name) | 
| - | 
| -        if name in ESSENTIAL_BUILTINS and isinstance(scope.node, | 
| -                                                     ast.FunctionDef): | 
| -            self.errors.append((node, 'A302 redefined built-in ' + name)) | 
| - | 
| -    def visit_Name(self, node): | 
| -        if isinstance(node.ctx, (ast.Store, ast.Param)): | 
| -            self._visit_stored_name(node, node.id) | 
| - | 
| -    def visit_arg(self, node): | 
| -        self._visit_stored_name(node, node.arg) | 
| - | 
| -    def _visit_with_scope(self, node): | 
| -        scope = self.Scope(node, names=set(), globals=[]) | 
| -        self.scope_stack.append(scope) | 
| -        self.generic_visit(node) | 
| -        del self.scope_stack[-1] | 
| -        return scope | 
| - | 
| -    def visit_Module(self, node): | 
| -        self._visit_block(node.body, block_required=True, | 
| -                          nodes_required=False, docstring=True) | 
| -        self._visit_with_scope(node) | 
| - | 
| -    def visit_FunctionDef(self, node): | 
| -        self._visit_stored_name(node, node.name) | 
| -        self._visit_block(node.body, block_required=True, docstring=True) | 
| - | 
| -        scope = self._visit_with_scope(node) | 
| -        global_names = set() | 
| - | 
| -        for declaration in scope.globals: | 
| -            for name in declaration.names: | 
| -                if name not in scope.names or name in global_names: | 
| -                    statement = get_statement(declaration) | 
| -                    self.errors.append((declaration, | 
| -                                        'A201 redundant {} declaration for ' | 
| -                                        '{}'.format(statement, name))) | 
| -                else: | 
| -                    global_names.add(name) | 
| - | 
| -    visit_ClassDef = visit_FunctionDef | 
| - | 
| -    def visit_Global(self, node): | 
| -        scope = self.scope_stack[-1] | 
| -        scope.globals.append(node) | 
| - | 
| -        if isinstance(scope.node, ast.Module): | 
| -            statement = get_statement(node) | 
| -            self.errors.append((node, 'A201 {} declaration on ' | 
| -                                      'top-level'.format(statement))) | 
| - | 
| -    visit_Nonlocal = visit_Global | 
| - | 
| -    def _visit_iter(self, node): | 
| -        if isinstance(node, (ast.Tuple, ast.Set)): | 
| -            self.errors.append((node, 'A101 use lists for data ' | 
| -                                      'that have order')) | 
| - | 
| -    def visit_comprehension(self, node): | 
| -        self._visit_iter(node.iter) | 
| -        self.generic_visit(node) | 
| - | 
| -    def visit_For(self, node): | 
| -        self._visit_iter(node.iter) | 
| -        self._visit_block(node.body, block_required=True) | 
| -        self._visit_block(node.orelse) | 
| -        self.generic_visit(node) | 
| - | 
| -    def visit_While(self, node): | 
| -        self._visit_block(node.body, block_required=True) | 
| -        self._visit_block(node.orelse) | 
| -        self.generic_visit(node) | 
| - | 
| -    def visit_BinOp(self, node): | 
| -        if isinstance(node.op, ast.Mod) and isinstance(node.left, ast.Str): | 
| -            self.errors.append((node, 'A107 use format() instead of ' | 
| -                                      '% operator for string formatting')) | 
| - | 
| -        multi_addition = (isinstance(node.op, ast.Add) and | 
| -                          isinstance(node.left, ast.BinOp) and | 
| -                          isinstance(node.left.op, ast.Add)) | 
| -        if multi_addition and (isinstance(node.left.left, ast.Str) or | 
| -                               isinstance(node.left.right, ast.Str) or | 
| -                               isinstance(node.right, ast.Str)): | 
| -            self.errors.append((node, 'A108 use format() instead of ' | 
| -                                      '+ operator when concatenating ' | 
| -                                      'more than two strings')) | 
| - | 
| -        self.generic_visit(node) | 
| - | 
| -    def visit_Compare(self, node): | 
| -        left = node.left | 
| -        single = len(node.ops) == 1 | 
| - | 
| -        for op, right in zip(node.ops, node.comparators): | 
| -            membership = isinstance(op, (ast.In, ast.NotIn)) | 
| -            symmetric = isinstance(op, (ast.Eq, ast.NotEq, ast.Is, ast.IsNot)) | 
| - | 
| -            if membership and isinstance(right, (ast.Tuple, ast.List)): | 
| -                self.errors.append((right, 'A102 use sets for distinct ' | 
| -                                           'unordered data')) | 
| - | 
| -            consts_first = single and not membership or symmetric | 
| -            if consts_first and is_const(left) and not is_const(right): | 
| -                self.errors.append((left, 'A103 yoda condition')) | 
| - | 
| -            left = right | 
| - | 
| -        self.generic_visit(node) | 
| - | 
| -    def _check_deprecated(self, node, name): | 
| -        substitute = DISCOURAGED_APIS.get(name) | 
| -        if substitute: | 
| -            self.errors.append((node, 'A301 use {}() instead of ' | 
| -                                      '{}()'.format(substitute, name))) | 
| - | 
| -    def visit_Call(self, node): | 
| -        func = get_identifier(node.func) | 
| -        arg = next(iter(node.args), None) | 
| -        redundant_literal = False | 
| - | 
| -        if isinstance(arg, ast.Lambda): | 
| -            if len(node.args) == 2 and func in {'map', 'filter', | 
| -                                                'imap', 'ifilter', | 
| -                                                'itertools.imap', | 
| -                                                'itertools.ifilter'}: | 
| -                self.errors.append((node, 'A104 use a comprehension ' | 
| -                                          'instead of calling {}() with ' | 
| -                                          'lambda function'.format(func))) | 
| -        elif isinstance(arg, (ast.List, ast.Tuple)): | 
| -            if func == 'dict': | 
| -                redundant_literal = all(isinstance(elt, (ast.Tuple, ast.List)) | 
| -                                        for elt in arg.elts) | 
| -            else: | 
| -                redundant_literal = func in {'list', 'set', 'tuple'} | 
| -        elif isinstance(arg, (ast.ListComp, ast.GeneratorExp)): | 
| -            if func == 'dict': | 
| -                redundant_literal = isinstance(arg.elt, (ast.Tuple, ast.List)) | 
| -            else: | 
| -                redundant_literal = func in {'list', 'set'} | 
| - | 
| -        if redundant_literal: | 
| -            self.errors.append((node, 'A105 use a {0} literal or ' | 
| -                                      'comprehension instead of calling ' | 
| -                                      '{0}()'.format(func))) | 
| - | 
| -        self._check_deprecated(node, func) | 
| -        self.generic_visit(node) | 
| - | 
| -    def visit_Import(self, node): | 
| -        for alias in node.names: | 
| -            self._visit_stored_name(node, alias.asname or alias.name) | 
| - | 
| -            if hasattr(node, 'module'): | 
| -                self._check_deprecated(node, '{}.{}'.format(node.module, | 
| -                                                            alias.name)) | 
| - | 
| -    visit_ImportFrom = visit_Import | 
| - | 
| -    def visit_Assign(self, node): | 
| -        if isinstance(node.value, ast.BinOp) and len(node.targets) == 1: | 
| -            target = node.targets[0] | 
| -            left_is_target = (isinstance(target, ast.Name) and | 
| -                              isinstance(node.value.left, ast.Name) and | 
| -                              target.id == node.value.left.id) | 
| -            if left_is_target: | 
| -                self.errors.append((node, 'A106 use augment assignment, ' | 
| -                                          'e.g. x += y instead x = x + y')) | 
| -        self.generic_visit(node) | 
| - | 
| -    def _visit_hash_keys(self, nodes, what): | 
| -        keys = [] | 
| -        namespace = collections.defaultdict(object, vars(builtins)) | 
| -        for node in nodes: | 
| -            key = evaluate(node, namespace) | 
| -            if key is VOLATILE: | 
| -                continue | 
| - | 
| -            if key in keys: | 
| -                self.errors.append((node, 'A207 duplicate ' + what)) | 
| -                continue | 
| - | 
| -            keys.append(key) | 
| - | 
| -    def visit_Dict(self, node): | 
| -        self._visit_hash_keys(node.keys, 'key in dict') | 
| - | 
| -    def visit_Set(self, node): | 
| -        self._visit_hash_keys(node.elts, 'item in set') | 
| - | 
| - | 
| -def check_ast(tree): | 
| -    visitor = TreeVisitor() | 
| -    visitor.visit(tree) | 
| - | 
| -    for node, error in visitor.errors: | 
| -        yield (node.lineno, node.col_offset, error, None) | 
| - | 
| - | 
| -def check_non_default_encoding(physical_line, line_number): | 
| -    if line_number <= 2 and re.search(r'^\s*#.*coding[:=]', physical_line): | 
| -        return (0, 'A303 non-default file encoding') | 
| - | 
| - | 
| -def check_quotes(logical_line, tokens, previous_logical, checker_state): | 
| -    first_token = True | 
| - | 
| -    token_strings = [t[1] for t in tokens] | 
| -    future_import = token_strings[:3] == ['from', '__future__', 'import'] | 
| - | 
| -    if future_import and 'unicode_literals' in token_strings: | 
| -        checker_state['has_unicode_literals'] = True | 
| - | 
| -    for kind, token, start, end, _ in tokens: | 
| -        if kind == tokenize.INDENT or kind == tokenize.DEDENT: | 
| -            continue | 
| - | 
| -        if kind == tokenize.STRING: | 
| -            match = re.search(r'^([rub]*)([\'"]{1,3})(.*)\2$', | 
| -                              token, re.IGNORECASE | re.DOTALL) | 
| -            prefixes, quote, text = match.groups() | 
| -            prefixes = prefixes.lower() | 
| - | 
| -            if 'u' in prefixes: | 
| -                yield (start, 'A112 use "from __future__ import ' | 
| -                              'unicode_literals" instead of ' | 
| -                              'prefixing literals with "u"') | 
| - | 
| -            if first_token and re.search(r'^(?:(?:def|class)\s|$)', | 
| -                                         previous_logical): | 
| -                pass  # Ignore docstrings | 
| -            elif start[0] != end[0]: | 
| -                pass  # Ignore multiline strings | 
| -            elif 'r' in prefixes: | 
| -                if quote != "'" and not (quote == '"' and "'" in text): | 
| -                    yield (start, 'A110 use single quotes for raw string') | 
| -            else: | 
| -                prefix = '' | 
| -                if sys.version_info[0] >= 3: | 
| -                    if 'b' in prefixes: | 
| -                        prefix = 'b' | 
| -                else: | 
| -                    u_literals = checker_state.get('has_unicode_literals') | 
| -                    if 'u' in prefixes or u_literals and 'b' not in prefixes: | 
| -                        prefix = 'u' | 
| - | 
| -                literal = '{0}{1}{2}{1}'.format(prefix, quote, text) | 
| -                if ascii(eval(literal)) != literal: | 
| -                    yield (start, "A110 string literal doesn't match " | 
| -                                  '{}()'.format(ascii.__name__)) | 
| - | 
| -        first_token = False | 
| - | 
| - | 
| -def check_redundant_parenthesis(tree, lines, file_tokens): | 
| -    orig = ast.dump(tree) | 
| -    nodes = get_descendant_nodes(tree) | 
| -    stack = [] | 
| - | 
| -    for i, (kind, token, _, _, _) in enumerate(file_tokens): | 
| -        if kind != tokenize.OP: | 
| -            continue | 
| - | 
| -        if token == '(': | 
| -            stack.append(i) | 
| -        elif token == ')': | 
| -            start = stack.pop() | 
| -            sample = lines[:] | 
| - | 
| -            for pos in [i, start]: | 
| -                _, _, (lineno, col1), (_, col2), _ = file_tokens[pos] | 
| -                lineno -= 1 | 
| -                sample[lineno] = (sample[lineno][:col1] + | 
| -                                  sample[lineno][col2:]) | 
| - | 
| -            try: | 
| -                modified = ast.parse(''.join(sample)) | 
| -            except SyntaxError: | 
| -                # Parentheses syntactically required. | 
| -                continue | 
| - | 
| -            # Parentheses logically required. | 
| -            if orig != ast.dump(modified): | 
| -                continue | 
| - | 
| -            pos = file_tokens[start][2] | 
| -            while True: | 
| -                node, parent = next(nodes) | 
| -                if pos < (getattr(node, 'lineno', -1), | 
| -                          getattr(node, 'col_offset', -1)): | 
| -                    break | 
| - | 
| -            # Allow redundant parentheses for readability, | 
| -            # when creating tuples (but not when unpacking variables), | 
| -            # nested operations and comparisons inside assignments. | 
| -            is_tuple = ( | 
| -                isinstance(node, ast.Tuple) and not ( | 
| -                    isinstance(parent, (ast.For, ast.comprehension)) and | 
| -                    node == parent.target or | 
| -                    isinstance(parent, ast.Assign) and | 
| -                    node in parent.targets | 
| -                ) | 
| -            ) | 
| -            is_nested_op = ( | 
| -                isinstance(node, (ast.BinOp, ast.BoolOp)) and | 
| -                isinstance(parent, (ast.BinOp, ast.BoolOp)) | 
| -            ) | 
| -            is_compare_in_assign = ( | 
| -                isinstance(parent, (ast.Assign, ast.keyword)) and | 
| -                any(isinstance(x, ast.Compare) for x in ast.walk(node)) | 
| -            ) | 
| -            if is_tuple or is_nested_op or is_compare_in_assign: | 
| -                continue | 
| - | 
| -            yield (pos[0], pos[1], 'A111 redundant parenthesis', None) | 
| - | 
| - | 
| -class DefaultConfigOverride: | 
| -    def __init__(self, _): | 
| -        pass | 
| - | 
| -    @classmethod | 
| -    def add_options(cls, parser): | 
| -        parser.extend_default_ignore([ | 
| -            # We don't want to make doc strings mandatory | 
| -            # but merely lint existing doc strings. | 
| -            'D1', | 
| -            # Adding a comma after variable args/kwargs | 
| -            # is a syntax error in Python 2 (and <= 3.4). | 
| -            'C815', | 
| -        ]) | 
| - | 
| -        # Remove everything but W503 & W504 from the built-in default ignores. | 
| -        parser.parser.defaults['ignore'] = 'W503,W504' | 
| - | 
| - | 
| -for checker in [check_ast, check_non_default_encoding, check_quotes, | 
| -                check_redundant_parenthesis, DefaultConfigOverride]: | 
| -    checker.name = 'eyeo' | 
| -    checker.version = __version__ | 
|  |