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