mypy now passes

This commit is contained in:
Anthony Sottile 2019-05-19 17:01:14 -07:00
parent b6ba6d4d03
commit fb7e9338cd
32 changed files with 255 additions and 212 deletions

View file

@ -35,7 +35,7 @@ python36:
script: tox -e py36
python37:
image: python:3.7-rc
image: python:3.7
stage: test
script: tox -e py37
@ -44,6 +44,11 @@ linters:
stage: test
script: tox -e linters
pre-commit:
image: python:3.7
stage: test
script: tox -e pre-commit
docs:
stage: test
script: tox -e docs

6
.pre-commit-config.yaml Normal file
View file

@ -0,0 +1,6 @@
repos:
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.701
hooks:
- id: mypy
exclude: ^(docs/|example-plugin/|tests/fixtures)

View file

@ -1,2 +0,0 @@
[mypy]
ignore_missing_imports = true

View file

@ -16,3 +16,28 @@ requires-dist =
pyflakes >= 2.1.0, < 2.2.0
pycodestyle >= 2.5.0, < 2.6.0
mccabe >= 0.6.0, < 0.7.0
[mypy]
check_untyped_defs = true
disallow_any_generics = true
disallow_incomplete_defs = true
# TODO: disallow_untyped_defs = true
no_implicit_optional = true
warn_unused_ignores = true
# TODO: until we opt in all the modules
[mypy-flake8.defaults]
disallow_untyped_defs = true
[mypy-flake8.exceptions]
disallow_untyped_defs = true
[mypy-flake8.formatting.*]
disallow_untyped_defs = true
[mypy-flake8.main.cli]
disallow_untyped_defs = true
[mypy-flake8.statistics]
disallow_untyped_defs = true
[mypy-flake8.utils]
disallow_untyped_defs = true
[mypy-tests.*]
disallow_untyped_defs = false

View file

@ -12,6 +12,9 @@ This module
import logging
import sys
if False: # `typing.TYPE_CHECKING` was introduced in 3.5.2
from typing import Type # `typing.Type` was introduced in 3.5.2
LOG = logging.getLogger(__name__)
LOG.addHandler(logging.NullHandler())
@ -61,7 +64,7 @@ def configure_logging(verbosity, filename=None, logformat=LOG_FORMAT):
if not filename or filename in ("stderr", "stdout"):
fileobj = getattr(sys, filename or "stderr")
handler_cls = logging.StreamHandler
handler_cls = logging.StreamHandler # type: Type[logging.Handler]
else:
fileobj = filename
handler_cls = logging.FileHandler

View file

@ -3,14 +3,13 @@ import collections
import errno
import logging
import signal
import sys
import tokenize
from typing import List, Optional, Tuple
from typing import Dict, List, Optional, Tuple
try:
import multiprocessing
except ImportError:
multiprocessing = None
multiprocessing = None # type: ignore
from flake8 import defaults
from flake8 import exceptions
@ -73,8 +72,7 @@ class Manager(object):
self.options = style_guide.options
self.checks = checker_plugins
self.jobs = self._job_count()
self.processes = []
self.checkers = []
self.checkers = [] # type: List[FileChecker]
self.statistics = {
"files": 0,
"logical lines": 0,
@ -195,7 +193,7 @@ class Manager(object):
)
def make_checkers(self, paths=None):
# type: (List[str]) -> None
# type: (Optional[List[str]]) -> None
"""Create checkers for each file."""
if paths is None:
paths = self.arguments
@ -270,8 +268,10 @@ class Manager(object):
def run_parallel(self):
"""Run the checkers in parallel."""
final_results = collections.defaultdict(list)
final_statistics = collections.defaultdict(dict)
# fmt: off
final_results = collections.defaultdict(list) # type: Dict[str, List[Tuple[str, int, int, str, Optional[str]]]] # noqa: E501
final_statistics = collections.defaultdict(dict) # type: Dict[str, Dict[str, None]] # noqa: E501
# fmt: on
try:
pool = multiprocessing.Pool(self.jobs, _pool_init)
@ -281,6 +281,7 @@ class Manager(object):
self.run_serial()
return
pool_closed = False
try:
pool_map = pool.imap_unordered(
_run_checks,
@ -295,9 +296,9 @@ class Manager(object):
final_statistics[filename] = statistics
pool.close()
pool.join()
pool = None
pool_closed = True
finally:
if pool is not None:
if not pool_closed:
pool.terminate()
pool.join()
@ -345,9 +346,6 @@ class Manager(object):
def stop(self):
"""Stop checking files."""
self._process_statistics()
for proc in self.processes:
LOG.info("Joining %s to the main process", proc.name)
proc.join()
class FileChecker(object):
@ -370,7 +368,9 @@ class FileChecker(object):
self.options = options
self.filename = filename
self.checks = checks
self.results = []
# fmt: off
self.results = [] # type: List[Tuple[str, int, int, str, Optional[str]]] # noqa: E501
# fmt: on
self.statistics = {
"tokens": 0,
"logical lines": 0,
@ -389,17 +389,17 @@ class FileChecker(object):
return "FileChecker for {}".format(self.filename)
def _make_processor(self):
# type: () -> Optional[processor.FileProcessor]
try:
return processor.FileProcessor(self.filename, self.options)
except IOError:
except IOError as e:
# If we can not read the file due to an IOError (e.g., the file
# does not exist or we do not have the permissions to open it)
# then we need to format that exception for the user.
# NOTE(sigmavirus24): Historically, pep8 has always reported this
# as an E902. We probably *want* a better error code for this
# going forward.
(exc_type, exception) = sys.exc_info()[:2]
message = "{0}: {1}".format(exc_type.__name__, exception)
message = "{0}: {1}".format(type(e).__name__, e)
self.report("E902", 0, 0, message)
return None
@ -446,7 +446,7 @@ class FileChecker(object):
token = ()
if len(exception.args) > 1:
token = exception.args[1]
if len(token) > 2:
if token and len(token) > 2:
row, column = token[1:3]
else:
row, column = (1, 0)
@ -482,14 +482,10 @@ class FileChecker(object):
"""Run all checks expecting an abstract syntax tree."""
try:
ast = self.processor.build_ast()
except (ValueError, SyntaxError, TypeError):
(exc_type, exception) = sys.exc_info()[:2]
row, column = self._extract_syntax_information(exception)
except (ValueError, SyntaxError, TypeError) as e:
row, column = self._extract_syntax_information(e)
self.report(
"E999",
row,
column,
"%s: %s" % (exc_type.__name__, exception.args[0]),
"E999", row, column, "%s: %s" % (type(e).__name__, e.args[0])
)
return

View file

@ -1,5 +1,10 @@
"""Exception classes for all of Flake8."""
from typing import Dict
if False: # `typing.TYPE_CHECKING` was introduced in 3.5.2
from flake8.plugins.manager import Plugin
class Flake8Exception(Exception):
"""Plain Flake8 exception."""
@ -19,13 +24,14 @@ class FailedToLoadPlugin(Flake8Exception):
FORMAT = 'Flake8 failed to load plugin "%(name)s" due to %(exc)s.'
def __init__(self, plugin, exception):
# type: (Plugin, Exception) -> None
"""Initialize our FailedToLoadPlugin exception."""
self.plugin = plugin
self.ep_name = self.plugin.name
self.original_exception = exception
super(FailedToLoadPlugin, self).__init__(plugin, exception)
def __str__(self):
def __str__(self): # type: () -> str
"""Format our exception message."""
return self.FORMAT % {
"name": self.ep_name,
@ -47,7 +53,7 @@ class InvalidSyntax(Flake8Exception):
self.column_number = 0
super(InvalidSyntax, self).__init__(exception)
def __str__(self):
def __str__(self): # type: () -> str
"""Format our exception message."""
return self.error_message
@ -58,6 +64,7 @@ class PluginRequestedUnknownParameters(Flake8Exception):
FORMAT = '"%(name)s" requested unknown parameters causing %(exc)s'
def __init__(self, plugin, exception):
# type: (Dict[str, str], Exception) -> None
"""Pop certain keyword arguments for initialization."""
self.plugin = plugin
self.original_exception = exception
@ -65,7 +72,7 @@ class PluginRequestedUnknownParameters(Flake8Exception):
plugin, exception
)
def __str__(self):
def __str__(self): # type: () -> str
"""Format our exception message."""
return self.FORMAT % {
"name": self.plugin["plugin_name"],
@ -79,12 +86,13 @@ class PluginExecutionFailed(Flake8Exception):
FORMAT = '"%(name)s" failed during execution due to "%(exc)s"'
def __init__(self, plugin, exception):
# type: (Dict[str, str], Exception) -> None
"""Utilize keyword arguments for message generation."""
self.plugin = plugin
self.original_exception = exception
super(PluginExecutionFailed, self).__init__(plugin, exception)
def __str__(self):
def __str__(self): # type: () -> str
"""Format our exception message."""
return self.FORMAT % {
"name": self.plugin["plugin_name"],
@ -99,40 +107,33 @@ class HookInstallationError(Flake8Exception):
class GitHookAlreadyExists(HookInstallationError):
"""Exception raised when the git pre-commit hook file already exists."""
def __init__(self, *args, **kwargs):
"""Initialize the path attribute."""
self.path = kwargs.pop("path")
super(GitHookAlreadyExists, self).__init__(*args, **kwargs)
def __str__(self):
"""Provide a nice message regarding the exception."""
msg = (
def __init__(self, path): # type: (str) -> None
"""Initialize the exception message from the `path`."""
self.path = path
tmpl = (
"The Git pre-commit hook ({0}) already exists. To convince "
"Flake8 to install the hook, please remove the existing "
"hook."
)
return msg.format(self.path)
super(GitHookAlreadyExists, self).__init__(tmpl.format(self.path))
class MercurialHookAlreadyExists(HookInstallationError):
"""Exception raised when a mercurial hook is already configured."""
hook_name = None
hook_name = None # type: str
def __init__(self, *args, **kwargs):
def __init__(self, path, value): # type: (str, str) -> None
"""Initialize the relevant attributes."""
self.path = kwargs.pop("path")
self.value = kwargs.pop("value")
super(MercurialHookAlreadyExists, self).__init__(*args, **kwargs)
def __str__(self):
"""Return a nicely formatted string for these errors."""
msg = (
self.path = path
self.value = value
tmpl = (
'The Mercurial {0} hook already exists with "{1}" in {2}. '
"To convince Flake8 to install the hook, please remove the "
"{0} configuration from the [hooks] section of your hgrc."
)
return msg.format(self.hook_name, self.value, self.path)
msg = tmpl.format(self.hook_name, self.value, self.path)
super(MercurialHookAlreadyExists, self).__init__(msg)
class MercurialCommitHookAlreadyExists(MercurialHookAlreadyExists):

View file

@ -1,6 +1,13 @@
"""The base class and interface for all formatting plugins."""
from __future__ import print_function
import optparse
from typing import IO, List, Optional, Tuple
if False: # `typing.TYPE_CHECKING` was introduced in 3.5.2
from flake8.statistics import Statistics
from flake8.style_guide import Violation
class BaseFormatter(object):
"""Class defining the formatter interface.
@ -25,6 +32,7 @@ class BaseFormatter(object):
"""
def __init__(self, options):
# type: (optparse.Values) -> None
"""Initialize with the options parsed from config and cli.
This also calls a hook, :meth:`after_init`, so subclasses do not need
@ -36,33 +44,30 @@ class BaseFormatter(object):
"""
self.options = options
self.filename = options.output_file
self.output_fd = None
self.output_fd = None # type: Optional[IO[str]]
self.newline = "\n"
self.after_init()
def after_init(self):
def after_init(self): # type: () -> None
"""Initialize the formatter further."""
pass
def beginning(self, filename):
def beginning(self, filename): # type: (str) -> None
"""Notify the formatter that we're starting to process a file.
:param str filename:
The name of the file that Flake8 is beginning to report results
from.
"""
pass
def finished(self, filename):
def finished(self, filename): # type: (str) -> None
"""Notify the formatter that we've finished processing a file.
:param str filename:
The name of the file that Flake8 has finished reporting results
from.
"""
pass
def start(self):
def start(self): # type: () -> None
"""Prepare the formatter to receive input.
This defaults to initializing :attr:`output_fd` if :attr:`filename`
@ -70,7 +75,7 @@ class BaseFormatter(object):
if self.filename:
self.output_fd = open(self.filename, "a")
def handle(self, error):
def handle(self, error): # type: (Violation) -> None
"""Handle an error reported by Flake8.
This defaults to calling :meth:`format`, :meth:`show_source`, and
@ -87,7 +92,7 @@ class BaseFormatter(object):
source = self.show_source(error)
self.write(line, source)
def format(self, error):
def format(self, error): # type: (Violation) -> Optional[str]
"""Format an error reported by Flake8.
This method **must** be implemented by subclasses.
@ -106,7 +111,7 @@ class BaseFormatter(object):
"Subclass of BaseFormatter did not implement" " format."
)
def show_statistics(self, statistics):
def show_statistics(self, statistics): # type: (Statistics) -> None
"""Format and print the statistics."""
for error_code in statistics.error_codes():
stats_for_error_code = statistics.statistics_for(error_code)
@ -122,6 +127,7 @@ class BaseFormatter(object):
)
def show_benchmarks(self, benchmarks):
# type: (List[Tuple[str, float]]) -> None
"""Format and print the benchmarks."""
# NOTE(sigmavirus24): The format strings are a little confusing, even
# to me, so here's a quick explanation:
@ -142,7 +148,7 @@ class BaseFormatter(object):
benchmark = float_format(statistic=statistic, value=value)
self._write(benchmark)
def show_source(self, error):
def show_source(self, error): # type: (Violation) -> Optional[str]
"""Show the physical line generating the error.
This also adds an indicator for the particular part of the line that
@ -170,7 +176,7 @@ class BaseFormatter(object):
# one
return error.physical_line + pointer
def _write(self, output):
def _write(self, output): # type: (str) -> None
"""Handle logic of whether to use an output file or print()."""
if self.output_fd is not None:
self.output_fd.write(output + self.newline)
@ -178,6 +184,7 @@ class BaseFormatter(object):
print(output, end=self.newline)
def write(self, line, source):
# type: (Optional[str], Optional[str]) -> None
"""Write the line either to the output file or stdout.
This handles deciding whether to write to a file or print to standard
@ -195,7 +202,7 @@ class BaseFormatter(object):
if source:
self._write(source)
def stop(self):
def stop(self): # type: () -> None
"""Clean up after reporting is finished."""
if self.output_fd is not None:
self.output_fd.close()

View file

@ -1,6 +1,11 @@
"""Default formatting class for Flake8."""
from typing import Optional, Set
from flake8.formatting import base
if False: # `typing.TYPE_CHECKING` was introduced in 3.5.2
from flake8.style_guide import Violation
class SimpleFormatter(base.BaseFormatter):
"""Simple abstraction for Default and Pylint formatter commonality.
@ -18,9 +23,9 @@ class SimpleFormatter(base.BaseFormatter):
"""
error_format = None
error_format = None # type: str
def format(self, error):
def format(self, error): # type: (Violation) -> Optional[str]
"""Format and write error out.
If an output filename is specified, write formatted errors to that
@ -44,7 +49,7 @@ class Default(SimpleFormatter):
error_format = "%(path)s:%(row)d:%(col)d: %(code)s %(text)s"
def after_init(self):
def after_init(self): # type: () -> None
"""Check for a custom format string."""
if self.options.format.lower() != "default":
self.error_format = self.options.format
@ -61,28 +66,27 @@ class FilenameOnly(SimpleFormatter):
error_format = "%(path)s"
def after_init(self):
def after_init(self): # type: () -> None
"""Initialize our set of filenames."""
self.filenames_already_printed = set()
self.filenames_already_printed = set() # type: Set[str]
def show_source(self, error):
def show_source(self, error): # type: (Violation) -> Optional[str]
"""Do not include the source code."""
pass
def format(self, error):
def format(self, error): # type: (Violation) -> Optional[str]
"""Ensure we only print each error once."""
if error.filename not in self.filenames_already_printed:
self.filenames_already_printed.add(error.filename)
return super(FilenameOnly, self).format(error)
else:
return None
class Nothing(base.BaseFormatter):
"""Print absolutely nothing."""
def format(self, error):
def format(self, error): # type: (Violation) -> Optional[str]
"""Do nothing."""
pass
def show_source(self, error):
def show_source(self, error): # type: (Violation) -> Optional[str]
"""Do not print the source."""
pass

View file

@ -2,9 +2,10 @@
from __future__ import print_function
import logging
import optparse
import sys
import time
from typing import List, Optional
from typing import Dict, List, Optional, Set
import flake8
from flake8 import checker
@ -18,11 +19,8 @@ from flake8.options import manager
from flake8.plugins import manager as plugin_manager
if False: # `typing.TYPE_CHECKING` was introduced in 3.5.2
# fmt: off
# `typing.Type` as introduced in 3.5.2
from typing import Type # noqa: F401 (until flake8 3.7)
from flake8.formatting.base import BaseFormatter # noqa: F401, E501 (until flake8 3.7)
# fmt: on
from typing import Type # `typing.Type` was introduced in 3.5.2
from flake8.formatting.base import BaseFormatter
LOG = logging.getLogger(__name__)
@ -32,7 +30,6 @@ class Application(object):
"""Abstract our application into a class."""
def __init__(self, program="flake8", version=flake8.__version__):
# type: (str, str) -> None
"""Initialize our application.
:param str program:
@ -43,7 +40,7 @@ class Application(object):
#: The timestamp when the Application instance was instantiated.
self.start_time = time.time()
#: The timestamp when the Application finished reported errors.
self.end_time = None
self.end_time = None # type: float
#: The name of the program being run
self.program = program
#: The version of the program being run
@ -56,33 +53,35 @@ class Application(object):
options.register_default_options(self.option_manager)
#: The preliminary options parsed from CLI before plugins are loaded,
#: into a :class:`optparse.Values` instance
self.prelim_opts = None
self.prelim_opts = None # type: optparse.Values
#: The preliminary arguments parsed from CLI before plugins are loaded
self.prelim_args = None
self.prelim_args = None # type: List[str]
#: The instance of :class:`flake8.options.config.ConfigFileFinder`
self.config_finder = None
#: The :class:`flake8.options.config.LocalPlugins` found in config
self.local_plugins = None
self.local_plugins = None # type: config.LocalPlugins
#: The instance of :class:`flake8.plugins.manager.Checkers`
self.check_plugins = None
self.check_plugins = None # type: plugin_manager.Checkers
# fmt: off
#: The instance of :class:`flake8.plugins.manager.ReportFormatters`
self.formatting_plugins = None
self.formatting_plugins = None # type: plugin_manager.ReportFormatters
# fmt: on
#: The user-selected formatter from :attr:`formatting_plugins`
self.formatter = None
self.formatter = None # type: BaseFormatter
#: The :class:`flake8.style_guide.StyleGuideManager` built from the
#: user's options
self.guide = None
self.guide = None # type: style_guide.StyleGuideManager
#: The :class:`flake8.checker.Manager` that will handle running all of
#: the checks selected by the user.
self.file_checker_manager = None
self.file_checker_manager = None # type: checker.Manager
#: The user-supplied options parsed into an instance of
#: :class:`optparse.Values`
self.options = None
self.options = None # type: optparse.Values
#: The left over arguments that were not parsed by
#: :attr:`option_manager`
self.args = None
self.args = None # type: List[str]
#: The number of errors, warnings, and other messages after running
#: flake8 and taking into account ignored errors and lines.
self.result_count = 0
@ -96,7 +95,7 @@ class Application(object):
#: Whether the program is processing a diff or not
self.running_against_diff = False
#: The parsed diff information
self.parsed_diff = {}
self.parsed_diff = {} # type: Dict[str, Set[int]]
def parse_preliminary_options_and_args(self, argv=None):
# type: (Optional[List[str]]) -> None

View file

@ -90,9 +90,7 @@ def install():
os.path.join(hooks_directory, "pre-commit")
)
if os.path.exists(pre_commit_file):
raise exceptions.GitHookAlreadyExists(
"File already exists", path=pre_commit_file
)
raise exceptions.GitHookAlreadyExists(path=pre_commit_file)
executable = get_executable()

View file

@ -7,6 +7,7 @@
import configparser
import os
import subprocess
from typing import Set
from flake8 import exceptions as exc
@ -101,7 +102,7 @@ def install():
def get_filenames_from(repository, kwargs):
seen_filenames = set()
seen_filenames = set() # type: Set[str]
node = kwargs["node"]
for revision in range(repository[node], len(repository)):
for filename in repository[revision].files():

View file

@ -1,6 +1,6 @@
"""The logic for Flake8's integration with setuptools."""
import os
from typing import List
from typing import List, Tuple
import setuptools
@ -48,7 +48,7 @@ class Flake8(setuptools.Command):
def package_files(self):
"""Collect the files/dirs included in the registered modules."""
seen_package_directories = ()
seen_package_directories = () # type: Tuple[str, ...]
directories = self.distribution.package_dir or {}
empty_directory_exists = "" in directories
packages = self.distribution.packages or []

View file

@ -17,7 +17,7 @@ def install(option, option_string, value, parser):
For more information about the callback signature, see:
https://docs.python.org/3/library/optparse.html#optparse-option-callbacks
"""
installer = _INSTALLERS.get(value)
installer = _INSTALLERS[value]
errored = False
successful = False
try:

View file

@ -4,6 +4,7 @@ import configparser
import logging
import os.path
import sys
from typing import Dict, List, Sequence, Tuple, Union
from flake8 import utils
@ -54,12 +55,15 @@ class ConfigFileFinder(object):
# caches to avoid double-reading config files
self._local_configs = None
self._local_found_files = []
self._local_found_files = [] # type: List[str]
self._user_config = None
self._cli_configs = {}
# fmt: off
self._cli_configs = {} # type: Dict[str, configparser.RawConfigParser]
# fmt: on
@staticmethod
def _read_config(files):
# type: (Union[Sequence[str], str]) -> Tuple[configparser.RawConfigParser, List[str]] # noqa: E501
config = configparser.RawConfigParser()
if isinstance(files, (str, type(u""))):
files = [files]
@ -83,6 +87,7 @@ class ConfigFileFinder(object):
return (config, found_files)
def cli_config(self, files):
# type: (str) -> configparser.RawConfigParser
"""Read and parse the config file specified on the command-line."""
if files not in self._cli_configs:
config, found_files = self._read_config(files)
@ -379,7 +384,7 @@ def get_local_plugins(config_finder, cli_config=None, isolated=False):
raw_paths = utils.parse_comma_separated_list(
config.get(section, "paths").strip()
)
norm_paths = []
norm_paths = [] # type: List[str]
for base_dir in base_dirs:
norm_paths.extend(
path

View file

@ -2,6 +2,7 @@
import collections
import logging
import optparse # pylint: disable=deprecated-module
from typing import Any, Callable, Dict, List, Optional, Set
from flake8 import utils
@ -108,7 +109,7 @@ class Option(object):
self.comma_separated_list = comma_separated_list
self.normalize_paths = normalize_paths
self.config_name = None
self.config_name = None # type: Optional[str]
if parse_from_config:
if not long_option_name:
raise ValueError(
@ -143,7 +144,7 @@ class Option(object):
"""Normalize the value based on the option configuration."""
if self.normalize_paths:
# Decide whether to parse a list of paths or a single path
normalize = utils.normalize_path
normalize = utils.normalize_path # type: Callable[..., Any]
if self.comma_separated_list:
normalize = utils.normalize_paths
return normalize(value, *normalize_args)
@ -200,13 +201,13 @@ class OptionManager(object):
self.parser = optparse.OptionParser(
prog=prog, version=version, usage=usage
)
self.config_options_dict = {}
self.options = []
self.config_options_dict = {} # type: Dict[str, Option]
self.options = [] # type: List[Option]
self.program_name = prog
self.version = version
self.registered_plugins = set()
self.extended_default_ignore = set()
self.extended_default_select = set()
self.registered_plugins = set() # type: Set[PluginVersion]
self.extended_default_ignore = set() # type: Set[str]
self.extended_default_select = set() # type: Set[str]
@staticmethod
def format_plugin(plugin):
@ -231,6 +232,7 @@ class OptionManager(object):
self.options.append(option)
if option.parse_from_config:
name = option.config_name
assert name is not None # nosec (for mypy)
self.config_options_dict[name] = option
self.config_options_dict[name.replace("_", "-")] = option
LOG.debug('Registered option "%s".', option)
@ -326,7 +328,7 @@ class OptionManager(object):
values = self.parser.get_default_values()
self.parser.rargs = rargs
self.parser.largs = largs = []
largs = [] # type: List[str]
self.parser.values = values
while rargs:
@ -340,7 +342,8 @@ class OptionManager(object):
optparse.BadOptionError,
optparse.OptionValueError,
) as err:
self.parser.largs.append(err.opt_str)
# TODO: https://gitlab.com/pycqa/flake8/issues/541
largs.append(err.opt_str) # type: ignore
args = largs + rargs
options, xargs = self.parser.check_values(values, args)

View file

@ -1,17 +1,12 @@
"""Plugin loading and management logic and classes."""
import logging
import sys
from typing import Any, Dict, List, Set
import entrypoints
from flake8 import exceptions
from flake8 import utils
if sys.version_info >= (3, 3):
import collections.abc as collections_abc
else:
import collections as collections_abc
LOG = logging.getLogger(__name__)
__all__ = ("Checkers", "Plugin", "PluginManager", "ReportFormatters")
@ -37,7 +32,7 @@ class Plugin(object):
self.name = name
self.entry_point = entry_point
self.local = local
self._plugin = None
self._plugin = None # type: Any
self._parameters = None
self._parameter_names = None
self._group = None
@ -236,8 +231,8 @@ class PluginManager(object): # pylint: disable=too-few-public-methods
Plugins from config (as "X = path.to:Plugin" strings).
"""
self.namespace = namespace
self.plugins = {}
self.names = []
self.plugins = {} # type: Dict[str, Plugin]
self.names = [] # type: List[str]
self._load_local_plugins(local_plugins or [])
self._load_entrypoint_plugins()
@ -310,7 +305,7 @@ class PluginManager(object): # pylint: disable=too-few-public-methods
:rtype:
tuple
"""
plugins_seen = set()
plugins_seen = set() # type: Set[str]
for entry_point_name in self.names:
plugin = self.plugins[entry_point_name]
plugin_name = plugin.plugin_name
@ -345,7 +340,7 @@ def version_for(plugin):
class PluginTypeManager(object):
"""Parent class for most of the specific plugin types."""
namespace = None
namespace = None # type: str
def __init__(self, local_plugins=None):
"""Initialize the plugin type's manager.
@ -398,9 +393,7 @@ class PluginTypeManager(object):
def _generate_call_function(method_name, optmanager, *args, **kwargs):
def generated_function(plugin): # noqa: D105
method = getattr(plugin, method_name, None)
if method is not None and isinstance(
method, collections_abc.Callable
):
if method is not None and callable(method):
return method(optmanager, *args, **kwargs)
return generated_function

View file

@ -10,6 +10,7 @@ except ImportError:
else:
demandimport.disable()
import os
from typing import List
import pyflakes
import pyflakes.checker
@ -59,8 +60,8 @@ class FlakesChecker(pyflakes.checker.Checker):
name = "pyflakes"
version = pyflakes.__version__
with_doctest = False
include_in_doctest = []
exclude_from_doctest = []
include_in_doctest = [] # type: List[str]
exclude_from_doctest = [] # type: List[str]
def __init__(self, tree, file_tokens, filename):
"""Initialize the PyFlakes plugin with an AST tree and filename."""

View file

@ -3,7 +3,7 @@ import contextlib
import logging
import sys
import tokenize
from typing import List
from typing import Any, Dict, List, Tuple
import flake8
from flake8 import defaults
@ -66,9 +66,9 @@ class FileProcessor(object):
#: Number of blank lines
self.blank_lines = 0
#: Checker states for each plugin?
self._checker_states = {}
self._checker_states = {} # type: Dict[str, Dict[Any, Any]]
#: Current checker state
self.checker_state = None
self.checker_state = None # type: Dict[Any, Any]
#: User provided option for hang closing
self.hang_closing = options.hang_closing
#: Character used for indentation
@ -93,8 +93,10 @@ class FileProcessor(object):
self.previous_logical = ""
#: Previous unindented (i.e. top-level) logical line
self.previous_unindented_logical_line = ""
# fmt: off
#: Current set of tokens
self.tokens = []
self.tokens = [] # type: List[Tuple[int, str, Tuple[int, int], Tuple[int, int], str]] # noqa: E501
# fmt: on
#: Total number of lines in the file
self.total_lines = len(self.lines)
#: Verbosity level of Flake8

View file

@ -1,15 +1,19 @@
"""Statistic collection logic for Flake8."""
import collections
from typing import Dict, Generator, List, Optional
if False: # `typing.TYPE_CHECKING` was introduced in 3.5.2
from flake8.style_guide import Violation
class Statistics(object):
"""Manager of aggregated statistics for a run of Flake8."""
def __init__(self):
def __init__(self): # type: () -> None
"""Initialize the underlying dictionary for our statistics."""
self._store = {}
self._store = {} # type: Dict[Key, Statistic]
def error_codes(self):
def error_codes(self): # type: () -> List[str]
"""Return all unique error codes stored.
:returns:
@ -19,7 +23,7 @@ class Statistics(object):
"""
return sorted({key.code for key in self._store})
def record(self, error):
def record(self, error): # type: (Violation) -> None
"""Add the fact that the error was seen in the file.
:param error:
@ -34,6 +38,7 @@ class Statistics(object):
self._store[key].increment()
def statistics_for(self, prefix, filename=None):
# type: (str, Optional[str]) -> Generator[Statistic, None, None]
"""Generate statistics for the prefix and filename.
If you have a :class:`Statistics` object that has recorded errors,
@ -74,11 +79,11 @@ class Key(collections.namedtuple("Key", ["filename", "code"])):
__slots__ = ()
@classmethod
def create_from(cls, error):
def create_from(cls, error): # type: (Violation) -> Key
"""Create a Key from :class:`flake8.style_guide.Violation`."""
return cls(filename=error.filename, code=error.code)
def matches(self, prefix, filename):
def matches(self, prefix, filename): # type: (str, Optional[str]) -> bool
"""Determine if this key matches some constraints.
:param str prefix:
@ -106,6 +111,7 @@ class Statistic(object):
"""
def __init__(self, error_code, filename, message, count):
# type: (str, str, str, int) -> None
"""Initialize our Statistic."""
self.error_code = error_code
self.filename = filename
@ -113,7 +119,7 @@ class Statistic(object):
self.count = count
@classmethod
def create_from(cls, error):
def create_from(cls, error): # type: (Violation) -> Statistic
"""Create a Statistic from a :class:`flake8.style_guide.Violation`."""
return cls(
error_code=error.code,
@ -122,6 +128,6 @@ class Statistic(object):
count=0,
)
def increment(self):
def increment(self): # type: () -> None
"""Increment the number of times we've seen this error in this file."""
self.count += 1

View file

@ -7,7 +7,7 @@ import itertools
import linecache
import logging
import sys
from typing import Optional, Union
from typing import Dict, List, Optional, Set, Union
from flake8 import defaults
from flake8 import statistics
@ -68,7 +68,7 @@ class Violation(_Violation):
"""Class representing a violation reported by Flake8."""
def is_inline_ignored(self, disable_noqa):
# type: (Violation) -> bool
# type: (bool) -> bool
"""Determine if a comment has been added to ignore this line.
:param bool disable_noqa:
@ -154,7 +154,7 @@ class DecisionEngine(object):
def __init__(self, options):
"""Initialize the engine."""
self.cache = {}
self.cache = {} # type: Dict[str, Decision]
self.selected = tuple(options.select)
self.extended_selected = tuple(
sorted(options.extended_default_select, reverse=True)
@ -332,7 +332,7 @@ class StyleGuideManager(object):
self.formatter = formatter
self.stats = statistics.Statistics()
self.decider = decider or DecisionEngine(options)
self.style_guides = []
self.style_guides = [] # type: List[StyleGuide]
self.default_style_guide = StyleGuide(
options, formatter, self.stats, decider=decider
)
@ -390,7 +390,7 @@ class StyleGuideManager(object):
text,
physical_line=None,
):
# type: (str, str, int, int, str, Optional[str]) -> int
# type: (str, str, int, Optional[int], str, Optional[str]) -> int
"""Handle an error reported by a check.
:param str code:
@ -419,6 +419,7 @@ class StyleGuideManager(object):
)
def add_diff_ranges(self, diffinfo):
# type: (Dict[str, Set[int]]) -> None
"""Update the StyleGuides to filter out information not in the diff.
This provides information to the underlying StyleGuides so that only
@ -448,7 +449,7 @@ class StyleGuide(object):
self.filename = filename
if self.filename:
self.filename = utils.normalize_path(self.filename)
self._parsed_diff = {}
self._parsed_diff = {} # type: Dict[str, Set[int]]
def __repr__(self):
"""Make it easier to debug which StyleGuide we're using."""
@ -514,7 +515,7 @@ class StyleGuide(object):
text,
physical_line=None,
):
# type: (str, str, int, int, str, Optional[str]) -> int
# type: (str, str, int, Optional[int], str, Optional[str]) -> int
"""Handle an error reported by a check.
:param str code:
@ -567,6 +568,7 @@ class StyleGuide(object):
return 0
def add_diff_ranges(self, diffinfo):
# type: (Dict[str, Set[int]]) -> None
"""Update the StyleGuide to filter out information not in the diff.
This provides information to the StyleGuide so that only the errors

View file

@ -3,13 +3,14 @@ import collections
import fnmatch as _fnmatch
import inspect
import io
import logging
import os
import platform
import re
import sys
import tokenize
from typing import Callable, Dict, Generator, List, Pattern, Sequence, Set
from typing import Tuple, Union
from typing import Callable, Dict, Generator, List, Optional, Pattern
from typing import Sequence, Set, Tuple, Union
from flake8 import exceptions
@ -19,6 +20,7 @@ if False: # `typing.TYPE_CHECKING` was introduced in 3.5.2
DIFF_HUNK_REGEXP = re.compile(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$")
COMMA_SEPARATED_LIST_RE = re.compile(r"[,\s]")
LOCAL_PLUGIN_LIST_RE = re.compile(r"[,\t\n\r\f\v]")
string_types = (str, type(u""))
def parse_comma_separated_list(value, regexp=COMMA_SEPARATED_LIST_RE):
@ -40,7 +42,7 @@ def parse_comma_separated_list(value, regexp=COMMA_SEPARATED_LIST_RE):
if not value:
return []
if not isinstance(value, (list, tuple)):
if isinstance(value, string_types):
value = regexp.split(value)
item_gen = (item.strip() for item in value)
@ -77,8 +79,8 @@ def _tokenize_files_to_codes_mapping(value):
return tokens
def parse_files_to_codes_mapping(value): # noqa: C901
# type: (Union[Sequence[str], str]) -> List[Tuple[List[str], List[str]]]
def parse_files_to_codes_mapping(value_): # noqa: C901
# type: (Union[Sequence[str], str]) -> List[Tuple[str, List[str]]]
"""Parse a files-to-codes maping.
A files-to-codes mapping a sequence of values specified as
@ -88,20 +90,22 @@ def parse_files_to_codes_mapping(value): # noqa: C901
:param value: String to be parsed and normalized.
:type value: str
"""
if isinstance(value, (list, tuple)):
value = "\n".join(value)
if not isinstance(value_, string_types):
value = "\n".join(value_)
else:
value = value_
ret = []
ret = [] # type: List[Tuple[str, List[str]]]
if not value.strip():
return ret
class State:
seen_sep = True
seen_colon = False
filenames = []
codes = []
filenames = [] # type: List[str]
codes = [] # type: List[str]
def _reset():
def _reset(): # type: () -> None
if State.codes:
for filename in State.filenames:
ret.append((filename, State.codes))
@ -110,11 +114,8 @@ def parse_files_to_codes_mapping(value): # noqa: C901
State.filenames = []
State.codes = []
def _unexpected_token():
# type: () -> exceptions.ExecutionError
def _indent(s):
# type: (str) -> str
def _unexpected_token(): # type: () -> exceptions.ExecutionError
def _indent(s): # type: (str) -> str
return " " + s.strip().replace("\n", "\n ")
return exceptions.ExecutionError(
@ -192,7 +193,7 @@ def normalize_path(path, parent=os.curdir):
return path.rstrip(separator + alternate_separator)
def _stdin_get_value_py3():
def _stdin_get_value_py3(): # type: () -> io.StringIO
stdin_value = sys.stdin.buffer.read()
fd = io.BytesIO(stdin_value)
try:
@ -211,13 +212,13 @@ def stdin_get_value():
stdin_value = io.BytesIO(sys.stdin.read())
else:
stdin_value = _stdin_get_value_py3()
stdin_get_value.cached_stdin = stdin_value
cached_value = stdin_get_value.cached_stdin
stdin_get_value.cached_stdin = stdin_value # type: ignore
cached_value = stdin_get_value.cached_stdin # type: ignore
return cached_value.getvalue()
def parse_unified_diff(diff=None):
# type: (str) -> Dict[str, Set[int]]
# type: (Optional[str]) -> Dict[str, Set[int]]
"""Parse the unified diff passed on stdin.
:returns:
@ -231,7 +232,7 @@ def parse_unified_diff(diff=None):
number_of_rows = None
current_path = None
parsed_paths = collections.defaultdict(set)
parsed_paths = collections.defaultdict(set) # type: Dict[str, Set[int]]
for line in diff.splitlines():
if number_of_rows:
# NOTE(sigmavirus24): Below we use a slice because stdin may be
@ -279,6 +280,7 @@ def parse_unified_diff(diff=None):
1 if not group else int(group)
for group in hunk_match.groups()
]
assert current_path is not None # nosec (for mypy)
parsed_paths[current_path].update(
range(row, row + number_of_rows)
)
@ -338,12 +340,12 @@ def is_using_stdin(paths):
return "-" in paths
def _default_predicate(*args):
def _default_predicate(*args): # type: (*str) -> bool
return False
def filenames_from(arg, predicate=None):
# type: (str, Callable[[str], bool]) -> Generator
# type: (str, Optional[Callable[[str], bool]]) -> Generator[str, None, None] # noqa: E501
"""Generate filenames from an argument.
:param str arg:
@ -384,8 +386,8 @@ def filenames_from(arg, predicate=None):
yield arg
def fnmatch(filename, patterns, default=True):
# type: (str, List[str], bool) -> bool
def fnmatch(filename, patterns):
# type: (str, List[str]) -> bool
"""Wrap :func:`fnmatch.fnmatch` to add some functionality.
:param str filename:
@ -399,7 +401,7 @@ def fnmatch(filename, patterns, default=True):
``default`` if patterns is empty.
"""
if not patterns:
return default
return True
return any(_fnmatch.fnmatch(filename, pattern) for pattern in patterns)
@ -452,6 +454,7 @@ def parameters_for(plugin):
def matches_filename(path, patterns, log_message, logger):
# type: (str, List[str], str, logging.Logger) -> bool
"""Use fnmatch to discern if a path exists in patterns.
:param str path:
@ -483,7 +486,7 @@ def matches_filename(path, patterns, log_message, logger):
return match
def get_python_version():
def get_python_version(): # type: () -> str
"""Find and format the python implementation and version.
:returns:

1
tests/__init__.py Normal file
View file

@ -0,0 +1 @@
"""This is here because mypy doesn't understand PEP 420."""

View file

@ -216,9 +216,7 @@ def test_report_order(results, expected_order):
# _handle_results is the first place which gets the sorted result
# Should something non-private be mocked instead?
handler = mock.Mock()
handler.side_effect = count_side_effect
manager._handle_results = handler
assert manager.report() == (len(results), len(results))
handler.assert_called_once_with('placeholder', expected_results)
handler = mock.Mock(side_effect=count_side_effect)
with mock.patch.object(manager, '_handle_results', handler):
assert manager.report() == (len(results), len(results))
handler.assert_called_once_with('placeholder', expected_results)

View file

@ -44,7 +44,9 @@ def test_format_needs_to_be_implemented():
"""Ensure BaseFormatter#format raises a NotImplementedError."""
formatter = base.BaseFormatter(options())
with pytest.raises(NotImplementedError):
formatter.format('foo')
formatter.format(
style_guide.Violation('A000', 'file.py', 1, 1, 'error text', None)
)
def test_show_source_returns_nothing_when_not_showing_source():
@ -73,6 +75,7 @@ def test_show_source_updates_physical_line_appropriately(line, column):
formatter = base.BaseFormatter(options(show_source=True))
error = style_guide.Violation('A000', 'file.py', 1, column, 'error', line)
output = formatter.show_source(error)
assert output
_, pointer = output.rsplit('\n', 1)
assert pointer.count(' ') == (column - 1)

View file

@ -72,8 +72,7 @@ def test_information(system, pyversion, pyimpl):
@mock.patch('json.dumps', return_value='{}')
def test_print_information_no_plugins(dumps, information, print_mock):
"""Verify we print and exit only when we have plugins."""
plugins = []
option_manager = mock.Mock(registered_plugins=set(plugins))
option_manager = mock.Mock(registered_plugins=set())
assert debug.print_information(
None, None, None, None, option_manager=option_manager,
) is None

View file

@ -74,11 +74,10 @@ def test_was_selected_selects_errors(select_list, enable_extensions,
def test_was_selected_implicitly_selects_errors():
"""Verify we detect users implicitly selecting an error."""
select_list = []
error_code = 'E121'
decider = style_guide.DecisionEngine(
create_options(
select=select_list,
select=[],
extended_default_select=['E'],
),
)

View file

@ -75,4 +75,4 @@ def test_config_name_needs_long_option_name():
def test_dest_is_not_overridden():
"""Show that we do not override custom destinations."""
opt = manager.Option('-s', '--short', dest='something_not_short')
assert opt.dest == 'something_not_short'
assert opt.dest == 'something_not_short' # type: ignore

View file

@ -1,17 +1,10 @@
"""Tests for flake8.plugins.manager.PluginTypeManager."""
import sys
import mock
import pytest
from flake8 import exceptions
from flake8.plugins import manager
if sys.version_info >= (3, 3):
import collections.abc as collections_abc
else:
import collections as collections_abc
TEST_NAMESPACE = "testing.plugin-type-manager"
@ -91,7 +84,7 @@ def test_generate_call_function():
'method_name', optmanager,
)
assert isinstance(func, collections_abc.Callable)
assert callable(func)
assert func(plugin) is optmanager
@ -168,15 +161,14 @@ def test_provide_options(PluginManager): # noqa: N803
PluginManager.return_value = create_mapping_manager_mock(plugins)
optmanager = object()
options = object()
extra_args = []
type_mgr = FakeTestType()
type_mgr.provide_options(optmanager, options, extra_args)
type_mgr.provide_options(optmanager, options, [])
for plugin in plugins:
plugin.provide_options.assert_called_with(optmanager,
options,
extra_args)
[])
@mock.patch('flake8.plugins.manager.PluginManager')

View file

@ -82,7 +82,7 @@ def test_recording_statistics():
assert isinstance(key, stats.Key)
assert isinstance(value, stats.Statistic)
assert storage[(DEFAULT_FILENAME, DEFAULT_ERROR_CODE)].count == 1
assert storage[stats.Key(DEFAULT_FILENAME, DEFAULT_ERROR_CODE)].count == 1
def test_statistics_for_single_record():

View file

@ -163,12 +163,6 @@ def test_fnmatch(filename, patterns, expected):
assert utils.fnmatch(filename, patterns) is expected
def test_fnmatch_returns_the_default_with_empty_default():
"""The default parameter should be returned when no patterns are given."""
sentinel = object()
assert utils.fnmatch('file.py', [], default=sentinel) is sentinel
def test_filenames_from_a_directory():
"""Verify that filenames_from walks a directory."""
filenames = list(utils.filenames_from('src/flake8/'))

View file

@ -70,13 +70,12 @@ deps =
commands =
doc8 docs/source/
[testenv:mypy]
[testenv:pre-commit]
basepython = python3
skip_install = true
deps =
mypy
deps = pre-commit
commands =
mypy src
pre-commit run --all-files --show-diff-on-failure
[testenv:bandit]
basepython = python3