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