Merge branch 'py3_plus' into 'master'

drop legacy python versions

Closes #690

See merge request pycqa/flake8!471
This commit is contained in:
Anthony Sottile 2021-03-31 14:22:37 +00:00
commit e0116d8e77
65 changed files with 629 additions and 731 deletions

View file

@ -1,8 +1,8 @@
# To activate, change the Appveyor settings to use `.appveyor.yml`. # To activate, change the Appveyor settings to use `.appveyor.yml`.
install: install:
- python -m pip install --upgrade setuptools tox virtualenv - C:\Python38-x64\python.exe -m pip install --upgrade setuptools tox virtualenv
build: off build: off
test_script: test_script:
- python -m tox -e py27,py36,py37,dogfood - C:\Python38-x64\python.exe -m tox -e py36,py37,dogfood

View file

@ -30,3 +30,4 @@ exclude_lines =
# Don't complain if non-runnable code isn't run: # Don't complain if non-runnable code isn't run:
^if __name__ == ['"]__main__['"]:$ ^if __name__ == ['"]__main__['"]:$
^\s*if False: ^\s*if False:
^\s*if TYPE_CHECKING:

View file

@ -13,26 +13,11 @@ after_script:
- pip install codecov - pip install codecov
- codecov --token=7d117e6b-aab6-4283-ab19-166dafc38cf5 - codecov --token=7d117e6b-aab6-4283-ab19-166dafc38cf5
pypy2:
image: pypy:2.7-7.2.0
stage: test
script: tox -e pypy
pypy3: pypy3:
image: pypy:3.6-7.2.0 image: pypy:3.6-7.2.0
stage: test stage: test
script: tox -e pypy3 script: tox -e pypy3
python2:
image: python:2.7
stage: test
script: tox -e py27
python35:
image: python:3.5
stage: test
script: tox -e py35
python36: python36:
image: python:3.6 image: python:3.6
stage: test stage: test

View file

@ -1,3 +1,4 @@
exclude: ^tests/fixtures/example-code/
repos: repos:
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.3.0 rev: v2.3.0
@ -7,12 +8,22 @@ repos:
- id: end-of-file-fixer - id: end-of-file-fixer
- id: trailing-whitespace - id: trailing-whitespace
exclude: ^tests/fixtures/diffs/ exclude: ^tests/fixtures/diffs/
- repo: https://github.com/asottile/reorder_python_imports
rev: v2.4.0
hooks:
- id: reorder-python-imports
args: [--application-directories, '.:src', --py36-plus]
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 20.8b1 rev: 20.8b1
hooks: hooks:
- id: black - id: black
args: [--line-length=78] args: [--line-length=78]
files: ^src/ files: ^src/
- repo: https://github.com/asottile/pyupgrade
rev: v2.11.0
hooks:
- id: pyupgrade
args: [--py36-plus]
- repo: https://github.com/pre-commit/mirrors-mypy - repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.720 rev: v0.720
hooks: hooks:

View file

@ -11,18 +11,12 @@ notifications:
matrix: matrix:
include: include:
- python: 2.7
env: TOXENV=py27
- python: 3.5
env: TOXENV=py35
- python: 3.6 - python: 3.6
env: TOXENV=py36 env: TOXENV=py36
- python: 3.7 - python: 3.7
env: TOXENV=py37 env: TOXENV=py37
- python: 3.8 - python: 3.8
env: TOXENV=py38 env: TOXENV=py38
- python: pypy
env: TOXENV=pypy
- python: 3.7 - python: 3.7
env: TOXENV=readme env: TOXENV=readme
- python: 3.7 - python: 3.7

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# #
# flake8 documentation build configuration file, created by # flake8 documentation build configuration file, created by
# sphinx-quickstart on Tue Jan 19 07:14:10 2016. # sphinx-quickstart on Tue Jan 19 07:14:10 2016.
@ -11,9 +10,8 @@
# #
# All configuration values have a default; values that are commented out # All configuration values have a default; values that are commented out
# serve to show the default. # serve to show the default.
import sys
import os import os
import sys
# If extensions (or modules to document with autodoc) are in another directory, # If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the # add these directories to sys.path here. If the directory is relative to the
@ -53,11 +51,12 @@ source_suffix = '.rst'
master_doc = 'index' master_doc = 'index'
# General information about the project. # General information about the project.
project = u'flake8' project = 'flake8'
copyright = u'2016, Ian Stapleton Cordasco' copyright = '2016, Ian Stapleton Cordasco'
author = u'Ian Stapleton Cordasco' author = 'Ian Stapleton Cordasco'
import flake8 import flake8
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the
# built documents. # built documents.
@ -234,8 +233,8 @@ latex_elements = {
# (source start file, target name, title, # (source start file, target name, title,
# author, documentclass [howto, manual, or own class]). # author, documentclass [howto, manual, or own class]).
latex_documents = [ latex_documents = [
(master_doc, 'flake8.tex', u'flake8 Documentation', (master_doc, 'flake8.tex', 'flake8 Documentation',
u'Ian Stapleton Cordasco', 'manual'), 'Ian Stapleton Cordasco', 'manual'),
] ]
# The name of an image file (relative to this directory) to place at the top of # The name of an image file (relative to this directory) to place at the top of
@ -264,7 +263,7 @@ latex_documents = [
# One entry per manual page. List of tuples # One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section). # (source start file, name, description, authors, manual section).
man_pages = [ man_pages = [
('manpage', 'flake8', u'Flake8 Command Line Documentation', ('manpage', 'flake8', 'Flake8 Command Line Documentation',
[author], 1) [author], 1)
] ]
@ -278,7 +277,7 @@ man_pages = [
# (source start file, target name, title, author, # (source start file, target name, title, author,
# dir menu entry, description, category) # dir menu entry, description, category)
texinfo_documents = [ texinfo_documents = [
('index', 'Flake8', u'Flake8 Documentation', u'Tarek Ziade', ('index', 'Flake8', 'Flake8 Documentation', 'Tarek Ziade',
'Flake8', 'Code checking using pycodestyle, pyflakes and mccabe', 'Flake8', 'Code checking using pycodestyle, pyflakes and mccabe',
'Miscellaneous'), 'Miscellaneous'),
] ]

View file

@ -81,13 +81,11 @@ for users.
Before releasing, the following tox test environments must pass: Before releasing, the following tox test environments must pass:
- Python 2.7 (a.k.a., ``tox -e py27``)
- Python 3.6 (a.k.a., ``tox -e py36``) - Python 3.6 (a.k.a., ``tox -e py36``)
- Python 3.7 (a.k.a., ``tox -e py37``) - Python 3.7 (a.k.a., ``tox -e py37``)
- PyPy (a.k.a., ``tox -e pypy``) - PyPy 3 (a.k.a., ``tox -e pypy3``)
- Linters (a.k.a., ``tox -e linters``) - Linters (a.k.a., ``tox -e linters``)

View file

@ -175,11 +175,7 @@ across multiple lines, insert a new-line after the opening parenthesis, e.g.,
statistic = next(stats_for_error_code) statistic = next(stats_for_error_code)
count = statistic.count count = statistic.count
count += sum(stat.count for stat in stats_for_error_code) count += sum(stat.count for stat in stats_for_error_code)
self._write('{count:<5} {error_code} {message}'.format( self._write(f'{count:<5} {error_code} {statistic.message}')
count=count,
error_code=error_code,
message=statistic.message,
))
In the first example, we put a few of the parameters all on one line, and then In the first example, we put a few of the parameters all on one line, and then
added the last two on their own. In the second example, each parameter has its added the last two on their own. In the second example, each parameter has its

View file

@ -56,7 +56,6 @@ like:
"Intended Audience :: Developers", "Intended Audience :: Developers",
"License :: OSI Approved :: MIT License", "License :: OSI Approved :: MIT License",
"Programming Language :: Python", "Programming Language :: Python",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Software Development :: Quality Assurance", "Topic :: Software Development :: Quality Assurance",

View file

@ -1,5 +1,4 @@
sphinx>=1.3.0,!=3.1.0 sphinx>=1.3.0,!=3.1.0
sphinx_rtd_theme sphinx_rtd_theme
sphinx-prompt sphinx-prompt
configparser
flake8-polyfill flake8-polyfill

View file

@ -100,8 +100,6 @@ generates its own :term:`error code`\ s for ``pyflakes``:
+------+---------------------------------------------------------------------+ +------+---------------------------------------------------------------------+
| F811 | redefinition of unused ``name`` from line ``N`` | | F811 | redefinition of unused ``name`` from line ``N`` |
+------+---------------------------------------------------------------------+ +------+---------------------------------------------------------------------+
| F812 | list comprehension redefines ``name`` from line ``N`` |
+------+---------------------------------------------------------------------+
| F821 | undefined name ``name`` | | F821 | undefined name ``name`` |
+------+---------------------------------------------------------------------+ +------+---------------------------------------------------------------------+
| F822 | undefined name ``name`` in ``__all__`` | | F822 | undefined name ``name`` in ``__all__`` |
@ -116,9 +114,6 @@ generates its own :term:`error code`\ s for ``pyflakes``:
| F901 | ``raise NotImplemented`` should be ``raise NotImplementedError`` | | F901 | ``raise NotImplemented`` should be ``raise NotImplementedError`` |
+------+---------------------------------------------------------------------+ +------+---------------------------------------------------------------------+
Note that some of these entries behave differently on Python 2 and Python 3,
for example F812 is specific to Python 2 only.
We also report one extra error: ``E999``. We report ``E999`` when we fail to We also report one extra error: ``E999``. We report ``E999`` when we fail to
compile a file into an Abstract Syntax Tree for the plugins that require it. compile a file into an Abstract Syntax Tree for the plugins that require it.

View file

@ -14,25 +14,25 @@ like so:
Where you simply allow the shell running in your terminal to locate |Flake8|. Where you simply allow the shell running in your terminal to locate |Flake8|.
In some cases, though, you may have installed |Flake8| for multiple versions In some cases, though, you may have installed |Flake8| for multiple versions
of Python (e.g., Python 2.7 and Python 3.5) and you need to call a specific of Python (e.g., Python 3.8 and Python 3.9) and you need to call a specific
version. In that case, you will have much better results using: version. In that case, you will have much better results using:
.. prompt:: bash .. prompt:: bash
python2.7 -m flake8 python3.8 -m flake8
Or Or
.. prompt:: bash .. prompt:: bash
python3.5 -m flake8 python3.9 -m flake8
Since that will tell the correct version of Python to run |Flake8|. Since that will tell the correct version of Python to run |Flake8|.
.. note:: .. note::
Installing |Flake8| once will not install it on both Python 2.7 and Installing |Flake8| once will not install it on both Python 3.8 and
Python 3.5. It will only install it for the version of Python that Python 3.9. It will only install it for the version of Python that
is running pip. is running pip.
It is also possible to specify command-line options directly to |Flake8|: It is also possible to specify command-line options directly to |Flake8|:

View file

@ -24,10 +24,8 @@ appropriate of:
pip install <plugin-name> pip install <plugin-name>
pip3 install <plugin-name> pip3 install <plugin-name>
python -m pip install <plugin-name> python -m pip install <plugin-name>
python2.7 -m pip install <plugin-name>
python3 -m pip install <plugin-name> python3 -m pip install <plugin-name>
python3.4 -m pip install <plugin-name> python3.9 -m pip install <plugin-name>
python3.5 -m pip install <plugin-name>
To install the plugin, where ``<plugin-name>`` is the package name on PyPI_. To install the plugin, where ``<plugin-name>`` is the package name on PyPI_.
To verify installation use: To verify installation use:

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
import setuptools import setuptools
setuptools.setup( setuptools.setup(
@ -21,11 +20,9 @@ setuptools.setup(
'Framework :: Flake8', 'Framework :: Flake8',
'License :: OSI Approved :: MIT License', 'License :: OSI Approved :: MIT License',
'Programming Language :: Python', 'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.9',
'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Software Development :: Quality Assurance', 'Topic :: Software Development :: Quality Assurance',
], ],

View file

@ -1,7 +1,6 @@
"""Module for an example Flake8 plugin.""" """Module for an example Flake8 plugin."""
from .on_by_default import ExampleOne
from .off_by_default import ExampleTwo from .off_by_default import ExampleTwo
from .on_by_default import ExampleOne
__all__ = ( __all__ = (
'ExampleOne', 'ExampleOne',

View file

@ -1,7 +1,7 @@
"""Our first example plugin.""" """Our first example plugin."""
class ExampleTwo(object): class ExampleTwo:
"""Second Example Plugin.""" """Second Example Plugin."""
name = 'off-by-default-example-plugin' name = 'off-by-default-example-plugin'
version = '1.0.0' version = '1.0.0'

View file

@ -1,7 +1,7 @@
"""Our first example plugin.""" """Our first example plugin."""
class ExampleOne(object): class ExampleOne:
"""First Example Plugin.""" """First Example Plugin."""
name = 'on-by-default-example-plugin' name = 'on-by-default-example-plugin'
version = '1.0.0' version = '1.0.0'
@ -11,5 +11,4 @@ class ExampleOne(object):
def run(self): def run(self):
"""Do nothing.""" """Do nothing."""
for message in []: yield from []
yield message

View file

@ -1,10 +1,4 @@
[pytest] [pytest]
norecursedirs = .git .* *.egg* old docs dist build norecursedirs = .git .* *.egg* old docs dist build
addopts = -rw addopts = -rw
filterwarnings = filterwarnings = error
error
# python3.4 raises this when importing setuptools
ignore:The value of convert_charrefs will become True in 3.5.*:DeprecationWarning
# python3 raises this when importing setuptools
ignore:the imp module is deprecated in favour of importlib.*:PendingDeprecationWarning
ignore:the imp module is deprecated in favour of importlib.*:DeprecationWarning

View file

@ -20,10 +20,7 @@ classifiers =
Intended Audience :: Developers Intended Audience :: Developers
License :: OSI Approved :: MIT License License :: OSI Approved :: MIT License
Programming Language :: Python Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3 Programming Language :: Python :: 3
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.8
@ -44,13 +41,9 @@ install_requires=
pyflakes >= 2.3.0, < 2.4.0 pyflakes >= 2.3.0, < 2.4.0
pycodestyle >= 2.7.0, < 2.8.0 pycodestyle >= 2.7.0, < 2.8.0
mccabe >= 0.6.0, < 0.7.0 mccabe >= 0.6.0, < 0.7.0
enum34; python_version<"3.4"
typing; python_version<"3.5"
configparser; python_version<"3.2"
functools32; python_version<"3.2"
importlib-metadata; python_version<"3.8" importlib-metadata; python_version<"3.8"
python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* python_requires = >=3.6
[options.packages.find] [options.packages.find]
where = src where = src

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""Packaging logic for Flake8.""" """Packaging logic for Flake8."""
import os import os
import sys import sys

View file

@ -11,9 +11,7 @@ This module
""" """
import logging import logging
import sys import sys
from typing import Type
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())
@ -64,7 +62,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 # type: Type[logging.Handler] handler_cls: Type[logging.Handler] = logging.StreamHandler
else: else:
fileobj = filename fileobj = filename
handler_cls = logging.FileHandler handler_cls = logging.FileHandler

View file

@ -1,14 +1,9 @@
"""Expose backports in a single place.""" """Expose backports in a single place."""
import sys import sys
if sys.version_info >= (3,): # pragma: no cover (PY3+)
from functools import lru_cache
else: # pragma: no cover (<PY3)
from functools32 import lru_cache
if sys.version_info >= (3, 8): # pragma: no cover (PY38+) if sys.version_info >= (3, 8): # pragma: no cover (PY38+)
import importlib.metadata as importlib_metadata import importlib.metadata as importlib_metadata
else: # pragma: no cover (<PY38) else: # pragma: no cover (<PY38)
import importlib_metadata import importlib_metadata
__all__ = ("lru_cache", "importlib_metadata") __all__ = ("importlib_metadata",)

View file

@ -60,7 +60,7 @@ def get_style_guide(**kwargs):
return StyleGuide(application) return StyleGuide(application)
class StyleGuide(object): class StyleGuide:
"""Public facing object that mimic's Flake8 2.0's StyleGuide. """Public facing object that mimic's Flake8 2.0's StyleGuide.
.. note:: .. note::
@ -81,7 +81,7 @@ class StyleGuide(object):
self._file_checker_manager = application.file_checker_manager self._file_checker_manager = application.file_checker_manager
@property @property
def options(self): # type: () -> argparse.Namespace def options(self) -> argparse.Namespace:
"""Return application's options. """Return application's options.
An instance of :class:`argparse.Namespace` containing parsed options. An instance of :class:`argparse.Namespace` containing parsed options.
@ -170,7 +170,7 @@ class StyleGuide(object):
return self.check_files([filename]) return self.check_files([filename])
class Report(object): class Report:
"""Public facing object that mimic's Flake8 2.0's API. """Public facing object that mimic's Flake8 2.0's API.
.. note:: .. note::
@ -210,6 +210,6 @@ class Report(object):
list list
""" """
return [ return [
"{} {} {}".format(s.count, s.error_code, s.message) f"{s.count} {s.error_code} {s.message}"
for s in self._stats.statistics_for(violation) for s in self._stats.statistics_for(violation)
] ]

View file

@ -4,20 +4,22 @@ import errno
import itertools import itertools
import logging import logging
import signal import signal
import sys
import tokenize import tokenize
from typing import Dict, List, Optional, Tuple from typing import Dict
from typing import List
try: from typing import Optional
import multiprocessing.pool from typing import Tuple
except ImportError:
multiprocessing = None # type: ignore
from flake8 import defaults from flake8 import defaults
from flake8 import exceptions from flake8 import exceptions
from flake8 import processor from flake8 import processor
from flake8 import utils from flake8 import utils
try:
import multiprocessing.pool
except ImportError:
multiprocessing = None # type: ignore
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
SERIAL_RETRY_ERRNOS = { SERIAL_RETRY_ERRNOS = {
@ -38,17 +40,14 @@ SERIAL_RETRY_ERRNOS = {
def _multiprocessing_is_fork(): # type () -> bool def _multiprocessing_is_fork(): # type () -> bool
"""Class state is only preserved when using the `fork` strategy.""" """Class state is only preserved when using the `fork` strategy."""
if sys.version_info >= (3, 4): return (
return ( multiprocessing
multiprocessing # https://github.com/python/typeshed/pull/3415
# https://github.com/python/typeshed/pull/3415 and multiprocessing.get_start_method() == "fork" # type: ignore
and multiprocessing.get_start_method() == "fork" # type: ignore )
)
else:
return multiprocessing and not utils.is_windows()
class Manager(object): class Manager:
"""Manage the parallelism and checker instances for each plugin and file. """Manage the parallelism and checker instances for each plugin and file.
This class will be responsible for the following: This class will be responsible for the following:
@ -86,8 +85,8 @@ 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._all_checkers = [] # type: List[FileChecker] self._all_checkers: List[FileChecker] = []
self.checkers = [] # type: List[FileChecker] self.checkers: List[FileChecker] = []
self.statistics = { self.statistics = {
"files": 0, "files": 0,
"logical lines": 0, "logical lines": 0,
@ -104,8 +103,7 @@ class Manager(object):
self.statistics[statistic] += checker.statistics[statistic] self.statistics[statistic] += checker.statistics[statistic]
self.statistics["files"] += len(self.checkers) self.statistics["files"] += len(self.checkers)
def _job_count(self): def _job_count(self) -> int:
# type: () -> int
# First we walk through all of our error cases: # First we walk through all of our error cases:
# - multiprocessing library is not present # - multiprocessing library is not present
# - we're running on windows in which case we know we have significant # - we're running on windows in which case we know we have significant
@ -166,8 +164,7 @@ class Manager(object):
) )
return reported_results_count return reported_results_count
def is_path_excluded(self, path): def is_path_excluded(self, path: str) -> bool:
# type: (str) -> bool
"""Check if a path is excluded. """Check if a path is excluded.
:param str path: :param str path:
@ -190,8 +187,7 @@ class Manager(object):
logger=LOG, logger=LOG,
) )
def make_checkers(self, paths=None): def make_checkers(self, paths: Optional[List[str]] = None) -> 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
@ -236,8 +232,7 @@ class Manager(object):
self.checkers = [c for c in self._all_checkers if c.should_process] self.checkers = [c for c in self._all_checkers if c.should_process]
LOG.info("Checking %d files", len(self.checkers)) LOG.info("Checking %d files", len(self.checkers))
def report(self): def report(self) -> Tuple[int, int]:
# type: () -> Tuple[int, int]
"""Report all of the errors found in the managed file checkers. """Report all of the errors found in the managed file checkers.
This iterates over each of the checkers and reports the errors sorted This iterates over each of the checkers and reports the errors sorted
@ -259,11 +254,11 @@ class Manager(object):
results_found += len(results) results_found += len(results)
return (results_found, results_reported) return (results_found, results_reported)
def run_parallel(self): # type: () -> None def run_parallel(self) -> None:
"""Run the checkers in parallel.""" """Run the checkers in parallel."""
# fmt: off # fmt: off
final_results = collections.defaultdict(list) # type: Dict[str, List[Tuple[str, int, int, str, Optional[str]]]] # noqa: E501 final_results: Dict[str, List[Tuple[str, int, int, str, Optional[str]]]] = collections.defaultdict(list) # noqa: E501
final_statistics = collections.defaultdict(dict) # type: Dict[str, Dict[str, int]] # noqa: E501 final_statistics: Dict[str, Dict[str, int]] = collections.defaultdict(dict) # noqa: E501
# fmt: on # fmt: on
pool = _try_initialize_processpool(self.jobs) pool = _try_initialize_processpool(self.jobs)
@ -298,12 +293,12 @@ class Manager(object):
checker.results = final_results[filename] checker.results = final_results[filename]
checker.statistics = final_statistics[filename] checker.statistics = final_statistics[filename]
def run_serial(self): # type: () -> None def run_serial(self) -> None:
"""Run the checkers in serial.""" """Run the checkers in serial."""
for checker in self.checkers: for checker in self.checkers:
checker.run_checks() checker.run_checks()
def run(self): # type: () -> None def run(self) -> None:
"""Run all the checkers. """Run all the checkers.
This will intelligently decide whether to run the checks in parallel This will intelligently decide whether to run the checks in parallel
@ -337,7 +332,7 @@ class Manager(object):
self._process_statistics() self._process_statistics()
class FileChecker(object): class FileChecker:
"""Manage running checks for a file and aggregate the results.""" """Manage running checks for a file and aggregate the results."""
def __init__(self, filename, checks, options): def __init__(self, filename, checks, options):
@ -357,9 +352,7 @@ class FileChecker(object):
self.options = options self.options = options
self.filename = filename self.filename = filename
self.checks = checks self.checks = checks
# fmt: off self.results: List[Tuple[str, int, int, str, Optional[str]]] = []
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,
@ -373,27 +366,30 @@ class FileChecker(object):
self.should_process = not self.processor.should_ignore_file() self.should_process = not self.processor.should_ignore_file()
self.statistics["physical lines"] = len(self.processor.lines) self.statistics["physical lines"] = len(self.processor.lines)
def __repr__(self): # type: () -> str def __repr__(self) -> str:
"""Provide helpful debugging representation.""" """Provide helpful debugging representation."""
return "FileChecker for {}".format(self.filename) return f"FileChecker for {self.filename}"
def _make_processor(self): def _make_processor(self) -> Optional[processor.FileProcessor]:
# type: () -> Optional[processor.FileProcessor]
try: try:
return processor.FileProcessor(self.filename, self.options) return processor.FileProcessor(self.filename, self.options)
except IOError as e: except OSError 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.
message = "{0}: {1}".format(type(e).__name__, e) self.report("E902", 0, 0, f"{type(e).__name__}: {e}")
self.report("E902", 0, 0, message)
return None return None
def report(self, error_code, line_number, column, text): def report(
# type: (Optional[str], int, int, str) -> str self,
error_code: Optional[str],
line_number: int,
column: int,
text: str,
) -> str:
"""Report an error by storing it in the results list.""" """Report an error by storing it in the results list."""
if error_code is None: if error_code is None:
error_code, text = text.split(" ", 1) error_code, text = text.split(" ", 1)
@ -469,14 +465,14 @@ class FileChecker(object):
column -= column_offset column -= column_offset
return row, column return row, column
def run_ast_checks(self): # type: () -> None def run_ast_checks(self) -> None:
"""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) as e: except (ValueError, SyntaxError, TypeError) as e:
row, column = self._extract_syntax_information(e) row, column = self._extract_syntax_information(e)
self.report( self.report(
"E999", row, column, "%s: %s" % (type(e).__name__, e.args[0]) "E999", row, column, f"{type(e).__name__}: {e.args[0]}"
) )
return return
@ -608,8 +604,9 @@ class FileChecker(object):
else: else:
self.run_logical_checks() self.run_logical_checks()
def check_physical_eol(self, token, prev_physical): def check_physical_eol(
# type: (processor._Token, str) -> None self, token: processor._Token, prev_physical: str
) -> None:
"""Run physical checks if and only if it is at the end of the line.""" """Run physical checks if and only if it is at the end of the line."""
# a newline token ends a single physical line. # a newline token ends a single physical line.
if processor.is_eol_token(token): if processor.is_eol_token(token):
@ -638,13 +635,14 @@ class FileChecker(object):
self.run_physical_checks(line + "\n") self.run_physical_checks(line + "\n")
def _pool_init(): # type: () -> None def _pool_init() -> None:
"""Ensure correct signaling of ^C using multiprocessing.Pool.""" """Ensure correct signaling of ^C using multiprocessing.Pool."""
signal.signal(signal.SIGINT, signal.SIG_IGN) signal.signal(signal.SIGINT, signal.SIG_IGN)
def _try_initialize_processpool(job_count): def _try_initialize_processpool(
# type: (int) -> Optional[multiprocessing.pool.Pool] job_count: int,
) -> Optional[multiprocessing.pool.Pool]:
"""Return a new process pool instance if we are able to create one.""" """Return a new process pool instance if we are able to create one."""
try: try:
return multiprocessing.Pool(job_count, _pool_init) return multiprocessing.Pool(job_count, _pool_init)
@ -673,8 +671,9 @@ def _run_checks(checker):
return checker.run_checks() return checker.run_checks()
def find_offset(offset, mapping): def find_offset(
# type: (int, processor._LogicalMapping) -> Tuple[int, int] offset: int, mapping: processor._LogicalMapping
) -> Tuple[int, int]:
"""Find the offset tuple for a single offset.""" """Find the offset tuple for a single offset."""
if isinstance(offset, tuple): if isinstance(offset, tuple):
return offset return offset

View file

@ -19,14 +19,13 @@ 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_name, exception): def __init__(self, plugin_name: str, exception: Exception) -> None:
# type: (str, Exception) -> None
"""Initialize our FailedToLoadPlugin exception.""" """Initialize our FailedToLoadPlugin exception."""
self.plugin_name = plugin_name self.plugin_name = plugin_name
self.original_exception = exception self.original_exception = exception
super(FailedToLoadPlugin, self).__init__(plugin_name, exception) super().__init__(plugin_name, exception)
def __str__(self): # type: () -> str def __str__(self) -> str:
"""Format our exception message.""" """Format our exception message."""
return self.FORMAT % { return self.FORMAT % {
"name": self.plugin_name, "name": self.plugin_name,
@ -37,18 +36,18 @@ class FailedToLoadPlugin(Flake8Exception):
class InvalidSyntax(Flake8Exception): class InvalidSyntax(Flake8Exception):
"""Exception raised when tokenizing a file fails.""" """Exception raised when tokenizing a file fails."""
def __init__(self, exception): # type: (Exception) -> None def __init__(self, exception: Exception) -> None:
"""Initialize our InvalidSyntax exception.""" """Initialize our InvalidSyntax exception."""
self.original_exception = exception self.original_exception = exception
self.error_message = "{0}: {1}".format( self.error_message = (
exception.__class__.__name__, exception.args[0] f"{type(exception).__name__}: {exception.args[0]}"
) )
self.error_code = "E902" self.error_code = "E902"
self.line_number = 1 self.line_number = 1
self.column_number = 0 self.column_number = 0
super(InvalidSyntax, self).__init__(exception) super().__init__(exception)
def __str__(self): # type: () -> str def __str__(self) -> str:
"""Format our exception message.""" """Format our exception message."""
return self.error_message return self.error_message
@ -58,16 +57,13 @@ 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: Dict[str, str], exception: Exception) -> None:
# 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
super(PluginRequestedUnknownParameters, self).__init__( super().__init__(plugin, exception)
plugin, exception
)
def __str__(self): # type: () -> str def __str__(self) -> 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"],
@ -80,14 +76,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: Dict[str, str], exception: Exception) -> None:
# 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().__init__(plugin, exception)
def __str__(self): # type: () -> str def __str__(self) -> 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"],

View file

@ -1,15 +1,17 @@
"""The base class and interface for all formatting plugins.""" """The base class and interface for all formatting plugins."""
from __future__ import print_function
import argparse import argparse
from typing import IO, List, Optional, Tuple from typing import IO
from typing import List
from typing import Optional
from typing import Tuple
from typing import TYPE_CHECKING
if False: # `typing.TYPE_CHECKING` was introduced in 3.5.2 if TYPE_CHECKING:
from flake8.statistics import Statistics from flake8.statistics import Statistics
from flake8.style_guide import Violation from flake8.style_guide import Violation
class BaseFormatter(object): class BaseFormatter:
"""Class defining the formatter interface. """Class defining the formatter interface.
.. attribute:: options .. attribute:: options
@ -31,8 +33,7 @@ class BaseFormatter(object):
output filename has been specified. output filename has been specified.
""" """
def __init__(self, options): def __init__(self, options: argparse.Namespace) -> None:
# type: (argparse.Namespace) -> 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
@ -46,14 +47,14 @@ class BaseFormatter(object):
""" """
self.options = options self.options = options
self.filename = options.output_file self.filename = options.output_file
self.output_fd = None # type: Optional[IO[str]] self.output_fd: Optional[IO[str]] = None
self.newline = "\n" self.newline = "\n"
self.after_init() self.after_init()
def after_init(self): # type: () -> None def after_init(self) -> None:
"""Initialize the formatter further.""" """Initialize the formatter further."""
def beginning(self, filename): # type: (str) -> None def beginning(self, filename: 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:
@ -61,7 +62,7 @@ class BaseFormatter(object):
from. from.
""" """
def finished(self, filename): # type: (str) -> None def finished(self, filename: 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:
@ -69,7 +70,7 @@ class BaseFormatter(object):
from. from.
""" """
def start(self): # type: () -> None def start(self) -> 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`
@ -77,7 +78,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): # type: (Violation) -> None def handle(self, error: "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
@ -94,7 +95,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): # type: (Violation) -> Optional[str] def format(self, error: "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.
@ -113,23 +114,16 @@ class BaseFormatter(object):
"Subclass of BaseFormatter did not implement" " format." "Subclass of BaseFormatter did not implement" " format."
) )
def show_statistics(self, statistics): # type: (Statistics) -> None def show_statistics(self, statistics: "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)
statistic = next(stats_for_error_code) statistic = next(stats_for_error_code)
count = statistic.count count = statistic.count
count += sum(stat.count for stat in stats_for_error_code) count += sum(stat.count for stat in stats_for_error_code)
self._write( self._write(f"{count:<5} {error_code} {statistic.message}")
"{count:<5} {error_code} {message}".format(
count=count,
error_code=error_code,
message=statistic.message,
)
)
def show_benchmarks(self, benchmarks): def show_benchmarks(self, benchmarks: List[Tuple[str, float]]) -> None:
# 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:
@ -150,7 +144,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): # type: (Violation) -> Optional[str] def show_source(self, error: "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
@ -179,17 +173,16 @@ class BaseFormatter(object):
) )
# Physical lines have a newline at the end, no need to add an extra # Physical lines have a newline at the end, no need to add an extra
# one # one
return "{}{}^".format(error.physical_line, indent) return f"{error.physical_line}{indent}^"
def _write(self, output): # type: (str) -> None def _write(self, output: 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)
if self.output_fd is None or self.options.tee: if self.output_fd is None or self.options.tee:
print(output, end=self.newline) print(output, end=self.newline)
def write(self, line, source): def write(self, line: Optional[str], source: Optional[str]) -> None:
# 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
@ -207,7 +200,7 @@ class BaseFormatter(object):
if source: if source:
self._write(source) self._write(source)
def stop(self): # type: () -> None def stop(self) -> 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,9 +1,11 @@
"""Default formatting class for Flake8.""" """Default formatting class for Flake8."""
from typing import Optional, Set from typing import Optional
from typing import Set
from typing import TYPE_CHECKING
from flake8.formatting import base from flake8.formatting import base
if False: # `typing.TYPE_CHECKING` was introduced in 3.5.2 if TYPE_CHECKING:
from flake8.style_guide import Violation from flake8.style_guide import Violation
@ -23,9 +25,9 @@ class SimpleFormatter(base.BaseFormatter):
""" """
error_format = None # type: str error_format: str
def format(self, error): # type: (Violation) -> Optional[str] def format(self, error: "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
@ -49,7 +51,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): # type: () -> None def after_init(self) -> 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
@ -66,18 +68,18 @@ class FilenameOnly(SimpleFormatter):
error_format = "%(path)s" error_format = "%(path)s"
def after_init(self): # type: () -> None def after_init(self) -> None:
"""Initialize our set of filenames.""" """Initialize our set of filenames."""
self.filenames_already_printed = set() # type: Set[str] self.filenames_already_printed: Set[str] = set()
def show_source(self, error): # type: (Violation) -> Optional[str] def show_source(self, error: "Violation") -> Optional[str]:
"""Do not include the source code.""" """Do not include the source code."""
def format(self, error): # type: (Violation) -> Optional[str] def format(self, error: "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().format(error)
else: else:
return None return None
@ -85,8 +87,8 @@ class FilenameOnly(SimpleFormatter):
class Nothing(base.BaseFormatter): class Nothing(base.BaseFormatter):
"""Print absolutely nothing.""" """Print absolutely nothing."""
def format(self, error): # type: (Violation) -> Optional[str] def format(self, error: "Violation") -> Optional[str]:
"""Do nothing.""" """Do nothing."""
def show_source(self, error): # type: (Violation) -> Optional[str] def show_source(self, error: "Violation") -> Optional[str]:
"""Do not print the source.""" """Do not print the source."""

View file

@ -1,11 +1,15 @@
"""Module containing the application logic for Flake8.""" """Module containing the application logic for Flake8."""
from __future__ import print_function
import argparse import argparse
import logging import logging
import sys import sys
import time import time
from typing import Dict, List, Optional, Set, Tuple from typing import Dict
from typing import List
from typing import Optional
from typing import Set
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
import flake8 import flake8
from flake8 import checker from flake8 import checker
@ -14,19 +18,19 @@ from flake8 import exceptions
from flake8 import style_guide from flake8 import style_guide
from flake8 import utils from flake8 import utils
from flake8.main import options from flake8.main import options
from flake8.options import aggregator, config from flake8.options import aggregator
from flake8.options import config
from flake8.options import manager 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 TYPE_CHECKING:
from typing import Type # `typing.Type` was introduced in 3.5.2
from flake8.formatting.base import BaseFormatter from flake8.formatting.base import BaseFormatter
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class Application(object): class Application:
"""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__):
@ -40,7 +44,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 # type: float self.end_time: float = None
#: 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
@ -59,26 +63,24 @@ class Application(object):
options.register_default_options(self.option_manager) options.register_default_options(self.option_manager)
#: The instance of :class:`flake8.plugins.manager.Checkers` #: The instance of :class:`flake8.plugins.manager.Checkers`
self.check_plugins = None # type: plugin_manager.Checkers self.check_plugins: plugin_manager.Checkers = None
# fmt: off
#: The instance of :class:`flake8.plugins.manager.ReportFormatters` #: The instance of :class:`flake8.plugins.manager.ReportFormatters`
self.formatting_plugins = None # type: plugin_manager.ReportFormatters self.formatting_plugins: plugin_manager.ReportFormatters = None
# fmt: on
#: The user-selected formatter from :attr:`formatting_plugins` #: The user-selected formatter from :attr:`formatting_plugins`
self.formatter = None # type: BaseFormatter self.formatter: BaseFormatter = None
#: 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 # type: style_guide.StyleGuideManager self.guide: style_guide.StyleGuideManager = None
#: 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 # type: checker.Manager self.file_checker_manager: checker.Manager = None
#: The user-supplied options parsed into an instance of #: The user-supplied options parsed into an instance of
#: :class:`argparse.Namespace` #: :class:`argparse.Namespace`
self.options = None # type: argparse.Namespace self.options: argparse.Namespace = None
#: 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 # type: List[str] self.args: List[str] = None
#: 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
@ -92,10 +94,11 @@ 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 = {} # type: Dict[str, Set[int]] self.parsed_diff: Dict[str, Set[int]] = {}
def parse_preliminary_options(self, argv): def parse_preliminary_options(
# type: (List[str]) -> Tuple[argparse.Namespace, List[str]] self, argv: List[str]
) -> Tuple[argparse.Namespace, List[str]]:
"""Get preliminary options from the CLI, pre-plugin-loading. """Get preliminary options from the CLI, pre-plugin-loading.
We need to know the values of a few standard options so that we can We need to know the values of a few standard options so that we can
@ -119,8 +122,7 @@ class Application(object):
rest.extend(("--output-file", args.output_file)) rest.extend(("--output-file", args.output_file))
return args, rest return args, rest
def exit(self): def exit(self) -> None:
# type: () -> None
"""Handle finalization and exiting the program. """Handle finalization and exiting the program.
This should be the last thing called on the application instance. It This should be the last thing called on the application instance. It
@ -136,8 +138,7 @@ class Application(object):
(self.result_count > 0) or self.catastrophic_failure (self.result_count > 0) or self.catastrophic_failure
) )
def find_plugins(self, config_finder): def find_plugins(self, config_finder: config.ConfigFileFinder) -> None:
# type: (config.ConfigFileFinder) -> None
"""Find and load the plugins for this application. """Find and load the plugins for this application.
Set the :attr:`check_plugins` and :attr:`formatting_plugins` attributes Set the :attr:`check_plugins` and :attr:`formatting_plugins` attributes
@ -159,8 +160,7 @@ class Application(object):
self.check_plugins.load_plugins() self.check_plugins.load_plugins()
self.formatting_plugins.load_plugins() self.formatting_plugins.load_plugins()
def register_plugin_options(self): def register_plugin_options(self) -> None:
# type: () -> None
"""Register options provided by plugins to our option manager.""" """Register options provided by plugins to our option manager."""
self.check_plugins.register_options(self.option_manager) self.check_plugins.register_options(self.option_manager)
self.check_plugins.register_plugin_versions(self.option_manager) self.check_plugins.register_plugin_versions(self.option_manager)
@ -168,10 +168,9 @@ class Application(object):
def parse_configuration_and_cli( def parse_configuration_and_cli(
self, self,
config_finder, # type: config.ConfigFileFinder config_finder: config.ConfigFileFinder,
argv, # type: List[str] argv: List[str],
): ) -> None:
# type: (...) -> None
"""Parse configuration files and the CLI options. """Parse configuration files and the CLI options.
:param config.ConfigFileFinder config_finder: :param config.ConfigFileFinder config_finder:
@ -211,8 +210,9 @@ class Application(object):
return formatter_plugin.execute return formatter_plugin.execute
def make_formatter(self, formatter_class=None): def make_formatter(
# type: (Optional[Type[BaseFormatter]]) -> None self, formatter_class: Optional[Type["BaseFormatter"]] = None
) -> None:
"""Initialize a formatter based on the parsed options.""" """Initialize a formatter based on the parsed options."""
format_plugin = self.options.format format_plugin = self.options.format
if 1 <= self.options.quiet < 2: if 1 <= self.options.quiet < 2:
@ -225,8 +225,7 @@ class Application(object):
self.formatter = formatter_class(self.options) self.formatter = formatter_class(self.options)
def make_guide(self): def make_guide(self) -> None:
# type: () -> None
"""Initialize our StyleGuide.""" """Initialize our StyleGuide."""
self.guide = style_guide.StyleGuideManager( self.guide = style_guide.StyleGuideManager(
self.options, self.formatter self.options, self.formatter
@ -235,8 +234,7 @@ class Application(object):
if self.running_against_diff: if self.running_against_diff:
self.guide.add_diff_ranges(self.parsed_diff) self.guide.add_diff_ranges(self.parsed_diff)
def make_file_checker_manager(self): def make_file_checker_manager(self) -> None:
# type: () -> None
"""Initialize our FileChecker Manager.""" """Initialize our FileChecker Manager."""
self.file_checker_manager = checker.Manager( self.file_checker_manager = checker.Manager(
style_guide=self.guide, style_guide=self.guide,
@ -244,8 +242,7 @@ class Application(object):
checker_plugins=self.check_plugins, checker_plugins=self.check_plugins,
) )
def run_checks(self, files=None): def run_checks(self, files: Optional[List[str]] = None) -> None:
# type: (Optional[List[str]]) -> None
"""Run the actual checks with the FileChecker Manager. """Run the actual checks with the FileChecker Manager.
This method encapsulates the logic to make a This method encapsulates the logic to make a
@ -278,15 +275,14 @@ class Application(object):
add_statistic = statistics.append add_statistic = statistics.append
for statistic in defaults.STATISTIC_NAMES + ("files",): for statistic in defaults.STATISTIC_NAMES + ("files",):
value = self.file_checker_manager.statistics[statistic] value = self.file_checker_manager.statistics[statistic]
total_description = "total " + statistic + " processed" total_description = f"total {statistic} processed"
add_statistic((total_description, value)) add_statistic((total_description, value))
per_second_description = statistic + " processed per second" per_second_description = f"{statistic} processed per second"
add_statistic((per_second_description, int(value / time_elapsed))) add_statistic((per_second_description, int(value / time_elapsed)))
self.formatter.show_benchmarks(statistics) self.formatter.show_benchmarks(statistics)
def report_errors(self): def report_errors(self) -> None:
# type: () -> None
"""Report all the errors found by flake8 3.0. """Report all the errors found by flake8 3.0.
This also updates the :attr:`result_count` attribute with the total This also updates the :attr:`result_count` attribute with the total
@ -308,8 +304,7 @@ class Application(object):
self.formatter.show_statistics(self.guide.stats) self.formatter.show_statistics(self.guide.stats)
def initialize(self, argv): def initialize(self, argv: List[str]) -> None:
# type: (List[str]) -> None
"""Initialize the application to be run. """Initialize the application to be run.
This finds the plugins, registers their options, and parses the This finds the plugins, registers their options, and parses the
@ -343,14 +338,12 @@ class Application(object):
self.report_benchmarks() self.report_benchmarks()
self.formatter.stop() self.formatter.stop()
def _run(self, argv): def _run(self, argv: List[str]) -> None:
# type: (List[str]) -> None
self.initialize(argv) self.initialize(argv)
self.run_checks() self.run_checks()
self.report() self.report()
def run(self, argv): def run(self, argv: List[str]) -> None:
# type: (List[str]) -> None
"""Run our application. """Run our application.
This method will also handle KeyboardInterrupt exceptions for the This method will also handle KeyboardInterrupt exceptions for the

View file

@ -1,12 +1,12 @@
"""Command-line implementation of flake8.""" """Command-line implementation of flake8."""
import sys import sys
from typing import List, Optional from typing import List
from typing import Optional
from flake8.main import application from flake8.main import application
def main(argv=None): def main(argv: Optional[List[str]] = None) -> None:
# type: (Optional[List[str]]) -> None
"""Execute the main bit of the application. """Execute the main bit of the application.
This handles the creation of an instance of :class:`Application`, runs it, This handles the creation of an instance of :class:`Application`, runs it,

View file

@ -1,10 +1,9 @@
"""Module containing the logic for our debugging logic.""" """Module containing the logic for our debugging logic."""
from __future__ import print_function
import argparse import argparse
import json import json
import platform import platform
from typing import Dict, List from typing import Dict
from typing import List
class DebugAction(argparse.Action): class DebugAction(argparse.Action):
@ -17,7 +16,7 @@ class DebugAction(argparse.Action):
used to delay response. used to delay response.
""" """
self._option_manager = kwargs.pop("option_manager") self._option_manager = kwargs.pop("option_manager")
super(DebugAction, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def __call__(self, parser, namespace, values, option_string=None): def __call__(self, parser, namespace, values, option_string=None):
"""Perform the argparse action for printing debug information.""" """Perform the argparse action for printing debug information."""
@ -60,6 +59,6 @@ def plugins_from(option_manager):
] ]
def dependencies(): # type: () -> List[Dict[str, str]] def dependencies() -> List[Dict[str, str]]:
"""Generate the list of dependencies we care about.""" """Generate the list of dependencies we care about."""
return [] return []

View file

@ -6,8 +6,7 @@ from flake8 import defaults
from flake8.main import debug from flake8.main import debug
def register_preliminary_options(parser): def register_preliminary_options(parser: argparse.ArgumentParser) -> None:
# type: (argparse.ArgumentParser) -> None
"""Register the preliminary options on our OptionManager. """Register the preliminary options on our OptionManager.
The preliminary options include: The preliminary options include:
@ -64,7 +63,7 @@ def register_preliminary_options(parser):
class JobsArgument: class JobsArgument:
"""Type callback for the --jobs argument.""" """Type callback for the --jobs argument."""
def __init__(self, arg): # type: (str) -> None def __init__(self, arg: str) -> None:
"""Parse and validate the --jobs argument. """Parse and validate the --jobs argument.
:param str arg: :param str arg:
@ -78,7 +77,7 @@ class JobsArgument:
self.n_jobs = int(arg) self.n_jobs = int(arg)
else: else:
raise argparse.ArgumentTypeError( raise argparse.ArgumentTypeError(
"{!r} must be 'auto' or an integer.".format(arg), f"{arg!r} must be 'auto' or an integer.",
) )
def __str__(self): def __str__(self):

View file

@ -5,7 +5,8 @@ applies the user-specified command-line configuration on top of it.
""" """
import argparse import argparse
import logging import logging
from typing import List, Tuple from typing import List
from typing import Tuple
from flake8.options import config from flake8.options import config
from flake8.options.manager import OptionManager from flake8.options.manager import OptionManager
@ -14,10 +15,10 @@ LOG = logging.getLogger(__name__)
def aggregate_options( def aggregate_options(
manager, # type: OptionManager manager: OptionManager,
config_finder, # type: config.ConfigFileFinder config_finder: config.ConfigFileFinder,
argv, # type: List[str] argv: List[str],
): # type: (...) -> Tuple[argparse.Namespace, List[str]] ) -> Tuple[argparse.Namespace, List[str]]:
"""Aggregate and merge CLI and config file options. """Aggregate and merge CLI and config file options.
:param flake8.options.manager.OptionManager manager: :param flake8.options.manager.OptionManager manager:

View file

@ -3,7 +3,9 @@ import collections
import configparser import configparser
import logging import logging
import os.path import os.path
from typing import List, Optional, Tuple from typing import List
from typing import Optional
from typing import Tuple
from flake8 import utils from flake8 import utils
@ -12,17 +14,16 @@ LOG = logging.getLogger(__name__)
__all__ = ("ConfigFileFinder", "MergedConfigParser") __all__ = ("ConfigFileFinder", "MergedConfigParser")
class ConfigFileFinder(object): class ConfigFileFinder:
"""Encapsulate the logic for finding and reading config files.""" """Encapsulate the logic for finding and reading config files."""
def __init__( def __init__(
self, self,
program_name, program_name: str,
extra_config_files=None, extra_config_files: Optional[List[str]] = None,
config_file=None, config_file: Optional[str] = None,
ignore_config_files=False, ignore_config_files: bool = False,
): ) -> None:
# type: (str, Optional[List[str]], Optional[str], bool) -> None
"""Initialize object to find config files. """Initialize object to find config files.
:param str program_name: :param str program_name:
@ -50,16 +51,15 @@ class ConfigFileFinder(object):
self.user_config_file = self._user_config_file(program_name) self.user_config_file = self._user_config_file(program_name)
# List of filenames to find in the local/project directory # List of filenames to find in the local/project directory
self.project_filenames = ("setup.cfg", "tox.ini", "." + program_name) self.project_filenames = ("setup.cfg", "tox.ini", f".{program_name}")
self.local_directory = os.path.abspath(os.curdir) self.local_directory = os.path.abspath(os.curdir)
@staticmethod @staticmethod
def _user_config_file(program_name): def _user_config_file(program_name: str) -> str:
# type: (str) -> str
if utils.is_windows(): if utils.is_windows():
home_dir = os.path.expanduser("~") home_dir = os.path.expanduser("~")
config_file_basename = "." + program_name config_file_basename = f".{program_name}"
else: else:
home_dir = os.environ.get( home_dir = os.environ.get(
"XDG_CONFIG_HOME", os.path.expanduser("~/.config") "XDG_CONFIG_HOME", os.path.expanduser("~/.config")
@ -69,8 +69,9 @@ class ConfigFileFinder(object):
return os.path.join(home_dir, config_file_basename) return os.path.join(home_dir, config_file_basename)
@staticmethod @staticmethod
def _read_config(*files): def _read_config(
# type: (*str) -> Tuple[configparser.RawConfigParser, List[str]] *files: str,
) -> Tuple[configparser.RawConfigParser, List[str]]:
config = configparser.RawConfigParser() config = configparser.RawConfigParser()
found_files = [] found_files = []
@ -91,8 +92,7 @@ class ConfigFileFinder(object):
) )
return (config, found_files) return (config, found_files)
def cli_config(self, files): def cli_config(self, files: str) -> configparser.RawConfigParser:
# 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."""
config, found_files = self._read_config(files) config, found_files = self._read_config(files)
if found_files: if found_files:
@ -154,7 +154,7 @@ class ConfigFileFinder(object):
return config return config
class MergedConfigParser(object): class MergedConfigParser:
"""Encapsulate merging different types of configuration files. """Encapsulate merging different types of configuration files.
This parses out the options registered that were specified in the This parses out the options registered that were specified in the
@ -344,7 +344,7 @@ def get_local_plugins(config_finder):
base_dirs = {os.path.dirname(cf) for cf in config_files} base_dirs = {os.path.dirname(cf) for cf in config_files}
section = "%s:local-plugins" % config_finder.program_name section = f"{config_finder.program_name}:local-plugins"
for plugin_type in ["extension", "report"]: for plugin_type in ["extension", "report"]:
if config.has_option(section, plugin_type): if config.has_option(section, plugin_type):
local_plugins_string = config.get(section, plugin_type).strip() local_plugins_string = config.get(section, plugin_type).strip()
@ -358,7 +358,7 @@ def get_local_plugins(config_finder):
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 = [] # type: List[str] norm_paths: List[str] = []
for base_dir in base_dirs: for base_dir in base_dirs:
norm_paths.extend( norm_paths.extend(
path path

View file

@ -5,14 +5,25 @@ import contextlib
import enum import enum
import functools import functools
import logging import logging
from typing import Any, Callable, cast, Dict, Generator, List, Mapping from typing import Any
from typing import Optional, Sequence, Set, Tuple, Union from typing import Callable
from typing import cast
from typing import Dict
from typing import Generator
from typing import List
from typing import Mapping
from typing import Optional
from typing import Sequence
from typing import Set
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import Union
from flake8 import utils from flake8 import utils
if False: # TYPE_CHECKING if TYPE_CHECKING:
from typing import NoReturn from typing import NoReturn
from typing import Type
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -21,7 +32,7 @@ LOG = logging.getLogger(__name__)
_ARG = enum.Enum("_ARG", "NO") _ARG = enum.Enum("_ARG", "NO")
_optparse_callable_map = { _optparse_callable_map: Dict[str, Union[Type[Any], _ARG]] = {
"int": int, "int": int,
"long": int, "long": int,
"string": str, "string": str,
@ -30,27 +41,25 @@ _optparse_callable_map = {
"choice": _ARG.NO, "choice": _ARG.NO,
# optparse allows this but does not document it # optparse allows this but does not document it
"str": str, "str": str,
} # type: Dict[str, Union[Type[Any], _ARG]] }
class _CallbackAction(argparse.Action): class _CallbackAction(argparse.Action):
"""Shim for optparse-style callback actions.""" """Shim for optparse-style callback actions."""
def __init__(self, *args, **kwargs): def __init__(self, *args: Any, **kwargs: Any) -> None:
# type: (*Any, **Any) -> None
self._callback = kwargs.pop("callback") self._callback = kwargs.pop("callback")
self._callback_args = kwargs.pop("callback_args", ()) self._callback_args = kwargs.pop("callback_args", ())
self._callback_kwargs = kwargs.pop("callback_kwargs", {}) self._callback_kwargs = kwargs.pop("callback_kwargs", {})
super(_CallbackAction, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def __call__( def __call__(
self, self,
parser, # type: argparse.ArgumentParser parser: argparse.ArgumentParser,
namespace, # type: argparse.Namespace namespace: argparse.Namespace,
values, # type: Optional[Union[Sequence[str], str]] values: Optional[Union[Sequence[str], str]],
option_string=None, # type: Optional[str] option_string: Optional[str] = None,
): ) -> None:
# type: (...) -> None
if not values: if not values:
values = None values = None
elif isinstance(values, list) and len(values) > 1: elif isinstance(values, list) and len(values) > 1:
@ -61,23 +70,24 @@ class _CallbackAction(argparse.Action):
values, values,
parser, parser,
*self._callback_args, *self._callback_args,
**self._callback_kwargs **self._callback_kwargs,
) )
def _flake8_normalize(value, *args, **kwargs): def _flake8_normalize(
# type: (str, *str, **bool) -> Union[str, List[str]] value: str, *args: str, **kwargs: bool
) -> Union[str, List[str]]:
comma_separated_list = kwargs.pop("comma_separated_list", False) comma_separated_list = kwargs.pop("comma_separated_list", False)
normalize_paths = kwargs.pop("normalize_paths", False) normalize_paths = kwargs.pop("normalize_paths", False)
if kwargs: if kwargs:
raise TypeError("Unexpected keyword args: {}".format(kwargs)) raise TypeError(f"Unexpected keyword args: {kwargs}")
ret = value # type: Union[str, List[str]] ret: Union[str, List[str]] = value
if comma_separated_list and isinstance(ret, utils.string_types): if comma_separated_list and isinstance(ret, str):
ret = utils.parse_comma_separated_list(value) ret = utils.parse_comma_separated_list(value)
if normalize_paths: if normalize_paths:
if isinstance(ret, utils.string_types): if isinstance(ret, str):
ret = utils.normalize_path(ret, *args) ret = utils.normalize_path(ret, *args)
else: else:
ret = utils.normalize_paths(ret, *args) ret = utils.normalize_paths(ret, *args)
@ -85,34 +95,34 @@ def _flake8_normalize(value, *args, **kwargs):
return ret return ret
class Option(object): class Option:
"""Our wrapper around an argparse argument parsers to add features.""" """Our wrapper around an argparse argument parsers to add features."""
def __init__( def __init__(
self, self,
short_option_name=_ARG.NO, # type: Union[str, _ARG] short_option_name: Union[str, _ARG] = _ARG.NO,
long_option_name=_ARG.NO, # type: Union[str, _ARG] long_option_name: Union[str, _ARG] = _ARG.NO,
# Options below here are taken from the optparse.Option class # Options below here are taken from the optparse.Option class
action=_ARG.NO, # type: Union[str, Type[argparse.Action], _ARG] action: Union[str, Type[argparse.Action], _ARG] = _ARG.NO,
default=_ARG.NO, # type: Union[Any, _ARG] default: Union[Any, _ARG] = _ARG.NO,
type=_ARG.NO, # type: Union[str, Callable[..., Any], _ARG] type: Union[str, Callable[..., Any], _ARG] = _ARG.NO,
dest=_ARG.NO, # type: Union[str, _ARG] dest: Union[str, _ARG] = _ARG.NO,
nargs=_ARG.NO, # type: Union[int, str, _ARG] nargs: Union[int, str, _ARG] = _ARG.NO,
const=_ARG.NO, # type: Union[Any, _ARG] const: Union[Any, _ARG] = _ARG.NO,
choices=_ARG.NO, # type: Union[Sequence[Any], _ARG] choices: Union[Sequence[Any], _ARG] = _ARG.NO,
help=_ARG.NO, # type: Union[str, _ARG] help: Union[str, _ARG] = _ARG.NO,
metavar=_ARG.NO, # type: Union[str, _ARG] metavar: Union[str, _ARG] = _ARG.NO,
# deprecated optparse-only options # deprecated optparse-only options
callback=_ARG.NO, # type: Union[Callable[..., Any], _ARG] callback: Union[Callable[..., Any], _ARG] = _ARG.NO,
callback_args=_ARG.NO, # type: Union[Sequence[Any], _ARG] callback_args: Union[Sequence[Any], _ARG] = _ARG.NO,
callback_kwargs=_ARG.NO, # type: Union[Mapping[str, Any], _ARG] callback_kwargs: Union[Mapping[str, Any], _ARG] = _ARG.NO,
# Options below are taken from argparse.ArgumentParser.add_argument # Options below are taken from argparse.ArgumentParser.add_argument
required=_ARG.NO, # type: Union[bool, _ARG] required: Union[bool, _ARG] = _ARG.NO,
# Options below here are specific to Flake8 # Options below here are specific to Flake8
parse_from_config=False, # type: bool parse_from_config: bool = False,
comma_separated_list=False, # type: bool comma_separated_list: bool = False,
normalize_paths=False, # type: bool normalize_paths: bool = False,
): # type: (...) -> None ) -> None:
"""Initialize an Option instance. """Initialize an Option instance.
The following are all passed directly through to argparse. The following are all passed directly through to argparse.
@ -203,7 +213,7 @@ class Option(object):
nargs = 0 nargs = 0
# optparse -> argparse for `type` # optparse -> argparse for `type`
if isinstance(type, utils.string_types): if isinstance(type, str):
LOG.warning( LOG.warning(
"option %s: please update from optparse string `type=` to " "option %s: please update from optparse string `type=` to "
"argparse callable `type=` -- this will be an error in the " "argparse callable `type=` -- this will be an error in the "
@ -240,7 +250,7 @@ class Option(object):
self.help = help self.help = help
self.metavar = metavar self.metavar = metavar
self.required = required self.required = required
self.option_kwargs = { self.option_kwargs: Dict[str, Union[Any, _ARG]] = {
"action": self.action, "action": self.action,
"default": self.default, "default": self.default,
"type": self.type, "type": self.type,
@ -254,14 +264,14 @@ class Option(object):
"help": self.help, "help": self.help,
"metavar": self.metavar, "metavar": self.metavar,
"required": self.required, "required": self.required,
} # type: Dict[str, Union[Any, _ARG]] }
# Set our custom attributes # Set our custom attributes
self.parse_from_config = parse_from_config self.parse_from_config = parse_from_config
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 # type: Optional[str] self.config_name: Optional[str] = None
if parse_from_config: if parse_from_config:
if long_option_name is _ARG.NO: if long_option_name is _ARG.NO:
raise ValueError( raise ValueError(
@ -273,26 +283,23 @@ class Option(object):
self._opt = None self._opt = None
@property @property
def filtered_option_kwargs(self): # type: () -> Dict[str, Any] def filtered_option_kwargs(self) -> Dict[str, Any]:
"""Return any actually-specified arguments.""" """Return any actually-specified arguments."""
return { return {
k: v for k, v in self.option_kwargs.items() if v is not _ARG.NO k: v for k, v in self.option_kwargs.items() if v is not _ARG.NO
} }
def __repr__(self): # type: () -> str # noqa: D105 def __repr__(self) -> str: # noqa: D105
parts = [] parts = []
for arg in self.option_args: for arg in self.option_args:
parts.append(arg) parts.append(arg)
for k, v in self.filtered_option_kwargs.items(): for k, v in self.filtered_option_kwargs.items():
parts.append("{}={!r}".format(k, v)) parts.append(f"{k}={v!r}")
return "Option({})".format(", ".join(parts)) return f"Option({', '.join(parts)})"
def normalize(self, value, *normalize_args): def normalize(self, value: Any, *normalize_args: str) -> Any:
# type: (Any, *str) -> Any
"""Normalize the value based on the option configuration.""" """Normalize the value based on the option configuration."""
if self.comma_separated_list and isinstance( if self.comma_separated_list and isinstance(value, str):
value, utils.string_types
):
value = utils.parse_comma_separated_list(value) value = utils.parse_comma_separated_list(value)
if self.normalize_paths: if self.normalize_paths:
@ -303,8 +310,9 @@ class Option(object):
return value return value
def normalize_from_setuptools(self, value): def normalize_from_setuptools(
# type: (str) -> Union[int, float, complex, bool, str] self, value: str
) -> Union[int, float, complex, bool, str]:
"""Normalize the value received from setuptools.""" """Normalize the value received from setuptools."""
value = self.normalize(value) value = self.normalize(value)
if self.type is int or self.action == "count": if self.type is int or self.action == "count":
@ -321,13 +329,12 @@ class Option(object):
return False return False
return value return value
def to_argparse(self): def to_argparse(self) -> Tuple[List[str], Dict[str, Any]]:
# type: () -> Tuple[List[str], Dict[str, Any]]
"""Convert a Flake8 Option to argparse ``add_argument`` arguments.""" """Convert a Flake8 Option to argparse ``add_argument`` arguments."""
return self.option_args, self.filtered_option_kwargs return self.option_args, self.filtered_option_kwargs
@property @property
def to_optparse(self): # type: () -> NoReturn def to_optparse(self) -> "NoReturn":
"""No longer functional.""" """No longer functional."""
raise AttributeError("to_optparse: flake8 now uses argparse") raise AttributeError("to_optparse: flake8 now uses argparse")
@ -337,16 +344,16 @@ PluginVersion = collections.namedtuple(
) )
class OptionManager(object): class OptionManager:
"""Manage Options and OptionParser while adding post-processing.""" """Manage Options and OptionParser while adding post-processing."""
def __init__( def __init__(
self, self,
prog, prog: str,
version, version: str,
usage="%(prog)s [options] file file ...", usage: str = "%(prog)s [options] file file ...",
parents=None, parents: Optional[List[argparse.ArgumentParser]] = None,
): # type: (str, str, str, Optional[List[argparse.ArgumentParser]]) -> None # noqa: E501 ) -> None: # noqa: E501
"""Initialize an instance of an OptionManager. """Initialize an instance of an OptionManager.
:param str prog: :param str prog:
@ -362,10 +369,10 @@ class OptionManager(object):
if parents is None: if parents is None:
parents = [] parents = []
self.parser = argparse.ArgumentParser( self.parser: argparse.ArgumentParser = argparse.ArgumentParser(
prog=prog, usage=usage, parents=parents prog=prog, usage=usage, parents=parents
) # type: argparse.ArgumentParser )
self._current_group = None # type: Optional[argparse._ArgumentGroup] self._current_group: Optional[argparse._ArgumentGroup] = None
self.version_action = cast( self.version_action = cast(
"argparse._VersionAction", "argparse._VersionAction",
self.parser.add_argument( self.parser.add_argument(
@ -373,16 +380,16 @@ class OptionManager(object):
), ),
) )
self.parser.add_argument("filenames", nargs="*", metavar="filename") self.parser.add_argument("filenames", nargs="*", metavar="filename")
self.config_options_dict = {} # type: Dict[str, Option] self.config_options_dict: Dict[str, Option] = {}
self.options = [] # type: List[Option] self.options: List[Option] = []
self.program_name = prog self.program_name = prog
self.version = version self.version = version
self.registered_plugins = set() # type: Set[PluginVersion] self.registered_plugins: Set[PluginVersion] = set()
self.extended_default_ignore = set() # type: Set[str] self.extended_default_ignore: Set[str] = set()
self.extended_default_select = set() # type: Set[str] self.extended_default_select: Set[str] = set()
@contextlib.contextmanager @contextlib.contextmanager
def group(self, name): # type: (str) -> Generator[None, None, None] def group(self, name: str) -> Generator[None, None, None]:
"""Attach options to an argparse group during this context.""" """Attach options to an argparse group during this context."""
group = self.parser.add_argument_group(name) group = self.parser.add_argument_group(name)
self._current_group, orig_group = group, self._current_group self._current_group, orig_group = group, self._current_group
@ -391,7 +398,7 @@ class OptionManager(object):
finally: finally:
self._current_group = orig_group self._current_group = orig_group
def add_option(self, *args, **kwargs): # type: (*Any, **Any) -> None def add_option(self, *args: Any, **kwargs: Any) -> None:
"""Create and register a new option. """Create and register a new option.
See parameters for :class:`~flake8.options.manager.Option` for See parameters for :class:`~flake8.options.manager.Option` for
@ -416,8 +423,7 @@ class OptionManager(object):
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)
def remove_from_default_ignore(self, error_codes): def remove_from_default_ignore(self, error_codes: Sequence[str]) -> None:
# type: (Sequence[str]) -> None
"""Remove specified error codes from the default ignore list. """Remove specified error codes from the default ignore list.
:param list error_codes: :param list error_codes:
@ -435,8 +441,7 @@ class OptionManager(object):
error_code, error_code,
) )
def extend_default_ignore(self, error_codes): def extend_default_ignore(self, error_codes: Sequence[str]) -> None:
# type: (Sequence[str]) -> None
"""Extend the default ignore list with the error codes provided. """Extend the default ignore list with the error codes provided.
:param list error_codes: :param list error_codes:
@ -446,8 +451,7 @@ class OptionManager(object):
LOG.debug("Extending default ignore list with %r", error_codes) LOG.debug("Extending default ignore list with %r", error_codes)
self.extended_default_ignore.update(error_codes) self.extended_default_ignore.update(error_codes)
def extend_default_select(self, error_codes): def extend_default_select(self, error_codes: Sequence[str]) -> None:
# type: (Sequence[str]) -> None
"""Extend the default select list with the error codes provided. """Extend the default select list with the error codes provided.
:param list error_codes: :param list error_codes:
@ -458,22 +462,21 @@ class OptionManager(object):
self.extended_default_select.update(error_codes) self.extended_default_select.update(error_codes)
def generate_versions( def generate_versions(
self, format_str="%(name)s: %(version)s", join_on=", " self, format_str: str = "%(name)s: %(version)s", join_on: str = ", "
): ) -> str:
# type: (str, str) -> str
"""Generate a comma-separated list of versions of plugins.""" """Generate a comma-separated list of versions of plugins."""
return join_on.join( return join_on.join(
format_str % plugin._asdict() format_str % plugin._asdict()
for plugin in sorted(self.registered_plugins) for plugin in sorted(self.registered_plugins)
) )
def update_version_string(self): # type: () -> None def update_version_string(self) -> None:
"""Update the flake8 version string.""" """Update the flake8 version string."""
self.version_action.version = "{} ({}) {}".format( self.version_action.version = "{} ({}) {}".format(
self.version, self.generate_versions(), utils.get_python_version() self.version, self.generate_versions(), utils.get_python_version()
) )
def generate_epilog(self): # type: () -> None def generate_epilog(self) -> None:
"""Create an epilog with the version and name of each of plugin.""" """Create an epilog with the version and name of each of plugin."""
plugin_version_format = "%(name)s: %(version)s" plugin_version_format = "%(name)s: %(version)s"
self.parser.epilog = "Installed plugins: " + self.generate_versions( self.parser.epilog = "Installed plugins: " + self.generate_versions(
@ -482,10 +485,9 @@ class OptionManager(object):
def parse_args( def parse_args(
self, self,
args=None, # type: Optional[List[str]] args: Optional[List[str]] = None,
values=None, # type: Optional[argparse.Namespace] values: Optional[argparse.Namespace] = None,
): ) -> Tuple[argparse.Namespace, List[str]]:
# type: (...) -> Tuple[argparse.Namespace, List[str]]
"""Proxy to calling the OptionParser's parse_args method.""" """Proxy to calling the OptionParser's parse_args method."""
self.generate_epilog() self.generate_epilog()
self.update_version_string() self.update_version_string()
@ -495,8 +497,9 @@ class OptionManager(object):
# TODO: refactor callers to not need this # TODO: refactor callers to not need this
return parsed_args, parsed_args.filenames return parsed_args, parsed_args.filenames
def parse_known_args(self, args=None): def parse_known_args(
# type: (Optional[List[str]]) -> Tuple[argparse.Namespace, List[str]] self, args: Optional[List[str]] = None
) -> Tuple[argparse.Namespace, List[str]]:
"""Parse only the known arguments from the argument values. """Parse only the known arguments from the argument values.
Replicate a little argparse behaviour while we're still on Replicate a little argparse behaviour while we're still on
@ -506,8 +509,9 @@ class OptionManager(object):
self.update_version_string() self.update_version_string()
return self.parser.parse_known_args(args) return self.parser.parse_known_args(args)
def register_plugin(self, name, version, local=False): def register_plugin(
# type: (str, str, bool) -> None self, name: str, version: str, local: bool = False
) -> None:
"""Register a plugin relying on the OptionManager. """Register a plugin relying on the OptionManager.
:param str name: :param str name:

View file

@ -1,6 +1,10 @@
"""Plugin loading and management logic and classes.""" """Plugin loading and management logic and classes."""
import logging import logging
from typing import Any, Dict, List, Optional, Set from typing import Any
from typing import Dict
from typing import List
from typing import Optional
from typing import Set
from flake8 import exceptions from flake8 import exceptions
from flake8 import utils from flake8 import utils
@ -13,7 +17,7 @@ __all__ = ("Checkers", "Plugin", "PluginManager", "ReportFormatters")
NO_GROUP_FOUND = object() NO_GROUP_FOUND = object()
class Plugin(object): class Plugin:
"""Wrap an EntryPoint from setuptools and other logic.""" """Wrap an EntryPoint from setuptools and other logic."""
def __init__(self, name, entry_point, local=False): def __init__(self, name, entry_point, local=False):
@ -31,16 +35,16 @@ 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 # type: Any self._plugin: Any = None
self._parameters = None self._parameters = None
self._parameter_names = None # type: Optional[List[str]] self._parameter_names: Optional[List[str]] = None
self._group = None self._group = None
self._plugin_name = None self._plugin_name = None
self._version = None self._version = None
def __repr__(self): # type: () -> str def __repr__(self) -> str:
"""Provide an easy to read description of the current plugin.""" """Provide an easy to read description of the current plugin."""
return 'Plugin(name="{0}", entry_point="{1}")'.format( return 'Plugin(name="{}", entry_point="{}")'.format(
self.name, self.entry_point.value self.name, self.entry_point.value
) )
@ -84,7 +88,7 @@ class Plugin(object):
return self._parameters return self._parameters
@property @property
def parameter_names(self): # type: () -> List[str] def parameter_names(self) -> List[str]:
"""List of argument names that need to be passed to the plugin.""" """List of argument names that need to be passed to the plugin."""
if self._parameter_names is None: if self._parameter_names is None:
self._parameter_names = list(self.parameters) self._parameter_names = list(self.parameters)
@ -100,7 +104,7 @@ class Plugin(object):
return self._plugin return self._plugin
@property @property
def version(self): # type: () -> str def version(self) -> str:
"""Return the version of the plugin.""" """Return the version of the plugin."""
version = self._version version = self._version
if version is None: if version is None:
@ -134,9 +138,9 @@ class Plugin(object):
self._plugin = self.entry_point.load() self._plugin = self.entry_point.load()
if not callable(self._plugin): if not callable(self._plugin):
msg = ( msg = (
"Plugin %r is not a callable. It might be written for an" f"Plugin {self._plugin!r} is not a callable. It might be "
" older version of flake8 and might not work with this" f"written for an older version of flake8 and might not work "
" version" % self._plugin f"with this version"
) )
LOG.critical(msg) LOG.critical(msg)
raise TypeError(msg) raise TypeError(msg)
@ -219,11 +223,12 @@ class Plugin(object):
self.disable(optmanager) self.disable(optmanager)
class PluginManager(object): # pylint: disable=too-few-public-methods class PluginManager: # pylint: disable=too-few-public-methods
"""Find and manage plugins consistently.""" """Find and manage plugins consistently."""
def __init__(self, namespace, local_plugins=None): def __init__(
# type: (str, Optional[List[str]]) -> None self, namespace: str, local_plugins: Optional[List[str]] = None
) -> None:
"""Initialize the manager. """Initialize the manager.
:param str namespace: :param str namespace:
@ -232,8 +237,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 = {} # type: Dict[str, Plugin] self.plugins: Dict[str, Plugin] = {}
self.names = [] # type: List[str] self.names: 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 +315,7 @@ class PluginManager(object): # pylint: disable=too-few-public-methods
:rtype: :rtype:
tuple tuple
""" """
plugins_seen = set() # type: Set[str] plugins_seen: Set[str] = set()
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
@ -342,10 +347,10 @@ def version_for(plugin):
return getattr(module, "__version__", None) return getattr(module, "__version__", None)
class PluginTypeManager(object): class PluginTypeManager:
"""Parent class for most of the specific plugin types.""" """Parent class for most of the specific plugin types."""
namespace = None # type: str namespace: 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.

View file

@ -1,16 +1,11 @@
"""Plugin built-in to Flake8 to treat pyflakes as a plugin.""" """Plugin built-in to Flake8 to treat pyflakes as a plugin."""
# -*- coding: utf-8 -*-
from __future__ import absolute_import
import os import os
from typing import List from typing import List
import pyflakes
import pyflakes.checker import pyflakes.checker
from flake8 import utils from flake8 import utils
FLAKE8_PYFLAKES_CODES = { FLAKE8_PYFLAKES_CODES = {
"UnusedImport": "F401", "UnusedImport": "F401",
"ImportShadowedByLoopVar": "F402", "ImportShadowedByLoopVar": "F402",
@ -69,8 +64,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 = [] # type: List[str] include_in_doctest: List[str] = []
exclude_from_doctest = [] # type: List[str] exclude_from_doctest: 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."""
@ -96,7 +91,7 @@ class FlakesChecker(pyflakes.checker.Checker):
if overlaped_by: if overlaped_by:
with_doctest = True with_doctest = True
super(FlakesChecker, self).__init__( super().__init__(
tree, tree,
filename=filename, filename=filename,
withDoctest=with_doctest, withDoctest=with_doctest,
@ -150,7 +145,7 @@ class FlakesChecker(pyflakes.checker.Checker):
if included_file == "": if included_file == "":
continue continue
if not included_file.startswith((os.sep, "./", "~/")): if not included_file.startswith((os.sep, "./", "~/")):
included_files.append("./" + included_file) included_files.append(f"./{included_file}")
else: else:
included_files.append(included_file) included_files.append(included_file)
cls.include_in_doctest = utils.normalize_paths(included_files) cls.include_in_doctest = utils.normalize_paths(included_files)
@ -160,7 +155,7 @@ class FlakesChecker(pyflakes.checker.Checker):
if excluded_file == "": if excluded_file == "":
continue continue
if not excluded_file.startswith((os.sep, "./", "~/")): if not excluded_file.startswith((os.sep, "./", "~/")):
excluded_files.append("./" + excluded_file) excluded_files.append(f"./{excluded_file}")
else: else:
excluded_files.append(excluded_file) excluded_files.append(excluded_file)
cls.exclude_from_doctest = utils.normalize_paths(excluded_files) cls.exclude_from_doctest = utils.normalize_paths(excluded_files)
@ -170,10 +165,10 @@ class FlakesChecker(pyflakes.checker.Checker):
) )
if inc_exc: if inc_exc:
raise ValueError( raise ValueError(
'"%s" was specified in both the ' f"{inc_exc!r} was specified in both the "
"include-in-doctest and exclude-from-doctest " f"include-in-doctest and exclude-from-doctest "
"options. You are not allowed to specify it in " f"options. You are not allowed to specify it in "
"both for doctesting." % inc_exc f"both for doctesting."
) )
def run(self): def run(self):

View file

@ -3,9 +3,13 @@ import argparse
import ast import ast
import contextlib import contextlib
import logging import logging
import sys
import tokenize import tokenize
from typing import Any, Dict, Generator, List, Optional, Tuple from typing import Any
from typing import Dict
from typing import Generator
from typing import List
from typing import Optional
from typing import Tuple
import flake8 import flake8
from flake8 import defaults from flake8 import defaults
@ -25,7 +29,7 @@ _LogicalMapping = List[Tuple[int, Tuple[int, int]]]
_Logical = Tuple[List[str], List[str], _LogicalMapping] _Logical = Tuple[List[str], List[str], _LogicalMapping]
class FileProcessor(object): class FileProcessor:
"""Processes a file and holdes state. """Processes a file and holdes state.
This processes a file by generating tokens, logical and physical lines, This processes a file by generating tokens, logical and physical lines,
@ -56,8 +60,12 @@ class FileProcessor(object):
#: always ``False``, included for compatibility #: always ``False``, included for compatibility
noqa = False noqa = False
def __init__(self, filename, options, lines=None): def __init__(
# type: (str, argparse.Namespace, Optional[List[str]]) -> None self,
filename: str,
options: argparse.Namespace,
lines: Optional[List[str]] = None,
) -> None:
"""Initialice our file processor. """Initialice our file processor.
:param str filename: :param str filename:
@ -74,13 +82,13 @@ 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 = {} # type: Dict[str, Dict[Any, Any]] self._checker_states: Dict[str, Dict[Any, Any]] = {}
#: Current checker state #: Current checker state
self.checker_state = {} # type: Dict[Any, Any] self.checker_state: 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
self.indent_char = None # type: Optional[str] self.indent_char: Optional[str] = None
#: Current level of indentation #: Current level of indentation
self.indent_level = 0 self.indent_level = 0
#: Number of spaces used for indentation #: Number of spaces used for indentation
@ -104,19 +112,19 @@ class FileProcessor(object):
#: 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 = ""
#: Current set of tokens #: Current set of tokens
self.tokens = [] # type: List[_Token] self.tokens: List[_Token] = []
#: 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
self.verbose = options.verbose self.verbose = options.verbose
#: Statistics dictionary #: Statistics dictionary
self.statistics = {"logical lines": 0} self.statistics = {"logical lines": 0}
self._file_tokens = None # type: Optional[List[_Token]] self._file_tokens: Optional[List[_Token]] = None
# map from line number to the line we'll search for `noqa` in # map from line number to the line we'll search for `noqa` in
self._noqa_line_mapping = None # type: Optional[Dict[int, str]] self._noqa_line_mapping: Optional[Dict[int, str]] = None
@property @property
def file_tokens(self): # type: () -> List[_Token] def file_tokens(self) -> List[_Token]:
"""Return the complete set of tokens for a file. """Return the complete set of tokens for a file.
Accessing this attribute *may* raise an InvalidSyntax exception. Accessing this attribute *may* raise an InvalidSyntax exception.
@ -135,28 +143,28 @@ class FileProcessor(object):
return self._file_tokens return self._file_tokens
@contextlib.contextmanager @contextlib.contextmanager
def inside_multiline(self, line_number): def inside_multiline(
# type: (int) -> Generator[None, None, None] self, line_number: int
) -> Generator[None, None, None]:
"""Context-manager to toggle the multiline attribute.""" """Context-manager to toggle the multiline attribute."""
self.line_number = line_number self.line_number = line_number
self.multiline = True self.multiline = True
yield yield
self.multiline = False self.multiline = False
def reset_blank_before(self): # type: () -> None def reset_blank_before(self) -> None:
"""Reset the blank_before attribute to zero.""" """Reset the blank_before attribute to zero."""
self.blank_before = 0 self.blank_before = 0
def delete_first_token(self): # type: () -> None def delete_first_token(self) -> None:
"""Delete the first token in the list of tokens.""" """Delete the first token in the list of tokens."""
del self.tokens[0] del self.tokens[0]
def visited_new_blank_line(self): # type: () -> None def visited_new_blank_line(self) -> None:
"""Note that we visited a new blank line.""" """Note that we visited a new blank line."""
self.blank_lines += 1 self.blank_lines += 1
def update_state(self, mapping): def update_state(self, mapping: _LogicalMapping) -> None:
# type: (_LogicalMapping) -> None
"""Update the indent level based on the logical line mapping.""" """Update the indent level based on the logical line mapping."""
(start_row, start_col) = mapping[0][1] (start_row, start_col) = mapping[0][1]
start_line = self.lines[start_row - 1] start_line = self.lines[start_row - 1]
@ -164,15 +172,14 @@ class FileProcessor(object):
if self.blank_before < self.blank_lines: if self.blank_before < self.blank_lines:
self.blank_before = self.blank_lines self.blank_before = self.blank_lines
def update_checker_state_for(self, plugin): def update_checker_state_for(self, plugin: Dict[str, Any]) -> None:
# type: (Dict[str, Any]) -> None
"""Update the checker_state attribute for the plugin.""" """Update the checker_state attribute for the plugin."""
if "checker_state" in plugin["parameters"]: if "checker_state" in plugin["parameters"]:
self.checker_state = self._checker_states.setdefault( self.checker_state = self._checker_states.setdefault(
plugin["name"], {} plugin["name"], {}
) )
def next_logical_line(self): # type: () -> None def next_logical_line(self) -> None:
"""Record the previous logical line. """Record the previous logical line.
This also resets the tokens list and the blank_lines count. This also resets the tokens list and the blank_lines count.
@ -185,11 +192,11 @@ class FileProcessor(object):
self.blank_lines = 0 self.blank_lines = 0
self.tokens = [] self.tokens = []
def build_logical_line_tokens(self): # type: () -> _Logical def build_logical_line_tokens(self) -> _Logical:
"""Build the mapping, comments, and logical line lists.""" """Build the mapping, comments, and logical line lists."""
logical = [] logical = []
comments = [] comments = []
mapping = [] # type: _LogicalMapping mapping: _LogicalMapping = []
length = 0 length = 0
previous_row = previous_column = None previous_row = previous_column = None
for token_type, text, start, end, line in self.tokens: for token_type, text, start, end, line in self.tokens:
@ -211,7 +218,7 @@ class FileProcessor(object):
if previous_text == "," or ( if previous_text == "," or (
previous_text not in "{[(" and text not in "}])" previous_text not in "{[(" and text not in "}])"
): ):
text = " " + text text = f" {text}"
elif previous_column != start_column: elif previous_column != start_column:
text = line[previous_column:start_column] + text text = line[previous_column:start_column] + text
logical.append(text) logical.append(text)
@ -220,12 +227,11 @@ class FileProcessor(object):
(previous_row, previous_column) = end (previous_row, previous_column) = end
return comments, logical, mapping return comments, logical, mapping
def build_ast(self): # type: () -> ast.AST def build_ast(self) -> ast.AST:
"""Build an abstract syntax tree from the list of lines.""" """Build an abstract syntax tree from the list of lines."""
return ast.parse("".join(self.lines)) return ast.parse("".join(self.lines))
def build_logical_line(self): def build_logical_line(self) -> Tuple[str, str, _LogicalMapping]:
# type: () -> Tuple[str, str, _LogicalMapping]
"""Build a logical line from the current tokens list.""" """Build a logical line from the current tokens list."""
comments, logical, mapping_list = self.build_logical_line_tokens() comments, logical, mapping_list = self.build_logical_line_tokens()
joined_comments = "".join(comments) joined_comments = "".join(comments)
@ -233,8 +239,7 @@ class FileProcessor(object):
self.statistics["logical lines"] += 1 self.statistics["logical lines"] += 1
return joined_comments, self.logical_line, mapping_list return joined_comments, self.logical_line, mapping_list
def split_line(self, token): def split_line(self, token: _Token) -> Generator[str, None, None]:
# type: (_Token) -> Generator[str, None, None]
"""Split a physical line's line based on new-lines. """Split a physical line's line based on new-lines.
This also auto-increments the line number for the caller. This also auto-increments the line number for the caller.
@ -243,8 +248,11 @@ class FileProcessor(object):
yield line yield line
self.line_number += 1 self.line_number += 1
def keyword_arguments_for(self, parameters, arguments=None): def keyword_arguments_for(
# type: (Dict[str, bool], Optional[Dict[str, Any]]) -> Dict[str, Any] self,
parameters: Dict[str, bool],
arguments: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
"""Generate the keyword arguments for a list of parameters.""" """Generate the keyword arguments for a list of parameters."""
if arguments is None: if arguments is None:
arguments = {} arguments = {}
@ -265,7 +273,7 @@ class FileProcessor(object):
) )
return arguments return arguments
def generate_tokens(self): # type: () -> Generator[_Token, None, None] def generate_tokens(self) -> Generator[_Token, None, None]:
"""Tokenize the file and yield the tokens. """Tokenize the file and yield the tokens.
:raises flake8.exceptions.InvalidSyntax: :raises flake8.exceptions.InvalidSyntax:
@ -281,13 +289,14 @@ class FileProcessor(object):
except (tokenize.TokenError, SyntaxError) as exc: except (tokenize.TokenError, SyntaxError) as exc:
raise exceptions.InvalidSyntax(exception=exc) raise exceptions.InvalidSyntax(exception=exc)
def _noqa_line_range(self, min_line, max_line): def _noqa_line_range(
# type: (int, int) -> Dict[int, str] self, min_line: int, max_line: int
) -> Dict[int, str]:
line_range = range(min_line, max_line + 1) line_range = range(min_line, max_line + 1)
joined = "".join(self.lines[min_line - 1 : max_line]) joined = "".join(self.lines[min_line - 1 : max_line])
return dict.fromkeys(line_range, joined) return dict.fromkeys(line_range, joined)
def noqa_line_for(self, line_number): # type: (int) -> Optional[str] def noqa_line_for(self, line_number: int) -> Optional[str]:
"""Retrieve the line which will be used to determine noqa.""" """Retrieve the line which will be used to determine noqa."""
if self._noqa_line_mapping is None: if self._noqa_line_mapping is None:
try: try:
@ -327,7 +336,7 @@ class FileProcessor(object):
# retrieve a physical line (since none exist). # retrieve a physical line (since none exist).
return self._noqa_line_mapping.get(line_number) return self._noqa_line_mapping.get(line_number)
def next_line(self): # type: () -> str def next_line(self) -> str:
"""Get the next line from the list.""" """Get the next line from the list."""
if self.line_number >= self.total_lines: if self.line_number >= self.total_lines:
return "" return ""
@ -337,8 +346,7 @@ class FileProcessor(object):
self.indent_char = line[0] self.indent_char = line[0]
return line return line
def read_lines(self): def read_lines(self) -> List[str]:
# type: () -> List[str]
"""Read the lines for this file checker.""" """Read the lines for this file checker."""
if self.filename is None or self.filename == "-": if self.filename is None or self.filename == "-":
self.filename = self.options.stdin_display_name or "stdin" self.filename = self.options.stdin_display_name or "stdin"
@ -347,13 +355,8 @@ class FileProcessor(object):
lines = self.read_lines_from_filename() lines = self.read_lines_from_filename()
return lines return lines
def _readlines_py2(self): def read_lines_from_filename(self) -> List[str]:
# type: () -> List[str] """Read the lines for a file."""
with open(self.filename, "rU") as fd:
return fd.readlines()
def _readlines_py3(self):
# type: () -> List[str]
try: try:
with tokenize.open(self.filename) as fd: with tokenize.open(self.filename) as fd:
return fd.readlines() return fd.readlines()
@ -363,22 +366,11 @@ class FileProcessor(object):
with open(self.filename, encoding="latin-1") as fd: with open(self.filename, encoding="latin-1") as fd:
return fd.readlines() return fd.readlines()
def read_lines_from_filename(self): def read_lines_from_stdin(self) -> List[str]:
# type: () -> List[str]
"""Read the lines for a file."""
if (2, 6) <= sys.version_info < (3, 0):
readlines = self._readlines_py2
elif (3, 0) <= sys.version_info < (4, 0):
readlines = self._readlines_py3
return readlines()
def read_lines_from_stdin(self):
# type: () -> List[str]
"""Read the lines from standard in.""" """Read the lines from standard in."""
return utils.stdin_get_lines() return utils.stdin_get_lines()
def should_ignore_file(self): def should_ignore_file(self) -> bool:
# type: () -> bool
"""Check if ``flake8: noqa`` is in the file to be ignored. """Check if ``flake8: noqa`` is in the file to be ignored.
:returns: :returns:
@ -400,8 +392,7 @@ class FileProcessor(object):
else: else:
return False return False
def strip_utf_bom(self): def strip_utf_bom(self) -> None:
# type: () -> None
"""Strip the UTF bom from the lines of the file.""" """Strip the UTF bom from the lines of the file."""
if not self.lines: if not self.lines:
# If we have nothing to analyze quit early # If we have nothing to analyze quit early
@ -418,23 +409,22 @@ class FileProcessor(object):
self.lines[0] = self.lines[0][3:] self.lines[0] = self.lines[0][3:]
def is_eol_token(token): # type: (_Token) -> bool def is_eol_token(token: _Token) -> bool:
"""Check if the token is an end-of-line token.""" """Check if the token is an end-of-line token."""
return token[0] in NEWLINE or token[4][token[3][1] :].lstrip() == "\\\n" return token[0] in NEWLINE or token[4][token[3][1] :].lstrip() == "\\\n"
def is_multiline_string(token): # type: (_Token) -> bool def is_multiline_string(token: _Token) -> bool:
"""Check if this is a multiline string.""" """Check if this is a multiline string."""
return token[0] == tokenize.STRING and "\n" in token[1] return token[0] == tokenize.STRING and "\n" in token[1]
def token_is_newline(token): # type: (_Token) -> bool def token_is_newline(token: _Token) -> bool:
"""Check if the token type is a newline token type.""" """Check if the token type is a newline token type."""
return token[0] in NEWLINE return token[0] in NEWLINE
def count_parentheses(current_parentheses_count, token_text): def count_parentheses(current_parentheses_count: int, token_text: str) -> int:
# type: (int, str) -> int
"""Count the number of parentheses.""" """Count the number of parentheses."""
if token_text in "([{": # nosec if token_text in "([{": # nosec
return current_parentheses_count + 1 return current_parentheses_count + 1
@ -443,12 +433,12 @@ def count_parentheses(current_parentheses_count, token_text):
return current_parentheses_count return current_parentheses_count
def log_token(log, token): # type: (logging.Logger, _Token) -> None def log_token(log: logging.Logger, token: _Token) -> None:
"""Log a token to a provided logging object.""" """Log a token to a provided logging object."""
if token[2][0] == token[3][0]: if token[2][0] == token[3][0]:
pos = "[%s:%s]" % (token[2][1] or "", token[3][1]) pos = "[{}:{}]".format(token[2][1] or "", token[3][1])
else: else:
pos = "l.%s" % token[3][0] pos = f"l.{token[3][0]}"
log.log( log.log(
flake8._EXTRA_VERBOSE, flake8._EXTRA_VERBOSE,
"l.%s\t%s\t%s\t%r" "l.%s\t%s\t%s\t%r"
@ -458,7 +448,7 @@ def log_token(log, token): # type: (logging.Logger, _Token) -> None
# NOTE(sigmavirus24): This was taken wholesale from # NOTE(sigmavirus24): This was taken wholesale from
# https://github.com/PyCQA/pycodestyle # https://github.com/PyCQA/pycodestyle
def expand_indent(line): # type: (str) -> int def expand_indent(line: str) -> int:
r"""Return the amount of indentation. r"""Return the amount of indentation.
Tabs are expanded to the next multiple of 8. Tabs are expanded to the next multiple of 8.
@ -488,7 +478,7 @@ def expand_indent(line): # type: (str) -> int
# NOTE(sigmavirus24): This was taken wholesale from # NOTE(sigmavirus24): This was taken wholesale from
# https://github.com/PyCQA/pycodestyle. The in-line comments were edited to be # https://github.com/PyCQA/pycodestyle. The in-line comments were edited to be
# more descriptive. # more descriptive.
def mutate_string(text): # type: (str) -> str def mutate_string(text: str) -> str:
"""Replace contents with 'xxx' to prevent syntax matching. """Replace contents with 'xxx' to prevent syntax matching.
>>> mutate_string('"abc"') >>> mutate_string('"abc"')

View file

@ -1,19 +1,23 @@
"""Statistic collection logic for Flake8.""" """Statistic collection logic for Flake8."""
import collections import collections
from typing import Dict, Generator, List, Optional from typing import Dict
from typing import Generator
from typing import List
from typing import Optional
from typing import TYPE_CHECKING
if False: # `typing.TYPE_CHECKING` was introduced in 3.5.2 if TYPE_CHECKING:
from flake8.style_guide import Violation from flake8.style_guide import Violation
class Statistics(object): class Statistics:
"""Manager of aggregated statistics for a run of Flake8.""" """Manager of aggregated statistics for a run of Flake8."""
def __init__(self): # type: () -> None def __init__(self) -> None:
"""Initialize the underlying dictionary for our statistics.""" """Initialize the underlying dictionary for our statistics."""
self._store = {} # type: Dict[Key, Statistic] self._store: Dict[Key, "Statistic"] = {}
def error_codes(self): # type: () -> List[str] def error_codes(self) -> List[str]:
"""Return all unique error codes stored. """Return all unique error codes stored.
:returns: :returns:
@ -23,7 +27,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): # type: (Violation) -> None def record(self, error: "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:
@ -37,8 +41,9 @@ class Statistics(object):
self._store[key] = Statistic.create_from(error) self._store[key] = Statistic.create_from(error)
self._store[key].increment() self._store[key].increment()
def statistics_for(self, prefix, filename=None): def statistics_for(
# type: (str, Optional[str]) -> Generator[Statistic, None, None] self, prefix: str, filename: Optional[str] = None
) -> 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,
@ -79,11 +84,11 @@ class Key(collections.namedtuple("Key", ["filename", "code"])):
__slots__ = () __slots__ = ()
@classmethod @classmethod
def create_from(cls, error): # type: (Violation) -> Key def create_from(cls, error: "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): # type: (str, Optional[str]) -> bool def matches(self, prefix: str, filename: Optional[str]) -> bool:
"""Determine if this key matches some constraints. """Determine if this key matches some constraints.
:param str prefix: :param str prefix:
@ -102,7 +107,7 @@ class Key(collections.namedtuple("Key", ["filename", "code"])):
) )
class Statistic(object): class Statistic:
"""Simple wrapper around the logic of each statistic. """Simple wrapper around the logic of each statistic.
Instead of maintaining a simple but potentially hard to reason about Instead of maintaining a simple but potentially hard to reason about
@ -110,8 +115,9 @@ class Statistic(object):
convenience methods on it. convenience methods on it.
""" """
def __init__(self, error_code, filename, message, count): def __init__(
# type: (str, str, str, int) -> None self, error_code: str, filename: str, message: str, count: int
) -> None:
"""Initialize our Statistic.""" """Initialize our Statistic."""
self.error_code = error_code self.error_code = error_code
self.filename = filename self.filename = filename
@ -119,7 +125,7 @@ class Statistic(object):
self.count = count self.count = count
@classmethod @classmethod
def create_from(cls, error): # type: (Violation) -> Statistic def create_from(cls, error: "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,
@ -128,6 +134,6 @@ class Statistic(object):
count=0, count=0,
) )
def increment(self): # type: () -> None def increment(self) -> 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

@ -4,16 +4,23 @@ import collections
import contextlib import contextlib
import copy import copy
import enum import enum
import functools
import itertools import itertools
import linecache import linecache
import logging import logging
from typing import Dict, Generator, List, Match, Optional, Sequence, Set from typing import Dict
from typing import Tuple, Union from typing import Generator
from typing import List
from typing import Match
from typing import Optional
from typing import Sequence
from typing import Set
from typing import Tuple
from typing import Union
from flake8 import defaults from flake8 import defaults
from flake8 import statistics from flake8 import statistics
from flake8 import utils from flake8 import utils
from flake8._compat import lru_cache
from flake8.formatting import base as base_formatter from flake8.formatting import base as base_formatter
__all__ = ("StyleGuide",) __all__ = ("StyleGuide",)
@ -42,8 +49,8 @@ class Decision(enum.Enum):
Selected = "selected error" Selected = "selected error"
@lru_cache(maxsize=512) @functools.lru_cache(maxsize=512)
def find_noqa(physical_line): # type: (str) -> Optional[Match[str]] def find_noqa(physical_line: str) -> Optional[Match[str]]:
return defaults.NOQA_INLINE_REGEXP.search(physical_line) return defaults.NOQA_INLINE_REGEXP.search(physical_line)
@ -63,8 +70,7 @@ _Violation = collections.namedtuple(
class Violation(_Violation): 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: bool) -> 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:
@ -105,8 +111,7 @@ class Violation(_Violation):
) )
return False return False
def is_in(self, diff): def is_in(self, diff: Dict[str, Set[int]]) -> bool:
# type: (Dict[str, Set[int]]) -> bool
"""Determine if the violation is included in a diff's line ranges. """Determine if the violation is included in a diff's line ranges.
This function relies on the parsed data added via This function relies on the parsed data added via
@ -142,16 +147,16 @@ class Violation(_Violation):
return self.line_number in line_numbers return self.line_number in line_numbers
class DecisionEngine(object): class DecisionEngine:
"""A class for managing the decision process around violations. """A class for managing the decision process around violations.
This contains the logic for whether a violation should be reported or This contains the logic for whether a violation should be reported or
ignored. ignored.
""" """
def __init__(self, options): # type: (argparse.Namespace) -> None def __init__(self, options: argparse.Namespace) -> None:
"""Initialize the engine.""" """Initialize the engine."""
self.cache = {} # type: Dict[str, Decision] self.cache: 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)
@ -169,16 +174,15 @@ class DecisionEngine(object):
self.using_default_ignore = set(self.ignored) == set(defaults.IGNORE) self.using_default_ignore = set(self.ignored) == set(defaults.IGNORE)
self.using_default_select = set(self.selected) == set(defaults.SELECT) self.using_default_select = set(self.selected) == set(defaults.SELECT)
def _in_all_selected(self, code): # type: (str) -> bool def _in_all_selected(self, code: str) -> bool:
return bool(self.all_selected) and code.startswith(self.all_selected) return bool(self.all_selected) and code.startswith(self.all_selected)
def _in_extended_selected(self, code): # type: (str) -> bool def _in_extended_selected(self, code: str) -> bool:
return bool(self.extended_selected) and code.startswith( return bool(self.extended_selected) and code.startswith(
self.extended_selected self.extended_selected
) )
def was_selected(self, code): def was_selected(self, code: str) -> Union[Selected, Ignored]:
# type: (str) -> Union[Selected, Ignored]
"""Determine if the code has been selected by the user. """Determine if the code has been selected by the user.
:param str code: :param str code:
@ -201,8 +205,7 @@ class DecisionEngine(object):
return Ignored.Implicitly return Ignored.Implicitly
def was_ignored(self, code): def was_ignored(self, code: str) -> Union[Selected, Ignored]:
# type: (str) -> Union[Selected, Ignored]
"""Determine if the code has been ignored by the user. """Determine if the code has been ignored by the user.
:param str code: :param str code:
@ -219,8 +222,7 @@ class DecisionEngine(object):
return Selected.Implicitly return Selected.Implicitly
def more_specific_decision_for(self, code): def more_specific_decision_for(self, code: str) -> Decision:
# type: (str) -> Decision
select = find_first_match(code, self.all_selected) select = find_first_match(code, self.all_selected)
extra_select = find_first_match(code, self.extended_selected) extra_select = find_first_match(code, self.extended_selected)
ignore = find_first_match(code, self.ignored) ignore = find_first_match(code, self.ignored)
@ -268,8 +270,7 @@ class DecisionEngine(object):
return Decision.Ignored return Decision.Ignored
return Decision.Selected return Decision.Selected
def make_decision(self, code): def make_decision(self, code: str) -> Decision:
# type: (str) -> Decision
"""Decide if code should be ignored or selected.""" """Decide if code should be ignored or selected."""
LOG.debug('Deciding if "%s" should be reported', code) LOG.debug('Deciding if "%s" should be reported', code)
selected = self.was_selected(code) selected = self.was_selected(code)
@ -295,8 +296,7 @@ class DecisionEngine(object):
decision = Decision.Ignored # pylint: disable=R0204 decision = Decision.Ignored # pylint: disable=R0204
return decision return decision
def decision_for(self, code): def decision_for(self, code: str) -> Decision:
# type: (str) -> Decision
"""Return the decision for a specific code. """Return the decision for a specific code.
This method caches the decisions for codes to avoid retracing the same This method caches the decisions for codes to avoid retracing the same
@ -318,15 +318,15 @@ class DecisionEngine(object):
return decision return decision
class StyleGuideManager(object): class StyleGuideManager:
"""Manage multiple style guides for a single run.""" """Manage multiple style guides for a single run."""
def __init__( def __init__(
self, self,
options, # type: argparse.Namespace options: argparse.Namespace,
formatter, # type: base_formatter.BaseFormatter formatter: base_formatter.BaseFormatter,
decider=None, # type: Optional[DecisionEngine] decider: Optional[DecisionEngine] = None,
): # type: (...) -> None ) -> None:
"""Initialize our StyleGuide. """Initialize our StyleGuide.
.. todo:: Add parameter documentation. .. todo:: Add parameter documentation.
@ -335,7 +335,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 = [] # type: List[StyleGuide] self.style_guides: List[StyleGuide] = []
self.default_style_guide = StyleGuide( self.default_style_guide = StyleGuide(
options, formatter, self.stats, decider=decider options, formatter, self.stats, decider=decider
) )
@ -346,8 +346,9 @@ class StyleGuideManager(object):
) )
) )
def populate_style_guides_with(self, options): def populate_style_guides_with(
# type: (argparse.Namespace) -> Generator[StyleGuide, None, None] self, options: argparse.Namespace
) -> Generator["StyleGuide", None, None]:
"""Generate style guides from the per-file-ignores option. """Generate style guides from the per-file-ignores option.
:param options: :param options:
@ -367,8 +368,8 @@ class StyleGuideManager(object):
filename=filename, extend_ignore_with=violations filename=filename, extend_ignore_with=violations
) )
@lru_cache(maxsize=None) @functools.lru_cache(maxsize=None)
def style_guide_for(self, filename): # type: (str) -> StyleGuide def style_guide_for(self, filename: str) -> "StyleGuide":
"""Find the StyleGuide for the filename in particular.""" """Find the StyleGuide for the filename in particular."""
guides = sorted( guides = sorted(
(g for g in self.style_guides if g.applies_to(filename)), (g for g in self.style_guides if g.applies_to(filename)),
@ -379,8 +380,9 @@ class StyleGuideManager(object):
return guides[0] return guides[0]
@contextlib.contextmanager @contextlib.contextmanager
def processing_file(self, filename): def processing_file(
# type: (str) -> Generator[StyleGuide, None, None] self, filename: str
) -> Generator["StyleGuide", None, None]:
"""Record the fact that we're processing the file's results.""" """Record the fact that we're processing the file's results."""
guide = self.style_guide_for(filename) guide = self.style_guide_for(filename)
with guide.processing_file(filename): with guide.processing_file(filename):
@ -388,14 +390,13 @@ class StyleGuideManager(object):
def handle_error( def handle_error(
self, self,
code, code: str,
filename, filename: str,
line_number, line_number: int,
column_number, column_number: Optional[int],
text, text: str,
physical_line=None, physical_line: Optional[str] = None,
): ) -> 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:
@ -423,8 +424,7 @@ class StyleGuideManager(object):
code, filename, line_number, column_number, text, physical_line code, filename, line_number, column_number, text, physical_line
) )
def add_diff_ranges(self, diffinfo): def add_diff_ranges(self, diffinfo: Dict[str, Set[int]]) -> None:
# 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
@ -437,16 +437,16 @@ class StyleGuideManager(object):
guide.add_diff_ranges(diffinfo) guide.add_diff_ranges(diffinfo)
class StyleGuide(object): class StyleGuide:
"""Manage a Flake8 user's style guide.""" """Manage a Flake8 user's style guide."""
def __init__( def __init__(
self, self,
options, # type: argparse.Namespace options: argparse.Namespace,
formatter, # type: base_formatter.BaseFormatter formatter: base_formatter.BaseFormatter,
stats, # type: statistics.Statistics stats: statistics.Statistics,
filename=None, # type: Optional[str] filename: Optional[str] = None,
decider=None, # type: Optional[DecisionEngine] decider: Optional[DecisionEngine] = None,
): ):
"""Initialize our StyleGuide. """Initialize our StyleGuide.
@ -459,14 +459,17 @@ 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 = {} # type: Dict[str, Set[int]] self._parsed_diff: Dict[str, Set[int]] = {}
def __repr__(self): # type: () -> str def __repr__(self) -> str:
"""Make it easier to debug which StyleGuide we're using.""" """Make it easier to debug which StyleGuide we're using."""
return "<StyleGuide [{}]>".format(self.filename) return f"<StyleGuide [{self.filename}]>"
def copy(self, filename=None, extend_ignore_with=None): def copy(
# type: (Optional[str], Optional[Sequence[str]]) -> StyleGuide self,
filename: Optional[str] = None,
extend_ignore_with: Optional[Sequence[str]] = None,
) -> "StyleGuide":
"""Create a copy of this style guide with different values.""" """Create a copy of this style guide with different values."""
filename = filename or self.filename filename = filename or self.filename
options = copy.deepcopy(self.options) options = copy.deepcopy(self.options)
@ -476,14 +479,15 @@ class StyleGuide(object):
) )
@contextlib.contextmanager @contextlib.contextmanager
def processing_file(self, filename): def processing_file(
# type: (str) -> Generator[StyleGuide, None, None] self, filename: str
) -> Generator["StyleGuide", None, None]:
"""Record the fact that we're processing the file's results.""" """Record the fact that we're processing the file's results."""
self.formatter.beginning(filename) self.formatter.beginning(filename)
yield self yield self
self.formatter.finished(filename) self.formatter.finished(filename)
def applies_to(self, filename): # type: (str) -> bool def applies_to(self, filename: str) -> bool:
"""Check if this StyleGuide applies to the file. """Check if this StyleGuide applies to the file.
:param str filename: :param str filename:
@ -499,12 +503,11 @@ class StyleGuide(object):
return utils.matches_filename( return utils.matches_filename(
filename, filename,
patterns=[self.filename], patterns=[self.filename],
log_message='{!r} does %(whether)smatch "%(path)s"'.format(self), log_message=f'{self!r} does %(whether)smatch "%(path)s"',
logger=LOG, logger=LOG,
) )
def should_report_error(self, code): def should_report_error(self, code: str) -> Decision:
# type: (str) -> Decision
"""Determine if the error code should be reported or ignored. """Determine if the error code should be reported or ignored.
This method only cares about the select and ignore rules as specified This method only cares about the select and ignore rules as specified
@ -520,14 +523,13 @@ class StyleGuide(object):
def handle_error( def handle_error(
self, self,
code, code: str,
filename, filename: str,
line_number, line_number: int,
column_number, column_number: Optional[int],
text, text: str,
physical_line=None, physical_line: Optional[str] = None,
): ) -> 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:
@ -579,8 +581,7 @@ class StyleGuide(object):
return 1 return 1
return 0 return 0
def add_diff_ranges(self, diffinfo): def add_diff_ranges(self, diffinfo: Dict[str, Set[int]]) -> None:
# 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
@ -592,14 +593,15 @@ class StyleGuide(object):
self._parsed_diff = diffinfo self._parsed_diff = diffinfo
def find_more_specific(selected, ignored): # type: (str, str) -> Decision def find_more_specific(selected: str, ignored: str) -> Decision:
if selected.startswith(ignored) and selected != ignored: if selected.startswith(ignored) and selected != ignored:
return Decision.Selected return Decision.Selected
return Decision.Ignored return Decision.Ignored
def find_first_match(error_code, code_list): def find_first_match(
# type: (str, Tuple[str, ...]) -> Optional[str] error_code: str, code_list: Tuple[str, ...]
) -> Optional[str]:
startswith = error_code.startswith startswith = error_code.startswith
for code in code_list: for code in code_list:
if startswith(code): if startswith(code):

View file

@ -1,6 +1,7 @@
"""Utility methods for flake8.""" """Utility methods for flake8."""
import collections import collections
import fnmatch as _fnmatch import fnmatch as _fnmatch
import functools
import inspect import inspect
import io import io
import logging import logging
@ -8,24 +9,33 @@ import os
import platform import platform
import re import re
import sys import sys
import textwrap
import tokenize import tokenize
from typing import Callable, Dict, Generator, List, Optional, Pattern from typing import Callable
from typing import Sequence, Set, Tuple, Union from typing import Dict
from typing import Generator
from typing import List
from typing import Optional
from typing import Pattern
from typing import Sequence
from typing import Set
from typing import Tuple
from typing import TYPE_CHECKING
from typing import Union
from flake8 import exceptions from flake8 import exceptions
from flake8._compat import lru_cache
if False: # `typing.TYPE_CHECKING` was introduced in 3.5.2 if TYPE_CHECKING:
from flake8.plugins.manager import Plugin from flake8.plugins.manager import Plugin
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(
# type: (str, Pattern[str]) -> List[str] value: str, regexp: Pattern[str] = COMMA_SEPARATED_LIST_RE
) -> List[str]:
"""Parse a comma-separated list. """Parse a comma-separated list.
:param value: :param value:
@ -40,7 +50,7 @@ def parse_comma_separated_list(value, regexp=COMMA_SEPARATED_LIST_RE):
:rtype: :rtype:
list list
""" """
assert isinstance(value, string_types), value assert isinstance(value, str), value
separated = regexp.split(value) separated = regexp.split(value)
item_gen = (item.strip() for item in separated) item_gen = (item.strip() for item in separated)
@ -59,8 +69,7 @@ _FILE_LIST_TOKEN_TYPES = [
] ]
def _tokenize_files_to_codes_mapping(value): def _tokenize_files_to_codes_mapping(value: str) -> List[_Token]:
# type: (str) -> List[_Token]
tokens = [] tokens = []
i = 0 i = 0
while i < len(value): while i < len(value):
@ -77,8 +86,9 @@ 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( # noqa: C901
# type: (Union[Sequence[str], str]) -> List[Tuple[str, List[str]]] value_: Union[Sequence[str], str]
) -> List[Tuple[str, List[str]]]:
"""Parse a files-to-codes mapping. """Parse a files-to-codes mapping.
A files-to-codes mapping a sequence of values specified as A files-to-codes mapping a sequence of values specified as
@ -88,22 +98,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 not isinstance(value_, string_types): if not isinstance(value_, str):
value = "\n".join(value_) value = "\n".join(value_)
else: else:
value = value_ value = value_
ret = [] # type: List[Tuple[str, List[str]]] ret: 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 = [] # type: List[str] filenames: List[str] = []
codes = [] # type: List[str] codes: List[str] = []
def _reset(): # type: () -> None def _reset() -> 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))
@ -112,16 +122,12 @@ def parse_files_to_codes_mapping(value_): # noqa: C901
State.filenames = [] State.filenames = []
State.codes = [] State.codes = []
def _unexpected_token(): # type: () -> exceptions.ExecutionError def _unexpected_token() -> exceptions.ExecutionError:
def _indent(s): # type: (str) -> str
return " " + s.strip().replace("\n", "\n ")
return exceptions.ExecutionError( return exceptions.ExecutionError(
"Expected `per-file-ignores` to be a mapping from file exclude " f"Expected `per-file-ignores` to be a mapping from file exclude "
"patterns to ignore codes.\n\n" f"patterns to ignore codes.\n\n"
"Configured `per-file-ignores` setting:\n\n{}".format( f"Configured `per-file-ignores` setting:\n\n"
_indent(value) f"{textwrap.indent(value.strip(), ' ')}"
)
) )
for token in _tokenize_files_to_codes_mapping(value): for token in _tokenize_files_to_codes_mapping(value):
@ -155,8 +161,9 @@ def parse_files_to_codes_mapping(value_): # noqa: C901
return ret return ret
def normalize_paths(paths, parent=os.curdir): def normalize_paths(
# type: (Sequence[str], str) -> List[str] paths: Sequence[str], parent: str = os.curdir
) -> List[str]:
"""Normalize a list of paths relative to a parent directory. """Normalize a list of paths relative to a parent directory.
:returns: :returns:
@ -168,8 +175,7 @@ def normalize_paths(paths, parent=os.curdir):
return [normalize_path(p, parent) for p in paths] return [normalize_path(p, parent) for p in paths]
def normalize_path(path, parent=os.curdir): def normalize_path(path: str, parent: str = os.curdir) -> str:
# type: (str, str) -> str
"""Normalize a single-path. """Normalize a single-path.
:returns: :returns:
@ -190,7 +196,9 @@ def normalize_path(path, parent=os.curdir):
return path.rstrip(separator + alternate_separator) return path.rstrip(separator + alternate_separator)
def _stdin_get_value_py3(): # type: () -> str @functools.lru_cache(maxsize=1)
def stdin_get_value() -> str:
"""Get and cache it so plugins can use it."""
stdin_value = sys.stdin.buffer.read() stdin_value = sys.stdin.buffer.read()
fd = io.BytesIO(stdin_value) fd = io.BytesIO(stdin_value)
try: try:
@ -201,25 +209,12 @@ def _stdin_get_value_py3(): # type: () -> str
return stdin_value.decode("utf-8") return stdin_value.decode("utf-8")
@lru_cache(maxsize=1) def stdin_get_lines() -> List[str]:
def stdin_get_value(): # type: () -> str
"""Get and cache it so plugins can use it."""
if sys.version_info < (3,):
return sys.stdin.read()
else:
return _stdin_get_value_py3()
def stdin_get_lines(): # type: () -> List[str]
"""Return lines of stdin split according to file splitting.""" """Return lines of stdin split according to file splitting."""
if sys.version_info < (3,): return list(io.StringIO(stdin_get_value()))
return list(io.BytesIO(stdin_get_value()))
else:
return list(io.StringIO(stdin_get_value()))
def parse_unified_diff(diff=None): def parse_unified_diff(diff: Optional[str] = None) -> 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:
@ -233,7 +228,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) # type: Dict[str, Set[int]] parsed_paths: Dict[str, Set[int]] = collections.defaultdict(set)
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
@ -291,8 +286,7 @@ def parse_unified_diff(diff=None):
return parsed_paths return parsed_paths
def is_windows(): def is_windows() -> bool:
# type: () -> bool
"""Determine if we're running on Windows. """Determine if we're running on Windows.
:returns: :returns:
@ -303,8 +297,7 @@ def is_windows():
return os.name == "nt" return os.name == "nt"
def is_using_stdin(paths): def is_using_stdin(paths: List[str]) -> bool:
# type: (List[str]) -> bool
"""Determine if we're going to read from stdin. """Determine if we're going to read from stdin.
:param list paths: :param list paths:
@ -317,12 +310,14 @@ def is_using_stdin(paths):
return "-" in paths return "-" in paths
def _default_predicate(*args): # type: (*str) -> bool def _default_predicate(*args: str) -> bool:
return False return False
def filenames_from(arg, predicate=None): def filenames_from(
# type: (str, Optional[Callable[[str], bool]]) -> Generator[str, None, None] # noqa: E501 arg: str, predicate: Optional[Callable[[str], bool]] = None
) -> Generator[str, None, None]:
# noqa: E501
"""Generate filenames from an argument. """Generate filenames from an argument.
:param str arg: :param str arg:
@ -362,8 +357,7 @@ def filenames_from(arg, predicate=None):
yield arg yield arg
def fnmatch(filename, patterns): def fnmatch(filename: str, patterns: Sequence[str]) -> bool:
# type: (str, Sequence[str]) -> bool
"""Wrap :func:`fnmatch.fnmatch` to add some functionality. """Wrap :func:`fnmatch.fnmatch` to add some functionality.
:param str filename: :param str filename:
@ -381,8 +375,7 @@ def fnmatch(filename, patterns):
return any(_fnmatch.fnmatch(filename, pattern) for pattern in patterns) return any(_fnmatch.fnmatch(filename, pattern) for pattern in patterns)
def parameters_for(plugin): def parameters_for(plugin: "Plugin") -> Dict[str, bool]:
# type: (Plugin) -> Dict[str, bool]
"""Return the parameters for the plugin. """Return the parameters for the plugin.
This will inspect the plugin and return either the function parameters This will inspect the plugin and return either the function parameters
@ -404,24 +397,11 @@ def parameters_for(plugin):
if is_class: # The plugin is a class if is_class: # The plugin is a class
func = plugin.plugin.__init__ func = plugin.plugin.__init__
if sys.version_info < (3, 3): parameters = {
argspec = inspect.getargspec(func) parameter.name: parameter.default is parameter.empty
start_of_optional_args = len(argspec[0]) - len(argspec[-1] or []) for parameter in inspect.signature(func).parameters.values()
parameter_names = argspec[0] if parameter.kind == parameter.POSITIONAL_OR_KEYWORD
parameters = collections.OrderedDict( }
[
(name, position < start_of_optional_args)
for position, name in enumerate(parameter_names)
]
)
else:
parameters = collections.OrderedDict(
[
(parameter.name, parameter.default is parameter.empty)
for parameter in inspect.signature(func).parameters.values()
if parameter.kind == parameter.POSITIONAL_OR_KEYWORD
]
)
if is_class: if is_class:
parameters.pop("self", None) parameters.pop("self", None)
@ -429,8 +409,12 @@ def parameters_for(plugin):
return parameters return parameters
def matches_filename(path, patterns, log_message, logger): def matches_filename(
# type: (str, Sequence[str], str, logging.Logger) -> bool path: str,
patterns: Sequence[str],
log_message: str,
logger: 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:
@ -462,7 +446,7 @@ def matches_filename(path, patterns, log_message, logger):
return match return match
def get_python_version(): # type: () -> str def get_python_version() -> str:
"""Find and format the python implementation and version. """Find and format the python implementation and version.
:returns: :returns:
@ -470,7 +454,7 @@ def get_python_version(): # type: () -> str
:rtype: :rtype:
str str
""" """
return "%s %s on %s" % ( return "{} {} on {}".format(
platform.python_implementation(), platform.python_implementation(),
platform.python_version(), platform.python_version(),
platform.system(), platform.system(),

View file

@ -1,3 +1,4 @@
from some.module.that.has.nested.sub.modules import ClassWithVeryVeryVeryVeryLongName # noqa: E501,F401 from some.module.that.has.nested.sub.modules import \
ClassWithVeryVeryVeryVeryLongName # noqa: E501,F401
# ClassWithVeryVeryVeryVeryLongName() # ClassWithVeryVeryVeryVeryLongName()

View file

@ -1,7 +1,7 @@
"""Module that is off sys.path by default, for testing local-plugin-paths.""" """Module that is off sys.path by default, for testing local-plugin-paths."""
class ExtensionTestPlugin2(object): class ExtensionTestPlugin2:
"""Extension test plugin in its own directory.""" """Extension test plugin in its own directory."""
name = 'ExtensionTestPlugin2' name = 'ExtensionTestPlugin2'

View file

@ -1,5 +1,6 @@
"""Integration tests for the checker submodule.""" """Integration tests for the checker submodule."""
import mock from unittest import mock
import pytest import pytest
from flake8 import checker from flake8 import checker
@ -20,7 +21,7 @@ EXPECTED_RESULT_PHYSICAL_LINE = (
) )
class PluginClass(object): class PluginClass:
"""Simple file plugin class yielding the expected report.""" """Simple file plugin class yielding the expected report."""
name = 'test' name = 'test'

View file

@ -1,8 +1,8 @@
"""Integration tests for the main entrypoint of flake8.""" """Integration tests for the main entrypoint of flake8."""
import json import json
import os import os
from unittest import mock
import mock
import pytest import pytest
from flake8 import utils from flake8 import utils
@ -280,13 +280,13 @@ def test_obtaining_args_from_sys_argv_when_not_explicity_provided(capsys):
def test_cli_config_option_respected(tmp_path): def test_cli_config_option_respected(tmp_path):
"""Test --config is used.""" """Test --config is used."""
config = tmp_path / "flake8.ini" config = tmp_path / "flake8.ini"
config.write_text(u"""\ config.write_text("""\
[flake8] [flake8]
ignore = F401 ignore = F401
""") """)
py_file = tmp_path / "t.py" py_file = tmp_path / "t.py"
py_file.write_text(u"import os\n") py_file.write_text("import os\n")
_call_main(["--config", str(config), str(py_file)]) _call_main(["--config", str(config), str(py_file)])
@ -294,13 +294,13 @@ ignore = F401
def test_cli_isolated_overrides_config_option(tmp_path): def test_cli_isolated_overrides_config_option(tmp_path):
"""Test --isolated overrides --config.""" """Test --isolated overrides --config."""
config = tmp_path / "flake8.ini" config = tmp_path / "flake8.ini"
config.write_text(u"""\ config.write_text("""\
[flake8] [flake8]
ignore = F401 ignore = F401
""") """)
py_file = tmp_path / "t.py" py_file = tmp_path / "t.py"
py_file.write_text(u"import os\n") py_file.write_text("import os\n")
_call_main(["--isolated", "--config", str(config), str(py_file)], retv=1) _call_main(["--isolated", "--config", str(config), str(py_file)], retv=1)

View file

@ -1,12 +1,11 @@
"""Integration tests for plugin loading.""" """Integration tests for plugin loading."""
from flake8.main import application from flake8.main import application
LOCAL_PLUGIN_CONFIG = 'tests/fixtures/config_files/local-plugin.ini' LOCAL_PLUGIN_CONFIG = 'tests/fixtures/config_files/local-plugin.ini'
LOCAL_PLUGIN_PATH_CONFIG = 'tests/fixtures/config_files/local-plugin-path.ini' LOCAL_PLUGIN_PATH_CONFIG = 'tests/fixtures/config_files/local-plugin-path.ini'
class ExtensionTestPlugin(object): class ExtensionTestPlugin:
"""Extension test plugin.""" """Extension test plugin."""
name = 'ExtensionTestPlugin' name = 'ExtensionTestPlugin'
@ -24,7 +23,7 @@ class ExtensionTestPlugin(object):
parser.add_option('--anopt') parser.add_option('--anopt')
class ReportTestPlugin(object): class ReportTestPlugin:
"""Report test plugin.""" """Report test plugin."""
name = 'ReportTestPlugin' name = 'ReportTestPlugin'

View file

@ -1,8 +1,8 @@
"""Tests for the Application class.""" """Tests for the Application class."""
import argparse import argparse
import sys import sys
from unittest import mock
import mock
import pytest import pytest
from flake8.main import application as app from flake8.main import application as app

View file

@ -1,7 +1,7 @@
"""Tests for the BaseFormatter object.""" """Tests for the BaseFormatter object."""
import argparse import argparse
from unittest import mock
import mock
import pytest import pytest
from flake8 import style_guide from flake8 import style_guide

View file

@ -1,7 +1,7 @@
"""Tests for the Manager object for FileCheckers.""" """Tests for the Manager object for FileCheckers."""
import errno import errno
from unittest import mock
import mock
import pytest import pytest
from flake8 import checker from flake8 import checker

View file

@ -1,9 +1,8 @@
# -*- coding: utf-8 -*-
"""Tests for the ConfigFileFinder.""" """Tests for the ConfigFileFinder."""
import configparser import configparser
import os import os
from unittest import mock
import mock
import pytest import pytest
from flake8.options import config from flake8.options import config

View file

@ -1,5 +1,6 @@
"""Tests for our debugging module.""" """Tests for our debugging module."""
import mock from unittest import mock
import pytest import pytest
from flake8.main import debug from flake8.main import debug

View file

@ -1,5 +1,6 @@
"""Unit tests for the FileChecker class.""" """Unit tests for the FileChecker class."""
import mock from unittest import mock
import pytest import pytest
import flake8 import flake8
@ -50,7 +51,7 @@ def test_nonexistent_file():
def test_raises_exception_on_failed_plugin(tmp_path, default_options): def test_raises_exception_on_failed_plugin(tmp_path, default_options):
"""Checks that a failing plugin results in PluginExecutionFailed.""" """Checks that a failing plugin results in PluginExecutionFailed."""
foobar = tmp_path / 'foobar.py' foobar = tmp_path / 'foobar.py'
foobar.write_text(u"I exist!") # Create temp file foobar.write_text("I exist!") # Create temp file
plugin = { plugin = {
"name": "failure", "name": "failure",
"plugin_name": "failure", # Both are necessary "plugin_name": "failure", # Both are necessary

View file

@ -1,8 +1,8 @@
"""Tests for the FileProcessor class.""" """Tests for the FileProcessor class."""
import ast import ast
import tokenize import tokenize
from unittest import mock
import mock
import pytest import pytest
from flake8 import processor from flake8 import processor
@ -46,7 +46,7 @@ def test_read_lines_unknown_encoding(tmpdir, default_options):
@pytest.mark.parametrize('first_line', [ @pytest.mark.parametrize('first_line', [
'\xEF\xBB\xBF"""Module docstring."""\n', '\xEF\xBB\xBF"""Module docstring."""\n',
u'\uFEFF"""Module docstring."""\n', '\uFEFF"""Module docstring."""\n',
]) ])
def test_strip_utf_bom(first_line, default_options): def test_strip_utf_bom(first_line, default_options):
r"""Verify that we strip '\xEF\xBB\xBF' from the first line.""" r"""Verify that we strip '\xEF\xBB\xBF' from the first line."""
@ -58,7 +58,7 @@ def test_strip_utf_bom(first_line, default_options):
@pytest.mark.parametrize('lines, expected', [ @pytest.mark.parametrize('lines, expected', [
(['\xEF\xBB\xBF"""Module docstring."""\n'], False), (['\xEF\xBB\xBF"""Module docstring."""\n'], False),
([u'\uFEFF"""Module docstring."""\n'], False), (['\uFEFF"""Module docstring."""\n'], False),
(['#!/usr/bin/python', '# flake8 is great', 'a = 1'], False), (['#!/usr/bin/python', '# flake8 is great', 'a = 1'], False),
(['#!/usr/bin/python', '# flake8: noqa', 'a = 1'], True), (['#!/usr/bin/python', '# flake8: noqa', 'a = 1'], True),
(['#!/usr/bin/python', '# flake8:noqa', 'a = 1'], True), (['#!/usr/bin/python', '# flake8:noqa', 'a = 1'], True),
@ -130,7 +130,7 @@ def test_noqa_line_for(default_options):
]) ])
for i in range(1, 4): for i in range(1, 4):
assert file_processor.noqa_line_for(i) == 'Line {0}\n'.format(i) assert file_processor.noqa_line_for(i) == f'Line {i}\n'
def test_noqa_line_for_continuation(default_options): def test_noqa_line_for_continuation(default_options):
@ -182,7 +182,7 @@ def test_next_line(default_options):
]) ])
for i in range(1, 4): for i in range(1, 4):
assert file_processor.next_line() == 'Line {}'.format(i) assert file_processor.next_line() == f'Line {i}'
assert file_processor.line_number == i assert file_processor.line_number == i

View file

@ -1,5 +1,5 @@
"""Tests for get_local_plugins.""" """Tests for get_local_plugins."""
import mock from unittest import mock
from flake8.options import config from flake8.options import config

View file

@ -1,8 +1,8 @@
"""Tests for Flake8's legacy API.""" """Tests for Flake8's legacy API."""
import argparse import argparse
import os.path import os.path
from unittest import mock
import mock
import pytest import pytest
from flake8.api import legacy as api from flake8.api import legacy as api

View file

@ -1,7 +1,7 @@
"""Unit tests for flake8.options.config.MergedConfigParser.""" """Unit tests for flake8.options.config.MergedConfigParser."""
import os import os
from unittest import mock
import mock
import pytest import pytest
from flake8.options import config from flake8.options import config

View file

@ -1,7 +1,7 @@
"""Unit tests for flake8.options.manager.Option.""" """Unit tests for flake8.options.manager.Option."""
import functools import functools
from unittest import mock
import mock
import pytest import pytest
from flake8.options import manager from flake8.options import manager

View file

@ -1,8 +1,8 @@
"""Unit tests for flake.options.manager.OptionManager.""" """Unit tests for flake.options.manager.OptionManager."""
import argparse import argparse
import os import os
from unittest import mock
import mock
import pytest import pytest
from flake8 import utils from flake8 import utils

View file

@ -1,7 +1,7 @@
"""Tests for flake8.plugins.manager.Plugin.""" """Tests for flake8.plugins.manager.Plugin."""
import argparse import argparse
from unittest import mock
import mock
import pytest import pytest
from flake8 import exceptions from flake8 import exceptions

View file

@ -1,5 +1,5 @@
"""Tests for flake8.plugins.manager.PluginManager.""" """Tests for flake8.plugins.manager.PluginManager."""
import mock from unittest import mock
from flake8._compat import importlib_metadata from flake8._compat import importlib_metadata
from flake8.plugins import manager from flake8.plugins import manager

View file

@ -1,5 +1,6 @@
"""Tests for flake8.plugins.manager.PluginTypeManager.""" """Tests for flake8.plugins.manager.PluginTypeManager."""
import mock from unittest import mock
import pytest import pytest
from flake8 import exceptions from flake8 import exceptions

View file

@ -111,8 +111,8 @@ def test_statistic_for_retrieves_more_than_one_value():
"""Show this works for more than a couple statistic values.""" """Show this works for more than a couple statistic values."""
aggregator = stats.Statistics() aggregator = stats.Statistics()
for i in range(50): for i in range(50):
aggregator.record(make_error(code='E1{:02d}'.format(i))) aggregator.record(make_error(code=f'E1{i:02d}'))
aggregator.record(make_error(code='W2{:02d}'.format(i))) aggregator.record(make_error(code=f'W2{i:02d}'))
statistics = list(aggregator.statistics_for('E')) statistics = list(aggregator.statistics_for('E'))
assert len(statistics) == 50 assert len(statistics) == 50

View file

@ -1,7 +1,7 @@
"""Tests for the flake8.style_guide.StyleGuide class.""" """Tests for the flake8.style_guide.StyleGuide class."""
import argparse import argparse
from unittest import mock
import mock
import pytest import pytest
from flake8 import statistics from flake8 import statistics

View file

@ -3,8 +3,8 @@ import io
import logging import logging
import os import os
import sys import sys
from unittest import mock
import mock
import pytest import pytest
from flake8 import exceptions from flake8 import exceptions
@ -134,7 +134,7 @@ def test_normalize_path(value, expected):
(["flake8", "pep8", "pyflakes", "mccabe"], (["flake8", "pep8", "pyflakes", "mccabe"],
["flake8", "pep8", "pyflakes", "mccabe"]), ["flake8", "pep8", "pyflakes", "mccabe"]),
(["../flake8", "../pep8", "../pyflakes", "../mccabe"], (["../flake8", "../pep8", "../pyflakes", "../mccabe"],
[os.path.abspath("../" + p) for p in RELATIVE_PATHS]), [os.path.abspath(f"../{p}") for p in RELATIVE_PATHS]),
]) ])
def test_normalize_paths(value, expected): def test_normalize_paths(value, expected):
"""Verify we normalizes a sequence of paths provided to the tool.""" """Verify we normalizes a sequence of paths provided to the tool."""
@ -242,7 +242,7 @@ def test_filenames_from_exclude_doesnt_exclude_directory_names(tmpdir):
def test_parameters_for_class_plugin(): def test_parameters_for_class_plugin():
"""Verify that we can retrieve the parameters for a class plugin.""" """Verify that we can retrieve the parameters for a class plugin."""
class FakeCheck(object): class FakeCheck:
def __init__(self, tree): def __init__(self, tree):
raise NotImplementedError raise NotImplementedError
@ -268,7 +268,7 @@ def test_parameters_for_function_plugin():
def read_diff_file(filename): def read_diff_file(filename):
"""Read the diff file in its entirety.""" """Read the diff file in its entirety."""
with open(filename, 'r') as fd: with open(filename) as fd:
content = fd.read() content = fd.read()
return content return content
@ -308,7 +308,6 @@ def test_matches_filename_for_excluding_dotfiles():
assert not utils.matches_filename('..', ('.*',), '', logger) assert not utils.matches_filename('..', ('.*',), '', logger)
@pytest.mark.xfail(sys.version_info < (3,), reason='py3+ only behaviour')
def test_stdin_get_value_crlf(): def test_stdin_get_value_crlf():
"""Ensure that stdin is normalized from crlf to lf.""" """Ensure that stdin is normalized from crlf to lf."""
stdin = io.TextIOWrapper(io.BytesIO(b'1\r\n2\r\n'), 'UTF-8') stdin = io.TextIOWrapper(io.BytesIO(b'1\r\n2\r\n'), 'UTF-8')

View file

@ -1,5 +1,6 @@
"""Tests for the flake8.style_guide.Violation class.""" """Tests for the flake8.style_guide.Violation class."""
import mock from unittest import mock
import pytest import pytest
from flake8 import style_guide from flake8 import style_guide

23
tox.ini
View file

@ -1,10 +1,9 @@
[tox] [tox]
minversion=2.3.1 minversion=2.3.1
envlist = py27,py35,py36,py37,py38,flake8,linters,docs envlist = py36,py37,py38,flake8,linters,docs
[testenv] [testenv]
deps = deps =
mock>=2.0.0
pytest!=3.0.5,!=5.2.3 pytest!=3.0.5,!=5.2.3
coverage coverage
commands = commands =
@ -14,14 +13,8 @@ commands =
# ensure 100% coverage of tests # ensure 100% coverage of tests
coverage report --fail-under 100 --include tests/* coverage report --fail-under 100 --include tests/*
[testenv:venv]
deps =
.
commands = {posargs}
# Dogfood our current master version # Dogfood our current master version
[testenv:dogfood] [testenv:dogfood]
basepython = python3
skip_install = true skip_install = true
deps = deps =
wheel wheel
@ -33,20 +26,17 @@ commands =
# Linters # Linters
[testenv:flake8] [testenv:flake8]
basepython = python3
skip_install = true skip_install = true
deps = deps =
flake8 flake8
flake8-bugbear flake8-bugbear
flake8-docstrings>=1.3.1 flake8-docstrings>=1.3.1
flake8-import-order>=0.9
flake8-typing-imports>=1.1 flake8-typing-imports>=1.1
pep8-naming pep8-naming
commands = commands =
flake8 src/flake8/ tests/ setup.py flake8 src/flake8/ tests/ setup.py
[testenv:pylint] [testenv:pylint]
basepython = python3
skip_install = true skip_install = true
deps = deps =
pyflakes pyflakes
@ -55,7 +45,6 @@ commands =
pylint src/flake8 pylint src/flake8
[testenv:doc8] [testenv:doc8]
basepython = python3
skip_install = true skip_install = true
deps = deps =
sphinx sphinx
@ -64,14 +53,12 @@ commands =
doc8 docs/source/ doc8 docs/source/
[testenv:pre-commit] [testenv:pre-commit]
basepython = python3
skip_install = true skip_install = true
deps = pre-commit deps = pre-commit
commands = commands =
pre-commit run --all-files --show-diff-on-failure pre-commit run --all-files --show-diff-on-failure
[testenv:bandit] [testenv:bandit]
basepython = python3
skip_install = true skip_install = true
deps = deps =
bandit bandit
@ -79,7 +66,6 @@ commands =
bandit -r src/flake8/ -c .bandit.yml bandit -r src/flake8/ -c .bandit.yml
[testenv:linters] [testenv:linters]
basepython = python3
skip_install = true skip_install = true
deps = deps =
{[testenv:flake8]deps} {[testenv:flake8]deps}
@ -96,7 +82,6 @@ commands =
# Documentation # Documentation
[testenv:docs] [testenv:docs]
basepython = python3
deps = deps =
-rdocs/source/requirements.txt -rdocs/source/requirements.txt
commands = commands =
@ -104,7 +89,6 @@ commands =
sphinx-build -E -W -c docs/source/ -b man docs/source/ docs/build/man sphinx-build -E -W -c docs/source/ -b man docs/source/ docs/build/man
[testenv:serve-docs] [testenv:serve-docs]
basepython = python3
skip_install = true skip_install = true
changedir = docs/build/html changedir = docs/build/html
deps = deps =
@ -112,7 +96,6 @@ commands =
python -m http.server {posargs} python -m http.server {posargs}
[testenv:readme] [testenv:readme]
basepython = python3
deps = deps =
readme_renderer readme_renderer
commands = commands =
@ -120,7 +103,6 @@ commands =
# Release tooling # Release tooling
[testenv:build] [testenv:build]
basepython = python3
skip_install = true skip_install = true
deps = deps =
wheel wheel
@ -129,7 +111,6 @@ commands =
python setup.py -q sdist bdist_wheel python setup.py -q sdist bdist_wheel
[testenv:release] [testenv:release]
basepython = python3
skip_install = true skip_install = true
deps = deps =
{[testenv:build]deps} {[testenv:build]deps}
@ -159,5 +140,3 @@ exclude =
.cache, .cache,
.eggs .eggs
max-complexity = 10 max-complexity = 10
import-order-style = google
application-import-names = flake8