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 script: tox -e py36
python37: python37:
image: python:3.7-rc image: python:3.7
stage: test stage: test
script: tox -e py37 script: tox -e py37
@ -44,6 +44,11 @@ linters:
stage: test stage: test
script: tox -e linters script: tox -e linters
pre-commit:
image: python:3.7
stage: test
script: tox -e pre-commit
docs: docs:
stage: test stage: test
script: tox -e docs 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 pyflakes >= 2.1.0, < 2.2.0
pycodestyle >= 2.5.0, < 2.6.0 pycodestyle >= 2.5.0, < 2.6.0
mccabe >= 0.6.0, < 0.7.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 logging
import sys 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 = logging.getLogger(__name__)
LOG.addHandler(logging.NullHandler()) 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"): if not filename or filename in ("stderr", "stdout"):
fileobj = getattr(sys, filename or "stderr") fileobj = getattr(sys, filename or "stderr")
handler_cls = logging.StreamHandler handler_cls = logging.StreamHandler # type: Type[logging.Handler]
else: else:
fileobj = filename fileobj = filename
handler_cls = logging.FileHandler handler_cls = logging.FileHandler

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
"""The logic for Flake8's integration with setuptools.""" """The logic for Flake8's integration with setuptools."""
import os import os
from typing import List from typing import List, Tuple
import setuptools import setuptools
@ -48,7 +48,7 @@ class Flake8(setuptools.Command):
def package_files(self): def package_files(self):
"""Collect the files/dirs included in the registered modules.""" """Collect the files/dirs included in the registered modules."""
seen_package_directories = () seen_package_directories = () # type: Tuple[str, ...]
directories = self.distribution.package_dir or {} directories = self.distribution.package_dir or {}
empty_directory_exists = "" in directories empty_directory_exists = "" in directories
packages = self.distribution.packages or [] 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: For more information about the callback signature, see:
https://docs.python.org/3/library/optparse.html#optparse-option-callbacks https://docs.python.org/3/library/optparse.html#optparse-option-callbacks
""" """
installer = _INSTALLERS.get(value) installer = _INSTALLERS[value]
errored = False errored = False
successful = False successful = False
try: try:

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -3,13 +3,14 @@ import collections
import fnmatch as _fnmatch import fnmatch as _fnmatch
import inspect import inspect
import io import io
import logging
import os import os
import platform import platform
import re import re
import sys import sys
import tokenize import tokenize
from typing import Callable, Dict, Generator, List, Pattern, Sequence, Set from typing import Callable, Dict, Generator, List, Optional, Pattern
from typing import Tuple, Union from typing import Sequence, Set, Tuple, Union
from flake8 import exceptions 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+))? @@.*$") DIFF_HUNK_REGEXP = re.compile(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$")
COMMA_SEPARATED_LIST_RE = re.compile(r"[,\s]") COMMA_SEPARATED_LIST_RE = re.compile(r"[,\s]")
LOCAL_PLUGIN_LIST_RE = re.compile(r"[,\t\n\r\f\v]") 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): 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: if not value:
return [] return []
if not isinstance(value, (list, tuple)): if isinstance(value, string_types):
value = regexp.split(value) value = regexp.split(value)
item_gen = (item.strip() for item in value) item_gen = (item.strip() for item in value)
@ -77,8 +79,8 @@ def _tokenize_files_to_codes_mapping(value):
return tokens return tokens
def parse_files_to_codes_mapping(value): # noqa: C901 def parse_files_to_codes_mapping(value_): # noqa: C901
# type: (Union[Sequence[str], str]) -> List[Tuple[List[str], List[str]]] # type: (Union[Sequence[str], str]) -> List[Tuple[str, List[str]]]
"""Parse a files-to-codes maping. """Parse a files-to-codes maping.
A files-to-codes mapping a sequence of values specified as 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. :param value: String to be parsed and normalized.
:type value: str :type value: str
""" """
if isinstance(value, (list, tuple)): if not isinstance(value_, string_types):
value = "\n".join(value) value = "\n".join(value_)
else:
value = value_
ret = [] ret = [] # type: List[Tuple[str, List[str]]]
if not value.strip(): if not value.strip():
return ret return ret
class State: class State:
seen_sep = True seen_sep = True
seen_colon = False seen_colon = False
filenames = [] filenames = [] # type: List[str]
codes = [] codes = [] # type: List[str]
def _reset(): def _reset(): # type: () -> None
if State.codes: if State.codes:
for filename in State.filenames: for filename in State.filenames:
ret.append((filename, State.codes)) ret.append((filename, State.codes))
@ -110,11 +114,8 @@ def parse_files_to_codes_mapping(value): # noqa: C901
State.filenames = [] State.filenames = []
State.codes = [] State.codes = []
def _unexpected_token(): def _unexpected_token(): # type: () -> exceptions.ExecutionError
# type: () -> exceptions.ExecutionError def _indent(s): # type: (str) -> str
def _indent(s):
# type: (str) -> str
return " " + s.strip().replace("\n", "\n ") return " " + s.strip().replace("\n", "\n ")
return exceptions.ExecutionError( return exceptions.ExecutionError(
@ -192,7 +193,7 @@ def normalize_path(path, parent=os.curdir):
return path.rstrip(separator + alternate_separator) 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() stdin_value = sys.stdin.buffer.read()
fd = io.BytesIO(stdin_value) fd = io.BytesIO(stdin_value)
try: try:
@ -211,13 +212,13 @@ def stdin_get_value():
stdin_value = io.BytesIO(sys.stdin.read()) stdin_value = io.BytesIO(sys.stdin.read())
else: else:
stdin_value = _stdin_get_value_py3() stdin_value = _stdin_get_value_py3()
stdin_get_value.cached_stdin = stdin_value stdin_get_value.cached_stdin = stdin_value # type: ignore
cached_value = stdin_get_value.cached_stdin cached_value = stdin_get_value.cached_stdin # type: ignore
return cached_value.getvalue() return cached_value.getvalue()
def parse_unified_diff(diff=None): 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. """Parse the unified diff passed on stdin.
:returns: :returns:
@ -231,7 +232,7 @@ def parse_unified_diff(diff=None):
number_of_rows = None number_of_rows = None
current_path = None current_path = None
parsed_paths = collections.defaultdict(set) parsed_paths = collections.defaultdict(set) # type: Dict[str, Set[int]]
for line in diff.splitlines(): for line in diff.splitlines():
if number_of_rows: if number_of_rows:
# NOTE(sigmavirus24): Below we use a slice because stdin may be # 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) 1 if not group else int(group)
for group in hunk_match.groups() for group in hunk_match.groups()
] ]
assert current_path is not None # nosec (for mypy)
parsed_paths[current_path].update( parsed_paths[current_path].update(
range(row, row + number_of_rows) range(row, row + number_of_rows)
) )
@ -338,12 +340,12 @@ def is_using_stdin(paths):
return "-" in paths return "-" in paths
def _default_predicate(*args): def _default_predicate(*args): # type: (*str) -> bool
return False return False
def filenames_from(arg, predicate=None): 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. """Generate filenames from an argument.
:param str arg: :param str arg:
@ -384,8 +386,8 @@ def filenames_from(arg, predicate=None):
yield arg yield arg
def fnmatch(filename, patterns, default=True): def fnmatch(filename, patterns):
# type: (str, List[str], bool) -> bool # type: (str, List[str]) -> bool
"""Wrap :func:`fnmatch.fnmatch` to add some functionality. """Wrap :func:`fnmatch.fnmatch` to add some functionality.
:param str filename: :param str filename:
@ -399,7 +401,7 @@ def fnmatch(filename, patterns, default=True):
``default`` if patterns is empty. ``default`` if patterns is empty.
""" """
if not patterns: if not patterns:
return default return True
return any(_fnmatch.fnmatch(filename, pattern) for pattern in patterns) 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): 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. """Use fnmatch to discern if a path exists in patterns.
:param str path: :param str path:
@ -483,7 +486,7 @@ def matches_filename(path, patterns, log_message, logger):
return match return match
def get_python_version(): def get_python_version(): # type: () -> str
"""Find and format the python implementation and version. """Find and format the python implementation and version.
:returns: :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 # _handle_results is the first place which gets the sorted result
# Should something non-private be mocked instead? # Should something non-private be mocked instead?
handler = mock.Mock() handler = mock.Mock(side_effect=count_side_effect)
handler.side_effect = count_side_effect with mock.patch.object(manager, '_handle_results', handler):
manager._handle_results = handler assert manager.report() == (len(results), len(results))
handler.assert_called_once_with('placeholder', expected_results)
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.""" """Ensure BaseFormatter#format raises a NotImplementedError."""
formatter = base.BaseFormatter(options()) formatter = base.BaseFormatter(options())
with pytest.raises(NotImplementedError): 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(): 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)) formatter = base.BaseFormatter(options(show_source=True))
error = style_guide.Violation('A000', 'file.py', 1, column, 'error', line) error = style_guide.Violation('A000', 'file.py', 1, column, 'error', line)
output = formatter.show_source(error) output = formatter.show_source(error)
assert output
_, pointer = output.rsplit('\n', 1) _, pointer = output.rsplit('\n', 1)
assert pointer.count(' ') == (column - 1) assert pointer.count(' ') == (column - 1)

View file

@ -72,8 +72,7 @@ def test_information(system, pyversion, pyimpl):
@mock.patch('json.dumps', return_value='{}') @mock.patch('json.dumps', return_value='{}')
def test_print_information_no_plugins(dumps, information, print_mock): def test_print_information_no_plugins(dumps, information, print_mock):
"""Verify we print and exit only when we have plugins.""" """Verify we print and exit only when we have plugins."""
plugins = [] option_manager = mock.Mock(registered_plugins=set())
option_manager = mock.Mock(registered_plugins=set(plugins))
assert debug.print_information( assert debug.print_information(
None, None, None, None, option_manager=option_manager, None, None, None, None, option_manager=option_manager,
) is None ) 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(): def test_was_selected_implicitly_selects_errors():
"""Verify we detect users implicitly selecting an error.""" """Verify we detect users implicitly selecting an error."""
select_list = []
error_code = 'E121' error_code = 'E121'
decider = style_guide.DecisionEngine( decider = style_guide.DecisionEngine(
create_options( create_options(
select=select_list, select=[],
extended_default_select=['E'], extended_default_select=['E'],
), ),
) )

View file

@ -75,4 +75,4 @@ def test_config_name_needs_long_option_name():
def test_dest_is_not_overridden(): def test_dest_is_not_overridden():
"""Show that we do not override custom destinations.""" """Show that we do not override custom destinations."""
opt = manager.Option('-s', '--short', dest='something_not_short') 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.""" """Tests for flake8.plugins.manager.PluginTypeManager."""
import sys
import mock import mock
import pytest import pytest
from flake8 import exceptions from flake8 import exceptions
from flake8.plugins import manager 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" TEST_NAMESPACE = "testing.plugin-type-manager"
@ -91,7 +84,7 @@ def test_generate_call_function():
'method_name', optmanager, 'method_name', optmanager,
) )
assert isinstance(func, collections_abc.Callable) assert callable(func)
assert func(plugin) is optmanager assert func(plugin) is optmanager
@ -168,15 +161,14 @@ def test_provide_options(PluginManager): # noqa: N803
PluginManager.return_value = create_mapping_manager_mock(plugins) PluginManager.return_value = create_mapping_manager_mock(plugins)
optmanager = object() optmanager = object()
options = object() options = object()
extra_args = []
type_mgr = FakeTestType() type_mgr = FakeTestType()
type_mgr.provide_options(optmanager, options, extra_args) type_mgr.provide_options(optmanager, options, [])
for plugin in plugins: for plugin in plugins:
plugin.provide_options.assert_called_with(optmanager, plugin.provide_options.assert_called_with(optmanager,
options, options,
extra_args) [])
@mock.patch('flake8.plugins.manager.PluginManager') @mock.patch('flake8.plugins.manager.PluginManager')

View file

@ -82,7 +82,7 @@ def test_recording_statistics():
assert isinstance(key, stats.Key) assert isinstance(key, stats.Key)
assert isinstance(value, stats.Statistic) 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(): 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 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(): def test_filenames_from_a_directory():
"""Verify that filenames_from walks a directory.""" """Verify that filenames_from walks a directory."""
filenames = list(utils.filenames_from('src/flake8/')) filenames = list(utils.filenames_from('src/flake8/'))

View file

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