diff --git a/.appveyor.yml b/.appveyor.yml
deleted file mode 100644
index 47cc8d6b..00000000
--- a/.appveyor.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-# To activate, change the Appveyor settings to use `.appveyor.yml`.
-install:
-  - python -m pip install --upgrade setuptools tox virtualenv
-
-build: off
-
-test_script:
-  - python -m tox -e py27,py36,py37,dogfood
diff --git a/.coveragerc b/.coveragerc
index 7d2dd681..d1b88068 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -30,3 +30,4 @@ exclude_lines =
     # Don't complain if non-runnable code isn't run:
     ^if __name__ == ['"]__main__['"]:$
     ^\s*if False:
+    ^\s*if TYPE_CHECKING:
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 00000000..365305da
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,33 @@
+Please read this brief portion of documentation before going any further: http://flake8.pycqa.org/en/latest/internal/contributing.html#filing-a-bug
+
+<!--
+*************************************************************************
+NOTE: flake8 is a linting framework and does not implement any checks
+
+if you are reporting a problem with a particular check, please track down
+the plugin which implements that check.
+
+some common ones:
+- F###: https://github.com/pycqa/pyflakes
+- E###, W###: https://github.com/pycqa/pycodestyle
+*************************************************************************
+-->
+
+
+*Please describe how you installed Flake8*
+
+Example:
+
+```
+$ pip install --user flake8
+$ brew install flake8
+# etc.
+```
+
+**Note**: Some *nix distributions patch Flake8 arbitrarily to accommodate incompatible software versions. If you're on one of those distributions, your issue may be closed and you will be asked to open an issue with your distribution package maintainers instead.
+
+*Please provide the exact, unmodified output of `flake8 --bug-report`*
+
+*Please describe the problem or feature*
+
+*If this is a bug report, please explain with examples (and example code) what you expected to happen and what actually happened.*
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 00000000..c9b32d7a
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,54 @@
+name: main
+
+on:
+  push:
+    branches: [main]
+    tags:
+  pull_request:
+
+jobs:
+  main:
+    strategy:
+      matrix:
+        include:
+        # linux
+        - os: ubuntu-latest
+          python: pypy-3.7
+          toxenv: py
+        - os: ubuntu-latest
+          python: 3.6
+          toxenv: py
+        - os: ubuntu-latest
+          python: 3.7
+          toxenv: py
+        - os: ubuntu-latest
+          python: 3.8
+          toxenv: py
+        - os: ubuntu-latest
+          python: 3.9
+          toxenv: py
+        - os: ubuntu-latest
+          python: '3.10.0-alpha - 3.10.999'
+          toxenv: py
+        # windows
+        - os: windows-latest
+          python: 3.6
+          toxenv: py
+        # misc
+        - os: ubuntu-latest
+          python: 3.9
+          toxenv: docs
+        - os: ubuntu-latest
+          python: 3.9
+          toxenv: linters
+        - os: ubuntu-latest
+          python: 3.9
+          toxenv: dogfood
+    runs-on: ${{ matrix.os }}
+    steps:
+    - uses: actions/checkout@v2
+    - uses: actions/setup-python@v2
+      with:
+        python-version: ${{ matrix.python }}
+    - run: python -mpip install --upgrade setuptools pip tox virtualenv
+    - run: tox -e ${{ matrix.toxenv }}
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
deleted file mode 100644
index 0ee5ccf9..00000000
--- a/.gitlab-ci.yml
+++ /dev/null
@@ -1,85 +0,0 @@
-image: python
-
-stages:
-- test
-- build
-- release
-
-before_script:
-- pip install pip --upgrade
-- pip install -r dev-requirements.txt
-
-after_script:
-- pip install codecov
-- codecov --token=7d117e6b-aab6-4283-ab19-166dafc38cf5
-
-pypy2:
-  image: pypy:2.7-7.2.0
-  stage: test
-  script: tox -e pypy
-
-pypy3:
-  image: pypy:3.6-7.2.0
-  stage: test
-  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:
-  image: python:3.6
-  stage: test
-  script: tox -e py36
-
-python37:
-  image: python:3.7
-  stage: test
-  script: tox -e py37
-
-python38:
-  image: python:3.8
-  stage: test
-  script: tox -e py38
-
-linters:
-  image: python:3.7
-  stage: test
-  script: tox -e linters
-
-pre-commit:
-  image: python:3.7
-  stage: test
-  script: tox -e pre-commit
-
-docs:
-  stage: test
-  script: tox -e docs
-
-dogfood:
-  image: python:3.7
-  stage: test
-  script: tox -e dogfood
-
-build:
-  stage: build
-  script: tox -e build
-  artifacts:
-    paths:
-    - dist/
-    expire_in: 1 month
-
-release:
-  stage: release
-  script: tox -e build
-  artifacts:
-    paths:
-    - dist
-  only:
-  - tags
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index e1b34f38..a3a97341 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,20 +1,29 @@
+exclude: ^tests/fixtures/
 repos:
 -   repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v2.3.0
+    rev: v4.0.1
     hooks:
+    -   id: check-yaml
     -   id: debug-statements
-        exclude: ^tests/fixtures/example-code/invalid-syntax.py$
     -   id: end-of-file-fixer
     -   id: trailing-whitespace
-        exclude: ^tests/fixtures/diffs/
+-   repo: https://github.com/asottile/reorder_python_imports
+    rev: v2.6.0
+    hooks:
+    -   id: reorder-python-imports
+        args: [--application-directories, '.:src', --py36-plus]
 -   repo: https://github.com/psf/black
-    rev: 20.8b1
+    rev: 21.9b0
     hooks:
     -   id: black
-        args: [--line-length=78]
-        files: ^src/
+        args: [--line-length=79]
+-   repo: https://github.com/asottile/pyupgrade
+    rev: v2.29.0
+    hooks:
+    -   id: pyupgrade
+        args: [--py36-plus]
 -   repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v0.720
+    rev: v0.910
     hooks:
     -   id: mypy
-        exclude: ^(docs/|example-plugin/|tests/fixtures)
+        exclude: ^(docs/|example-plugin/)
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 36d1a6a8..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,37 +0,0 @@
-language: python
-cache: pip
-before_script:
-- pip install --upgrade tox setuptools virtualenv
-
-# test script
-script: tox
-notifications:
-  on_success: change
-  on_failure: always
-
-matrix:
-  include:
-    - python: 2.7
-      env: TOXENV=py27
-    - python: 3.5
-      env: TOXENV=py35
-    - python: 3.6
-      env: TOXENV=py36
-    - python: 3.7
-      env: TOXENV=py37
-    - python: 3.8
-      env: TOXENV=py38
-    - python: pypy
-      env: TOXENV=pypy
-    - python: 3.7
-      env: TOXENV=readme
-    - python: 3.7
-      env: TOXENV=flake8
-    - python: 3.7
-      env: TOXENV=pylint
-    - python: 3.7
-      env: TOXENV=doc8
-    - python: 3.7
-      env: TOXENV=bandit
-    - python: 3.7
-      env: TOXENV=docs
diff --git a/README.rst b/README.rst
index a182b9bd..ddac9528 100644
--- a/README.rst
+++ b/README.rst
@@ -1,3 +1,11 @@
+.. image:: https://github.com/PyCQA/flake8/workflows/main/badge.svg
+   :target: https://github.com/PyCQA/flake8/actions?query=workflow%3Amain
+   :alt: build status
+
+.. image:: https://results.pre-commit.ci/badge/github/PyCQA/flake8/main.svg
+   :target: https://results.pre-commit.ci/latest/github/PyCQA/flake8/main
+   :alt: pre-commit.ci status
+
 ========
  Flake8
 ========
@@ -55,10 +63,10 @@ Links
 
 * `Flake8 Documentation <http://flake8.pycqa.org/en/latest/>`_
 
-* `GitLab Project <https://gitlab.com/pycqa/flake8>`_
+* `GitHub Project <https://github.com/pycqa/flake8>`_
 
 * `All (Open and Closed) Issues
-  <https://gitlab.com/pycqa/flake8/issues?scope=all&sort=updated_desc&state=all>`_
+  <https://github.com/pycqa/flake8/issues?q=is%3Aissue>`_
 
 * `Code-Quality Archives
   <https://mail.python.org/mailman/listinfo/code-quality>`_
diff --git a/codecov.yml b/codecov.yml
index f7fc0bea..475c4c57 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -1,5 +1,5 @@
 codecov:
-  branch: master
+  branch: main
   bot: null
 
 coverage:
@@ -12,7 +12,7 @@ coverage:
       default:
         server: chat.freenode.net
         channel: '##python-code-quality'
-        branches: master
+        branches: main
         threshold: 2
         message: null
 
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 8dfe578d..f6a4d4ed 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 #
 # flake8 documentation build configuration file, created by
 # sphinx-quickstart on Tue Jan 19 07:14:10 2016.
@@ -11,54 +10,54 @@
 #
 # All configuration values have a default; values that are commented out
 # serve to show the default.
-
-import sys
 import os
+import sys
 
 # 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
 # documentation root, use os.path.abspath to make it absolute, like shown here.
-#sys.path.insert(0, os.path.abspath('.'))
+# sys.path.insert(0, os.path.abspath('.'))
 
 # -- General configuration ------------------------------------------------
 
 # If your documentation needs a minimal Sphinx version, state it here.
-needs_sphinx = '1.3'
+needs_sphinx = "1.3"
 
 # Add any Sphinx extension module names here, as strings. They can be
 # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 # ones.
 extensions = [
-    'sphinx.ext.autodoc',
-    'sphinx.ext.doctest',
-    'sphinx.ext.extlinks',
-    'sphinx.ext.intersphinx',
-    'sphinx.ext.todo',
-    'sphinx.ext.coverage',
-    'sphinx.ext.viewcode',
-    'sphinx-prompt',
+    "sphinx.ext.autodoc",
+    "sphinx.ext.doctest",
+    "sphinx.ext.extlinks",
+    "sphinx.ext.intersphinx",
+    "sphinx.ext.todo",
+    "sphinx.ext.coverage",
+    "sphinx.ext.viewcode",
+    "sphinx-prompt",
 ]
 
 # Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ["_templates"]
 
 # The suffix(es) of source filenames.
 # You can specify multiple suffix as a list of string:
 # source_suffix = ['.rst', '.md']
-source_suffix = '.rst'
+source_suffix = ".rst"
 
 # The encoding of source files.
-#source_encoding = 'utf-8-sig'
+# source_encoding = 'utf-8-sig'
 
 # The master toctree document.
-master_doc = 'index'
+master_doc = "index"
 
 # General information about the project.
-project = u'flake8'
-copyright = u'2016, Ian Stapleton Cordasco'
-author = u'Ian Stapleton Cordasco'
+project = "flake8"
+copyright = "2016, Ian Stapleton Cordasco"
+author = "Ian Stapleton Cordasco"
 
 import flake8
+
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
 # built documents.
@@ -81,9 +80,9 @@
 
 # There are two options for replacing |today|: either, you set today to some
 # non-false value, then it is used:
-#today = ''
+# today = ''
 # Else, today_fmt is used as the format for a strftime call.
-#today_fmt = '%B %d, %Y'
+# today_fmt = '%B %d, %Y'
 
 # List of patterns, relative to source directory, that match files and
 # directories to ignore when looking for source files.
@@ -91,27 +90,27 @@
 
 # The reST default role (used for this markup: `text`) to use for all
 # documents.
-#default_role = None
+# default_role = None
 
 # If true, '()' will be appended to :func: etc. cross-reference text.
-#add_function_parentheses = True
+# add_function_parentheses = True
 
 # If true, the current module name will be prepended to all description
 # unit titles (such as .. function::).
-#add_module_names = True
+# add_module_names = True
 
 # If true, sectionauthor and moduleauthor directives will be shown in the
 # output. They are ignored by default.
-#show_authors = False
+# show_authors = False
 
 # The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = "sphinx"
 
 # A list of ignored prefixes for module index sorting.
-#modindex_common_prefix = []
+# modindex_common_prefix = []
 
 # If true, keep warnings as "system message" paragraphs in the built documents.
-#keep_warnings = False
+# keep_warnings = False
 
 # If true, `todo` and `todoList` produce output, else they produce nothing.
 todo_include_todos = True
@@ -121,31 +120,31 @@
 
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
-html_theme = 'sphinx_rtd_theme'
+html_theme = "sphinx_rtd_theme"
 
 # Theme options are theme-specific and customize the look and feel of a theme
 # further.  For a list of options available for each theme, see the
 # documentation.
-#html_theme_options = {}
+# html_theme_options = {}
 
 # Add any paths that contain custom themes here, relative to this directory.
-#html_theme_path = []
+# html_theme_path = []
 
 # The name for this set of Sphinx documents.  If None, it defaults to
 # "<project> v<release> documentation".
-#html_title = None
+# html_title = None
 
 # A shorter title for the navigation bar.  Default is the same as html_title.
-#html_short_title = None
+# html_short_title = None
 
 # The name of an image file (relative to this directory) to place at the top
 # of the sidebar.
-#html_logo = None
+# html_logo = None
 
 # The name of an image file (within the static path) to use as favicon of the
 # docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
 # pixels large.
-#html_favicon = None
+# html_favicon = None
 
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
@@ -155,109 +154,111 @@
 # Add any extra paths that contain custom files (such as robots.txt or
 # .htaccess) here, relative to this directory. These files are copied
 # directly to the root of the documentation.
-#html_extra_path = []
+# html_extra_path = []
 
 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 # using the given strftime format.
-#html_last_updated_fmt = '%b %d, %Y'
+# html_last_updated_fmt = '%b %d, %Y'
 
 # If true, SmartyPants will be used to convert quotes and dashes to
 # typographically correct entities.
-#html_use_smartypants = True
+# html_use_smartypants = True
 
 # Custom sidebar templates, maps document names to template names.
-#html_sidebars = {}
+# html_sidebars = {}
 
 # Additional templates that should be rendered to pages, maps page names to
 # template names.
-#html_additional_pages = {}
+# html_additional_pages = {}
 
 # If false, no module index is generated.
-#html_domain_indices = True
+# html_domain_indices = True
 
 # If false, no index is generated.
-#html_use_index = True
+# html_use_index = True
 
 # If true, the index is split into individual pages for each letter.
-#html_split_index = False
+# html_split_index = False
 
 # If true, links to the reST sources are added to the pages.
-#html_show_sourcelink = True
+# html_show_sourcelink = True
 
 # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-#html_show_sphinx = True
+# html_show_sphinx = True
 
 # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-#html_show_copyright = True
+# html_show_copyright = True
 
 # If true, an OpenSearch description file will be output, and all pages will
 # contain a <link> tag referring to it.  The value of this option must be the
 # base URL from which the finished HTML is served.
-#html_use_opensearch = ''
+# html_use_opensearch = ''
 
 # This is the file name suffix for HTML files (e.g. ".xhtml").
-#html_file_suffix = None
+# html_file_suffix = None
 
 # Language to be used for generating the HTML full-text search index.
 # Sphinx supports the following languages:
 #   'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
 #   'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
-#html_search_language = 'en'
+# html_search_language = 'en'
 
 # A dictionary with options for the search language support, empty by default.
 # Now only 'ja' uses this config value
-#html_search_options = {'type': 'default'}
+# html_search_options = {'type': 'default'}
 
 # The name of a javascript file (relative to the configuration directory) that
 # implements a search results scorer. If empty, the default will be used.
-#html_search_scorer = 'scorer.js'
+# html_search_scorer = 'scorer.js'
 
 # Output file base name for HTML help builder.
-htmlhelp_basename = 'flake8doc'
+htmlhelp_basename = "flake8doc"
 
 # -- Options for LaTeX output ---------------------------------------------
 
 latex_elements = {
-# The paper size ('letterpaper' or 'a4paper').
-#'papersize': 'letterpaper',
-
-# The font size ('10pt', '11pt' or '12pt').
-#'pointsize': '10pt',
-
-# Additional stuff for the LaTeX preamble.
-#'preamble': '',
-
-# Latex figure (float) alignment
-#'figure_align': 'htbp',
+    # The paper size ('letterpaper' or 'a4paper').
+    #'papersize': 'letterpaper',
+    # The font size ('10pt', '11pt' or '12pt').
+    #'pointsize': '10pt',
+    # Additional stuff for the LaTeX preamble.
+    #'preamble': '',
+    # Latex figure (float) alignment
+    #'figure_align': 'htbp',
 }
 
 # Grouping the document tree into LaTeX files. List of tuples
 # (source start file, target name, title,
 #  author, documentclass [howto, manual, or own class]).
 latex_documents = [
-    (master_doc, 'flake8.tex', u'flake8 Documentation',
-     u'Ian Stapleton Cordasco', 'manual'),
+    (
+        master_doc,
+        "flake8.tex",
+        "flake8 Documentation",
+        "Ian Stapleton Cordasco",
+        "manual",
+    ),
 ]
 
 # The name of an image file (relative to this directory) to place at the top of
 # the title page.
-#latex_logo = None
+# latex_logo = None
 
 # For "manual" documents, if this is true, then toplevel headings are parts,
 # not chapters.
-#latex_use_parts = False
+# latex_use_parts = False
 
 # If true, show page references after internal links.
-#latex_show_pagerefs = False
+# latex_show_pagerefs = False
 
 # If true, show URL addresses after external links.
-#latex_show_urls = False
+# latex_show_urls = False
 
 # Documents to append as an appendix to all manuals.
-#latex_appendices = []
+# latex_appendices = []
 
 # If false, no module index is generated.
-#latex_domain_indices = True
+# latex_domain_indices = True
 
 
 # -- Options for manual page output ---------------------------------------
@@ -265,12 +266,11 @@
 # One entry per manual page. List of tuples
 # (source start file, name, description, authors, manual section).
 man_pages = [
-    ('manpage', 'flake8', u'Flake8 Command Line Documentation',
-     [author], 1)
+    ("manpage", "flake8", "Flake8 Command Line Documentation", [author], 1)
 ]
 
 # If true, show URL addresses after external links.
-#man_show_urls = False
+# man_show_urls = False
 
 
 # -- Options for Texinfo output -------------------------------------------
@@ -279,26 +279,32 @@
 # (source start file, target name, title, author,
 #  dir menu entry, description, category)
 texinfo_documents = [
-    ('index', 'Flake8', u'Flake8 Documentation', u'Tarek Ziade',
-     'Flake8', 'Code checking using pycodestyle, pyflakes and mccabe',
-     'Miscellaneous'),
+    (
+        "index",
+        "Flake8",
+        "Flake8 Documentation",
+        "Tarek Ziade",
+        "Flake8",
+        "Code checking using pycodestyle, pyflakes and mccabe",
+        "Miscellaneous",
+    ),
 ]
 
 # Documents to append as an appendix to all manuals.
-#texinfo_appendices = []
+# texinfo_appendices = []
 
 # If false, no module index is generated.
-#texinfo_domain_indices = True
+# texinfo_domain_indices = True
 
 # How to display URL addresses: 'footnote', 'no', or 'inline'.
-#texinfo_show_urls = 'footnote'
+# texinfo_show_urls = 'footnote'
 
 # If true, do not generate a @detailmenu in the "Top" node's menu.
-#texinfo_no_detailmenu = False
+# texinfo_no_detailmenu = False
 
 
 # Example configuration for intersphinx: refer to the Python standard library.
-intersphinx_mapping = {'python': ('https://docs.python.org/3/', None)}
+intersphinx_mapping = {"python": ("https://docs.python.org/3/", None)}
 
 extlinks = {
     "issue": ("https://github.com/pycqa/flake8/issues/%s", "#"),
diff --git a/docs/source/faq.rst b/docs/source/faq.rst
index 3f2bc043..e81768f1 100644
--- a/docs/source/faq.rst
+++ b/docs/source/faq.rst
@@ -23,7 +23,7 @@ What is the next version of Flake8?
 ===================================
 
 In general we try to use milestones to indicate this. If the last release
-on PyPI is 3.1.5 and you see a milestone for 3.2.0 in GitLab, there's a
+on PyPI is 3.1.5 and you see a milestone for 3.2.0 in GitHub, there's a
 good chance that 3.2.0 is the next release.
 
 
diff --git a/docs/source/internal/contributing.rst b/docs/source/internal/contributing.rst
index d5c8d8d2..26cc029c 100644
--- a/docs/source/internal/contributing.rst
+++ b/docs/source/internal/contributing.rst
@@ -51,7 +51,7 @@ Filing a Bug
 ============
 
 When filing a bug against |Flake8|, please fill out the issue template as it
-is provided to you by `GitLab`_. If your bug is in reference to one of the
+is provided to you by `GitHub`_. If your bug is in reference to one of the
 checks that |Flake8| reports by default, please do not report them to |Flake8|
 unless |Flake8| is doing something to prevent the check from running or you
 have some reason to believe |Flake8| is inhibiting the effectiveness of the
@@ -131,7 +131,7 @@ documentation generation and refresh the documentation you're working on.
 Contributing Code
 =================
 
-|Flake8| development happens on `GitLab`_. Code contributions should be
+|Flake8| development happens on `GitHub`_. Code contributions should be
 submitted there.
 
 Merge requests should:
@@ -202,8 +202,8 @@ delivered.
 .. _tox:
     https://tox.readthedocs.io/
 
-.. _GitLab:
-    https://gitlab.com/pycqa/flake8
+.. _GitHub:
+    https://github.com/pycqa/flake8
 
 .. _pycodestyle:
     https://github.com/pycqa/pycodestyle
diff --git a/docs/source/internal/option_handling.rst b/docs/source/internal/option_handling.rst
index e9098bdd..00c688fa 100644
--- a/docs/source/internal/option_handling.rst
+++ b/docs/source/internal/option_handling.rst
@@ -129,13 +129,8 @@ In |Flake8| 2, configuration file discovery and management was handled by
 pep8.  In pep8's 1.6 release series, it drastically broke how discovery and
 merging worked (as a result of trying to improve it). To avoid a dependency
 breaking |Flake8| again in the future, we have created our own discovery and
-management.
-As part of managing this ourselves, we decided to change management/discovery
-for 3.0.0. We have done the following:
-
-- User files (files stored in a user's home directory or in the XDG directory
-  inside their home directory) are the first files read. For example, if the
-  user has a ``~/.flake8`` file, we will read that first.
+management in 3.0.0. In 4.0.0 we have once again changed how this works and we
+removed support for user-level config files.
 
 - Project files (files stored in the current directory) are read next and
   merged on top of the user file. In other words, configuration in project
@@ -157,7 +152,7 @@ To facilitate the configuration file management, we've taken a different
 approach to discovery and management of files than pep8. In pep8 1.5, 1.6, and
 1.7 configuration discovery and management was centralized in `66 lines of
 very terse python`_ which was confusing and not very explicit. The terseness
-of this function (|Flake8|'s authors believe) caused the confusion and
+of this function (|Flake8| 3.0.0's authors believe) caused the confusion and
 problems with pep8's 1.6 series. As such, |Flake8| has separated out
 discovery, management, and merging into a module to make reasoning about each
 of these pieces easier and more explicit (as well as easier to test).
@@ -176,32 +171,28 @@ to parse those configuration files.
 .. note:: ``local_config_files`` also filters out non-existent files.
 
 Configuration file merging and managemnt is controlled by the
-:class:`~flake8.options.config.MergedConfigParser`. This requires the instance
+:class:`~flake8.options.config.ConfigParser`. This requires the instance
 of :class:`~flake8.options.manager.OptionManager` that the program is using,
 the list of appended config files, and the list of extra arguments. This
 object is currently the sole user of the
 :class:`~flake8.options.config.ConfigFileFinder` object. It appropriately
 initializes the object and uses it in each of
 
-- :meth:`~flake8.options.config.MergedConfigParser.parse_cli_config`
-- :meth:`~flake8.options.config.MergedConfigParser.parse_local_config`
-- :meth:`~flake8.options.config.MergedConfigParser.parse_user_config`
+- :meth:`~flake8.options.config.ConfigParser.parse_cli_config`
+- :meth:`~flake8.options.config.ConfigParser.parse_local_config`
 
-Finally,
-:meth:`~flake8.options.config.MergedConfigParser.merge_user_and_local_config`
-takes the user and local configuration files that are parsed by
-:meth:`~flake8.options.config.MergedConfigParser.parse_local_config` and
-:meth:`~flake8.options.config.MergedConfigParser.parse_user_config`. The
-main usage of the ``MergedConfigParser`` is in
+Finally, :meth:`~flake8.options.config.ConfigParser.parse` returns the
+appropriate configuration dictionary for this execution of |Flake8|. The
+main usage of the ``ConfigParser`` is in
 :func:`~flake8.options.aggregator.aggregate_options`.
 
 Aggregating Configuration File and Command Line Arguments
 ---------------------------------------------------------
 
 :func:`~flake8.options.aggregator.aggregate_options` accepts an instance of
-:class:`~flake8.options.maanger.OptionManager` and does the work to parse the
+:class:`~flake8.options.manager.OptionManager` and does the work to parse the
 command-line arguments passed by the user necessary for creating an instance
-of :class:`~flake8.options.config.MergedConfigParser`.
+of :class:`~flake8.options.config.ConfigParser`.
 
 After parsing the configuration file, we determine the default ignore list. We
 use the defaults from the OptionManager and update those with the parsed
@@ -229,6 +220,6 @@ API Documentation
     :members:
     :special-members:
 
-.. autoclass:: flake8.options.config.MergedConfigParser
+.. autoclass:: flake8.options.config.ConfigParser
     :members:
     :special-members:
diff --git a/docs/source/internal/releases.rst b/docs/source/internal/releases.rst
index 0ed0ac93..1107c98b 100644
--- a/docs/source/internal/releases.rst
+++ b/docs/source/internal/releases.rst
@@ -30,7 +30,7 @@ Historically, |Flake8| has generated major releases for:
 
 - Large scale refactoring (2.0, 3.0)
 
-- Subtly breaking CLI changes (3.0)
+- Subtly breaking CLI changes (3.0, 4.0)
 
 - Breaking changes to its plugin interface (3.0)
 
@@ -81,13 +81,11 @@ for users.
 
 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.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``)
 
diff --git a/docs/source/internal/start-to-finish.rst b/docs/source/internal/start-to-finish.rst
index faed9bd5..5e310835 100644
--- a/docs/source/internal/start-to-finish.rst
+++ b/docs/source/internal/start-to-finish.rst
@@ -47,21 +47,6 @@ both cases, however, you end up in :func:`flake8.main.cli.main`. This is the
 primary way that users will end up starting Flake8. This function creates an
 instance of |Application|.
 
-via Setuptools
---------------
-
-If you're invoking |Flake8| from your ``setup.py`` then you actually end up in
-:meth:`flake8.main.setuptools_command.Flake8.run`. This then collects the
-files that are included in the package information and creates an instance of
-|Application|.
-
-via Git or Mercurial
---------------------
-
-In both cases, they call their respective ``hook`` functions which create
-instances of |Application|.
-
-
 Application Logic
 =================
 
diff --git a/docs/source/internal/writing-code.rst b/docs/source/internal/writing-code.rst
index 9d260b72..daf1d572 100644
--- a/docs/source/internal/writing-code.rst
+++ b/docs/source/internal/writing-code.rst
@@ -59,7 +59,6 @@ accepts as well as what it returns.
             filepaths = list(copy_indexed_files_to(tempdir, lazy))
             app.initialize(['.'])
             app.options.exclude = update_excludes(app.options.exclude, tempdir)
-            app.options._running_from_vcs = True
             app.run_checks(filepaths)
 
         app.report_errors()
@@ -176,11 +175,7 @@ across multiple lines, insert a new-line after the opening parenthesis, e.g.,
             statistic = next(stats_for_error_code)
             count = statistic.count
             count += sum(stat.count for stat in stats_for_error_code)
-            self._write('{count:<5} {error_code} {message}'.format(
-                count=count,
-                error_code=error_code,
-                message=statistic.message,
-            ))
+            self._write(f'{count:<5} {error_code} {statistic.message}')
 
 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
diff --git a/docs/source/manpage.rst b/docs/source/manpage.rst
index 9c002b9a..9636b1f4 100644
--- a/docs/source/manpage.rst
+++ b/docs/source/manpage.rst
@@ -70,9 +70,6 @@ All options available as of Flake8 3.1.0::
                           Enable plugins and extensions that are otherwise
                           disabled by default
     --exit-zero           Exit with status code "0" even if there are errors.
-    --install-hook=INSTALL_HOOK
-                          Install a hook that is run prior to a commit for the
-                          supported version control system.
     -j JOBS, --jobs=JOBS  Number of subprocesses to use to run checks in
                           parallel. This is ignored on Windows. The default,
                           "auto", will auto-detect the number of processors
@@ -147,4 +144,4 @@ McCabe: https://github.com/pycqa/mccabe
 BUGS
 ====
 
-Please report all bugs to https://gitlab.com/pycqa/flake8
+Please report all bugs to https://github.com/pycqa/flake8
diff --git a/docs/source/plugin-development/registering-plugins.rst b/docs/source/plugin-development/registering-plugins.rst
index 34b368ad..b9e87fb7 100644
--- a/docs/source/plugin-development/registering-plugins.rst
+++ b/docs/source/plugin-development/registering-plugins.rst
@@ -40,7 +40,7 @@ like:
         description="our extension to flake8",
         author="Me",
         author_email="example@example.com",
-        url="https://gitlab.com/me/flake8_example",
+        url="https://github.com/me/flake8_example",
         packages=[
             "flake8_example",
         ],
@@ -56,7 +56,6 @@ like:
             "Intended Audience :: Developers",
             "License :: OSI Approved :: MIT License",
             "Programming Language :: Python",
-            "Programming Language :: Python :: 2",
             "Programming Language :: Python :: 3",
             "Topic :: Software Development :: Libraries :: Python Modules",
             "Topic :: Software Development :: Quality Assurance",
diff --git a/docs/source/release-notes/2.3.0.rst b/docs/source/release-notes/2.3.0.rst
index 120efa9f..341d06ef 100644
--- a/docs/source/release-notes/2.3.0.rst
+++ b/docs/source/release-notes/2.3.0.rst
@@ -5,6 +5,4 @@
   instead of ``stdout``.
 
 - **Bug** Fix interleaving of output while using multiprocessing
-  (`GitLab#17`_)
-
-.. _GitLab#17: https://gitlab.com/pycqa/flake8/issues/17
+  (:issue:`60`)
diff --git a/docs/source/release-notes/2.4.0.rst b/docs/source/release-notes/2.4.0.rst
index 94cba9a6..f8860dc4 100644
--- a/docs/source/release-notes/2.4.0.rst
+++ b/docs/source/release-notes/2.4.0.rst
@@ -2,32 +2,25 @@
 ------------------
 
 - **Bug** Print filenames when using multiprocessing and ``-q`` option.
-  (`GitLab#31`_)
+  (:issue:`74`)
 
 - **Bug** Put upper cap on dependencies. The caps for 2.4.0 are:
 
-  - ``pep8 < 1.6`` (Related to `GitLab#35`_)
+  - ``pep8 < 1.6`` (Related to :issue:`78`)
 
   - ``mccabe < 0.4``
 
   - ``pyflakes < 0.9``
 
-  See also `GitLab#32`_
+  See also :issue:`75`
 
 - **Bug** Files excluded in a config file were not being excluded when flake8
-  was run from a git hook. (`GitHub#2`_)
+  was run from a git hook. (:issue:`2`)
 
 - **Improvement** Print warnings for users who are providing mutually
-  exclusive options to flake8. (`GitLab#8`_, `GitLab!18`_)
+  exclusive options to flake8. (:issue:`51`, :issue:`386`)
 
 - **Feature** Allow git hook configuration to live in ``.git/config``.
-  See the updated `VCS hooks docs`_ for more details. (`GitLab!20`_)
-
-.. _GitHub#2: https://github.com/pycqa/flake8/pull/2
-.. _GitLab#8: https://gitlab.com/pycqa/flake8/issues/8
-.. _GitLab#31: https://gitlab.com/pycqa/flake8/issues/31
-.. _GitLab#32: https://gitlab.com/pycqa/flake8/issues/32
-.. _GitLab#35: https://gitlab.com/pycqa/flake8/issues/35
-.. _GitLab!18: https://gitlab.com/pycqa/flake8/merge_requests/18
-.. _GitLab!20: https://gitlab.com/pycqa/flake8/merge_requests/20
+  See the updated `VCS hooks docs`_ for more details. (:issue:`387`)
+
 .. _VCS hooks docs: https://flake8.readthedocs.io/en/latest/user/using-hooks.html
diff --git a/docs/source/release-notes/2.4.1.rst b/docs/source/release-notes/2.4.1.rst
index 3448bc4d..0e74929c 100644
--- a/docs/source/release-notes/2.4.1.rst
+++ b/docs/source/release-notes/2.4.1.rst
@@ -2,11 +2,8 @@
 ------------------
 
 - **Bug** Do not raise a ``SystemError`` unless there were errors in the
-  setuptools command. (`GitLab#39`_, `GitLab!23`_)
+  setuptools command. (:issue:`82`, :issue:`390`)
 
 - **Bug** Do not verify dependencies of extensions loaded via entry-points.
 
 - **Improvement** Blacklist versions of pep8 we know are broken
-
-.. _GitLab#39: https://gitlab.com/pycqa/flake8/issues/39
-.. _GitLab!23: https://gitlab.com/pycqa/flake8/merge_requests/23
diff --git a/docs/source/release-notes/2.5.0.rst b/docs/source/release-notes/2.5.0.rst
index 1558fcf7..dafd4164 100644
--- a/docs/source/release-notes/2.5.0.rst
+++ b/docs/source/release-notes/2.5.0.rst
@@ -4,22 +4,15 @@
 - **Improvement** Raise cap on PyFlakes for Python 3.5 support
 
 - **Improvement** Avoid deprecation warnings when loading extensions
-  (`GitLab#59`_, `GitLab#90`_)
+  (:issue:`102`, :issue:`445`)
 
 - **Improvement** Separate logic to enable "off-by-default" extensions
-  (`GitLab#67`_)
+  (:issue:`110`)
 
-- **Bug** Properly parse options to setuptools Flake8 command (`GitLab!41`_)
+- **Bug** Properly parse options to setuptools Flake8 command (:issue:`408`)
 
 - **Bug** Fix exceptions when output on stdout is truncated before Flake8
-  finishes writing the output (`GitLab#69`_)
+  finishes writing the output (:issue:`112`)
 
 - **Bug** Fix error on OS X where Flake8 can no longer acquire or create new
-  semaphores (`GitLab#74`_)
-
-.. _GitLab!41: https://gitlab.com/pycqa/flake8/merge_requests/41
-.. _GitLab#59: https://gitlab.com/pycqa/flake8/issues/59
-.. _GitLab#67: https://gitlab.com/pycqa/flake8/issues/67
-.. _GitLab#69: https://gitlab.com/pycqa/flake8/issues/69
-.. _GitLab#74: https://gitlab.com/pycqa/flake8/issues/74
-.. _GitLab#90: https://gitlab.com/pycqa/flake8/issues/90
+  semaphores (:issue:`117`)
diff --git a/docs/source/release-notes/2.5.1.rst b/docs/source/release-notes/2.5.1.rst
index 8a114c8e..cd73315f 100644
--- a/docs/source/release-notes/2.5.1.rst
+++ b/docs/source/release-notes/2.5.1.rst
@@ -2,12 +2,8 @@
 ------------------
 
 - **Bug** Properly look for ``.flake8`` in current working directory
-  (`GitLab#103`_)
+  (:issue:`458`)
 
 - **Bug** Monkey-patch ``pep8.stdin_get_value`` to cache the actual value in
   stdin. This helps plugins relying on the function when run with
-  multiprocessing. (`GitLab#105`_, `GitLab#107`_)
-
-.. _GitLab#103: https://gitlab.com/pycqa/flake8/issues/103
-.. _GitLab#105: https://gitlab.com/pycqa/flake8/issues/105
-.. _GitLab#107: https://gitlab.com/pycqa/flake8/issues/107
+  multiprocessing. (:issue:`460`, :issue:`462`)
diff --git a/docs/source/release-notes/3.0.1.rst b/docs/source/release-notes/3.0.1.rst
index 0092dc89..39b27be4 100644
--- a/docs/source/release-notes/3.0.1.rst
+++ b/docs/source/release-notes/3.0.1.rst
@@ -2,18 +2,11 @@
 -------------------
 
 - Fix regression in handling of ``# noqa`` for multiline strings.
-  (See also `GitLab#177`_)
+  (See also :issue:`1024`)
 
 - Fix regression in handling of ``--output-file`` when not also using
-  ``--verbose``. (See also `GitLab#180`_)
+  ``--verbose``. (See also :issue:`1026`)
 
-- Fix regression in handling of ``--quiet``. (See also `GitLab#180`_)
+- Fix regression in handling of ``--quiet``. (See also :issue:`1026`)
 
-- Fix regression in handling of ``--statistics``. (See also `GitLab#180`_)
-
-
-.. links
-.. _GitLab#177:
-    https://gitlab.com/pycqa/flake8/issues/177
-.. _GitLab#180:
-    https://gitlab.com/pycqa/flake8/issues/180
+- Fix regression in handling of ``--statistics``. (See also :issue:`1026`)
diff --git a/docs/source/release-notes/3.0.2.rst b/docs/source/release-notes/3.0.2.rst
index d39cf558..3cf588ac 100644
--- a/docs/source/release-notes/3.0.2.rst
+++ b/docs/source/release-notes/3.0.2.rst
@@ -1,17 +1,10 @@
 3.0.2 -- 2016-07-26
 -------------------
 
-- Fix local config file discovery.  (See also `GitLab#181`_)
+- Fix local config file discovery.  (See also :issue:`528`)
 
 - Fix indexing of column numbers. We accidentally were starting column indices
   at 0 instead of 1.
 
 - Fix regression in handling of errors like E402 that rely on a combination of
-  attributes. (See also `GitLab#186`_)
-
-
-.. links
-.. _GitLab#181:
-    https://gitlab.com/pycqa/flake8/issues/181
-.. _GitLab#186:
-    https://gitlab.com/pycqa/flake8/issues/186
+  attributes. (See also :issue:`530`)
diff --git a/docs/source/release-notes/3.0.3.rst b/docs/source/release-notes/3.0.3.rst
index 5eed69d9..74ee4c09 100644
--- a/docs/source/release-notes/3.0.3.rst
+++ b/docs/source/release-notes/3.0.3.rst
@@ -8,37 +8,25 @@
   This raises an informative error when a plugin fails to load because its
   entry_point is not callable, which can happen with a plugin which is buggy or
   not updated for the current version of flake8. This is nicer than raising a
-  `PicklingError` about failing to pickle a module (See also `GitLab#164`_)
+  `PicklingError` about failing to pickle a module (See also :issue:`1014`)
 
 - Fix ``# noqa`` comments followed by a ``:`` and explanation broken by
-  3.0.0 (See also `GitLab#178`_)
+  3.0.0 (See also :issue:`1025`)
 
 - Always open our output file in append mode so we do not overwrite log
-  messages. (See also `GitLab#193`_)
+  messages. (See also :issue:`535`)
 
 - When normalizing path values read from configuration, keep in context the
   directory where the configuration was found so that relative paths work.
-  (See also `GitLab#194`_)
+  (See also :issue:`1036`)
 
 - Fix issue where users were unable to ignore plugin errors that were on
-  by default. (See also `GitLab#195`_)
+  by default. (See also :issue:`1037`)
 
 - Fix our legacy API StyleGuide's ``init_report`` method to actually override
-  the previous formatter. (See also `GitLab#200`_)
+  the previous formatter. (See also :issue:`136`)
 
 
 .. links
-.. _GitLab#164:
-    https://gitlab.com/pycqa/flake8/issues/164
-.. _GitLab#178:
-    https://gitlab.com/pycqa/flake8/issues/178
-.. _GitLab#193:
-    https://gitlab.com/pycqa/flake8/issues/193
-.. _GitLab#194:
-    https://gitlab.com/pycqa/flake8/issues/193
-.. _GitLab#195:
-    https://gitlab.com/pycqa/flake8/issues/195
-.. _GitLab#200:
-    https://gitlab.com/pycqa/flake8/issues/200
 .. _this Python bug report:
     https://bugs.python.org/issue27649
diff --git a/docs/source/release-notes/3.0.4.rst b/docs/source/release-notes/3.0.4.rst
index bdea2798..3ad92b17 100644
--- a/docs/source/release-notes/3.0.4.rst
+++ b/docs/source/release-notes/3.0.4.rst
@@ -2,19 +2,10 @@
 -------------------
 
 - Side-step a Pickling Error when using Flake8 with multiprocessing on Unix
-  systems. (See also `GitLab#164`_)
+  systems. (See also :issue:`1014`)
 
 - Fix an Attribute Error raised when dealing with Invalid Syntax. (See also
-  `GitLab#203`_)
+  :issue:`539`)
 
 - Fix an unhandled Syntax Error when tokenizing files. (See also
-  `GitLab#205`_)
-
-
-.. links
-.. _GitLab#164:
-    https://gitlab.com/pycqa/flake8/issues/164
-.. _GitLab#203:
-    https://gitlab.com/pycqa/flake8/issues/203
-.. _GitLab#205:
-    https://gitlab.com/pycqa/flake8/issues/205
+  :issue:`540`)
diff --git a/docs/source/release-notes/3.1.0.rst b/docs/source/release-notes/3.1.0.rst
index a51b3097..c3ca92d1 100644
--- a/docs/source/release-notes/3.1.0.rst
+++ b/docs/source/release-notes/3.1.0.rst
@@ -1,44 +1,44 @@
 3.1.0 -- 2016-11-14
 -------------------
 
-You can view the `3.1.0 milestone`_ on GitLab for more details.
+You can view the `3.1.0 milestone`_ on GitHub for more details.
 
 - Add ``--bug-report`` flag to make issue reporters' lives easier.
 
 - Collect configuration files from the current directory when using our Git
-  hook. (See also `GitLab#210`_, `GitLab#218`_, `GitLab#223`_)
+  hook. (See also :issue:`142`, :issue:`150`, :issue:`155`)
 
 - Avoid unhandled exceptions when dealing with SyntaxErrors. (See also
-  `GitLab#214`_, `GitLab#238`_)
+  :issue:`146`, :issue:`170`)
 
-- Exit early if the value for ``--diff`` is empty. (See also `GitLab#226`_)
+- Exit early if the value for ``--diff`` is empty. (See also :issue:`158`)
 
-- Handle empty ``--stdin-display-name`` values. (See also `GitLab#235`_)
+- Handle empty ``--stdin-display-name`` values. (See also :issue:`167`)
 
 - Properly report the column number of Syntax Errors. We were assuming that
   all reports of column numbers were 0-indexed, however, SyntaxErrors report
   the column number as 1-indexed. This caused us to report a column number
   that was 1 past the actual position. Further, when combined with
   SyntaxErrors that occur at a newline, this caused the position to be
-  visually off by two. (See also `GitLab#237`_)
+  visually off by two. (See also :issue:`169`)
 
 - Fix the behaviour of ``--enable-extensions``. Previously, items specified
   here were still ignored due to the fact that the off-by-default extension
-  codes were being left in the ``ignore`` list. (See also `GitLab#239`_)
+  codes were being left in the ``ignore`` list. (See also :issue:`171`)
 
 - Fix problems around ``--select`` and ``--ignore`` behaviour that prevented
   codes that were neither explicitly selected nor explicitly ignored from
-  being reported. (See also `GitLab#242`_)
+  being reported. (See also :issue:`174`)
 
 - Truly be quiet when the user specifies ``-q`` one or more times. Previously,
   we were showing the if the user specified ``-q`` and ``--show-source``. We
-  have fixed this bug. (See also `GitLab#245`_)
+  have fixed this bug. (See also :issue:`177`)
 
 - Add new File Processor attribute, ``previous_unindented_logical_line`` to
-  accommodate pycodestyle 2.1.0. (See also `GitLab#246`_)
+  accommodate pycodestyle 2.1.0. (See also :issue:`178`)
 
-- When something goes wrong, exit non-zero. (See also `GitLab#248`_,
-  `GitLab#209`_)
+- When something goes wrong, exit non-zero. (See also :issue:`180`,
+  :issue:`141`)
 
 - Add ``--tee`` as an option to allow use of ``--output-file`` and printing to
   standard out.
@@ -49,32 +49,4 @@ You can view the `3.1.0 milestone`_ on GitLab for more details.
 
 .. links
 .. _3.1.0 milestone:
-    https://gitlab.com/pycqa/flake8/milestones/12
-.. _GitLab#209:
-    https://gitlab.com/pycqa/flake8/issues/209
-.. _GitLab#210:
-    https://gitlab.com/pycqa/flake8/issues/210
-.. _GitLab#214:
-    https://gitlab.com/pycqa/flake8/issues/214
-.. _GitLab#218:
-    https://gitlab.com/pycqa/flake8/issues/218
-.. _GitLab#223:
-    https://gitlab.com/pycqa/flake8/issues/223
-.. _GitLab#226:
-    https://gitlab.com/pycqa/flake8/issues/226
-.. _GitLab#235:
-    https://gitlab.com/pycqa/flake8/issues/235
-.. _GitLab#237:
-    https://gitlab.com/pycqa/flake8/issues/237
-.. _GitLab#238:
-    https://gitlab.com/pycqa/flake8/issues/238
-.. _GitLab#239:
-    https://gitlab.com/pycqa/flake8/issues/239
-.. _GitLab#242:
-    https://gitlab.com/pycqa/flake8/issues/242
-.. _GitLab#245:
-    https://gitlab.com/pycqa/flake8/issues/245
-.. _GitLab#246:
-    https://gitlab.com/pycqa/flake8/issues/246
-.. _GitLab#248:
-    https://gitlab.com/pycqa/flake8/issues/248
+    https://github.com/pycqa/flake8/milestone/12
diff --git a/docs/source/release-notes/3.1.1.rst b/docs/source/release-notes/3.1.1.rst
index c22adde0..a7f4081c 100644
--- a/docs/source/release-notes/3.1.1.rst
+++ b/docs/source/release-notes/3.1.1.rst
@@ -1,18 +1,14 @@
 3.1.1 -- 2016-11-14
 -------------------
 
-You can view the `3.1.1 milestone`_ on GitLab for more details.
+You can view the `3.1.1 milestone`_ on GitHub for more details.
 
 - Do not attempt to install/distribute a ``man`` file with the Python package;
-  leave this for others to do. (See also `GitLab#254`_)
+  leave this for others to do. (See also :issue:`186`)
 
 - Fix packaging bug where wheel version constraints specified in setup.cfg did
-  not match the constraints in setup.py. (See also `GitLab#255`_)
+  not match the constraints in setup.py. (See also :issue:`187`)
 
 .. links
 .. _3.1.1 milestone:
-    https://gitlab.com/pycqa/flake8/milestones/13
-.. _GitLab#254:
-    https://gitlab.com/pycqa/flake8/issues/254
-.. _GitLab#255:
-    https://gitlab.com/pycqa/flake8/issues/255
+    https://github.com/pycqa/flake8/milestone/13
diff --git a/docs/source/release-notes/3.2.0.rst b/docs/source/release-notes/3.2.0.rst
index 123bc33a..a7599ef7 100644
--- a/docs/source/release-notes/3.2.0.rst
+++ b/docs/source/release-notes/3.2.0.rst
@@ -1,13 +1,11 @@
 3.2.0 -- 2016-11-14
 -------------------
 
-You can view the `3.2.0 milestone`_ on GitLab for more details.
+You can view the `3.2.0 milestone`_ on GitHub for more details.
 
 - Allow for pycodestyle 2.2.0 which fixes a bug in E305 (See also
-  `GitLab#256`_)
+  :issue:`188`)
 
 .. links
 .. _3.2.0 milestone:
-    https://gitlab.com/pycqa/flake8/milestones/14
-.. _GitLab#256:
-    https://gitlab.com/pycqa/flake8/issues/256
+    https://github.com/pycqa/flake8/milestone/14
diff --git a/docs/source/release-notes/3.2.1.rst b/docs/source/release-notes/3.2.1.rst
index 16d4f28b..0a4c3c70 100644
--- a/docs/source/release-notes/3.2.1.rst
+++ b/docs/source/release-notes/3.2.1.rst
@@ -1,27 +1,19 @@
 3.2.1 -- 2016-11-21
 -------------------
 
-You can view the `3.2.1 milestone`_ on GitLab for more details.
+You can view the `3.2.1 milestone`_ on GitHub for more details.
 
 - Fix subtle bug when deciding whether to report an on-by-default's violation
-  (See also `GitLab#257`_)
+  (See also :issue:`189`)
 
 - Fix another bug around SyntaxErrors not being reported at the right column
-  and row (See also `GitLab#259`_ and `GitLab#237`_ for a related, previously
+  and row (See also :issue:`191` and :issue:`169` for a related, previously
   fixed bug)
 
 - Fix regression from 2.x where we run checks against explicitly provided
   files, even if they don't match the filename patterns. (See also
-  `GitLab#266`_)
+  :issue:`198`)
 
 .. links
 .. _3.2.1 milestone:
-    https://gitlab.com/pycqa/flake8/milestones/15
-.. _GitLab#237:
-    https://gitlab.com/pycqa/flake8/issues/237
-.. _GitLab#257:
-    https://gitlab.com/pycqa/flake8/issues/257
-.. _GitLab#259:
-    https://gitlab.com/pycqa/flake8/issues/259
-.. _GitLab#266:
-    https://gitlab.com/pycqa/flake8/issues/266
+    https://github.com/pycqa/flake8/milestone/15
diff --git a/docs/source/release-notes/3.3.0.rst b/docs/source/release-notes/3.3.0.rst
index 982210e3..431ab15b 100644
--- a/docs/source/release-notes/3.3.0.rst
+++ b/docs/source/release-notes/3.3.0.rst
@@ -1,7 +1,7 @@
 3.3.0 -- 2017-02-06
 -------------------
 
-You can view the `3.3.0 milestone`_ on GitLab for more details.
+You can view the `3.3.0 milestone`_ on GitHub for more details.
 
 - Add support for Python 3.6 (via dependencies). **Note** Flake8 does not
   guarantee that all plugins will support Python 3.6.
@@ -9,49 +9,29 @@ You can view the `3.3.0 milestone`_ on GitLab for more details.
 - Added unique error codes for all missing PyFlakes messages. (14 new
   codes, see "Error / Violation Codes")
 
-- Dramatically improve the performance of Flake8. (See also `GitLab!156`_)
+- Dramatically improve the performance of Flake8. (See also :issue:`829`)
 
 - Display the local file path instead of the temporary file path when
-  using the git hook. (See also `GitLab#244`_)
+  using the git hook. (See also :issue:`176`)
 
 - Add methods to Report class that will be called when Flake8 starts and
-  finishes processing a file. (See also `GitLab#251`_)
+  finishes processing a file. (See also :issue:`183`)
 
 - Fix problem where hooks should only check \*.py files. (See also
-  `GitLab#268`_)
+  :issue:`200`)
 
 - Fix handling of SyntaxErrors that do not include physical line information.
-  (See also `GitLab#279`_)
+  (See also :issue:`542`)
 
 - Update upper bound on PyFlakes to allow for PyFlakes 1.5.0.  (See also
-  `GitLab#290`_)
+  :issue:`549`)
 
 - Update setuptools integration to less eagerly deduplicate packages.
-  (See also `GitLab#295`_)
+  (See also :issue:`552`)
 
 - Force ``flake8 --version`` to be repeatable between invocations. (See also
-  `GitLab#297`_)
+  :issue:`554`)
 
 .. all links
 .. _3.3.0 milestone:
-    https://gitlab.com/pycqa/flake8/milestones/16
-
-.. issue links
-.. _GitLab#244:
-    https://gitlab.com/pycqa/flake8/issues/244
-.. _GitLab#251:
-    https://gitlab.com/pycqa/flake8/issues/251
-.. _GitLab#268:
-    https://gitlab.com/pycqa/flake8/issues/268
-.. _GitLab#279:
-    https://gitlab.com/pycqa/flake8/issues/279
-.. _GitLab#290:
-    https://gitlab.com/pycqa/flake8/issues/290
-.. _GitLab#295:
-    https://gitlab.com/pycqa/flake8/issues/295
-.. _GitLab#297:
-    https://gitlab.com/pycqa/flake8/issues/297
-
-.. merge request links
-.. _GitLab!156:
-    https://gitlab.com/pycqa/flake8/merge_requests/156
+    https://github.com/pycqa/flake8/milestone/16
diff --git a/docs/source/release-notes/3.4.0.rst b/docs/source/release-notes/3.4.0.rst
index 08ee558c..c4a53d0a 100644
--- a/docs/source/release-notes/3.4.0.rst
+++ b/docs/source/release-notes/3.4.0.rst
@@ -1,41 +1,25 @@
 3.4.0 -- 2017-07-27
 -------------------
 
-You can view the `3.4.0 milestone`_ on GitLab for more details.
+You can view the `3.4.0 milestone`_ on GitHub for more details.
 
 - Refine logic around ``--select`` and ``--ignore`` when combined with the
-  default values for each. (See also `GitLab#318`_)
+  default values for each. (See also :issue:`572`)
 
 - Handle spaces as an alternate separate for error codes, e.g.,
-  ``--ignore 'E123 E234'``. (See also `GitLab#329`_)
+  ``--ignore 'E123 E234'``. (See also :issue:`580`)
 
 - Filter out empty select and ignore codes, e.g., ``--ignore E123,,E234``.
-  (See also `GitLab#330`_)
+  (See also :issue:`581`)
 
-- Specify dependencies appropriately in ``setup.py`` (See also `Gitlab#341_`)
+- Specify dependencies appropriately in ``setup.py`` (See also :issue:`592`)
 
 - Fix bug in parsing ``--quiet`` and ``--verbose`` from config files.
-  (See also `GitLab!193`_)
+  (See also :issue:`1169`)
 
 - Remove unused import of ``os`` in the git hook template (See also
-  `GitLab!194`_)
+  :issue:`1170`)
 
 .. all links
 .. _3.4.0 milestone:
-    https://gitlab.com/pycqa/flake8/milestones/18
-
-.. issue links
-.. _GitLab#318:
-    https://gitlab.com/pycqa/flake8/issues/318
-.. _GitLab#329:
-    https://gitlab.com/pycqa/flake8/issues/329
-.. _GitLab#330:
-    https://gitlab.com/pycqa/flake8/issues/330
-.. _GitLab#341:
-    https://gitlab.com/pycqa/flake8/issues/341
-
-.. merge request links
-.. _GitLab!193:
-    https://gitlab.com/pycqa/flake8/merge_requests/193
-.. _GitLab!194:
-    https://gitlab.com/pycqa/flake8/merge_requests/194
+    https://github.com/pycqa/flake8/milestone/17
diff --git a/docs/source/release-notes/3.4.1.rst b/docs/source/release-notes/3.4.1.rst
index 81d0eef4..dbbb4378 100644
--- a/docs/source/release-notes/3.4.1.rst
+++ b/docs/source/release-notes/3.4.1.rst
@@ -1,17 +1,11 @@
 3.4.1 -- 2017-07-28
 -------------------
 
-You can view the `3.4.1 milestone`_ on GitLab for more details.
+You can view the `3.4.1 milestone`_ on GitHub for more details.
 
 - Fix minor regression when users specify only a ``--select`` list with items
-  in the enabled/extended select list.  (See also `GitLab#354`_)
+  in the enabled/extended select list.  (See also :issue:`605`)
 
 .. all links
 .. _3.4.1 milestone:
-    https://gitlab.com/pycqa/flake8/milestones/19
-
-.. issue links
-.. _GitLab#354:
-    https://gitlab.com/pycqa/flake8/issues/354
-
-.. merge request links
+    https://github.com/pycqa/flake8/milestone/18
diff --git a/docs/source/release-notes/3.5.0.rst b/docs/source/release-notes/3.5.0.rst
index ff3a140d..75a5b70d 100644
--- a/docs/source/release-notes/3.5.0.rst
+++ b/docs/source/release-notes/3.5.0.rst
@@ -1,46 +1,32 @@
 3.5.0 -- 2017-10-23
 -------------------
 
-You can view the `3.5.0 milestone`_ on GitLab for more details.
+You can view the `3.5.0 milestone`_ on GitHub for more details.
 
 New Dependency Information
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-- Allow for PyFlakes 1.6.0 (See also `GitLab#359`_)
+- Allow for PyFlakes 1.6.0 (See also :issue:`1058`)
 
 - Start using new PyCodestyle checks for bare excepts and ambiguous identifier
-  (See also `GitLab#361`_)
+  (See also :issue:`611`)
 
 Features
 ~~~~~~~~
 
-- Print out information about configuring VCS hooks (See also `GitLab#335`_)
+- Print out information about configuring VCS hooks (See also :issue:`586`)
 
 - Allow users to develop plugins "local" to a repository without using
   setuptools. See our documentation on local plugins for more information.
-  (See also `GitLab#357`_)
+  (See also :issue:`608`)
 
 Bugs Fixed
 ~~~~~~~~~~
 
 - Catch and helpfully report ``UnicodeDecodeError``\ s when parsing
-  configuration files. (See also `GitLab#358`_)
+  configuration files. (See also :issue:`609`)
 
 
 .. all links
 .. _3.5.0 milestone:
-    https://gitlab.com/pycqa/flake8/milestones/20
-
-.. issue links
-.. _GitLab#335:
-    https://gitlab.com/pycqa/flake8/issues/335
-.. _GitLab#357:
-    https://gitlab.com/pycqa/flake8/issues/357
-.. _GitLab#358:
-    https://gitlab.com/pycqa/flake8/issues/358
-.. _GitLab#359:
-    https://gitlab.com/pycqa/flake8/issues/359
-.. _GitLab#361:
-    https://gitlab.com/pycqa/flake8/issues/361
-
-.. merge request links
+    https://github.com/pycqa/flake8/milestone/19
diff --git a/docs/source/release-notes/3.6.0.rst b/docs/source/release-notes/3.6.0.rst
index 052e8c35..140a3bfd 100644
--- a/docs/source/release-notes/3.6.0.rst
+++ b/docs/source/release-notes/3.6.0.rst
@@ -1,117 +1,61 @@
 3.6.0 -- 2018-10-23
 -------------------
 
-You can view the `3.6.0 milestone`_ on GitLab for more details.
+You can view the `3.6.0 milestone`_ on GitHub for more details.
 
 New Dependency Information
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-- pycodestyle has been updated to >= 2.4.0, < 2.5.0 (See also `GitLab#381`_,
-  `GitLab#415`_, `GitLab!212`_, `GitLab!230`_, `GitLab!231`_)
+- pycodestyle has been updated to >= 2.4.0, < 2.5.0 (See also :issue:`1068`,
+  :issue:`652`, :issue:`869`, :issue:`881`, :issue:`1239`)
 
-- Pyflakes has been updated to >= 2.0.0, < 2.1.0 (See also `GitLab#422`_,
-  `GitLab!239`_)
+- Pyflakes has been updated to >= 2.0.0, < 2.1.0 (See also :issue:`655`,
+  :issue:`883`)
 
 - flake8 requires python 2.x >= 2.7 or python 3.x >= 3.4 (See also
-  `GitLab!225`_)
+  :issue:`876`)
 
 Features
 ~~~~~~~~
 
 - Add ``paths`` to allow local plugins to exist outside of ``sys.path`` (See
-  also `GitLab#379`_, `GitLab!211`_)
+  also :issue:`1067`, :issue:`1237`)
 
 - Copy ``setup.cfg`` files to the temporary git hook execution directory (See
-  also `GitLab!215`_)
+  also :issue:`1299`)
 
 - Only skip a file if ``# flake8: noqa`` is on a line by itself (See also
-  `GitLab#453`_, `GitLab!219`_)
+  :issue:`259`, :issue:`873`)
 
-- Provide a better user experience for broken plugins (See also `GitLab!221`_)
+- Provide a better user experience for broken plugins (See also :issue:`1178`)
 
 - Report ``E902`` when a file passed on the command line does not exist (See
-  also `GitLab#405`_, `GitLab!227`_)
+  also :issue:`645`, :issue:`878`)
 
 - Add ``--extend-ignore`` for extending the default ``ignore`` instead of
-  overriding it (See also `GitLab#365`_, `GitLab!233`_)
+  overriding it (See also :issue:`1061`, :issue:`1180`)
 
 Bugs Fixed
 ~~~~~~~~~~
 
-- Respect a formatter's newline setting when printing (See also `GitLab!222`_)
+- Respect a formatter's newline setting when printing (See also :issue:`1238`)
 
-- Fix leaking of processes in the legacy api (See also `GitLab#410`_,
-  `GitLab!228`_)
+- Fix leaking of processes in the legacy api (See also :issue:`650`,
+  :issue:`879`)
 
 - Fix a ``SyntaxWarning`` for an invalid escape sequence (See also
-  `GitLab!244`_)
+  :issue:`1186`)
 
 - Fix ``DeprecationWarning`` due to import of ``abc`` classes from the
-  ``collections`` module (See also `GitLab!249`_)
+  ``collections`` module (See also :issue:`887`)
 
 - Defer ``setuptools`` import to improve flake8 startup time (See also
-  `GitLab!250`_)
+  :issue:`1190`)
 
 - Fix inconsistent line endings in ``FileProcessor.lines`` when running under
-  python 3.x (See also `GitLab#457`_, `GitLab!255`_)
+  python 3.x (See also :issue:`263`, :issue:`889`)
 
 
 .. all links
 .. _3.6.0 milestone:
-    https://gitlab.com/pycqa/flake8/milestones/21
-
-.. issue links
-.. _GitLab#365:
-    https://gitlab.com/pycqa/flake8/issues/365
-.. _GitLab#379:
-    https://gitlab.com/pycqa/flake8/issues/379
-.. _GitLab#381:
-    https://gitlab.com/pycqa/flake8/issues/381
-.. _GitLab#405:
-    https://gitlab.com/pycqa/flake8/issues/405
-.. _GitLab#410:
-    https://gitlab.com/pycqa/flake8/issues/410
-.. _GitLab#415:
-    https://gitlab.com/pycqa/flake8/issues/415
-.. _GitLab#422:
-    https://gitlab.com/pycqa/flake8/issues/422
-.. _GitLab#453:
-    https://gitlab.com/pycqa/flake8/issues/453
-.. _GitLab#457:
-    https://gitlab.com/pycqa/flake8/issues/457
-
-.. merge request links
-.. _GitLab!211:
-    https://gitlab.com/pycqa/flake8/merge_requests/211
-.. _GitLab!212:
-    https://gitlab.com/pycqa/flake8/merge_requests/212
-.. _GitLab!215:
-    https://gitlab.com/pycqa/flake8/merge_requests/215
-.. _GitLab!219:
-    https://gitlab.com/pycqa/flake8/merge_requests/219
-.. _GitLab!221:
-    https://gitlab.com/pycqa/flake8/merge_requests/221
-.. _GitLab!222:
-    https://gitlab.com/pycqa/flake8/merge_requests/222
-.. _GitLab!225:
-    https://gitlab.com/pycqa/flake8/merge_requests/225
-.. _GitLab!227:
-    https://gitlab.com/pycqa/flake8/merge_requests/227
-.. _GitLab!228:
-    https://gitlab.com/pycqa/flake8/merge_requests/228
-.. _GitLab!230:
-    https://gitlab.com/pycqa/flake8/merge_requests/230
-.. _GitLab!231:
-    https://gitlab.com/pycqa/flake8/merge_requests/231
-.. _GitLab!233:
-    https://gitlab.com/pycqa/flake8/merge_requests/233
-.. _GitLab!239:
-    https://gitlab.com/pycqa/flake8/merge_requests/239
-.. _GitLab!244:
-    https://gitlab.com/pycqa/flake8/merge_requests/244
-.. _GitLab!249:
-    https://gitlab.com/pycqa/flake8/merge_requests/249
-.. _GitLab!250:
-    https://gitlab.com/pycqa/flake8/merge_requests/250
-.. _GitLab!255:
-    https://gitlab.com/pycqa/flake8/merge_requests/255
+    https://github.com/pycqa/flake8/milestone/20
diff --git a/docs/source/release-notes/3.7.0.rst b/docs/source/release-notes/3.7.0.rst
index 75fcca53..024b90cd 100644
--- a/docs/source/release-notes/3.7.0.rst
+++ b/docs/source/release-notes/3.7.0.rst
@@ -1,85 +1,45 @@
 3.7.0 -- 2019-01-29
 -------------------
 
-You can view the `3.7.0 milestone`_ on GitLab for more details.
+You can view the `3.7.0 milestone`_ on GitHub for more details.
 
 New Dependency Information
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-- Add dependency on ``entrypoints`` >= 0.3, < 0.4 (See also `GitLab!264`_,
-  `GitLab!288`_)
+- Add dependency on ``entrypoints`` >= 0.3, < 0.4 (See also :issue:`897`,
+  :issue:`1197`)
 
-- Pyflakes has been updated to >= 2.1.0, < 2.2.0 (See also `GitLab!283`_,
-  `GitLab!285`_)
+- Pyflakes has been updated to >= 2.1.0, < 2.2.0 (See also :issue:`912`,
+  :issue:`913`)
 
-- pycodestyle has been updated to >= 2.5.0, < 2.6.0 (See also `GitLab!287`_)
+- pycodestyle has been updated to >= 2.5.0, < 2.6.0 (See also :issue:`915`)
 
 Features
 ~~~~~~~~
 
-- Add support for ``per-file-ignores`` (See also `GitLab!259`_, `GitLab#156`_,
-  `GitLab!281`_, `GitLab#471`_)
+- Add support for ``per-file-ignores`` (See also :issue:`892`, :issue:`511`,
+  :issue:`911`, :issue:`277`)
 
-- Enable use of ``float`` and ``complex`` option types (See also `GitLab!261`_,
-  `GitLab#452`_)
+- Enable use of ``float`` and ``complex`` option types (See also :issue:`894`,
+  :issue:`258`)
 
 - Improve startup performance by switching from ``pkg_resources`` to
-  ``entrypoints`` (See also `GitLab!264`_)
+  ``entrypoints`` (See also :issue:`897`)
 
 - Add metadata for use through the `pre-commit`_ git hooks framework (See also
-  `GitLab!268`_, `GitLab!284`_)
+  :issue:`901`, :issue:`1196`)
 
 - Allow physical line checks to return more than one result (See also
-  `GitLab!269`_)
+  :issue:`902`)
 
 - Allow ``# noqa:X123`` comments without space between the colon and codes
-  list (See also `GitLab!273`_, `GitLab#470`_)
+  list (See also :issue:`906`, :issue:`276`)
 
 - Remove broken and unused ``flake8.listen`` plugin type (See also
-  `GitLab!274`_, `GitLab#480`_)
+  :issue:`907`, :issue:`663`)
 
 .. all links
 .. _3.7.0 milestone:
-    https://gitlab.com/pycqa/flake8/milestones/23
+    https://github.com/pycqa/flake8/milestone/22
 .. _pre-commit:
     https://pre-commit.com/
-
-.. issue links
-.. _GitLab#156:
-    https://gitlab.com/pycqa/flake8/issues/156
-.. _GitLab#452:
-    https://gitlab.com/pycqa/flake8/issues/452
-.. _GitLab#470:
-    https://gitlab.com/pycqa/flake8/issues/470
-.. _GitLab#471:
-    https://gitlab.com/pycqa/flake8/issues/471
-.. _GitLab#480:
-    https://gitlab.com/pycqa/flake8/issues/480
-
-.. merge request links
-.. _GitLab!259:
-    https://gitlab.com/pycqa/flake8/merge_requests/259
-.. _GitLab!261:
-    https://gitlab.com/pycqa/flake8/merge_requests/261
-.. _GitLab!264:
-    https://gitlab.com/pycqa/flake8/merge_requests/264
-.. _GitLab!268:
-    https://gitlab.com/pycqa/flake8/merge_requests/268
-.. _GitLab!269:
-    https://gitlab.com/pycqa/flake8/merge_requests/269
-.. _GitLab!273:
-    https://gitlab.com/pycqa/flake8/merge_requests/273
-.. _GitLab!274:
-    https://gitlab.com/pycqa/flake8/merge_requests/274
-.. _GitLab!281:
-    https://gitlab.com/pycqa/flake8/merge_requests/281
-.. _GitLab!283:
-    https://gitlab.com/pycqa/flake8/merge_requests/283
-.. _GitLab!284:
-    https://gitlab.com/pycqa/flake8/merge_requests/284
-.. _GitLab!285:
-    https://gitlab.com/pycqa/flake8/merge_requests/285
-.. _GitLab!287:
-    https://gitlab.com/pycqa/flake8/merge_requests/287
-.. _GitLab!288:
-    https://gitlab.com/pycqa/flake8/merge_requests/288
diff --git a/docs/source/release-notes/3.7.1.rst b/docs/source/release-notes/3.7.1.rst
index 913c4a98..218ac073 100644
--- a/docs/source/release-notes/3.7.1.rst
+++ b/docs/source/release-notes/3.7.1.rst
@@ -1,22 +1,14 @@
 3.7.1 -- 2019-01-30
 -------------------
 
-You can view the `3.7.1 milestone`_ on GitLab for more details.
+You can view the `3.7.1 milestone`_ on GitHub for more details.
 
 Bugs Fixed
 ~~~~~~~~~~
 
 - Fix capitalized filenames in ``per-file-ignores`` setting (See also
-  `GitLab!290`_, `GitLab#488`_)
+  :issue:`917`, :issue:`287`)
 
 .. all links
 .. _3.7.1 milestone:
-    https://gitlab.com/pycqa/flake8/milestones/24
-
-.. issue links
-.. _GitLab#488:
-    https://gitlab.com/pycqa/flake8/issues/488
-
-.. merge request links
-.. _GitLab!290:
-    https://gitlab.com/pycqa/flake8/merge_requests/290
+    https://github.com/pycqa/flake8/milestone/23
diff --git a/docs/source/release-notes/3.7.2.rst b/docs/source/release-notes/3.7.2.rst
index 87bc8c36..98d64fbb 100644
--- a/docs/source/release-notes/3.7.2.rst
+++ b/docs/source/release-notes/3.7.2.rst
@@ -1,36 +1,20 @@
 3.7.2 -- 2019-01-30
 -------------------
 
-You can view the `3.7.2 milestone`_ on GitLab for more details.
+You can view the `3.7.2 milestone`_ on GitHub for more details.
 
 Bugs Fixed
 ~~~~~~~~~~
 
-- Fix broken ``flake8 --diff`` (regressed in 3.7.0) (See also `GitLab!292`_,
-  `GitLab#490`_)
+- Fix broken ``flake8 --diff`` (regressed in 3.7.0) (See also :issue:`919`,
+  :issue:`667`)
 
-- Fix typo in plugin exception reporting (See also `GitLab!275`_,
-  `GitLab#491`_)
+- Fix typo in plugin exception reporting (See also :issue:`908`,
+  :issue:`668`)
 
 - Fix ``AttributeError`` while attempting to use the legacy api (regressed in
-  3.7.0) (See also `GitLab!293`_, `GitLab#497`_)
+  3.7.0) (See also :issue:`1198`, :issue:`673`)
 
 .. all links
 .. _3.7.2 milestone:
-    https://gitlab.com/pycqa/flake8/milestones/25
-
-.. issue links
-.. _GitLab#490:
-    https://gitlab.com/pycqa/flake8/issues/490
-.. _GitLab#491:
-    https://gitlab.com/pycqa/flake8/issues/491
-.. _GitLab#497:
-    https://gitlab.com/pycqa/flake8/issues/497
-
-.. merge request links
-.. _GitLab!292:
-    https://gitlab.com/pycqa/flake8/merge_requests/292
-.. _GitLab!275:
-    https://gitlab.com/pycqa/flake8/merge_requests/275
-.. _GitLab!293:
-    https://gitlab.com/pycqa/flake8/merge_requests/293
+    https://github.com/pycqa/flake8/milestone/24
diff --git a/docs/source/release-notes/3.7.3.rst b/docs/source/release-notes/3.7.3.rst
index 1db1d8f5..69f4eec5 100644
--- a/docs/source/release-notes/3.7.3.rst
+++ b/docs/source/release-notes/3.7.3.rst
@@ -1,43 +1,23 @@
 3.7.3 -- 2019-01-30
 -------------------
 
-You can view the `3.7.3 milestone`_ on GitLab for more details.
+You can view the `3.7.3 milestone`_ on GitHub for more details.
 
 Bugs Fixed
 ~~~~~~~~~~
 
-- Fix imports of ``typing`` in python 3.5.0 / 3.5.1 (See also `GitLab!294`_,
-  `GitLab#498`_)
+- Fix imports of ``typing`` in python 3.5.0 / 3.5.1 (See also :issue:`1199`,
+  :issue:`674`)
 
-- Fix ``flake8 --statistics`` (See also `GitLab!295`_, `GitLab#499`_)
+- Fix ``flake8 --statistics`` (See also :issue:`920`, :issue:`675`)
 
 - Gracefully ignore ``flake8-per-file-ignores`` plugin if installed (See also
-  `GitLab!297`_, `GitLab#495`_)
+  :issue:`1201`, :issue:`671`)
 
 - Improve error message for malformed ``per-file-ignores`` (See also
-  `GitLab!298`_, `GitLab#489`_)
+  :issue:`921`, :issue:`288`)
 
 
 .. all links
 .. _3.7.3 milestone:
-    https://gitlab.com/pycqa/flake8/milestones/26
-
-.. issue links
-.. _GitLab#489:
-    https://gitlab.com/pycqa/flake8/issues/489
-.. _GitLab#495:
-    https://gitlab.com/pycqa/flake8/issues/495
-.. _GitLab#498:
-    https://gitlab.com/pycqa/flake8/issues/498
-.. _GitLab#499:
-    https://gitlab.com/pycqa/flake8/issues/499
-
-.. merge request links
-.. _GitLab!294:
-    https://gitlab.com/pycqa/flake8/merge_requests/294
-.. _GitLab!295:
-    https://gitlab.com/pycqa/flake8/merge_requests/295
-.. _GitLab!297:
-    https://gitlab.com/pycqa/flake8/merge_requests/297
-.. _GitLab!298:
-    https://gitlab.com/pycqa/flake8/merge_requests/298
+    https://github.com/pycqa/flake8/milestone/25
diff --git a/docs/source/release-notes/3.7.4.rst b/docs/source/release-notes/3.7.4.rst
index bfde2092..80a90247 100644
--- a/docs/source/release-notes/3.7.4.rst
+++ b/docs/source/release-notes/3.7.4.rst
@@ -1,23 +1,15 @@
 3.7.4 -- 2019-01-31
 -------------------
 
-You can view the `3.7.4 milestone`_ on GitLab for more details.
+You can view the `3.7.4 milestone`_ on GitHub for more details.
 
 Bugs Fixed
 ~~~~~~~~~~
 
 - Fix performance regression with lots of ``per-file-ignores`` and errors
-  (See also `GitLab!299`_, `GitLab#501`_)
+  (See also :issue:`922`, :issue:`677`)
 
 
 .. all links
 .. _3.7.4 milestone:
-    https://gitlab.com/pycqa/flake8/milestones/27
-
-.. issue links
-.. _GitLab#501:
-    https://gitlab.com/pycqa/flake8/issues/501
-
-.. merge request links
-.. _GitLab!299:
-    https://gitlab.com/pycqa/flake8/merge_requests/299
+    https://github.com/pycqa/flake8/milestone/26
diff --git a/docs/source/release-notes/3.7.5.rst b/docs/source/release-notes/3.7.5.rst
index e6bf37ab..769893c5 100644
--- a/docs/source/release-notes/3.7.5.rst
+++ b/docs/source/release-notes/3.7.5.rst
@@ -1,23 +1,15 @@
 3.7.5 -- 2019-02-04
 -------------------
 
-You can view the `3.7.5 milestone`_ on GitLab for more details.
+You can view the `3.7.5 milestone`_ on GitHub for more details.
 
 Bugs Fixed
 ~~~~~~~~~~
 
 - Fix reporting of pyflakes "referenced before assignment" error (See also
-  `GitLab!301`_, `GitLab#503`_)
+  :issue:`923`, :issue:`679`)
 
 
 .. all links
 .. _3.7.5 milestone:
-    https://gitlab.com/pycqa/flake8/milestones/28
-
-.. issue links
-.. _GitLab#503:
-    https://gitlab.com/pycqa/flake8/issues/503
-
-.. merge request links
-.. _GitLab!301:
-    https://gitlab.com/pycqa/flake8/merge_requests/301
+    https://github.com/pycqa/flake8/milestone/27
diff --git a/docs/source/release-notes/3.7.6.rst b/docs/source/release-notes/3.7.6.rst
index 775edd5f..a18eaefa 100644
--- a/docs/source/release-notes/3.7.6.rst
+++ b/docs/source/release-notes/3.7.6.rst
@@ -1,27 +1,17 @@
 3.7.6 -- 2019-02-18
 -------------------
 
-You can view the `3.7.6 milestone`_ on GitLab for more details.
+You can view the `3.7.6 milestone`_ on GitHub for more details.
 
 Bugs Fixed
 ~~~~~~~~~~
 
 - Fix ``--per-file-ignores`` for multi-letter error codes (See also
-  `GitLab!303`_, `GitLab#507`_)
+  :issue:`1203`, :issue:`683`)
 
-- Improve flake8 speed when only 1 filename is passed (See also `GitLab!305`_)
+- Improve flake8 speed when only 1 filename is passed (See also :issue:`1204`)
 
 
 .. all links
 .. _3.7.6 milestone:
-    https://gitlab.com/pycqa/flake8/milestones/29
-
-.. issue links
-.. _GitLab#507:
-    https://gitlab.com/pycqa/flake8/issues/507
-
-.. merge request links
-.. _GitLab!303:
-    https://gitlab.com/pycqa/flake8/merge_requests/303
-.. _GitLab!305:
-    https://gitlab.com/pycqa/flake8/merge_requests/305
+    https://github.com/pycqa/flake8/milestone/28
diff --git a/docs/source/release-notes/3.7.7.rst b/docs/source/release-notes/3.7.7.rst
index 7948f005..b7a221a6 100644
--- a/docs/source/release-notes/3.7.7.rst
+++ b/docs/source/release-notes/3.7.7.rst
@@ -1,23 +1,15 @@
 3.7.7 -- 2019-02-25
 -------------------
 
-You can view the `3.7.7 milestone`_ on GitLab for more details.
+You can view the `3.7.7 milestone`_ on GitHub for more details.
 
 Bugs Fixed
 ~~~~~~~~~~
 
 - Fix crashes in plugins causing ``flake8`` to hang while unpickling errors
-  (See also `GitLab!308`_, `GitLab#505`_)
+  (See also :issue:`1206`, :issue:`681`)
 
 
 .. all links
 .. _3.7.7 milestone:
-    https://gitlab.com/pycqa/flake8/milestones/30
-
-.. issue links
-.. _GitLab#505:
-    https://gitlab.com/pycqa/flake8/issues/505
-
-.. merge request links
-.. _GitLab!308:
-    https://gitlab.com/pycqa/flake8/merge_requests/308
+    https://github.com/pycqa/flake8/milestone/29
diff --git a/docs/source/release-notes/3.7.8.rst b/docs/source/release-notes/3.7.8.rst
index 40478a2f..e0af4a3e 100644
--- a/docs/source/release-notes/3.7.8.rst
+++ b/docs/source/release-notes/3.7.8.rst
@@ -1,41 +1,23 @@
 3.7.8 -- 2019-07-08
 -------------------
 
-You can view the `3.7.8 milestone`_ on GitLab for more details.
+You can view the `3.7.8 milestone`_ on GitHub for more details.
 
 Bugs Fixed
 ~~~~~~~~~~
 
 - Fix handling of ``Application.parse_preliminary_options_and_args`` when
-  argv is an empty list (See also `GitLab!310`_, `GitLab#518`_)
+  argv is an empty list (See also :issue:`1303`, :issue:`694`)
 
-- Fix crash when a file parses but fails to tokenize (See also `GitLab!314`_,
-  `GitLab#532`_)
+- Fix crash when a file parses but fails to tokenize (See also :issue:`1210`,
+  :issue:`1088`)
 
-- Log the full traceback on plugin exceptions (See also `GitLab!317`_)
+- Log the full traceback on plugin exceptions (See also :issue:`926`)
 
-- Fix ``# noqa: ...`` comments with multi-letter codes (See also `GitLab!326`_,
-  `GitLab#549`_)
+- Fix ``# noqa: ...`` comments with multi-letter codes (See also :issue:`931`,
+  :issue:`1101`)
 
 
 .. all links
 .. _3.7.8 milestone:
-    https://gitlab.com/pycqa/flake8/milestones/31
-
-.. issue links
-.. _GitLab#518:
-    https://gitlab.com/pycqa/flake8/issues/518
-.. _GitLab#532:
-    https://gitlab.com/pycqa/flake8/issues/532
-.. _GitLab#549:
-    https://gitlab.com/pycqa/flake8/issues/549
-
-.. merge request links
-.. _GitLab!310:
-    https://gitlab.com/pycqa/flake8/merge_requests/310
-.. _GitLab!314:
-    https://gitlab.com/pycqa/flake8/merge_requests/314
-.. _GitLab!317:
-    https://gitlab.com/pycqa/flake8/merge_requests/317
-.. _GitLab!326:
-    https://gitlab.com/pycqa/flake8/merge_requests/326
+    https://github.com/pycqa/flake8/milestone/30
diff --git a/docs/source/release-notes/3.7.9.rst b/docs/source/release-notes/3.7.9.rst
index b1a2ce2d..29467a05 100644
--- a/docs/source/release-notes/3.7.9.rst
+++ b/docs/source/release-notes/3.7.9.rst
@@ -1,23 +1,15 @@
 3.7.9 -- 2019-10-28
 -------------------
 
-You can view the `3.7.9 milestone`_ on GitLab for more details.
+You can view the `3.7.9 milestone`_ on GitHub for more details.
 
 Bugs Fixed
 ~~~~~~~~~~
 
 - Disable multiprocessing when the multiprocessing method is ``spawn`` (such
-  as on macos in python3.8) (See also `GitLab!367`_, `GitLab#587`_)
+  as on macos in python3.8) (See also :issue:`956`, :issue:`315`)
 
 
 .. all links
 .. _3.7.9 milestone:
-    https://gitlab.com/pycqa/flake8/milestones/33
-
-.. issue links
-.. _GitLab#587:
-    https://gitlab.com/pycqa/flake8/issues/587
-
-.. merge request links
-.. _GitLab!367:
-    https://gitlab.com/pycqa/flake8/merge_requests/367
+    https://github.com/pycqa/flake8/milestone/32
diff --git a/docs/source/release-notes/3.8.0.rst b/docs/source/release-notes/3.8.0.rst
index cf31f0a5..08eb6a10 100644
--- a/docs/source/release-notes/3.8.0.rst
+++ b/docs/source/release-notes/3.8.0.rst
@@ -1,225 +1,139 @@
 3.8.0 -- 2020-05-11
 -------------------
 
-You can view the `3.8.0 milestone`_ on GitLab for more details.
+You can view the `3.8.0 milestone`_ on GitHub for more details.
 
 Bugs Fixed
 ~~~~~~~~~~
 
 - Fix logical checks which report positions out of bounds (See also
-  `GitLab!422`_, `GitLab#635`_)
+  :issue:`987`, :issue:`723`)
 
 - Fix ``--exclude=.*`` accidentally matching ``.`` and ``..`` (See also
-  `GitLab!424`_, `GitLab#632`_)
+  :issue:`441`, :issue:`360`)
 
 Deprecations
 ~~~~~~~~~~~~
 
-- Add deprecation message for vcs hooks (See also `GitLab!420`_,
-  `GitLab#568`_)
+- Add deprecation message for vcs hooks (See also :issue:`985`,
+  :issue:`296`)
 
 
 3.8.0a2 -- 2020-04-24
 ---------------------
 
-You can view the `3.8.0 milestone`_ on GitLab for more details.
+You can view the `3.8.0 milestone`_ on GitHub for more details.
 
 Bugs Fixed
 ~~~~~~~~~~
 
-- Fix ``type="str"`` optparse options (See also `GitLab!419`_)
+- Fix ``type="str"`` optparse options (See also :issue:`984`)
 
 
 3.8.0a1 -- 2020-04-24
 ---------------------
 
-You can view the `3.8.0 milestone`_ on GitLab for more details.
+You can view the `3.8.0 milestone`_ on GitHub for more details.
 
 New Dependency Information
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 - Remove dependency on ``entrypoints`` and add dependency on
-  ``importlib-metadata`` (only for ``python<3.8``) (See also `GitLab!388`_,
-  `GitLab#569`_)
+  ``importlib-metadata`` (only for ``python<3.8``) (See also :issue:`1297`,
+  :issue:`297`)
 
-- Pyflakes has been updated to >= 2.2.0, < 2.3.0 (See also `GitLab!417`_)
+- Pyflakes has been updated to >= 2.2.0, < 2.3.0 (See also :issue:`982`)
 
-- pycodestyle has been updated to >= 2.6.0a1, < 2.7.0 (See also `GitLab!418`_)
+- pycodestyle has been updated to >= 2.6.0a1, < 2.7.0 (See also :issue:`983`)
 
 Features
 ~~~~~~~~
 
 - Add ``--extend-exclude`` option to add to ``--exclude`` without overwriting
-  (See also `GitLab!315`_, `GitLab#535`_)
+  (See also :issue:`1211`, :issue:`1091`)
 
 - Move argument parsing from ``optparse`` to ``argparse`` (See also
-  `GitLab!341`_
+  :issue:`939`
 
-- Group plugin options in ``--help`` (See also `GitLab!342`_, `GitLab#565`_)
+- Group plugin options in ``--help`` (See also :issue:`1219`, :issue:`294`)
 
 - Remove parsing of ``verbose`` from configuration files as it was not
-  consistently applied (See also `GitLab!360`_, `GitLab#439`_)
+  consistently applied (See also :issue:`1245`, :issue:`245`)
 
 - Remove parsing of ``output_file`` from configuration files as it was not
-  consistently applied (See also `GitLab!361`_)
+  consistently applied (See also :issue:`1246`)
 
 - Resolve configuration files relative to ``cwd`` instead of common prefix of
   passed filenames.  You may need to change ``flake8 subproject`` to
-  ``cd subproject && flake8 .`` (See also `GitLab!363`_)
+  ``cd subproject && flake8 .`` (See also :issue:`952`)
 
-- Officially support python3.8 (See also `GitLab!377`_)
+- Officially support python3.8 (See also :issue:`963`)
 
 - ``--disable-noqa`` now also disables ``# flake8: noqa`` (See also
-  `GitLab!380`_, `GitLab#590`_)
+  :issue:`1296`, :issue:`318`)
 
-- Ensure that a missing file produces a ``E902`` error (See also `GitLab!404`_,
-  `GitLab#600`_)
+- Ensure that a missing file produces a ``E902`` error (See also :issue:`1262`,
+  :issue:`328`)
 
 - ``# noqa`` comments now apply to all of the lines in an explicit ``\``
   continuation or in a line continued by a multi-line string (See also
-  `GitLab!413`_, `GitLab#375`_)
+  :issue:`1266`, :issue:`621`)
 
 Bugs Fixed
 ~~~~~~~~~~
 
 - Fix ``--exclude=./t.py`` to only match ``t.py`` at the top level (See also
-  `GitLab!311`_, `GitLab#382`_)
+  :issue:`1208`, :issue:`628`)
 
 - Fix ``--show-source`` when a file is indented with tabs (See also
-  `GitLab!339`_, `GitLab#563`_)
+  :issue:`1218`, :issue:`719`)
 
 - Fix crash when ``--max-line-length`` is given a non-integer (See also
-  `GitLab!341`_, `GitLab#541`_)
+  :issue:`939`, :issue:`704`)
 
 - Prevent flip-flopping of ``indent_char`` causing extra ``E101`` errors (See
-  also `GitLab!357`_, `pycodestyle#886`_)
+  also :issue:`949`, `pycodestyle#886`_)
 
 - Only enable multiprocessing when the method is ``fork`` fixing issues
-  on macos with python3.8+ (See also `GitLab!366`_, `GitLab#587`_) (note: this
+  on macos with python3.8+ (See also :issue:`955`, :issue:`315`) (note: this
   fix also landed in 3.7.9)
 
 - ``noqa`` is now only handled by flake8 fixing specific-noqa.  Plugins
   requesting this parameter will always receive ``False`` (See also
-  `GitLab!331`_, `GitLab#552`_)
+  :issue:`1214`, :issue:`1104`)
 
 - Fix duplicate loading of plugins when invoked via ``python -m flake8`` (See
-  also `GitLab!388`_)
+  also :issue:`1297`)
 
 - Fix early exit when ``--exit-zero`` and ``--diff`` are provided and the diff
-  is empty (See also `GitLab!391`_)
+  is empty (See also :issue:`970`)
 
 - Consistently split lines when ``\f`` is present when reading from stdin (See
-  also `GitLab!406`_, `GitLab#270`_)
+  also :issue:`976`, :issue:`202`)
 
 Deprecations
 ~~~~~~~~~~~~
 
 - ``python setup.py flake8`` (setuptools integration) is now deprecated and
-  will be removed in a future version (See also `GitLab!330`_, `GitLab#544`_)
+  will be removed in a future version (See also :issue:`935`, :issue:`1098`)
 
 - ``type='string'`` (optparse) types are deprecated, use
   ``type=callable`` (argparse) instead.  Support for ``type='string'`` will
-  be removed in a future version (See also `GitLab!341`_)
+  be removed in a future version (See also :issue:`939`)
 
 - ``%default`` in plugin option help text is deprecated, use ``%(default)s``
   instead.  Support for ``%default`` will be removed in a future version (See
-  also `GitLab!341`_)
+  also :issue:`939`)
 
 - optparse-style ``action='callback'`` setting for options is deprecated, use
   argparse action classes instead.  This will be removed in a future version
-  (See also `GitLab!341`_)
+  (See also :issue:`939`)
 
 
 .. all links
 .. _3.8.0 milestone:
-    https://gitlab.com/pycqa/flake8/-/milestones/32
-
-.. merge request links
-.. _GitLab#270:
-   https://gitlab.com/pycqa/flake8/-/issues/270
-.. _GitLab#375:
-   https://gitlab.com/pycqa/flake8/-/issues/375
-.. _GitLab#382:
-   https://gitlab.com/pycqa/flake8/-/issues/382
-.. _GitLab#439:
-   https://gitlab.com/pycqa/flake8/-/issues/439
-.. _GitLab#535:
-   https://gitlab.com/pycqa/flake8/-/issues/535
-.. _GitLab#541:
-   https://gitlab.com/pycqa/flake8/-/issues/541
-.. _GitLab#544:
-   https://gitlab.com/pycqa/flake8/-/issues/544
-.. _GitLab#552:
-   https://gitlab.com/pycqa/flake8/-/issues/552
-.. _GitLab#563:
-   https://gitlab.com/pycqa/flake8/-/issues/563
-.. _GitLab#565:
-   https://gitlab.com/pycqa/flake8/-/issues/565
-.. _GitLab#568:
-   https://gitlab.com/pycqa/flake8/-/issues/568
-.. _GitLab#569:
-   https://gitlab.com/pycqa/flake8/-/issues/569
-.. _GitLab#587:
-   https://gitlab.com/pycqa/flake8/-/issues/587
-.. _GitLab#590:
-   https://gitlab.com/pycqa/flake8/-/issues/590
-.. _GitLab#600:
-   https://gitlab.com/pycqa/flake8/-/issues/600
-.. _GitLab#632:
-   https://gitlab.com/pycqa/flake8/-/issues/632
-.. _GitLab#635:
-   https://gitlab.com/pycqa/flake8/-/issues/635
-.. _pycodestyle#886:
-   https://github.com/PyCQA/pycodestyle/issues/886
+    https://github.com/pycqa/flake8/milestone/31
 
 .. issue links
-.. _GitLab!311:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/311
-.. _GitLab!315:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/315
-.. _GitLab!330:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/330
-.. _GitLab!331:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/331
-.. _GitLab!339:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/339
-.. _GitLab!341:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/341
-.. _GitLab!342:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/342
-.. _GitLab!357:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/357
-.. _GitLab!360:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/360
-.. _GitLab!361:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/361
-.. _GitLab!363:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/363
-.. _GitLab!366:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/366
-.. _GitLab!377:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/377
-.. _GitLab!380:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/380
-.. _GitLab!388:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/388
-.. _GitLab!391:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/391
-.. _GitLab!404:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/404
-.. _GitLab!406:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/406
-.. _GitLab!413:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/413
-.. _GitLab!417:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/417
-.. _GitLab!418:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/418
-.. _GitLab!419:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/419
-.. _GitLab!420:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/420
-.. _GitLab!422:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/422
-.. _GitLab!424:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/424
+.. _pycodestyle#886:
+   https://github.com/PyCQA/pycodestyle/issues/886
diff --git a/docs/source/release-notes/3.8.1.rst b/docs/source/release-notes/3.8.1.rst
index 92bf041e..21e433f5 100644
--- a/docs/source/release-notes/3.8.1.rst
+++ b/docs/source/release-notes/3.8.1.rst
@@ -1,23 +1,15 @@
 3.8.1 -- 2020-05-11
 -------------------
 
-You can view the `3.8.1 milestone`_ on GitLab for more details.
+You can view the `3.8.1 milestone`_ on GitHub for more details.
 
 Bugs Fixed
 ~~~~~~~~~~
 
-- Fix ``--output-file`` (regression in 3.8.0) (See also `GitLab!427`_,
-  `GitLab#637`_)
+- Fix ``--output-file`` (regression in 3.8.0) (See also :issue:`990`,
+  :issue:`725`)
 
 
 .. all links
 .. _3.8.1 milestone:
-    https://gitlab.com/pycqa/flake8/-/milestones/34
-
-.. issue links
-.. _GitLab#637:
-    https://gitlab.com/pycqa/flake8/issues/637
-
-.. merge request links
-.. _GitLab!427:
-    https://gitlab.com/pycqa/flake8/merge_requests/427
+    https://github.com/pycqa/flake8/milestone/33
diff --git a/docs/source/release-notes/3.8.2.rst b/docs/source/release-notes/3.8.2.rst
index 813858e6..2f78eaff 100644
--- a/docs/source/release-notes/3.8.2.rst
+++ b/docs/source/release-notes/3.8.2.rst
@@ -1,40 +1,22 @@
 3.8.2 -- 2020-05-22
 -------------------
 
-You can view the `3.8.2 milestone`_ on GitLab for more details.
+You can view the `3.8.2 milestone`_ on GitHub for more details.
 
 Bugs Fixed
 ~~~~~~~~~~
 
-- Improve performance by eliminating unnecessary sort (See also `GitLab!429`_)
+- Improve performance by eliminating unnecessary sort (See also :issue:`991`)
 
 - Improve messaging of ``--jobs`` argument by utilizing ``argparse`` (See also
-  `GitLab!428`_, `GitLab#567`_)
+  :issue:`1269`, :issue:`1110`)
 
 - Fix file configuration options to be relative to the config passed on the
-  command line (See also `GitLab!431`_, `GitLab#651`_)
+  command line (See also :issue:`442`, :issue:`736`)
 
 - Fix incorrect handling of ``--extend-exclude`` by treating its values as
-  files (See also `GitLab!432`_, `GitLab#653`_)
+  files (See also :issue:`1271`, :issue:`738`)
 
 .. all links
 .. _3.8.2 milestone:
-    https://gitlab.com/pycqa/flake8/-/milestones/35
-
-.. issue links
-.. _GitLab#567:
-    https://gitlab.com/pycqa/flake8/issues/567
-.. _GitLab#651:
-    https://gitlab.com/pycqa/flake8/issues/651
-.. _GitLab#653:
-    https://gitlab.com/pycqa/flake8/issues/653
-
-.. merge request links
-.. _GitLab!428:
-    https://gitlab.com/pycqa/flake8/merge_requests/428
-.. _GitLab!429:
-    https://gitlab.com/pycqa/flake8/merge_requests/429
-.. _GitLab!431:
-    https://gitlab.com/pycqa/flake8/merge_requests/431
-.. _GitLab!432:
-    https://gitlab.com/pycqa/flake8/merge_requests/432
+    https://github.com/pycqa/flake8/milestone/34
diff --git a/docs/source/release-notes/3.8.3.rst b/docs/source/release-notes/3.8.3.rst
index d2d071b2..64fa62aa 100644
--- a/docs/source/release-notes/3.8.3.rst
+++ b/docs/source/release-notes/3.8.3.rst
@@ -1,29 +1,17 @@
 3.8.3 -- 2020-06-08
 -------------------
 
-You can view the `3.8.3 milestone`_ on GitLab for more details.
+You can view the `3.8.3 milestone`_ on GitHub for more details.
 
 Bugs Fixed
 ~~~~~~~~~~
 
-- Also catch ``SyntaxError`` when tokenizing (See also `GitLab!433`_,
-  `GitLab#662`_)
+- Also catch ``SyntaxError`` when tokenizing (See also :issue:`992`,
+  :issue:`747`)
 
-- Fix ``--jobs`` default display in ``flake8 --help`` (See also `GitLab!434`_,
-  `GitLab#665`_)
+- Fix ``--jobs`` default display in ``flake8 --help`` (See also :issue:`1272`,
+  :issue:`750`)
 
 .. all links
 .. _3.8.3 milestone:
-    https://gitlab.com/pycqa/flake8/-/milestones/36
-
-.. issue links
-.. _GitLab#662:
-    https://gitlab.com/pycqa/flake8/issues/662
-.. _GitLab#665:
-    https://gitlab.com/pycqa/flake8/issues/665
-
-.. merge request links
-.. _GitLab!433:
-    https://gitlab.com/pycqa/flake8/merge_requests/433
-.. _GitLab!434:
-    https://gitlab.com/pycqa/flake8/merge_requests/434
+    https://github.com/pycqa/flake8/milestone/35
diff --git a/docs/source/release-notes/3.8.4.rst b/docs/source/release-notes/3.8.4.rst
index a5cb9343..01260ed8 100644
--- a/docs/source/release-notes/3.8.4.rst
+++ b/docs/source/release-notes/3.8.4.rst
@@ -1,23 +1,17 @@
 3.8.4 -- 2020-10-02
 -------------------
 
-You can view the `3.8.4 milestone`_ on GitLab for more details.
+You can view the `3.8.4 milestone`_ on GitHub for more details.
 
 Bugs Fixed
 ~~~~~~~~~~
 
 - Fix multiprocessing errors on platforms without ``sem_open`` syscall.  (See
-  also `GitLab!448`_)
+  also :issue:`1282`)
 
 - Fix skipping of physical checks on the last line of a file which does not
-  end in a newline (See also `GitLab!451`_)
+  end in a newline (See also :issue:`997`)
 
 .. all links
 .. _3.8.4 milestone:
-    https://gitlab.com/pycqa/flake8/-/milestones/37
-
-.. merge request links
-.. _GitLab!448:
-    https://gitlab.com/pycqa/flake8/merge_requests/448
-.. _GitLab!451:
-    https://gitlab.com/pycqa/flake8/merge_requests/451
+    https://github.com/pycqa/flake8/milestone/36
diff --git a/docs/source/release-notes/3.9.0.rst b/docs/source/release-notes/3.9.0.rst
index 5d539042..e1024feb 100644
--- a/docs/source/release-notes/3.9.0.rst
+++ b/docs/source/release-notes/3.9.0.rst
@@ -1,45 +1,33 @@
 3.9.0 -- 2021-03-14
 -------------------
 
-You can view the `3.9.0 milestone`_ on GitLab for more details.
+You can view the `3.9.0 milestone`_ on GitHub for more details.
 
 New Dependency Information
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-- Pyflakes has been updated to >= 2.3.0, < 2.4.0 (See also `GitLab!466`_)
+- Pyflakes has been updated to >= 2.3.0, < 2.4.0 (See also :issue:`1006`)
 
-- pycodestyle has been updated to >= 2.7.0, < 2.8.0 (See also `GitLab!467`_)
+- pycodestyle has been updated to >= 2.7.0, < 2.8.0 (See also :issue:`1007`)
 
 Deprecations
 ~~~~~~~~~~~~
 
-- Drop support for python 3.4 (See also `GitLab!457`_)
+- Drop support for python 3.4 (See also :issue:`1283`)
 
 Features
 ~~~~~~~~
 
 - Add ``--no-show-source`` option to disable ``--show-source`` (See also
-  `GitLab!441`_)
+  :issue:`995`)
 
 Bugs Fixed
 ~~~~~~~~~~
 
 - Fix handling of ``crlf`` line endings when linting stdin (See also
-  `GitLab!461`_)
+  :issue:`1002`)
 
 
 .. all links
 .. _3.9.0 milestone:
-    https://gitlab.com/pycqa/flake8/-/milestones/38
-
-.. merge request links
-.. _GitLab!441:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/441
-.. _GitLab!457:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/457
-.. _GitLab!461:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/461
-.. _GitLab!466:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/466
-.. _GitLab!467:
-   https://gitlab.com/pycqa/flake8/-/merge_requests/467
+    https://github.com/pycqa/flake8/milestone/37
diff --git a/docs/source/release-notes/4.0.0.rst b/docs/source/release-notes/4.0.0.rst
new file mode 100644
index 00000000..b6c9870f
--- /dev/null
+++ b/docs/source/release-notes/4.0.0.rst
@@ -0,0 +1,43 @@
+4.0.0 -- 2021-10-10
+-------------------
+
+You can view the `4.0.0 milestone`_ on GitHub for more details.
+
+Backwards Incompatible Changes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- Remove ``--install-hook`` vcs integration (See also :issue:`1008`).
+- Remove ``setuptools`` command (See also :issue:`1009`).
+- Migrate from GitLab to GitHub (See also :pull:`1305`).
+- Due to constant confusion by users, user-level |Flake8| configuration files
+  are no longer supported. Files will not be searched for in the user's home
+  directory (e.g., ``~/.flake8``) nor in the XDG config directory (e.g.,
+  ``~/.config/flake8``).  (See also :pull:`1404`).
+
+New Dependency Information
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- pycodestyle has been updated to >= 2.8.0, < 2.9.0 (See also :pull:`1406`).
+- Pyflakes has been updated to >= 2.4.0, < 2.5.0 (See also :pull:`1406`).
+- flake8 requires python >= 3.6 (See also :issue:`1010`).
+
+Features
+~~~~~~~~
+
+- Add ``--extend-select`` option (See also :pull:`1312` :issue:`1061`).
+- Automatically create directories for output files (See also :pull:`1329`).
+
+Bugs Fixed
+~~~~~~~~~~
+
+- ``ast`` parse before tokenizing to improve ``SyntaxError`` errors (See also
+  :pull:`1320` :issue:`740`).
+- Fix warning in ``--indent-size`` argparse help (See also :pull:`1367`).
+- Fix handling ``SyntaxError`` in python 3.10+ (See also :pull:`1374`
+  :issue:`1372`).
+- Fix writing non-cp1252-encodable when output is piped on windows (See also
+  :pull:`1382` :issue:`1381`).
+
+.. all links
+.. _4.0.0 milestone:
+    https://github.com/PyCQA/flake8/milestone/39
diff --git a/docs/source/release-notes/4.0.1.rst b/docs/source/release-notes/4.0.1.rst
new file mode 100644
index 00000000..402e4b04
--- /dev/null
+++ b/docs/source/release-notes/4.0.1.rst
@@ -0,0 +1,15 @@
+4.0.1 -- 2021-10-11
+-------------------
+
+You can view the `4.0.1 milestone`_ on GitHub for more details.
+
+Bugs Fixed
+~~~~~~~~~~
+
+- Fix parallel execution collecting a ``SyntaxError`` (See also :pull:`1410`
+  :issue:`1408`).
+
+
+.. all links
+.. _4.0.1 milestone:
+    https://github.com/PyCQA/flake8/milestone/41
diff --git a/docs/source/release-notes/index.rst b/docs/source/release-notes/index.rst
index dd202535..c5d20a3a 100644
--- a/docs/source/release-notes/index.rst
+++ b/docs/source/release-notes/index.rst
@@ -5,6 +5,13 @@
 All of the release notes that have been recorded for Flake8 are organized here
 with the newest releases first.
 
+4.x Release Series
+==================
+
+.. toctree::
+   4.0.1
+   4.0.0
+
 3.x Release Series
 ==================
 
diff --git a/docs/source/requirements.txt b/docs/source/requirements.txt
index 3a7ffae1..984ca54c 100644
--- a/docs/source/requirements.txt
+++ b/docs/source/requirements.txt
@@ -1,5 +1,4 @@
 sphinx>=1.3.0,!=3.1.0
 sphinx_rtd_theme
 sphinx-prompt
-configparser
 flake8-polyfill
diff --git a/docs/source/user/error-codes.rst b/docs/source/user/error-codes.rst
index e74675f9..9eab3f7a 100644
--- a/docs/source/user/error-codes.rst
+++ b/docs/source/user/error-codes.rst
@@ -100,8 +100,6 @@ generates its own :term:`error code`\ s for ``pyflakes``:
 +------+---------------------------------------------------------------------+
 | F811 | redefinition of unused ``name`` from line ``N``                     |
 +------+---------------------------------------------------------------------+
-| F812 | list comprehension redefines ``name`` from line ``N``               |
-+------+---------------------------------------------------------------------+
 | F821 | undefined name ``name``                                             |
 +------+---------------------------------------------------------------------+
 | 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``    |
 +------+---------------------------------------------------------------------+
 
-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
 compile a file into an Abstract Syntax Tree for the plugins that require it.
 
diff --git a/docs/source/user/index.rst b/docs/source/user/index.rst
index 5a24b219..90d5b14e 100644
--- a/docs/source/user/index.rst
+++ b/docs/source/user/index.rst
@@ -8,8 +8,6 @@
 
 - invoked via Python
 
-- called by Git or Mercurial on or around committing
-
 This guide will cover all of these and the nuances for using |Flake8|.
 
 .. note::
diff --git a/docs/source/user/invocation.rst b/docs/source/user/invocation.rst
index 099b72a5..170a62ae 100644
--- a/docs/source/user/invocation.rst
+++ b/docs/source/user/invocation.rst
@@ -14,25 +14,25 @@ like so:
 
 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
-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:
 
 .. prompt:: bash
 
-    python2.7 -m flake8
+    python3.8 -m flake8
 
 Or
 
 .. prompt:: bash
 
-    python3.5 -m flake8
+    python3.9 -m flake8
 
 Since that will tell the correct version of Python to run |Flake8|.
 
 .. note::
 
-    Installing |Flake8| once will not install it on both Python 2.7 and
-    Python 3.5. It will only install it for the version of Python that
+    Installing |Flake8| once will not install it on both Python 3.8 and
+    Python 3.9. It will only install it for the version of Python that
     is running pip.
 
 It is also possible to specify command-line options directly to |Flake8|:
@@ -98,7 +98,7 @@ And you should see something like:
                             unified diff provided on standard in by the user.
       --exclude=patterns    Comma-separated list of files or directories to
                             exclude.(Default:
-                            .svn,CVS,.bzr,.hg,.git,__pycache__,.tox)
+                            .svn,CVS,.bzr,.hg,.git,__pycache__,.tox,.eggs,*.egg)
       --filename=patterns   Only check for filenames matching the patterns in this
                             comma-separated list. (Default: *.py)
       --format=format       Format errors according to the chosen formatter.
@@ -115,6 +115,10 @@ And you should see something like:
                             run. (Default: 79)
       --select=errors       Comma-separated list of errors and warnings to enable.
                             For example, ``--select=E4,E51,W234``. (Default: )
+      --extend-select errors
+                            Comma-separated list of errors and warnings to add to
+                            the list of selected ones. For example, ``--extend-
+                            select=E4,E51,W234``.
       --disable-noqa        Disable the effect of "# noqa". This will report
                             errors on lines with "# noqa" at the end.
       --show-source         Show the source generate each error or warning.
diff --git a/docs/source/user/options.rst b/docs/source/user/options.rst
index d2c0edbc..faeb17e9 100644
--- a/docs/source/user/options.rst
+++ b/docs/source/user/options.rst
@@ -68,6 +68,8 @@ Index of Options
 
 - :option:`flake8 --select`
 
+- :option:`flake8 --extend-select`
+
 - :option:`flake8 --disable-noqa`
 
 - :option:`flake8 --show-source`
@@ -78,8 +80,6 @@ Index of Options
 
 - :option:`flake8 --exit-zero`
 
-- :option:`flake8 --install-hook`
-
 - :option:`flake8 --jobs`
 
 - :option:`flake8 --output-file`
@@ -634,6 +634,38 @@ Options and their Descriptions
             F
 
 
+.. option:: --extend-select=<errors>
+
+    :ref:`Go back to index <top>`
+
+    .. versionadded:: 4.0.0
+
+    Specify a list of codes to add to the list of selected ones. Similar
+    considerations as in :option:`--select` apply here with regard to the
+    value.
+
+    The difference to the :option:`--select` option is, that this option can be
+    used to selectively add individual codes without overriding the default
+    list entirely.
+
+    Command-line example:
+
+    .. prompt:: bash
+
+        flake8 --extend-select=E4,E51,W234 dir/
+
+    This **can** be specified in config files.
+
+    Example config file usage:
+
+    .. code-block:: ini
+
+        extend-select =
+            E4,
+            E51,
+            W234
+
+
 .. option:: --disable-noqa
 
     :ref:`Go back to index <top>`
@@ -751,28 +783,6 @@ Options and their Descriptions
     This **can not** be specified in config files.
 
 
-.. option:: --install-hook=VERSION_CONTROL_SYSTEM
-
-    :ref:`Go back to index <top>`
-
-    Install a hook for your version control system that is executed before
-    or during commit.
-
-    The available options are:
-
-    - git
-    - mercurial
-
-    Command-line usage:
-
-    .. prompt:: bash
-
-        flake8 --install-hook=git
-        flake8 --install-hook=mercurial
-
-    This **can not** be specified in config files.
-
-
 .. option:: --jobs=<n>
 
     :ref:`Go back to index <top>`
@@ -782,8 +792,8 @@ Options and their Descriptions
 
     .. note::
 
-        This option is ignored on Windows because :mod:`multiprocessing` does
-        not support Windows across all supported versions of Python.
+        This option is ignored on platforms where ``fork`` is not a
+        supported ``multiprocessing`` method.
 
     This defaults to: ``auto``
 
diff --git a/docs/source/user/using-hooks.rst b/docs/source/user/using-hooks.rst
index d156ad0f..5a2e2b06 100644
--- a/docs/source/user/using-hooks.rst
+++ b/docs/source/user/using-hooks.rst
@@ -10,7 +10,7 @@ started is to add this configuration to your ``.pre-commit-config.yaml``:
 
 .. code-block:: yaml
 
-    -   repo: https://gitlab.com/pycqa/flake8
+    -   repo: https://github.com/pycqa/flake8
         rev: ''  # pick a git hash / tag to point to
         hooks:
         -   id: flake8
@@ -36,83 +36,7 @@ plugins, use the ``additional_dependencies`` setting.
         -   id: flake8
             additional_dependencies: [flake8-docstrings]
 
-
-Built-in Hook Integration
-=========================
-
-.. note::
-
-    It is strongly suggested to use |Flake8| via `pre-commit`_ over the
-    built-in hook mechanisms.  ``pre-commit`` smooths out many of the rough
-    edges of ``git`` and is much more battle-tested than the |Flake8|
-    hook implementation.
-
-|Flake8| can be integrated into your development workflow in many ways. A
-default installation of |Flake8| can install pre-commit hooks for both
-`Git`_ and `Mercurial`_. To install a built-in hook, you can use the
-:option:`flake8 --install-hook` command-line option. For example, you can
-install a git pre-commit hook by running:
-
-.. prompt:: bash
-
-    flake8 --install-hook git
-
-This will install the pre-commit hook into ``.git/hooks/``. Alternatively,
-you can install the mercurial commit hook by running
-
-.. prompt:: bash
-
-    flake8 --install-hook mercurial
-
-
-Preventing Commits
-==================
-
-By default, |Flake8| does not prevent you from creating a commit with these
-hooks. Both hooks can be configured to be strict easily.
-
-Both our Git and Mercurial hooks check for the presence of ``flake8.strict``
-in each VCS' config. For example, you might configure this like so:
-
-.. prompt:: bash
-
-    git config --bool flake8.strict true
-    hg config flake8.strict true
-
-
-Checking All Modified Files Currently Tracked
-=============================================
-
-.. note::
-
-    Mercurial does not have the concept of an index or "stage" as best as I
-    understand.
-
-|Flake8| aims to make smart choices that keep things fast for users where
-possible. As a result, the |Flake8| Git pre-commit will default to only
-checking files that have been staged (i.e., added to the index). If, however,
-you are keen to be lazy and not independently add files to your git index, you
-can set ``flake8.lazy`` to ``true`` (similar to how you would set
-``flake8.strict`` above) and this will check all tracked files.
-
-This is to support users who often find themselves doing things like:
-
-.. prompt:: bash
-
-    git commit -a
-
-.. note::
-
-    If you have files you have not yet added to the index, |Flake8| will not
-    see these and will not check them for you. You must ``git-add`` them
-    first.
-
-
 .. _pre-commit:
     https://pre-commit.com/
 .. _pre-commit docs:
     https://pre-commit.com/#pre-commit-configyaml---hooks
-.. _Git:
-    https://git-scm.com/
-.. _Mercurial:
-    https://www.mercurial-scm.org/
diff --git a/docs/source/user/using-plugins.rst b/docs/source/user/using-plugins.rst
index 39715782..5d577c51 100644
--- a/docs/source/user/using-plugins.rst
+++ b/docs/source/user/using-plugins.rst
@@ -8,7 +8,7 @@ its extensibility. Our community has developed :term:`plugin`\ s that augment
 developers of these plugins often have some style they wish to enforce.
 
 For example, `flake8-docstrings`_ adds a check for :pep:`257` style
-conformance. Others attempt to enforce consistency, like `flake8-future`_.
+conformance. Others attempt to enforce consistency, like `flake8-quotes`_.
 
 .. note::
 
@@ -24,10 +24,8 @@ appropriate of:
     pip install <plugin-name>
     pip3 install <plugin-name>
     python -m pip install <plugin-name>
-    python2.7 -m pip install <plugin-name>
     python3 -m pip install <plugin-name>
-    python3.4 -m pip install <plugin-name>
-    python3.5 -m pip install <plugin-name>
+    python3.9 -m pip install <plugin-name>
 
 To install the plugin, where ``<plugin-name>`` is the package name on PyPI_.
 To verify installation use:
@@ -62,5 +60,5 @@ documented this for you.
     https://pypi.org/
 .. _flake8-docstrings:
     https://pypi.org/project/flake8-docstrings/
-.. _flake8-future:
-    https://pypi.org/project/flake8-future/
+.. _flake8-quotes:
+    https://pypi.org/project/flake8-quotes/
diff --git a/example-plugin/setup.py b/example-plugin/setup.py
index 2da68f6a..70d56fad 100644
--- a/example-plugin/setup.py
+++ b/example-plugin/setup.py
@@ -1,32 +1,29 @@
-# -*- coding: utf-8 -*-
 import setuptools
 
 setuptools.setup(
-    name='flake8-example-plugin',
-    license='MIT',
-    version='1.0.0',
-    description='Example plugin to Flake8',
-    author='Ian Cordasco',
-    author_email='graffatcolmingov@gmail.com',
-    url='https://gitlab.com/pycqa/flake8',
-    package_dir={'': 'src/'},
-    packages=['flake8_example_plugin'],
+    name="flake8-example-plugin",
+    license="MIT",
+    version="1.0.0",
+    description="Example plugin to Flake8",
+    author="Ian Cordasco",
+    author_email="graffatcolmingov@gmail.com",
+    url="https://github.com/pycqa/flake8",
+    package_dir={"": "src/"},
+    packages=["flake8_example_plugin"],
     entry_points={
-        'flake8.extension': [
-            'X1 = flake8_example_plugin:ExampleOne',
-            'X2 = flake8_example_plugin:ExampleTwo',
+        "flake8.extension": [
+            "X1 = flake8_example_plugin:ExampleOne",
+            "X2 = flake8_example_plugin:ExampleTwo",
         ],
     },
     classifiers=[
-        'Framework :: Flake8',
-        'License :: OSI Approved :: MIT License',
-        'Programming Language :: Python',
-        'Programming Language :: Python :: 2',
-        'Programming Language :: Python :: 2.7',
-        'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.4',
-        'Programming Language :: Python :: 3.5',
-        'Topic :: Software Development :: Libraries :: Python Modules',
-        'Topic :: Software Development :: Quality Assurance',
+        "Framework :: Flake8",
+        "License :: OSI Approved :: MIT License",
+        "Programming Language :: Python",
+        "Programming Language :: Python :: 3",
+        "Programming Language :: Python :: 3.8",
+        "Programming Language :: Python :: 3.9",
+        "Topic :: Software Development :: Libraries :: Python Modules",
+        "Topic :: Software Development :: Quality Assurance",
     ],
 )
diff --git a/example-plugin/src/flake8_example_plugin/__init__.py b/example-plugin/src/flake8_example_plugin/__init__.py
index 33d76f34..3f6f1632 100644
--- a/example-plugin/src/flake8_example_plugin/__init__.py
+++ b/example-plugin/src/flake8_example_plugin/__init__.py
@@ -1,9 +1,8 @@
 """Module for an example Flake8 plugin."""
-
-from .on_by_default import ExampleOne
 from .off_by_default import ExampleTwo
+from .on_by_default import ExampleOne
 
 __all__ = (
-    'ExampleOne',
-    'ExampleTwo',
+    "ExampleOne",
+    "ExampleTwo",
 )
diff --git a/example-plugin/src/flake8_example_plugin/off_by_default.py b/example-plugin/src/flake8_example_plugin/off_by_default.py
index d7683289..93dfb38a 100644
--- a/example-plugin/src/flake8_example_plugin/off_by_default.py
+++ b/example-plugin/src/flake8_example_plugin/off_by_default.py
@@ -1,10 +1,11 @@
 """Our first example plugin."""
 
 
-class ExampleTwo(object):
+class ExampleTwo:
     """Second Example Plugin."""
-    name = 'off-by-default-example-plugin'
-    version = '1.0.0'
+
+    name = "off-by-default-example-plugin"
+    version = "1.0.0"
 
     off_by_default = True
 
@@ -13,5 +14,9 @@ def __init__(self, tree):
 
     def run(self):
         """Do nothing."""
-        yield (1, 0, 'X200 The off-by-default plugin was enabled',
-               'OffByDefaultPlugin')
+        yield (
+            1,
+            0,
+            "X200 The off-by-default plugin was enabled",
+            "OffByDefaultPlugin",
+        )
diff --git a/example-plugin/src/flake8_example_plugin/on_by_default.py b/example-plugin/src/flake8_example_plugin/on_by_default.py
index a3242972..d712718c 100644
--- a/example-plugin/src/flake8_example_plugin/on_by_default.py
+++ b/example-plugin/src/flake8_example_plugin/on_by_default.py
@@ -1,15 +1,15 @@
 """Our first example plugin."""
 
 
-class ExampleOne(object):
+class ExampleOne:
     """First Example Plugin."""
-    name = 'on-by-default-example-plugin'
-    version = '1.0.0'
+
+    name = "on-by-default-example-plugin"
+    version = "1.0.0"
 
     def __init__(self, tree):
         self.tree = tree
 
     def run(self):
         """Do nothing."""
-        for message in []:
-            yield message
+        yield from []
diff --git a/pytest.ini b/pytest.ini
index 4a00d9ae..19782517 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -3,8 +3,4 @@ norecursedirs = .git .* *.egg* old docs dist build
 addopts = -rw
 filterwarnings =
     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
+    ignore:SelectableGroups:DeprecationWarning
diff --git a/setup.cfg b/setup.cfg
index 0d3ab08b..763992ba 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,18 +1,16 @@
-[bdist_wheel]
-universal = 1
-
 [metadata]
 name = flake8
 version = attr: flake8.__version__
-license = MIT
-license_file = LICENSE
 description = the modular source code checker: pep8 pyflakes and co
 long_description = file: README.rst
+long_description_content_type = text/x-rst
+url = https://github.com/pycqa/flake8
 author = Tarek Ziade
 author_email = tarek@ziade.org
 maintainer = Ian Stapleton Cordasco
 maintainer_email = graffatcolmingov@gmail.com
-url = https://gitlab.com/pycqa/flake8
+license = MIT
+license_file = LICENSE
 classifiers =
     Development Status :: 5 - Production/Stable
     Environment :: Console
@@ -20,44 +18,37 @@ classifiers =
     Intended Audience :: Developers
     License :: OSI Approved :: MIT License
     Programming Language :: Python
-    Programming Language :: Python :: 2
-    Programming Language :: Python :: 2.7
     Programming Language :: Python :: 3
-    Programming Language :: Python :: 3.5
+    Programming Language :: Python :: 3 :: Only
     Programming Language :: Python :: 3.6
     Programming Language :: Python :: 3.7
     Programming Language :: Python :: 3.8
     Programming Language :: Python :: 3.9
+    Programming Language :: Python :: 3.10
+    Programming Language :: Python :: Implementation :: CPython
+    Programming Language :: Python :: Implementation :: PyPy
     Topic :: Software Development :: Libraries :: Python Modules
     Topic :: Software Development :: Quality Assurance
 
 [options]
-package_dir=
-    =src
 packages = find:
-
-install_requires=
+package_dir =
+    =src
 # We document the reasoning for using ranges here:
 # http://flake8.pycqa.org/en/latest/faq.html#why-does-flake8-use-ranges-for-its-dependencies
 # And in which releases we will update those ranges here:
 # http://flake8.pycqa.org/en/latest/internal/releases.html#releasing-flake8
-    pyflakes >= 2.3.0, < 2.4.0
-    pycodestyle >= 2.7.0, < 2.8.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"
-
-python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*
+install_requires =
+    mccabe>=0.6.0,<0.7.0
+    pycodestyle>=2.8.0,<2.9.0
+    pyflakes>=2.4.0,<2.5.0
+    importlib-metadata<4.3;python_version<"3.8"
+python_requires = >=3.6
 
 [options.packages.find]
 where = src
 
 [options.entry_points]
-distutils.commands=
-    flake8 = flake8.main.setuptools_command:Flake8
 console_scripts =
     flake8 = flake8.main.cli:main
 flake8.extension =
@@ -104,6 +95,9 @@ flake8.report =
     quiet-filename = flake8.formatting.default:FilenameOnly
     quiet-nothing = flake8.formatting.default:Nothing
 
+[bdist_wheel]
+universal = 1
+
 [mypy]
 check_untyped_defs = true
 disallow_any_generics = true
diff --git a/setup.py b/setup.py
index 20ad3d31..3822d9ec 100644
--- a/setup.py
+++ b/setup.py
@@ -1,10 +1,9 @@
-# -*- coding: utf-8 -*-
 """Packaging logic for Flake8."""
 import os
 import sys
 
 import setuptools
 
-sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))  # noqa
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
 
 setuptools.setup()
diff --git a/src/flake8/__init__.py b/src/flake8/__init__.py
index 27b936b5..6b407488 100644
--- a/src/flake8/__init__.py
+++ b/src/flake8/__init__.py
@@ -11,17 +11,13 @@
 """
 import logging
 import sys
-
-if False:  # `typing.TYPE_CHECKING` was introduced in 3.5.2
-    from typing import Type  # `typing.Type` was introduced in 3.5.2
+from typing import Type
 
 LOG = logging.getLogger(__name__)
 LOG.addHandler(logging.NullHandler())
 
-__version__ = "3.9.2"
-__version_info__ = tuple(
-    int(i) for i in __version__.split(".") if i.isdigit()
-)
+__version__ = "4.0.1"
+__version_info__ = tuple(int(i) for i in __version__.split(".") if i.isdigit())
 
 
 # There is nothing lower than logging.DEBUG (10) in the logging library,
@@ -64,7 +60,7 @@ def configure_logging(verbosity, filename=None, logformat=LOG_FORMAT):
 
     if not filename or filename in ("stderr", "stdout"):
         fileobj = getattr(sys, filename or "stderr")
-        handler_cls = logging.StreamHandler  # type: Type[logging.Handler]
+        handler_cls: Type[logging.Handler] = logging.StreamHandler
     else:
         fileobj = filename
         handler_cls = logging.FileHandler
diff --git a/src/flake8/_compat.py b/src/flake8/_compat.py
index 85af0a35..18809e2b 100644
--- a/src/flake8/_compat.py
+++ b/src/flake8/_compat.py
@@ -1,14 +1,9 @@
 """Expose backports in a single place."""
 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+)
     import importlib.metadata as importlib_metadata
 else:  # pragma: no cover (<PY38)
     import importlib_metadata
 
-__all__ = ("lru_cache", "importlib_metadata")
+__all__ = ("importlib_metadata",)
diff --git a/src/flake8/api/legacy.py b/src/flake8/api/legacy.py
index 79236982..f80cb3d5 100644
--- a/src/flake8/api/legacy.py
+++ b/src/flake8/api/legacy.py
@@ -60,7 +60,7 @@ def get_style_guide(**kwargs):
     return StyleGuide(application)
 
 
-class StyleGuide(object):
+class StyleGuide:
     """Public facing object that mimic's Flake8 2.0's StyleGuide.
 
     .. note::
@@ -81,7 +81,7 @@ def __init__(self, application):
         self._file_checker_manager = application.file_checker_manager
 
     @property
-    def options(self):  # type: () -> argparse.Namespace
+    def options(self) -> argparse.Namespace:
         """Return application's options.
 
         An instance of :class:`argparse.Namespace` containing parsed options.
@@ -170,7 +170,7 @@ def input_file(self, filename, lines=None, expected=None, line_offset=0):
         return self.check_files([filename])
 
 
-class Report(object):
+class Report:
     """Public facing object that mimic's Flake8 2.0's API.
 
     .. note::
@@ -210,6 +210,6 @@ def get_statistics(self, violation):
             list
         """
         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)
         ]
diff --git a/src/flake8/checker.py b/src/flake8/checker.py
index b4ef97d2..14c122b4 100644
--- a/src/flake8/checker.py
+++ b/src/flake8/checker.py
@@ -4,19 +4,23 @@
 import itertools
 import logging
 import signal
-import sys
 import tokenize
-from typing import Dict, List, Optional, Tuple
+from typing import Dict
+from typing import List
+from typing import Optional
+from typing import Tuple
+
+from flake8 import defaults
+from flake8 import exceptions
+from flake8 import processor
+from flake8 import utils
 
 try:
     import multiprocessing.pool
 except ImportError:
     multiprocessing = None  # type: ignore
 
-from flake8 import defaults
-from flake8 import exceptions
-from flake8 import processor
-from flake8 import utils
+Results = List[Tuple[str, int, int, str, Optional[str]]]
 
 LOG = logging.getLogger(__name__)
 
@@ -27,7 +31,7 @@
     # > In those cases, we should replace the customized Queue Report
     # > class with pep8's StandardReport class to ensure users don't run
     # > into this problem.
-    # > (See also: https://gitlab.com/pycqa/flake8/issues/74)
+    # > (See also: https://github.com/pycqa/flake8/issues/117)
     errno.ENOSPC,
     # NOTE(sigmavirus24): When adding to this list, include the reasoning
     # on the lines before the error code and always append your error
@@ -38,17 +42,10 @@
 
 def _multiprocessing_is_fork():  # type () -> bool
     """Class state is only preserved when using the `fork` strategy."""
-    if sys.version_info >= (3, 4):
-        return (
-            multiprocessing
-            # https://github.com/python/typeshed/pull/3415
-            and multiprocessing.get_start_method() == "fork"  # type: ignore
-        )
-    else:
-        return multiprocessing and not utils.is_windows()
+    return multiprocessing and multiprocessing.get_start_method() == "fork"
 
 
-class Manager(object):
+class Manager:
     """Manage the parallelism and checker instances for each plugin and file.
 
     This class will be responsible for the following:
@@ -86,8 +83,8 @@ def __init__(self, style_guide, arguments, checker_plugins):
         self.options = style_guide.options
         self.checks = checker_plugins
         self.jobs = self._job_count()
-        self._all_checkers = []  # type: List[FileChecker]
-        self.checkers = []  # type: List[FileChecker]
+        self._all_checkers: List[FileChecker] = []
+        self.checkers: List[FileChecker] = []
         self.statistics = {
             "files": 0,
             "logical lines": 0,
@@ -104,8 +101,7 @@ def _process_statistics(self):
                 self.statistics[statistic] += checker.statistics[statistic]
         self.statistics["files"] += len(self.checkers)
 
-    def _job_count(self):
-        # type: () -> int
+    def _job_count(self) -> int:
         # First we walk through all of our error cases:
         # - multiprocessing library is not present
         # - we're running on windows in which case we know we have significant
@@ -166,8 +162,7 @@ def _handle_results(self, filename, results):
             )
         return reported_results_count
 
-    def is_path_excluded(self, path):
-        # type: (str) -> bool
+    def is_path_excluded(self, path: str) -> bool:
         """Check if a path is excluded.
 
         :param str path:
@@ -190,8 +185,7 @@ def is_path_excluded(self, path):
             logger=LOG,
         )
 
-    def make_checkers(self, paths=None):
-        # type: (Optional[List[str]]) -> None
+    def make_checkers(self, paths: Optional[List[str]] = None) -> None:
         """Create checkers for each file."""
         if paths is None:
             paths = self.arguments
@@ -200,7 +194,6 @@ def make_checkers(self, paths=None):
             paths = ["."]
 
         filename_patterns = self.options.filename
-        running_from_vcs = self.options._running_from_vcs
         running_from_diff = self.options.diff
 
         # NOTE(sigmavirus24): Yes this is a little unsightly, but it's our
@@ -218,10 +211,8 @@ def should_create_file_checker(filename, argument):
             # the event that the argument and the filename are identical.
             # If it was specified explicitly, the user intended for it to be
             # checked.
-            explicitly_provided = (
-                not running_from_vcs
-                and not running_from_diff
-                and (argument == filename)
+            explicitly_provided = not running_from_diff and (
+                argument == filename
             )
             return (
                 explicitly_provided or matches_filename_patterns
@@ -239,8 +230,7 @@ def should_create_file_checker(filename, argument):
         self.checkers = [c for c in self._all_checkers if c.should_process]
         LOG.info("Checking %d files", len(self.checkers))
 
-    def report(self):
-        # type: () -> Tuple[int, int]
+    def report(self) -> Tuple[int, int]:
         """Report all of the errors found in the managed file checkers.
 
         This iterates over each of the checkers and reports the errors sorted
@@ -253,20 +243,18 @@ def report(self):
         """
         results_reported = results_found = 0
         for checker in self._all_checkers:
-            results = sorted(
-                checker.results, key=lambda tup: (tup[1], tup[2])
-            )
+            results = sorted(checker.results, key=lambda tup: (tup[1], tup[2]))
             filename = checker.display_name
             with self.style_guide.processing_file(filename):
                 results_reported += self._handle_results(filename, results)
             results_found += len(results)
         return (results_found, results_reported)
 
-    def run_parallel(self):  # type: () -> None
+    def run_parallel(self) -> None:
         """Run the checkers in parallel."""
         # fmt: off
-        final_results = collections.defaultdict(list)  # type: Dict[str, List[Tuple[str, int, int, str, Optional[str]]]]  # noqa: E501
-        final_statistics = collections.defaultdict(dict)  # type: Dict[str, Dict[str, int]]  # noqa: E501
+        final_results: Dict[str, List[Tuple[str, int, int, str, Optional[str]]]] = collections.defaultdict(list)  # noqa: E501
+        final_statistics: Dict[str, Dict[str, int]] = collections.defaultdict(dict)  # noqa: E501
         # fmt: on
 
         pool = _try_initialize_processpool(self.jobs)
@@ -301,19 +289,19 @@ def run_parallel(self):  # type: () -> None
             checker.results = final_results[filename]
             checker.statistics = final_statistics[filename]
 
-    def run_serial(self):  # type: () -> None
+    def run_serial(self) -> None:
         """Run the checkers in serial."""
         for checker in self.checkers:
             checker.run_checks()
 
-    def run(self):  # type: () -> None
+    def run(self) -> None:
         """Run all the checkers.
 
         This will intelligently decide whether to run the checks in parallel
         or whether to run them in serial.
 
         If running the checks in parallel causes a problem (e.g.,
-        https://gitlab.com/pycqa/flake8/issues/74) this also implements
+        https://github.com/pycqa/flake8/issues/117) this also implements
         fallback to serial processing.
         """
         try:
@@ -340,7 +328,7 @@ def stop(self):
         self._process_statistics()
 
 
-class FileChecker(object):
+class FileChecker:
     """Manage running checks for a file and aggregate the results."""
 
     def __init__(self, filename, checks, options):
@@ -360,9 +348,7 @@ def __init__(self, filename, checks, options):
         self.options = options
         self.filename = filename
         self.checks = checks
-        # fmt: off
-        self.results = []  # type: List[Tuple[str, int, int, str, Optional[str]]]  # noqa: E501
-        # fmt: on
+        self.results: Results = []
         self.statistics = {
             "tokens": 0,
             "logical lines": 0,
@@ -376,34 +362,37 @@ def __init__(self, filename, checks, options):
             self.should_process = not self.processor.should_ignore_file()
             self.statistics["physical lines"] = len(self.processor.lines)
 
-    def __repr__(self):  # type: () -> str
+    def __repr__(self) -> str:
         """Provide helpful debugging representation."""
-        return "FileChecker for {}".format(self.filename)
+        return f"FileChecker for {self.filename}"
 
-    def _make_processor(self):
-        # type: () -> Optional[processor.FileProcessor]
+    def _make_processor(self) -> Optional[processor.FileProcessor]:
         try:
             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
             # does not exist or we do not have the permissions to open it)
             # then we need to format that exception for the user.
             # NOTE(sigmavirus24): Historically, pep8 has always reported this
             # as an E902. We probably *want* a better error code for this
             # going forward.
-            message = "{0}: {1}".format(type(e).__name__, e)
-            self.report("E902", 0, 0, message)
+            self.report("E902", 0, 0, f"{type(e).__name__}: {e}")
             return None
 
-    def report(self, error_code, line_number, column, text):
-        # type: (Optional[str], int, int, str) -> str
+    def report(
+        self,
+        error_code: Optional[str],
+        line_number: int,
+        column: int,
+        text: str,
+    ) -> str:
         """Report an error by storing it in the results list."""
         if error_code is None:
             error_code, text = text.split(" ", 1)
 
         # If we're recovering from a problem in _make_processor, we will not
         # have this attribute.
-        if hasattr(self, "processor"):
+        if hasattr(self, "processor") and self.processor is not None:
             line = self.processor.noqa_line_for(line_number)
         else:
             line = None
@@ -414,6 +403,7 @@ def report(self, error_code, line_number, column, text):
     def run_check(self, plugin, **arguments):
         """Run the check in a single plugin."""
         LOG.debug("Running %r with %r", plugin, arguments)
+        assert self.processor is not None
         try:
             self.processor.keyword_arguments_for(
                 plugin["parameters"], arguments
@@ -436,23 +426,45 @@ def run_check(self, plugin, **arguments):
             )
 
     @staticmethod
-    def _extract_syntax_information(exception):
-        token = ()
-        if len(exception.args) > 1:
+    def _extract_syntax_information(exception: Exception) -> Tuple[int, int]:
+        if (
+            len(exception.args) > 1
+            and exception.args[1]
+            and len(exception.args[1]) > 2
+        ):
             token = exception.args[1]
-            if token and len(token) > 2:
-                row, column = token[1:3]
+            row, column = token[1:3]
+        elif (
+            isinstance(exception, tokenize.TokenError)
+            and len(exception.args) == 2
+            and len(exception.args[1]) == 2
+        ):
+            token = ()
+            row, column = exception.args[1]
         else:
+            token = ()
             row, column = (1, 0)
 
-        if column > 0 and token and isinstance(exception, SyntaxError):
+        if (
+            column > 0
+            and token
+            and isinstance(exception, SyntaxError)
+            and len(token) == 4  # Python 3.9 or earlier
+        ):
             # NOTE(sigmavirus24): SyntaxErrors report 1-indexed column
             # numbers. We need to decrement the column number by 1 at
             # least.
             column_offset = 1
             row_offset = 0
-            # See also: https://gitlab.com/pycqa/flake8/issues/237
-            physical_line = token[-1]
+            # See also: https://github.com/pycqa/flake8/issues/169,
+            # https://github.com/PyCQA/flake8/issues/1372
+            # On Python 3.9 and earlier, token will be a 4-item tuple with the
+            # last item being the string. Starting with 3.10, they added to
+            # the tuple so now instead of it ending with the code that failed
+            # to parse, it ends with the end of the section of code that
+            # failed to parse. Luckily the absolute position in the tuple is
+            # stable across versions so we can use that here
+            physical_line = token[3]
 
             # NOTE(sigmavirus24): Not all "tokens" have a string as the last
             # argument. In this event, let's skip trying to find the correct
@@ -461,7 +473,7 @@ def _extract_syntax_information(exception):
                 # NOTE(sigmavirus24): SyntaxErrors also don't exactly have a
                 # "physical" line so much as what was accumulated by the point
                 # tokenizing failed.
-                # See also: https://gitlab.com/pycqa/flake8/issues/237
+                # See also: https://github.com/pycqa/flake8/issues/169
                 lines = physical_line.rstrip("\n").split("\n")
                 row_offset = len(lines) - 1
                 logical_line = lines[0]
@@ -472,16 +484,10 @@ def _extract_syntax_information(exception):
             column -= column_offset
         return row, column
 
-    def run_ast_checks(self):  # type: () -> None
+    def run_ast_checks(self) -> None:
         """Run all checks expecting an abstract syntax tree."""
-        try:
-            ast = self.processor.build_ast()
-        except (ValueError, SyntaxError, TypeError) as e:
-            row, column = self._extract_syntax_information(e)
-            self.report(
-                "E999", row, column, "%s: %s" % (type(e).__name__, e.args[0])
-            )
-            return
+        assert self.processor is not None
+        ast = self.processor.build_ast()
 
         for plugin in self.checks["ast_plugins"]:
             checker = self.run_check(plugin, tree=ast)
@@ -501,6 +507,7 @@ def run_ast_checks(self):  # type: () -> None
 
     def run_logical_checks(self):
         """Run all checks expecting a logical line."""
+        assert self.processor is not None
         comments, logical_line, mapping = self.processor.build_logical_line()
         if not mapping:
             return
@@ -529,6 +536,7 @@ def run_physical_checks(self, physical_line):
 
         A single physical check may return multiple errors.
         """
+        assert self.processor is not None
         for plugin in self.checks["physical_line_plugins"]:
             self.processor.update_checker_state_for(plugin)
             result = self.run_check(plugin, physical_line=physical_line)
@@ -557,10 +565,10 @@ def run_physical_checks(self, physical_line):
     def process_tokens(self):
         """Process tokens and trigger checks.
 
-        This can raise a :class:`flake8.exceptions.InvalidSyntax` exception.
         Instead of using this directly, you should use
         :meth:`flake8.checker.FileChecker.run_checks`.
         """
+        assert self.processor is not None
         parens = 0
         statistics = self.statistics
         file_processor = self.processor
@@ -582,18 +590,17 @@ def process_tokens(self):
             self.run_physical_checks(file_processor.lines[-1])
             self.run_logical_checks()
 
-    def run_checks(self):
+    def run_checks(self) -> Tuple[str, Results, Dict[str, int]]:
         """Run checks against the file."""
+        assert self.processor is not None
         try:
-            self.process_tokens()
             self.run_ast_checks()
-        except exceptions.InvalidSyntax as exc:
-            self.report(
-                exc.error_code,
-                exc.line_number,
-                exc.column_number,
-                exc.error_message,
-            )
+            self.process_tokens()
+        except (SyntaxError, tokenize.TokenError) as e:
+            code = "E902" if isinstance(e, tokenize.TokenError) else "E999"
+            row, column = self._extract_syntax_information(e)
+            self.report(code, row, column, f"{type(e).__name__}: {e.args[0]}")
+            return self.filename, self.results, self.statistics
 
         logical_lines = self.processor.statistics["logical lines"]
         self.statistics["logical lines"] = logical_lines
@@ -601,6 +608,7 @@ def run_checks(self):
 
     def handle_newline(self, token_type):
         """Handle the logic when encountering a newline token."""
+        assert self.processor is not None
         if token_type == tokenize.NEWLINE:
             self.run_logical_checks()
             self.processor.reset_blank_before()
@@ -611,9 +619,11 @@ def handle_newline(self, token_type):
         else:
             self.run_logical_checks()
 
-    def check_physical_eol(self, token, prev_physical):
-        # type: (processor._Token, str) -> None
+    def check_physical_eol(
+        self, token: processor._Token, prev_physical: str
+    ) -> None:
         """Run physical checks if and only if it is at the end of the line."""
+        assert self.processor is not None
         # a newline token ends a single physical line.
         if processor.is_eol_token(token):
             # if the file does not end with a newline, the NEWLINE
@@ -641,13 +651,14 @@ def check_physical_eol(self, token, prev_physical):
                     self.run_physical_checks(line + "\n")
 
 
-def _pool_init():  # type: () -> None
+def _pool_init() -> None:
     """Ensure correct signaling of ^C using multiprocessing.Pool."""
     signal.signal(signal.SIGINT, signal.SIG_IGN)
 
 
-def _try_initialize_processpool(job_count):
-    # type: (int) -> Optional[multiprocessing.pool.Pool]
+def _try_initialize_processpool(
+    job_count: int,
+) -> Optional[multiprocessing.pool.Pool]:
     """Return a new process pool instance if we are able to create one."""
     try:
         return multiprocessing.Pool(job_count, _pool_init)
@@ -666,8 +677,8 @@ def calculate_pool_chunksize(num_checkers, num_jobs):
     - For chunksize, see: https://docs.python.org/3/library/multiprocessing.html#multiprocessing.pool.Pool.imap  # noqa
     - This formula, while not perfect, aims to give each worker two batches of
       work.
-    - See: https://gitlab.com/pycqa/flake8/merge_requests/156#note_18878876
-    - See: https://gitlab.com/pycqa/flake8/issues/265
+    - See: https://github.com/pycqa/flake8/issues/829#note_18878876
+    - See: https://github.com/pycqa/flake8/issues/197
     """
     return max(num_checkers // (num_jobs * 2), 1)
 
@@ -676,8 +687,9 @@ def _run_checks(checker):
     return checker.run_checks()
 
 
-def find_offset(offset, mapping):
-    # type: (int, processor._LogicalMapping) -> Tuple[int, int]
+def find_offset(
+    offset: int, mapping: processor._LogicalMapping
+) -> Tuple[int, int]:
     """Find the offset tuple for a single offset."""
     if isinstance(offset, tuple):
         return offset
diff --git a/src/flake8/exceptions.py b/src/flake8/exceptions.py
index bef6f4b5..45db94d3 100644
--- a/src/flake8/exceptions.py
+++ b/src/flake8/exceptions.py
@@ -19,14 +19,13 @@ class FailedToLoadPlugin(Flake8Exception):
 
     FORMAT = 'Flake8 failed to load plugin "%(name)s" due to %(exc)s.'
 
-    def __init__(self, plugin_name, exception):
-        # type: (str, Exception) -> None
+    def __init__(self, plugin_name: str, exception: Exception) -> None:
         """Initialize our FailedToLoadPlugin exception."""
         self.plugin_name = plugin_name
         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."""
         return self.FORMAT % {
             "name": self.plugin_name,
@@ -34,40 +33,18 @@ def __str__(self):  # type: () -> str
         }
 
 
-class InvalidSyntax(Flake8Exception):
-    """Exception raised when tokenizing a file fails."""
-
-    def __init__(self, exception):  # type: (Exception) -> None
-        """Initialize our InvalidSyntax exception."""
-        self.original_exception = exception
-        self.error_message = "{0}: {1}".format(
-            exception.__class__.__name__, exception.args[0]
-        )
-        self.error_code = "E902"
-        self.line_number = 1
-        self.column_number = 0
-        super(InvalidSyntax, self).__init__(exception)
-
-    def __str__(self):  # type: () -> str
-        """Format our exception message."""
-        return self.error_message
-
-
 class PluginRequestedUnknownParameters(Flake8Exception):
     """The plugin requested unknown parameters."""
 
     FORMAT = '"%(name)s" requested unknown parameters causing %(exc)s'
 
-    def __init__(self, plugin, exception):
-        # type: (Dict[str, str], Exception) -> None
+    def __init__(self, plugin: Dict[str, str], exception: Exception) -> None:
         """Pop certain keyword arguments for initialization."""
         self.plugin = plugin
         self.original_exception = exception
-        super(PluginRequestedUnknownParameters, self).__init__(
-            plugin, exception
-        )
+        super().__init__(plugin, exception)
 
-    def __str__(self):  # type: () -> str
+    def __str__(self) -> str:
         """Format our exception message."""
         return self.FORMAT % {
             "name": self.plugin["plugin_name"],
@@ -80,64 +57,15 @@ class PluginExecutionFailed(Flake8Exception):
 
     FORMAT = '"%(name)s" failed during execution due to "%(exc)s"'
 
-    def __init__(self, plugin, exception):
-        # type: (Dict[str, str], Exception) -> None
+    def __init__(self, plugin: Dict[str, str], exception: Exception) -> None:
         """Utilize keyword arguments for message generation."""
         self.plugin = plugin
         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."""
         return self.FORMAT % {
             "name": self.plugin["plugin_name"],
             "exc": self.original_exception,
         }
-
-
-class HookInstallationError(Flake8Exception):
-    """Parent exception for all hooks errors."""
-
-
-class GitHookAlreadyExists(HookInstallationError):
-    """Exception raised when the git pre-commit hook file already exists."""
-
-    def __init__(self, path):  # type: (str) -> None
-        """Initialize the exception message from the `path`."""
-        self.path = path
-        tmpl = (
-            "The Git pre-commit hook ({0}) already exists. To convince "
-            "Flake8 to install the hook, please remove the existing "
-            "hook."
-        )
-        super(GitHookAlreadyExists, self).__init__(tmpl.format(self.path))
-
-
-class MercurialHookAlreadyExists(HookInstallationError):
-    """Exception raised when a mercurial hook is already configured."""
-
-    hook_name = None  # type: str
-
-    def __init__(self, path, value):  # type: (str, str) -> None
-        """Initialize the relevant attributes."""
-        self.path = path
-        self.value = value
-        tmpl = (
-            'The Mercurial {0} hook already exists with "{1}" in {2}. '
-            "To convince Flake8 to install the hook, please remove the "
-            "{0} configuration from the [hooks] section of your hgrc."
-        )
-        msg = tmpl.format(self.hook_name, self.value, self.path)
-        super(MercurialHookAlreadyExists, self).__init__(msg)
-
-
-class MercurialCommitHookAlreadyExists(MercurialHookAlreadyExists):
-    """Exception raised when the hg commit hook is already configured."""
-
-    hook_name = "commit"
-
-
-class MercurialQRefreshHookAlreadyExists(MercurialHookAlreadyExists):
-    """Exception raised when the hg commit hook is already configured."""
-
-    hook_name = "qrefresh"
diff --git a/src/flake8/formatting/base.py b/src/flake8/formatting/base.py
index ae78f494..7919f92e 100644
--- a/src/flake8/formatting/base.py
+++ b/src/flake8/formatting/base.py
@@ -1,15 +1,19 @@
 """The base class and interface for all formatting plugins."""
-from __future__ import print_function
-
 import argparse
-from typing import IO, List, Optional, Tuple
-
-if False:  # `typing.TYPE_CHECKING` was introduced in 3.5.2
+import os
+import sys
+from typing import IO
+from typing import List
+from typing import Optional
+from typing import Tuple
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
     from flake8.statistics import Statistics
     from flake8.style_guide import Violation
 
 
-class BaseFormatter(object):
+class BaseFormatter:
     """Class defining the formatter interface.
 
     .. attribute:: options
@@ -31,8 +35,7 @@ class BaseFormatter(object):
         output filename has been specified.
     """
 
-    def __init__(self, options):
-        # type: (argparse.Namespace) -> None
+    def __init__(self, options: argparse.Namespace) -> None:
         """Initialize with the options parsed from config and cli.
 
         This also calls a hook, :meth:`after_init`, so subclasses do not need
@@ -46,14 +49,14 @@ def __init__(self, options):
         """
         self.options = options
         self.filename = options.output_file
-        self.output_fd = None  # type: Optional[IO[str]]
+        self.output_fd: Optional[IO[str]] = None
         self.newline = "\n"
         self.after_init()
 
-    def after_init(self):  # type: () -> None
+    def after_init(self) -> None:
         """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.
 
         :param str filename:
@@ -61,7 +64,7 @@ def beginning(self, filename):  # type: (str) -> None
             from.
         """
 
-    def finished(self, filename):  # type: (str) -> None
+    def finished(self, filename: str) -> None:
         """Notify the formatter that we've finished processing a file.
 
         :param str filename:
@@ -69,15 +72,17 @@ def finished(self, filename):  # type: (str) -> None
             from.
         """
 
-    def start(self):  # type: () -> None
+    def start(self) -> None:
         """Prepare the formatter to receive input.
 
         This defaults to initializing :attr:`output_fd` if :attr:`filename`
         """
         if self.filename:
+            dirname = os.path.dirname(os.path.abspath(self.filename))
+            os.makedirs(dirname, exist_ok=True)
             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.
 
         This defaults to calling :meth:`format`, :meth:`show_source`, and
@@ -94,7 +99,7 @@ def handle(self, error):  # type: (Violation) -> None
         source = self.show_source(error)
         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.
 
         This method **must** be implemented by subclasses.
@@ -113,23 +118,16 @@ def format(self, error):  # type: (Violation) -> Optional[str]
             "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."""
         for error_code in statistics.error_codes():
             stats_for_error_code = statistics.statistics_for(error_code)
             statistic = next(stats_for_error_code)
             count = statistic.count
             count += sum(stat.count for stat in stats_for_error_code)
-            self._write(
-                "{count:<5} {error_code} {message}".format(
-                    count=count,
-                    error_code=error_code,
-                    message=statistic.message,
-                )
-            )
-
-    def show_benchmarks(self, benchmarks):
-        # type: (List[Tuple[str, float]]) -> None
+            self._write(f"{count:<5} {error_code} {statistic.message}")
+
+    def show_benchmarks(self, benchmarks: List[Tuple[str, float]]) -> None:
         """Format and print the benchmarks."""
         # NOTE(sigmavirus24): The format strings are a little confusing, even
         # to me, so here's a quick explanation:
@@ -150,7 +148,7 @@ def show_benchmarks(self, benchmarks):
                 benchmark = float_format(statistic=statistic, value=value)
             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.
 
         This also adds an indicator for the particular part of the line that
@@ -179,17 +177,16 @@ def show_source(self, error):  # type: (Violation) -> Optional[str]
         )
         # Physical lines have a newline at the end, no need to add an extra
         # 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()."""
         if self.output_fd is not None:
             self.output_fd.write(output + self.newline)
         if self.output_fd is None or self.options.tee:
-            print(output, end=self.newline)
+            sys.stdout.buffer.write(output.encode() + self.newline.encode())
 
-    def write(self, line, source):
-        # type: (Optional[str], Optional[str]) -> None
+    def write(self, line: Optional[str], source: Optional[str]) -> None:
         """Write the line either to the output file or stdout.
 
         This handles deciding whether to write to a file or print to standard
@@ -207,7 +204,7 @@ def write(self, line, source):
         if source:
             self._write(source)
 
-    def stop(self):  # type: () -> None
+    def stop(self) -> None:
         """Clean up after reporting is finished."""
         if self.output_fd is not None:
             self.output_fd.close()
diff --git a/src/flake8/formatting/default.py b/src/flake8/formatting/default.py
index 55a5d016..0a8e09dd 100644
--- a/src/flake8/formatting/default.py
+++ b/src/flake8/formatting/default.py
@@ -1,9 +1,11 @@
 """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
 
-if False:  # `typing.TYPE_CHECKING` was introduced in 3.5.2
+if TYPE_CHECKING:
     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.
 
         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"
 
-    def after_init(self):  # type: () -> None
+    def after_init(self) -> None:
         """Check for a custom format string."""
         if self.options.format.lower() != "default":
             self.error_format = self.options.format
@@ -66,18 +68,18 @@ class FilenameOnly(SimpleFormatter):
 
     error_format = "%(path)s"
 
-    def after_init(self):  # type: () -> None
+    def after_init(self) -> None:
         """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."""
 
-    def format(self, error):  # type: (Violation) -> Optional[str]
+    def format(self, error: "Violation") -> Optional[str]:
         """Ensure we only print each error once."""
         if error.filename not in self.filenames_already_printed:
             self.filenames_already_printed.add(error.filename)
-            return super(FilenameOnly, self).format(error)
+            return super().format(error)
         else:
             return None
 
@@ -85,8 +87,8 @@ def format(self, error):  # type: (Violation) -> Optional[str]
 class Nothing(base.BaseFormatter):
     """Print absolutely nothing."""
 
-    def format(self, error):  # type: (Violation) -> Optional[str]
+    def format(self, error: "Violation") -> Optional[str]:
         """Do nothing."""
 
-    def show_source(self, error):  # type: (Violation) -> Optional[str]
+    def show_source(self, error: "Violation") -> Optional[str]:
         """Do not print the source."""
diff --git a/src/flake8/main/application.py b/src/flake8/main/application.py
index 5c9e076a..44a55248 100644
--- a/src/flake8/main/application.py
+++ b/src/flake8/main/application.py
@@ -1,11 +1,15 @@
 """Module containing the application logic for Flake8."""
-from __future__ import print_function
-
 import argparse
 import logging
 import sys
 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
 from flake8 import checker
@@ -14,19 +18,19 @@
 from flake8 import style_guide
 from flake8 import utils
 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.plugins import manager as plugin_manager
 
-if False:  # `typing.TYPE_CHECKING` was introduced in 3.5.2
-    from typing import Type  # `typing.Type` was introduced in 3.5.2
+if TYPE_CHECKING:
     from flake8.formatting.base import BaseFormatter
 
 
 LOG = logging.getLogger(__name__)
 
 
-class Application(object):
+class Application:
     """Abstract our application into a class."""
 
     def __init__(self, program="flake8", version=flake8.__version__):
@@ -40,7 +44,7 @@ def __init__(self, program="flake8", version=flake8.__version__):
         #: The timestamp when the Application instance was instantiated.
         self.start_time = time.time()
         #: The timestamp when the Application finished reported errors.
-        self.end_time = None  # type: float
+        self.end_time: Optional[float] = None
         #: The name of the program being run
         self.program = program
         #: The version of the program being run
@@ -59,26 +63,26 @@ def __init__(self, program="flake8", version=flake8.__version__):
         options.register_default_options(self.option_manager)
 
         #: The instance of :class:`flake8.plugins.manager.Checkers`
-        self.check_plugins = None  # type: plugin_manager.Checkers
-        # fmt: off
+        self.check_plugins: Optional[plugin_manager.Checkers] = None
         #: The instance of :class:`flake8.plugins.manager.ReportFormatters`
-        self.formatting_plugins = None  # type: plugin_manager.ReportFormatters
-        # fmt: on
+        self.formatting_plugins: Optional[
+            plugin_manager.ReportFormatters
+        ] = None
         #: The user-selected formatter from :attr:`formatting_plugins`
-        self.formatter = None  # type: BaseFormatter
+        self.formatter: Optional[BaseFormatter] = None
         #: The :class:`flake8.style_guide.StyleGuideManager` built from the
         #: user's options
-        self.guide = None  # type: style_guide.StyleGuideManager
+        self.guide: Optional[style_guide.StyleGuideManager] = None
         #: The :class:`flake8.checker.Manager` that will handle running all of
         #: the checks selected by the user.
-        self.file_checker_manager = None  # type: checker.Manager
+        self.file_checker_manager: Optional[checker.Manager] = None
 
         #: The user-supplied options parsed into an instance of
         #: :class:`argparse.Namespace`
-        self.options = None  # type: argparse.Namespace
+        self.options: Optional[argparse.Namespace] = None
         #: The left over arguments that were not parsed by
         #: :attr:`option_manager`
-        self.args = None  # type: List[str]
+        self.args: Optional[List[str]] = None
         #: The number of errors, warnings, and other messages after running
         #: flake8 and taking into account ignored errors and lines.
         self.result_count = 0
@@ -92,10 +96,11 @@ def __init__(self, program="flake8", version=flake8.__version__):
         #: Whether the program is processing a diff or not
         self.running_against_diff = False
         #: 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):
-        # type: (List[str]) -> Tuple[argparse.Namespace, List[str]]
+    def parse_preliminary_options(
+        self, argv: List[str]
+    ) -> Tuple[argparse.Namespace, List[str]]:
         """Get preliminary options from the CLI, pre-plugin-loading.
 
         We need to know the values of a few standard options so that we can
@@ -119,13 +124,13 @@ def parse_preliminary_options(self, argv):
             rest.extend(("--output-file", args.output_file))
         return args, rest
 
-    def exit(self):
-        # type: () -> None
+    def exit(self) -> None:
         """Handle finalization and exiting the program.
 
         This should be the last thing called on the application instance. It
         will check certain options and exit appropriately.
         """
+        assert self.options is not None
         if self.options.count:
             print(self.result_count)
 
@@ -136,8 +141,7 @@ def exit(self):
                 (self.result_count > 0) or self.catastrophic_failure
             )
 
-    def find_plugins(self, config_finder):
-        # type: (config.ConfigFileFinder) -> None
+    def find_plugins(self, config_finder: config.ConfigFileFinder) -> None:
         """Find and load the plugins for this application.
 
         Set the :attr:`check_plugins` and :attr:`formatting_plugins` attributes
@@ -159,19 +163,19 @@ def find_plugins(self, config_finder):
         self.check_plugins.load_plugins()
         self.formatting_plugins.load_plugins()
 
-    def register_plugin_options(self):
-        # type: () -> None
+    def register_plugin_options(self) -> None:
         """Register options provided by plugins to our option manager."""
+        assert self.check_plugins is not None
         self.check_plugins.register_options(self.option_manager)
         self.check_plugins.register_plugin_versions(self.option_manager)
+        assert self.formatting_plugins is not None
         self.formatting_plugins.register_options(self.option_manager)
 
     def parse_configuration_and_cli(
         self,
-        config_finder,  # type: config.ConfigFileFinder
-        argv,  # type: List[str]
-    ):
-        # type: (...) -> None
+        config_finder: config.ConfigFileFinder,
+        argv: List[str],
+    ) -> None:
         """Parse configuration files and the CLI options.
 
         :param config.ConfigFileFinder config_finder:
@@ -191,17 +195,18 @@ def parse_configuration_and_cli(
             if not self.parsed_diff:
                 self.exit()
 
-        self.options._running_from_vcs = False
-
+        assert self.check_plugins is not None
         self.check_plugins.provide_options(
             self.option_manager, self.options, self.args
         )
+        assert self.formatting_plugins is not None
         self.formatting_plugins.provide_options(
             self.option_manager, self.options, self.args
         )
 
     def formatter_for(self, formatter_plugin_name):
         """Retrieve the formatter class by plugin name."""
+        assert self.formatting_plugins is not None
         default_formatter = self.formatting_plugins["default"]
         formatter_plugin = self.formatting_plugins.get(formatter_plugin_name)
         if formatter_plugin is None:
@@ -213,9 +218,11 @@ def formatter_for(self, formatter_plugin_name):
 
         return formatter_plugin.execute
 
-    def make_formatter(self, formatter_class=None):
-        # type: (Optional[Type[BaseFormatter]]) -> None
+    def make_formatter(
+        self, formatter_class: Optional[Type["BaseFormatter"]] = None
+    ) -> None:
         """Initialize a formatter based on the parsed options."""
+        assert self.options is not None
         format_plugin = self.options.format
         if 1 <= self.options.quiet < 2:
             format_plugin = "quiet-filename"
@@ -227,9 +234,10 @@ def make_formatter(self, formatter_class=None):
 
         self.formatter = formatter_class(self.options)
 
-    def make_guide(self):
-        # type: () -> None
+    def make_guide(self) -> None:
         """Initialize our StyleGuide."""
+        assert self.formatter is not None
+        assert self.options is not None
         self.guide = style_guide.StyleGuideManager(
             self.options, self.formatter
         )
@@ -237,8 +245,7 @@ def make_guide(self):
         if self.running_against_diff:
             self.guide.add_diff_ranges(self.parsed_diff)
 
-    def make_file_checker_manager(self):
-        # type: () -> None
+    def make_file_checker_manager(self) -> None:
         """Initialize our FileChecker Manager."""
         self.file_checker_manager = checker.Manager(
             style_guide=self.guide,
@@ -246,8 +253,7 @@ def make_file_checker_manager(self):
             checker_plugins=self.check_plugins,
         )
 
-    def run_checks(self, files=None):
-        # type: (Optional[List[str]]) -> None
+    def run_checks(self, files: Optional[List[str]] = None) -> None:
         """Run the actual checks with the FileChecker Manager.
 
         This method encapsulates the logic to make a
@@ -257,6 +263,7 @@ def run_checks(self, files=None):
         :param list files:
             List of filenames to process
         """
+        assert self.file_checker_manager is not None
         if self.running_against_diff:
             files = sorted(self.parsed_diff)
         self.file_checker_manager.start(files)
@@ -272,29 +279,33 @@ def run_checks(self, files=None):
 
     def report_benchmarks(self):
         """Aggregate, calculate, and report benchmarks for this run."""
+        assert self.options is not None
         if not self.options.benchmark:
             return
 
+        assert self.file_checker_manager is not None
+        assert self.end_time is not None
         time_elapsed = self.end_time - self.start_time
         statistics = [("seconds elapsed", time_elapsed)]
         add_statistic = statistics.append
         for statistic in defaults.STATISTIC_NAMES + ("files",):
             value = self.file_checker_manager.statistics[statistic]
-            total_description = "total " + statistic + " processed"
+            total_description = f"total {statistic} processed"
             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)))
 
+        assert self.formatter is not None
         self.formatter.show_benchmarks(statistics)
 
-    def report_errors(self):
-        # type: () -> None
+    def report_errors(self) -> None:
         """Report all the errors found by flake8 3.0.
 
         This also updates the :attr:`result_count` attribute with the total
         number of errors, warnings, and other messages found.
         """
         LOG.info("Reporting errors")
+        assert self.file_checker_manager is not None
         results = self.file_checker_manager.report()
         self.total_result_count, self.result_count = results
         LOG.info(
@@ -305,13 +316,15 @@ def report_errors(self):
 
     def report_statistics(self):
         """Aggregate and report statistics from this run."""
+        assert self.options is not None
         if not self.options.statistics:
             return
 
+        assert self.formatter is not None
+        assert self.guide is not None
         self.formatter.show_statistics(self.guide.stats)
 
-    def initialize(self, argv):
-        # type: (List[str]) -> None
+    def initialize(self, argv: List[str]) -> None:
         """Initialize the application to be run.
 
         This finds the plugins, registers their options, and parses the
@@ -339,20 +352,19 @@ def initialize(self, argv):
 
     def report(self):
         """Report errors, statistics, and benchmarks."""
+        assert self.formatter is not None
         self.formatter.start()
         self.report_errors()
         self.report_statistics()
         self.report_benchmarks()
         self.formatter.stop()
 
-    def _run(self, argv):
-        # type: (List[str]) -> None
+    def _run(self, argv: List[str]) -> None:
         self.initialize(argv)
         self.run_checks()
         self.report()
 
-    def run(self, argv):
-        # type: (List[str]) -> None
+    def run(self, argv: List[str]) -> None:
         """Run our application.
 
         This method will also handle KeyboardInterrupt exceptions for the
diff --git a/src/flake8/main/cli.py b/src/flake8/main/cli.py
index 1bc91557..ddbc7c07 100644
--- a/src/flake8/main/cli.py
+++ b/src/flake8/main/cli.py
@@ -1,12 +1,12 @@
 """Command-line implementation of flake8."""
 import sys
-from typing import List, Optional
+from typing import List
+from typing import Optional
 
 from flake8.main import application
 
 
-def main(argv=None):
-    # type: (Optional[List[str]]) -> None
+def main(argv: Optional[List[str]] = None) -> None:
     """Execute the main bit of the application.
 
     This handles the creation of an instance of :class:`Application`, runs it,
diff --git a/src/flake8/main/debug.py b/src/flake8/main/debug.py
index 83a5a11b..9f087c9a 100644
--- a/src/flake8/main/debug.py
+++ b/src/flake8/main/debug.py
@@ -1,10 +1,9 @@
 """Module containing the logic for our debugging logic."""
-from __future__ import print_function
-
 import argparse
 import json
 import platform
-from typing import Dict, List
+from typing import Dict
+from typing import List
 
 
 class DebugAction(argparse.Action):
@@ -17,7 +16,7 @@ def __init__(self, *args, **kwargs):
         used to delay response.
         """
         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):
         """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."""
     return []
diff --git a/src/flake8/main/git.py b/src/flake8/main/git.py
deleted file mode 100644
index 683396a0..00000000
--- a/src/flake8/main/git.py
+++ /dev/null
@@ -1,262 +0,0 @@
-"""Module containing the main git hook interface and helpers.
-
-.. autofunction:: hook
-.. autofunction:: install
-
-"""
-import contextlib
-import os
-import os.path
-import shutil
-import stat
-import subprocess
-import sys
-import tempfile
-
-from flake8 import defaults
-from flake8 import exceptions
-
-__all__ = ("hook", "install")
-
-
-def hook(lazy=False, strict=False):
-    """Execute Flake8 on the files in git's index.
-
-    Determine which files are about to be committed and run Flake8 over them
-    to check for violations.
-
-    :param bool lazy:
-        Find files not added to the index prior to committing. This is useful
-        if you frequently use ``git commit -a`` for example. This defaults to
-        False since it will otherwise include files not in the index.
-    :param bool strict:
-        If True, return the total number of errors/violations found by Flake8.
-        This will cause the hook to fail.
-    :returns:
-        Total number of errors found during the run.
-    :rtype:
-        int
-    """
-    # NOTE(sigmavirus24): Delay import of application until we need it.
-    from flake8.main import application
-
-    app = application.Application()
-    with make_temporary_directory() as tempdir:
-        filepaths = list(copy_indexed_files_to(tempdir, lazy))
-        app.initialize(["."])
-        app.options.exclude = update_excludes(app.options.exclude, tempdir)
-        app.options._running_from_vcs = True
-        # Apparently there are times when there are no files to check (e.g.,
-        # when amending a commit). In those cases, let's not try to run checks
-        # against nothing.
-        if filepaths:
-            app.run_checks(filepaths)
-
-    # If there were files to check, update their paths and report the errors
-    if filepaths:
-        update_paths(app.file_checker_manager, tempdir)
-        app.report_errors()
-
-    if strict:
-        return app.result_count
-    return 0
-
-
-def install():
-    """Install the git hook script.
-
-    This searches for the ``.git`` directory and will install an executable
-    pre-commit python script in the hooks sub-directory if one does not
-    already exist.
-
-    It will also print a message to stdout about how to configure the hook.
-
-    :returns:
-        True if successful, False if the git directory doesn't exist.
-    :rtype:
-        bool
-    :raises:
-        flake8.exceptions.GitHookAlreadyExists
-    """
-    git_directory = find_git_directory()
-    if git_directory is None or not os.path.exists(git_directory):
-        return False
-
-    hooks_directory = os.path.join(git_directory, "hooks")
-    if not os.path.exists(hooks_directory):
-        os.mkdir(hooks_directory)
-
-    pre_commit_file = os.path.abspath(
-        os.path.join(hooks_directory, "pre-commit")
-    )
-    if os.path.exists(pre_commit_file):
-        raise exceptions.GitHookAlreadyExists(path=pre_commit_file)
-
-    executable = get_executable()
-
-    with open(pre_commit_file, "w") as fd:
-        fd.write(_HOOK_TEMPLATE.format(executable=executable))
-
-    # NOTE(sigmavirus24): The following sets:
-    # - read, write, and execute permissions for the owner
-    # - read permissions for people in the group
-    # - read permissions for other people
-    # The owner needs the file to be readable, writable, and executable
-    # so that git can actually execute it as a hook.
-    pre_commit_permissions = stat.S_IRWXU | stat.S_IRGRP | stat.S_IROTH
-    os.chmod(pre_commit_file, pre_commit_permissions)
-
-    print("git pre-commit hook installed, for configuration options see")
-    print("http://flake8.pycqa.org/en/latest/user/using-hooks.html")
-
-    return True
-
-
-def get_executable():
-    if sys.executable is not None:
-        return sys.executable
-    return "/usr/bin/env python"
-
-
-def find_git_directory():
-    rev_parse = piped_process(["git", "rev-parse", "--git-dir"])
-
-    (stdout, _) = rev_parse.communicate()
-    stdout = to_text(stdout)
-
-    if rev_parse.returncode == 0:
-        return stdout.strip()
-    return None
-
-
-def copy_indexed_files_to(temporary_directory, lazy):
-    # some plugins (e.g. flake8-isort) need these files to run their checks
-    setup_cfgs = find_setup_cfgs(lazy)
-    for filename in setup_cfgs:
-        contents = get_staged_contents_from(filename)
-        copy_file_to(temporary_directory, filename, contents)
-
-    modified_files = find_modified_files(lazy)
-    for filename in modified_files:
-        contents = get_staged_contents_from(filename)
-        yield copy_file_to(temporary_directory, filename, contents)
-
-
-def copy_file_to(destination_directory, filepath, contents):
-    directory, filename = os.path.split(os.path.abspath(filepath))
-    temporary_directory = make_temporary_directory_from(
-        destination_directory, directory
-    )
-    if not os.path.exists(temporary_directory):
-        os.makedirs(temporary_directory)
-    temporary_filepath = os.path.join(temporary_directory, filename)
-    with open(temporary_filepath, "wb") as fd:
-        fd.write(contents)
-    return temporary_filepath
-
-
-def make_temporary_directory_from(destination, directory):
-    prefix = os.path.commonprefix([directory, destination])
-    common_directory_path = os.path.relpath(directory, start=prefix)
-    return os.path.join(destination, common_directory_path)
-
-
-def find_modified_files(lazy):
-    diff_index_cmd = [
-        "git",
-        "diff-index",
-        "--cached",
-        "--name-only",
-        "--diff-filter=ACMRTUXB",
-        "HEAD",
-    ]
-    if lazy:
-        diff_index_cmd.remove("--cached")
-
-    diff_index = piped_process(diff_index_cmd)
-    (stdout, _) = diff_index.communicate()
-    stdout = to_text(stdout)
-    return stdout.splitlines()
-
-
-def find_setup_cfgs(lazy):
-    setup_cfg_cmd = ["git", "ls-files", "--cached", "*setup.cfg"]
-    if lazy:
-        setup_cfg_cmd.remove("--cached")
-    extra_files = piped_process(setup_cfg_cmd)
-    (stdout, _) = extra_files.communicate()
-    stdout = to_text(stdout)
-    return stdout.splitlines()
-
-
-def get_staged_contents_from(filename):
-    git_show = piped_process(["git", "show", ":{0}".format(filename)])
-    (stdout, _) = git_show.communicate()
-    return stdout
-
-
-@contextlib.contextmanager
-def make_temporary_directory():
-    temporary_directory = tempfile.mkdtemp()
-    yield temporary_directory
-    shutil.rmtree(temporary_directory, ignore_errors=True)
-
-
-def to_text(string):
-    """Ensure that the string is text."""
-    if callable(getattr(string, "decode", None)):
-        return string.decode("utf-8")
-    return string
-
-
-def piped_process(command):
-    return subprocess.Popen(
-        command, stdout=subprocess.PIPE, stderr=subprocess.PIPE
-    )
-
-
-def git_config_for(parameter):
-    config = piped_process(["git", "config", "--get", "--bool", parameter])
-    (stdout, _) = config.communicate()
-    return to_text(stdout).strip()
-
-
-def config_for(parameter):
-    environment_variable = "flake8_{0}".format(parameter).upper()
-    git_variable = "flake8.{0}".format(parameter)
-    value = os.environ.get(environment_variable, git_config_for(git_variable))
-    return value.lower() in defaults.TRUTHY_VALUES
-
-
-def update_excludes(exclude_list, temporary_directory_path):
-    return [
-        (temporary_directory_path + pattern)
-        if os.path.isabs(pattern)
-        else pattern
-        for pattern in exclude_list
-    ]
-
-
-def update_paths(checker_manager, temp_prefix):
-    temp_prefix_length = len(temp_prefix)
-    for checker in checker_manager.checkers:
-        filename = checker.display_name
-        if filename.startswith(temp_prefix):
-            checker.display_name = os.path.relpath(
-                filename[temp_prefix_length:]
-            )
-
-
-_HOOK_TEMPLATE = """#!{executable}
-import sys
-
-from flake8.main import git
-
-if __name__ == '__main__':
-    sys.exit(
-        git.hook(
-            strict=git.config_for('strict'),
-            lazy=git.config_for('lazy'),
-        )
-    )
-"""
diff --git a/src/flake8/main/mercurial.py b/src/flake8/main/mercurial.py
deleted file mode 100644
index c387e848..00000000
--- a/src/flake8/main/mercurial.py
+++ /dev/null
@@ -1,145 +0,0 @@
-"""Module containing the main mecurial hook interface and helpers.
-
-.. autofunction:: hook
-.. autofunction:: install
-
-"""
-import configparser
-import os
-import subprocess
-from typing import Set
-
-from flake8 import exceptions as exc
-
-__all__ = ("hook", "install")
-
-
-def hook(ui, repo, **kwargs):
-    """Execute Flake8 on the repository provided by Mercurial.
-
-    To understand the parameters read more of the Mercurial documentation
-    around Hooks: https://www.mercurial-scm.org/wiki/Hook.
-
-    We avoid using the ``ui`` attribute because it can cause issues with
-    the GPL license that Mercurial is under. We don't import it, but we
-    avoid using it all the same.
-    """
-    from flake8.main import application
-
-    hgrc = find_hgrc(create_if_missing=False)
-    if hgrc is None:
-        print("Cannot locate your root mercurial repository.")
-        raise SystemExit(True)
-
-    hgconfig = configparser_for(hgrc)
-    strict = hgconfig.get("flake8", "strict", fallback=True)
-
-    filenames = list(get_filenames_from(repo, kwargs))
-
-    app = application.Application()
-    app.initialize(filenames)
-    app.options._running_from_vcs = True
-    app.run_checks()
-    app.report()
-
-    if strict:
-        return app.result_count
-    return 0
-
-
-def install():
-    """Ensure that the mercurial hooks are installed.
-
-    This searches for the ``.hg/hgrc`` configuration file and will add commit
-    and qrefresh hooks to it, if they do not already exist.
-
-    It will also print a message to stdout about how to configure the hook.
-
-    :returns:
-        True if successful, False if the ``.hg/hgrc`` file doesn't exist.
-    :rtype:
-        bool
-    :raises:
-        flake8.exceptions.MercurialCommitHookAlreadyExists
-    :raises:
-        flake8.exceptions.MercurialQRefreshHookAlreadyExists
-    """
-    hgrc = find_hgrc(create_if_missing=True)
-    if hgrc is None:
-        return False
-
-    hgconfig = configparser_for(hgrc)
-
-    if not hgconfig.has_section("hooks"):
-        hgconfig.add_section("hooks")
-
-    if hgconfig.has_option("hooks", "commit"):
-        raise exc.MercurialCommitHookAlreadyExists(
-            path=hgrc, value=hgconfig.get("hooks", "commit")
-        )
-
-    if hgconfig.has_option("hooks", "qrefresh"):
-        raise exc.MercurialQRefreshHookAlreadyExists(
-            path=hgrc, value=hgconfig.get("hooks", "qrefresh")
-        )
-
-    hgconfig.set("hooks", "commit", "python:flake8.main.mercurial.hook")
-    hgconfig.set("hooks", "qrefresh", "python:flake8.main.mercurial.hook")
-
-    if not hgconfig.has_section("flake8"):
-        hgconfig.add_section("flake8")
-
-    if not hgconfig.has_option("flake8", "strict"):
-        hgconfig.set("flake8", "strict", False)
-
-    with open(hgrc, "w") as fd:
-        hgconfig.write(fd)
-
-    print("mercurial hooks installed, for configuration options see")
-    print("http://flake8.pycqa.org/en/latest/user/using-hooks.html")
-
-    return True
-
-
-def get_filenames_from(repository, kwargs):
-    seen_filenames = set()  # type: Set[str]
-    node = kwargs["node"]
-    for revision in range(repository[node], len(repository)):
-        for filename in repository[revision].files():
-            full_filename = os.path.join(repository.root, filename)
-            have_seen_filename = full_filename in seen_filenames
-            filename_does_not_exist = not os.path.exists(full_filename)
-            if have_seen_filename or filename_does_not_exist:
-                continue
-
-            seen_filenames.add(full_filename)
-            if full_filename.endswith(".py"):
-                yield full_filename
-
-
-def find_hgrc(create_if_missing=False):
-    root = subprocess.Popen(
-        ["hg", "root"], stdout=subprocess.PIPE, stderr=subprocess.PIPE
-    )
-
-    (hg_directory, _) = root.communicate()
-    if callable(getattr(hg_directory, "decode", None)):
-        hg_directory = hg_directory.decode("utf-8")
-
-    if not os.path.isdir(hg_directory):
-        return None
-
-    hgrc = os.path.abspath(os.path.join(hg_directory, ".hg", "hgrc"))
-    if not os.path.exists(hgrc):
-        if create_if_missing:
-            open(hgrc, "w").close()
-        else:
-            return None
-
-    return hgrc
-
-
-def configparser_for(path):
-    parser = configparser.ConfigParser(interpolation=None)
-    parser.read(path)
-    return parser
diff --git a/src/flake8/main/options.py b/src/flake8/main/options.py
index 0a6ad45f..c35dbc61 100644
--- a/src/flake8/main/options.py
+++ b/src/flake8/main/options.py
@@ -4,11 +4,9 @@
 
 from flake8 import defaults
 from flake8.main import debug
-from flake8.main import vcs
 
 
-def register_preliminary_options(parser):
-    # type: (argparse.ArgumentParser) -> None
+def register_preliminary_options(parser: argparse.ArgumentParser) -> None:
     """Register the preliminary options on our OptionManager.
 
     The preliminary options include:
@@ -65,7 +63,7 @@ def register_preliminary_options(parser):
 class JobsArgument:
     """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.
 
         :param str arg:
@@ -79,7 +77,7 @@ def __init__(self, arg):  # type: (str) -> None
             self.n_jobs = int(arg)
         else:
             raise argparse.ArgumentTypeError(
-                "{!r} must be 'auto' or an integer.".format(arg),
+                f"{arg!r} must be 'auto' or an integer.",
             )
 
     def __str__(self):
@@ -107,6 +105,7 @@ def register_default_options(option_manager):
     - ``--max-doc-length``
     - ``--indent-size``
     - ``--select``
+    - ``--extend-select``
     - ``--disable-noqa``
     - ``--show-source``
     - ``--statistics``
@@ -261,7 +260,7 @@ def register_default_options(option_manager):
         metavar="n",
         default=defaults.INDENT_SIZE,
         parse_from_config=True,
-        help="Number of spaces used for indentation (Default: %default)",
+        help="Number of spaces used for indentation (Default: %(default)s)",
     )
 
     add_option(
@@ -274,6 +273,18 @@ def register_default_options(option_manager):
         " For example, ``--select=E4,E51,W234``. (Default: %(default)s)",
     )
 
+    add_option(
+        "--extend-select",
+        metavar="errors",
+        default="",
+        parse_from_config=True,
+        comma_separated_list=True,
+        help=(
+            "Comma-separated list of errors and warnings to add to the list "
+            "of selected ones. For example, ``--extend-select=E4,E51,W234``."
+        ),
+    )
+
     add_option(
         "--disable-noqa",
         default=False,
@@ -322,14 +333,6 @@ def register_default_options(option_manager):
         help='Exit with status code "0" even if there are errors.',
     )
 
-    add_option(
-        "--install-hook",
-        action=vcs.InstallAction,
-        choices=vcs.choices(),
-        help="Install a hook that is run prior to a commit for the supported "
-        "version control system.",
-    )
-
     add_option(
         "-j",
         "--jobs",
diff --git a/src/flake8/main/setuptools_command.py b/src/flake8/main/setuptools_command.py
deleted file mode 100644
index fde290e6..00000000
--- a/src/flake8/main/setuptools_command.py
+++ /dev/null
@@ -1,115 +0,0 @@
-"""The logic for Flake8's integration with setuptools."""
-from distutils import log
-import os
-from typing import List, Tuple
-
-import setuptools
-
-from flake8.main import application as app
-
-UNSET = object()
-
-
-class Flake8(setuptools.Command):
-    """Run Flake8 via setuptools/distutils for registered modules."""
-
-    description = "Run Flake8 on modules registered in setup.py"
-    # NOTE(sigmavirus24): If we populated this with a list of tuples, users
-    # could do something like ``python setup.py flake8 --ignore=E123,E234``
-    # but we would have to redefine it and we can't define it dynamically.
-    # Since I refuse to copy-and-paste the options here or maintain two lists
-    # of options, and since this will break when users use plugins that
-    # provide command-line options, we are leaving this empty. If users want
-    # to configure this command, they can do so through config files.
-    user_options = []  # type: List[str]
-
-    def initialize_options(self):
-        """Override this method to initialize our application."""
-        self.flake8 = app.Application()
-        self.flake8.initialize([])
-        options = self.flake8.option_manager.options
-        for option in options:
-            if option.parse_from_config:
-                setattr(self, option.config_name, UNSET)
-
-    def finalize_options(self):
-        """Override this to parse the parameters."""
-        options = self.flake8.option_manager.options
-        for option in options:
-            if option.parse_from_config:
-                name = option.config_name
-                value = getattr(self, name, UNSET)
-                if value is UNSET:
-                    continue
-                setattr(
-                    self.flake8.options,
-                    name,
-                    option.normalize_from_setuptools(value),
-                )
-
-    def package_files(self):
-        """Collect the files/dirs included in the registered modules."""
-        seen_package_directories = ()  # type: Tuple[str, ...]
-        directories = self.distribution.package_dir or {}
-        empty_directory_exists = "" in directories
-        packages = self.distribution.packages or []
-        for package in packages:
-            package_directory = package
-            if package in directories:
-                package_directory = directories[package]
-            elif empty_directory_exists:
-                package_directory = os.path.join(
-                    directories[""], package_directory
-                )
-
-            # NOTE(sigmavirus24): Do not collect submodules, e.g.,
-            # if we have:
-            #  - flake8/
-            #  - flake8/plugins/
-            # Flake8 only needs ``flake8/`` to be provided. It will
-            # recurse on its own.
-            if package_directory.startswith(seen_package_directories):
-                continue
-
-            seen_package_directories += (package_directory + ".",)
-            yield package_directory
-
-    def module_files(self):
-        """Collect the files listed as py_modules."""
-        modules = self.distribution.py_modules or []
-        filename_from = "{0}.py".format
-        for module in modules:
-            yield filename_from(module)
-
-    def distribution_files(self):
-        """Collect package and module files."""
-        for package in self.package_files():
-            yield package
-
-        for module in self.module_files():
-            yield module
-
-        yield "setup.py"
-
-    def run(self):
-        """Run the Flake8 application."""
-        self.flake8.run_checks(list(self.distribution_files()))
-        self.flake8.formatter.start()
-        self.flake8.report_errors()
-        self.flake8.report_statistics()
-        self.flake8.report_benchmarks()
-        self.flake8.formatter.stop()
-        try:
-            self.flake8.exit()
-        except SystemExit as e:
-            # Cause system exit only if exit code is not zero (terminates
-            # other possibly remaining/pending setuptools commands).
-            if e.code:
-                raise
-        finally:
-            self.announce(
-                "WARNING: flake8 setuptools integration is deprecated and "
-                "scheduled for removal in 4.x.  For more information, see "
-                "https://gitlab.com/pycqa/flake8/issues/544",
-                log.WARN,
-            )
diff --git a/src/flake8/main/vcs.py b/src/flake8/main/vcs.py
deleted file mode 100644
index bf153959..00000000
--- a/src/flake8/main/vcs.py
+++ /dev/null
@@ -1,48 +0,0 @@
-"""Module containing some of the logic for our VCS installation logic."""
-from __future__ import print_function
-
-import argparse
-import sys
-
-from flake8 import exceptions as exc
-from flake8.main import git
-from flake8.main import mercurial
-
-
-# NOTE(sigmavirus24): In the future, we may allow for VCS hooks to be defined
-# as plugins, e.g., adding a flake8.vcs entry-point. In that case, this
-# dictionary should disappear, and this module might contain more code for
-# managing those bits (in conjunction with flake8.plugins.manager).
-_INSTALLERS = {"git": git.install, "mercurial": mercurial.install}
-
-
-class InstallAction(argparse.Action):
-    """argparse action to run the hook installation."""
-
-    def __call__(self, parser, namespace, value, option_string=None):
-        """Perform the argparse action for installing vcs hooks."""
-        installer = _INSTALLERS[value]
-        errored = False
-        successful = False
-        try:
-            successful = installer()
-        except exc.HookInstallationError as hook_error:
-            print(str(hook_error))
-            errored = True
-
-        if not successful:
-            print("Could not find the {0} directory".format(value))
-
-        print(
-            "\nWARNING: flake8 vcs hooks integration is deprecated and "
-            "scheduled for removal in 4.x.  For more information, see "
-            "https://gitlab.com/pycqa/flake8/issues/568",
-            file=sys.stderr,
-        )
-
-        raise SystemExit(not successful and errored)
-
-
-def choices():
-    """Return the list of VCS choices."""
-    return list(_INSTALLERS)
diff --git a/src/flake8/options/aggregator.py b/src/flake8/options/aggregator.py
index 31adf9dd..73a0f36e 100644
--- a/src/flake8/options/aggregator.py
+++ b/src/flake8/options/aggregator.py
@@ -5,7 +5,8 @@
 """
 import argparse
 import logging
-from typing import List, Tuple
+from typing import List
+from typing import Tuple
 
 from flake8.options import config
 from flake8.options.manager import OptionManager
@@ -14,10 +15,10 @@
 
 
 def aggregate_options(
-    manager,  # type: OptionManager
-    config_finder,  # type: config.ConfigFileFinder
-    argv,  # type: List[str]
-):  # type: (...) -> Tuple[argparse.Namespace, List[str]]
+    manager: OptionManager,
+    config_finder: config.ConfigFileFinder,
+    argv: List[str],
+) -> Tuple[argparse.Namespace, List[str]]:
     """Aggregate and merge CLI and config file options.
 
     :param flake8.options.manager.OptionManager manager:
@@ -25,7 +26,7 @@ def aggregate_options(
     :param flake8.options.config.ConfigFileFinder config_finder:
         The config file finder to use.
     :param list argv:
-        The list of remaining command-line argumentsthat were unknown during
+        The list of remaining command-line arguments that were unknown during
         preliminary option parsing to pass to ``manager.parse_args``.
     :returns:
         Tuple of the parsed options and extra arguments returned by
@@ -37,7 +38,7 @@ def aggregate_options(
     default_values, _ = manager.parse_args([])
 
     # Make our new configuration file mergerator
-    config_parser = config.MergedConfigParser(
+    config_parser = config.ConfigParser(
         option_manager=manager, config_finder=config_finder
     )
 
diff --git a/src/flake8/options/config.py b/src/flake8/options/config.py
index 3dbaae7d..fc3b2054 100644
--- a/src/flake8/options/config.py
+++ b/src/flake8/options/config.py
@@ -3,26 +3,27 @@
 import configparser
 import logging
 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
 
 LOG = logging.getLogger(__name__)
 
-__all__ = ("ConfigFileFinder", "MergedConfigParser")
+__all__ = ("ConfigFileFinder", "ConfigParser")
 
 
-class ConfigFileFinder(object):
+class ConfigFileFinder:
     """Encapsulate the logic for finding and reading config files."""
 
     def __init__(
         self,
-        program_name,
-        extra_config_files=None,
-        config_file=None,
-        ignore_config_files=False,
-    ):
-        # type: (str, Optional[List[str]], Optional[str], bool) -> None
+        program_name: str,
+        extra_config_files: Optional[List[str]] = None,
+        config_file: Optional[str] = None,
+        ignore_config_files: bool = False,
+    ) -> None:
         """Initialize object to find config files.
 
         :param str program_name:
@@ -47,30 +48,16 @@ def __init__(
 
         # User configuration file.
         self.program_name = program_name
-        self.user_config_file = self._user_config_file(program_name)
 
         # 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)
 
     @staticmethod
-    def _user_config_file(program_name):
-        # type: (str) -> str
-        if utils.is_windows():
-            home_dir = os.path.expanduser("~")
-            config_file_basename = "." + program_name
-        else:
-            home_dir = os.environ.get(
-                "XDG_CONFIG_HOME", os.path.expanduser("~/.config")
-            )
-            config_file_basename = program_name
-
-        return os.path.join(home_dir, config_file_basename)
-
-    @staticmethod
-    def _read_config(*files):
-        # type: (*str) -> Tuple[configparser.RawConfigParser, List[str]]
+    def _read_config(
+        *files: str,
+    ) -> Tuple[configparser.RawConfigParser, List[str]]:
         config = configparser.RawConfigParser()
 
         found_files = []
@@ -91,8 +78,7 @@ def _read_config(*files):
                 )
         return (config, found_files)
 
-    def cli_config(self, files):
-        # type: (str) -> configparser.RawConfigParser
+    def cli_config(self, files: str) -> configparser.RawConfigParser:
         """Read and parse the config file specified on the command-line."""
         config, found_files = self._read_config(files)
         if found_files:
@@ -146,15 +132,8 @@ def local_configs(self):
         """Parse all local config files into one config object."""
         return self.local_configs_with_files()[0]
 
-    def user_config(self):
-        """Parse the user config file into a config object."""
-        config, found_files = self._read_config(self.user_config_file)
-        if found_files:
-            LOG.debug("Found user configuration files: %s", found_files)
-        return config
-
 
-class MergedConfigParser(object):
+class ConfigParser:
     """Encapsulate merging different types of configuration files.
 
     This parses out the options registered that were specified in the
@@ -167,7 +146,7 @@ class MergedConfigParser(object):
     GETBOOL_ACTIONS = {"store_true", "store_false"}
 
     def __init__(self, option_manager, config_finder):
-        """Initialize the MergedConfigParser instance.
+        """Initialize the ConfigParser instance.
 
         :param flake8.options.manager.OptionManager option_manager:
             Initialized OptionManager.
@@ -239,19 +218,6 @@ def parse_local_config(self):
         LOG.debug("Parsing local configuration files.")
         return self._parse_config(config)
 
-    def parse_user_config(self):
-        """Parse and return the user configuration files."""
-        config = self.config_finder.user_config()
-        if not self.is_configured_by(config):
-            LOG.debug(
-                "User configuration files have no %s section",
-                self.program_name,
-            )
-            return {}
-
-        LOG.debug("Parsing user configuration files.")
-        return self._parse_config(config)
-
     def parse_cli_config(self, config_path):
         """Parse and return the file specified by --config."""
         config = self.config_finder.cli_config(config_path)
@@ -265,28 +231,8 @@ def parse_cli_config(self, config_path):
         LOG.debug("Parsing CLI configuration files.")
         return self._parse_config(config, os.path.dirname(config_path))
 
-    def merge_user_and_local_config(self):
-        """Merge the parsed user and local configuration files.
-
-        :returns:
-            Dictionary of the parsed and merged configuration options.
-        :rtype:
-            dict
-        """
-        user_config = self.parse_user_config()
-        config = self.parse_local_config()
-
-        for option, value in user_config.items():
-            config.setdefault(option, value)
-
-        return config
-
     def parse(self):
-        """Parse and return the local and user config files.
-
-        First this copies over the parsed local configuration and then
-        iterates over the options in the user configuration and sets them if
-        they were not set by the local configuration file.
+        """Parse and return the local config files.
 
         :returns:
             Dictionary of parsed configuration options
@@ -309,7 +255,7 @@ def parse(self):
             )
             return self.parse_cli_config(self.config_finder.config_file)
 
-        return self.merge_user_and_local_config()
+        return self.parse_local_config()
 
 
 def get_local_plugins(config_finder):
@@ -344,7 +290,7 @@ def get_local_plugins(config_finder):
 
     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"]:
         if config.has_option(section, plugin_type):
             local_plugins_string = config.get(section, plugin_type).strip()
@@ -358,7 +304,7 @@ def get_local_plugins(config_finder):
         raw_paths = utils.parse_comma_separated_list(
             config.get(section, "paths").strip()
         )
-        norm_paths = []  # type: List[str]
+        norm_paths: List[str] = []
         for base_dir in base_dirs:
             norm_paths.extend(
                 path
@@ -369,6 +315,4 @@ def get_local_plugins(config_finder):
     return local_plugins
 
 
-LocalPlugins = collections.namedtuple(
-    "LocalPlugins", "extension report paths"
-)
+LocalPlugins = collections.namedtuple("LocalPlugins", "extension report paths")
diff --git a/src/flake8/options/manager.py b/src/flake8/options/manager.py
index 09bf862a..28cee4d0 100644
--- a/src/flake8/options/manager.py
+++ b/src/flake8/options/manager.py
@@ -5,14 +5,25 @@
 import enum
 import functools
 import logging
-from typing import Any, Callable, cast, Dict, Generator, List, Mapping
-from typing import Optional, Sequence, Set, Tuple, Union
+from typing import Any
+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
 
-if False:  # TYPE_CHECKING
+if TYPE_CHECKING:
     from typing import NoReturn
-    from typing import Type
 
 LOG = logging.getLogger(__name__)
 
@@ -21,7 +32,7 @@
 _ARG = enum.Enum("_ARG", "NO")
 
 
-_optparse_callable_map = {
+_optparse_callable_map: Dict[str, Union[Type[Any], _ARG]] = {
     "int": int,
     "long": int,
     "string": str,
@@ -30,27 +41,25 @@
     "choice": _ARG.NO,
     # optparse allows this but does not document it
     "str": str,
-}  # type: Dict[str, Union[Type[Any], _ARG]]
+}
 
 
 class _CallbackAction(argparse.Action):
     """Shim for optparse-style callback actions."""
 
-    def __init__(self, *args, **kwargs):
-        # type: (*Any, **Any) -> None
+    def __init__(self, *args: Any, **kwargs: Any) -> None:
         self._callback = kwargs.pop("callback")
         self._callback_args = kwargs.pop("callback_args", ())
         self._callback_kwargs = kwargs.pop("callback_kwargs", {})
-        super(_CallbackAction, self).__init__(*args, **kwargs)
+        super().__init__(*args, **kwargs)
 
     def __call__(
         self,
-        parser,  # type: argparse.ArgumentParser
-        namespace,  # type: argparse.Namespace
-        values,  # type: Optional[Union[Sequence[str], str]]
-        option_string=None,  # type: Optional[str]
-    ):
-        # type: (...) -> None
+        parser: argparse.ArgumentParser,
+        namespace: argparse.Namespace,
+        values: Optional[Union[Sequence[str], str]],
+        option_string: Optional[str] = None,
+    ) -> None:
         if not values:
             values = None
         elif isinstance(values, list) and len(values) > 1:
@@ -61,23 +70,24 @@ def __call__(
             values,
             parser,
             *self._callback_args,
-            **self._callback_kwargs
+            **self._callback_kwargs,
         )
 
 
-def _flake8_normalize(value, *args, **kwargs):
-    # type: (str, *str, **bool) -> Union[str, List[str]]
+def _flake8_normalize(
+    value: str, *args: str, **kwargs: bool
+) -> Union[str, List[str]]:
     comma_separated_list = kwargs.pop("comma_separated_list", False)
     normalize_paths = kwargs.pop("normalize_paths", False)
     if kwargs:
-        raise TypeError("Unexpected keyword args: {}".format(kwargs))
+        raise TypeError(f"Unexpected keyword args: {kwargs}")
 
-    ret = value  # type: Union[str, List[str]]
-    if comma_separated_list and isinstance(ret, utils.string_types):
+    ret: Union[str, List[str]] = value
+    if comma_separated_list and isinstance(ret, str):
         ret = utils.parse_comma_separated_list(value)
 
     if normalize_paths:
-        if isinstance(ret, utils.string_types):
+        if isinstance(ret, str):
             ret = utils.normalize_path(ret, *args)
         else:
             ret = utils.normalize_paths(ret, *args)
@@ -85,34 +95,34 @@ def _flake8_normalize(value, *args, **kwargs):
     return ret
 
 
-class Option(object):
+class Option:
     """Our wrapper around an argparse argument parsers to add features."""
 
     def __init__(
         self,
-        short_option_name=_ARG.NO,  # type: Union[str, _ARG]
-        long_option_name=_ARG.NO,  # type: Union[str, _ARG]
+        short_option_name: Union[str, _ARG] = _ARG.NO,
+        long_option_name: Union[str, _ARG] = _ARG.NO,
         # Options below here are taken from the optparse.Option class
-        action=_ARG.NO,  # type: Union[str, Type[argparse.Action], _ARG]
-        default=_ARG.NO,  # type: Union[Any, _ARG]
-        type=_ARG.NO,  # type: Union[str, Callable[..., Any], _ARG]
-        dest=_ARG.NO,  # type: Union[str, _ARG]
-        nargs=_ARG.NO,  # type: Union[int, str, _ARG]
-        const=_ARG.NO,  # type: Union[Any, _ARG]
-        choices=_ARG.NO,  # type: Union[Sequence[Any], _ARG]
-        help=_ARG.NO,  # type: Union[str, _ARG]
-        metavar=_ARG.NO,  # type: Union[str, _ARG]
+        action: Union[str, Type[argparse.Action], _ARG] = _ARG.NO,
+        default: Union[Any, _ARG] = _ARG.NO,
+        type: Union[str, Callable[..., Any], _ARG] = _ARG.NO,
+        dest: Union[str, _ARG] = _ARG.NO,
+        nargs: Union[int, str, _ARG] = _ARG.NO,
+        const: Union[Any, _ARG] = _ARG.NO,
+        choices: Union[Sequence[Any], _ARG] = _ARG.NO,
+        help: Union[str, _ARG] = _ARG.NO,
+        metavar: Union[str, _ARG] = _ARG.NO,
         # deprecated optparse-only options
-        callback=_ARG.NO,  # type: Union[Callable[..., Any], _ARG]
-        callback_args=_ARG.NO,  # type: Union[Sequence[Any], _ARG]
-        callback_kwargs=_ARG.NO,  # type: Union[Mapping[str, Any], _ARG]
+        callback: Union[Callable[..., Any], _ARG] = _ARG.NO,
+        callback_args: Union[Sequence[Any], _ARG] = _ARG.NO,
+        callback_kwargs: Union[Mapping[str, Any], _ARG] = _ARG.NO,
         # 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
-        parse_from_config=False,  # type: bool
-        comma_separated_list=False,  # type: bool
-        normalize_paths=False,  # type: bool
-    ):  # type: (...) -> None
+        parse_from_config: bool = False,
+        comma_separated_list: bool = False,
+        normalize_paths: bool = False,
+    ) -> None:
         """Initialize an Option instance.
 
         The following are all passed directly through to argparse.
@@ -203,7 +213,7 @@ def __init__(
                 nargs = 0
 
         # optparse -> argparse for `type`
-        if isinstance(type, utils.string_types):
+        if isinstance(type, str):
             LOG.warning(
                 "option %s: please update from optparse string `type=` to "
                 "argparse callable `type=` -- this will be an error in the "
@@ -240,7 +250,7 @@ def __init__(
         self.help = help
         self.metavar = metavar
         self.required = required
-        self.option_kwargs = {
+        self.option_kwargs: Dict[str, Union[Any, _ARG]] = {
             "action": self.action,
             "default": self.default,
             "type": self.type,
@@ -254,14 +264,14 @@ def __init__(
             "help": self.help,
             "metavar": self.metavar,
             "required": self.required,
-        }  # type: Dict[str, Union[Any, _ARG]]
+        }
 
         # Set our custom attributes
         self.parse_from_config = parse_from_config
         self.comma_separated_list = comma_separated_list
         self.normalize_paths = normalize_paths
 
-        self.config_name = None  # type: Optional[str]
+        self.config_name: Optional[str] = None
         if parse_from_config:
             if long_option_name is _ARG.NO:
                 raise ValueError(
@@ -273,26 +283,23 @@ def __init__(
         self._opt = None
 
     @property
-    def filtered_option_kwargs(self):  # type: () -> Dict[str, Any]
+    def filtered_option_kwargs(self) -> Dict[str, Any]:
         """Return any actually-specified arguments."""
         return {
             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 = []
         for arg in self.option_args:
             parts.append(arg)
         for k, v in self.filtered_option_kwargs.items():
-            parts.append("{}={!r}".format(k, v))
-        return "Option({})".format(", ".join(parts))
+            parts.append(f"{k}={v!r}")
+        return f"Option({', '.join(parts)})"
 
-    def normalize(self, value, *normalize_args):
-        # type: (Any, *str) -> Any
+    def normalize(self, value: Any, *normalize_args: str) -> Any:
         """Normalize the value based on the option configuration."""
-        if self.comma_separated_list and isinstance(
-            value, utils.string_types
-        ):
+        if self.comma_separated_list and isinstance(value, str):
             value = utils.parse_comma_separated_list(value)
 
         if self.normalize_paths:
@@ -303,8 +310,9 @@ def normalize(self, value, *normalize_args):
 
         return value
 
-    def normalize_from_setuptools(self, value):
-        # type: (str) -> Union[int, float, complex, bool, str]
+    def normalize_from_setuptools(
+        self, value: str
+    ) -> Union[int, float, complex, bool, str]:
         """Normalize the value received from setuptools."""
         value = self.normalize(value)
         if self.type is int or self.action == "count":
@@ -321,13 +329,12 @@ def normalize_from_setuptools(self, value):
                 return False
         return value
 
-    def to_argparse(self):
-        # type: () -> Tuple[List[str], Dict[str, Any]]
+    def to_argparse(self) -> Tuple[List[str], Dict[str, Any]]:
         """Convert a Flake8 Option to argparse ``add_argument`` arguments."""
         return self.option_args, self.filtered_option_kwargs
 
     @property
-    def to_optparse(self):  # type: () -> NoReturn
+    def to_optparse(self) -> "NoReturn":
         """No longer functional."""
         raise AttributeError("to_optparse: flake8 now uses argparse")
 
@@ -337,16 +344,16 @@ def to_optparse(self):  # type: () -> NoReturn
 )
 
 
-class OptionManager(object):
+class OptionManager:
     """Manage Options and OptionParser while adding post-processing."""
 
     def __init__(
         self,
-        prog,
-        version,
-        usage="%(prog)s [options] file file ...",
-        parents=None,
-    ):  # type: (str, str, str, Optional[List[argparse.ArgumentParser]]) -> None  # noqa: E501
+        prog: str,
+        version: str,
+        usage: str = "%(prog)s [options] file file ...",
+        parents: Optional[List[argparse.ArgumentParser]] = None,
+    ) -> None:  # noqa: E501
         """Initialize an instance of an OptionManager.
 
         :param str prog:
@@ -362,10 +369,10 @@ def __init__(
         if parents is None:
             parents = []
 
-        self.parser = argparse.ArgumentParser(
+        self.parser: argparse.ArgumentParser = argparse.ArgumentParser(
             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(
             "argparse._VersionAction",
             self.parser.add_argument(
@@ -373,16 +380,16 @@ def __init__(
             ),
         )
         self.parser.add_argument("filenames", nargs="*", metavar="filename")
-        self.config_options_dict = {}  # type: Dict[str, Option]
-        self.options = []  # type: List[Option]
+        self.config_options_dict: Dict[str, Option] = {}
+        self.options: List[Option] = []
         self.program_name = prog
         self.version = version
-        self.registered_plugins = set()  # type: Set[PluginVersion]
-        self.extended_default_ignore = set()  # type: Set[str]
-        self.extended_default_select = set()  # type: Set[str]
+        self.registered_plugins: Set[PluginVersion] = set()
+        self.extended_default_ignore: Set[str] = set()
+        self.extended_default_select: Set[str] = set()
 
     @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."""
         group = self.parser.add_argument_group(name)
         self._current_group, orig_group = group, self._current_group
@@ -391,7 +398,7 @@ def group(self, name):  # type: (str) -> Generator[None, None, None]
         finally:
             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.
 
         See parameters for :class:`~flake8.options.manager.Option` for
@@ -416,8 +423,7 @@ def add_option(self, *args, **kwargs):  # type: (*Any, **Any) -> None
             self.config_options_dict[name.replace("_", "-")] = option
         LOG.debug('Registered option "%s".', option)
 
-    def remove_from_default_ignore(self, error_codes):
-        # type: (Sequence[str]) -> None
+    def remove_from_default_ignore(self, error_codes: Sequence[str]) -> None:
         """Remove specified error codes from the default ignore list.
 
         :param list error_codes:
@@ -435,8 +441,7 @@ def remove_from_default_ignore(self, error_codes):
                     error_code,
                 )
 
-    def extend_default_ignore(self, error_codes):
-        # type: (Sequence[str]) -> None
+    def extend_default_ignore(self, error_codes: Sequence[str]) -> None:
         """Extend the default ignore list with the error codes provided.
 
         :param list error_codes:
@@ -446,8 +451,7 @@ def extend_default_ignore(self, error_codes):
         LOG.debug("Extending default ignore list with %r", error_codes)
         self.extended_default_ignore.update(error_codes)
 
-    def extend_default_select(self, error_codes):
-        # type: (Sequence[str]) -> None
+    def extend_default_select(self, error_codes: Sequence[str]) -> None:
         """Extend the default select list with the error codes provided.
 
         :param list error_codes:
@@ -458,22 +462,21 @@ def extend_default_select(self, error_codes):
         self.extended_default_select.update(error_codes)
 
     def generate_versions(
-        self, format_str="%(name)s: %(version)s", join_on=", "
-    ):
-        # type: (str, str) -> str
+        self, format_str: str = "%(name)s: %(version)s", join_on: str = ", "
+    ) -> str:
         """Generate a comma-separated list of versions of plugins."""
         return join_on.join(
             format_str % plugin._asdict()
             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."""
         self.version_action.version = "{} ({}) {}".format(
             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."""
         plugin_version_format = "%(name)s: %(version)s"
         self.parser.epilog = "Installed plugins: " + self.generate_versions(
@@ -482,10 +485,9 @@ def generate_epilog(self):  # type: () -> None
 
     def parse_args(
         self,
-        args=None,  # type: Optional[List[str]]
-        values=None,  # type: Optional[argparse.Namespace]
-    ):
-        # type: (...) -> Tuple[argparse.Namespace, List[str]]
+        args: Optional[List[str]] = None,
+        values: Optional[argparse.Namespace] = None,
+    ) -> Tuple[argparse.Namespace, List[str]]:
         """Proxy to calling the OptionParser's parse_args method."""
         self.generate_epilog()
         self.update_version_string()
@@ -495,8 +497,9 @@ def parse_args(
         # TODO: refactor callers to not need this
         return parsed_args, parsed_args.filenames
 
-    def parse_known_args(self, args=None):
-        # type: (Optional[List[str]]) -> Tuple[argparse.Namespace, List[str]]
+    def parse_known_args(
+        self, args: Optional[List[str]] = None
+    ) -> Tuple[argparse.Namespace, List[str]]:
         """Parse only the known arguments from the argument values.
 
         Replicate a little argparse behaviour while we're still on
@@ -506,8 +509,9 @@ def parse_known_args(self, args=None):
         self.update_version_string()
         return self.parser.parse_known_args(args)
 
-    def register_plugin(self, name, version, local=False):
-        # type: (str, str, bool) -> None
+    def register_plugin(
+        self, name: str, version: str, local: bool = False
+    ) -> None:
         """Register a plugin relying on the OptionManager.
 
         :param str name:
diff --git a/src/flake8/plugins/manager.py b/src/flake8/plugins/manager.py
index 3d4371db..6f32e1f1 100644
--- a/src/flake8/plugins/manager.py
+++ b/src/flake8/plugins/manager.py
@@ -1,6 +1,10 @@
 """Plugin loading and management logic and classes."""
 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 utils
@@ -13,7 +17,7 @@
 NO_GROUP_FOUND = object()
 
 
-class Plugin(object):
+class Plugin:
     """Wrap an EntryPoint from setuptools and other logic."""
 
     def __init__(self, name, entry_point, local=False):
@@ -31,16 +35,16 @@ def __init__(self, name, entry_point, local=False):
         self.name = name
         self.entry_point = entry_point
         self.local = local
-        self._plugin = None  # type: Any
+        self._plugin: Any = None
         self._parameters = None
-        self._parameter_names = None  # type: Optional[List[str]]
+        self._parameter_names: Optional[List[str]] = None
         self._group = None
         self._plugin_name = None
         self._version = None
 
-    def __repr__(self):  # type: () -> str
+    def __repr__(self) -> str:
         """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
         )
 
@@ -84,7 +88,7 @@ def parameters(self):
         return self._parameters
 
     @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."""
         if self._parameter_names is None:
             self._parameter_names = list(self.parameters)
@@ -100,7 +104,7 @@ def plugin(self):
         return self._plugin
 
     @property
-    def version(self):  # type: () -> str
+    def version(self) -> str:
         """Return the version of the plugin."""
         version = self._version
         if version is None:
@@ -134,9 +138,9 @@ def _load(self):
         self._plugin = self.entry_point.load()
         if not callable(self._plugin):
             msg = (
-                "Plugin %r is not a callable. It might be written for an"
-                " older version of flake8 and might not work with this"
-                " version" % self._plugin
+                f"Plugin {self._plugin!r} is not a callable. It might be "
+                f"written for an older version of flake8 and might not work "
+                f"with this version"
             )
             LOG.critical(msg)
             raise TypeError(msg)
@@ -219,11 +223,12 @@ def register_options(self, 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."""
 
-    def __init__(self, namespace, local_plugins=None):
-        # type: (str, Optional[List[str]]) -> None
+    def __init__(
+        self, namespace: str, local_plugins: Optional[List[str]] = None
+    ) -> None:
         """Initialize the manager.
 
         :param str namespace:
@@ -232,8 +237,8 @@ def __init__(self, namespace, local_plugins=None):
             Plugins from config (as "X = path.to:Plugin" strings).
         """
         self.namespace = namespace
-        self.plugins = {}  # type: Dict[str, Plugin]
-        self.names = []  # type: List[str]
+        self.plugins: Dict[str, Plugin] = {}
+        self.names: List[str] = []
         self._load_local_plugins(local_plugins or [])
         self._load_entrypoint_plugins()
 
@@ -246,7 +251,9 @@ def _load_local_plugins(self, local_plugins):
         for plugin_str in local_plugins:
             name, _, entry_str = plugin_str.partition("=")
             name, entry_str = name.strip(), entry_str.strip()
-            entry_point = importlib_metadata.EntryPoint(name, entry_str, None)
+            entry_point = importlib_metadata.EntryPoint(
+                name, entry_str, self.namespace
+            )
             self._load_plugin_from_entrypoint(entry_point, local=True)
 
     def _load_entrypoint_plugins(self):
@@ -310,7 +317,7 @@ def versions(self):
         :rtype:
             tuple
         """
-        plugins_seen = set()  # type: Set[str]
+        plugins_seen: Set[str] = set()
         for entry_point_name in self.names:
             plugin = self.plugins[entry_point_name]
             plugin_name = plugin.plugin_name
@@ -342,10 +349,10 @@ def version_for(plugin):
     return getattr(module, "__version__", None)
 
 
-class PluginTypeManager(object):
+class PluginTypeManager:
     """Parent class for most of the specific plugin types."""
 
-    namespace = None  # type: str
+    namespace: str
 
     def __init__(self, local_plugins=None):
         """Initialize the plugin type's manager.
@@ -396,7 +403,7 @@ def plugins(self):
 
     @staticmethod
     def _generate_call_function(method_name, optmanager, *args, **kwargs):
-        def generated_function(plugin):  # noqa: D105
+        def generated_function(plugin):
             method = getattr(plugin, method_name, None)
             if method is not None and callable(method):
                 return method(optmanager, *args, **kwargs)
@@ -465,8 +472,7 @@ def to_dictionary(self):
                 plugin.to_dictionary() for plugin in self.logical_line_plugins
             ],
             "physical_line_plugins": [
-                plugin.to_dictionary()
-                for plugin in self.physical_line_plugins
+                plugin.to_dictionary() for plugin in self.physical_line_plugins
             ],
         }
 
diff --git a/src/flake8/plugins/pyflakes.py b/src/flake8/plugins/pyflakes.py
index b9b30d06..4d1d7b83 100644
--- a/src/flake8/plugins/pyflakes.py
+++ b/src/flake8/plugins/pyflakes.py
@@ -1,23 +1,11 @@
 """Plugin built-in to Flake8 to treat pyflakes as a plugin."""
-# -*- coding: utf-8 -*-
-from __future__ import absolute_import
-
-try:
-    # The 'demandimport' breaks pyflakes and flake8.plugins.pyflakes
-    from mercurial import demandimport
-except ImportError:
-    pass
-else:
-    demandimport.disable()
 import os
 from typing import List
 
-import pyflakes
 import pyflakes.checker
 
 from flake8 import utils
 
-
 FLAKE8_PYFLAKES_CODES = {
     "UnusedImport": "F401",
     "ImportShadowedByLoopVar": "F402",
@@ -76,8 +64,8 @@ class FlakesChecker(pyflakes.checker.Checker):
     name = "pyflakes"
     version = pyflakes.__version__
     with_doctest = False
-    include_in_doctest = []  # type: List[str]
-    exclude_from_doctest = []  # type: List[str]
+    include_in_doctest: List[str] = []
+    exclude_from_doctest: List[str] = []
 
     def __init__(self, tree, file_tokens, filename):
         """Initialize the PyFlakes plugin with an AST tree and filename."""
@@ -103,7 +91,7 @@ def __init__(self, tree, file_tokens, filename):
                 if overlaped_by:
                     with_doctest = True
 
-        super(FlakesChecker, self).__init__(
+        super().__init__(
             tree,
             filename=filename,
             withDoctest=with_doctest,
@@ -157,7 +145,7 @@ def parse_options(cls, options):
             if included_file == "":
                 continue
             if not included_file.startswith((os.sep, "./", "~/")):
-                included_files.append("./" + included_file)
+                included_files.append(f"./{included_file}")
             else:
                 included_files.append(included_file)
         cls.include_in_doctest = utils.normalize_paths(included_files)
@@ -167,7 +155,7 @@ def parse_options(cls, options):
             if excluded_file == "":
                 continue
             if not excluded_file.startswith((os.sep, "./", "~/")):
-                excluded_files.append("./" + excluded_file)
+                excluded_files.append(f"./{excluded_file}")
             else:
                 excluded_files.append(excluded_file)
         cls.exclude_from_doctest = utils.normalize_paths(excluded_files)
@@ -177,10 +165,10 @@ def parse_options(cls, options):
         )
         if inc_exc:
             raise ValueError(
-                '"%s" was specified in both the '
-                "include-in-doctest and exclude-from-doctest "
-                "options. You are not allowed to specify it in "
-                "both for doctesting." % inc_exc
+                f"{inc_exc!r} was specified in both the "
+                f"include-in-doctest and exclude-from-doctest "
+                f"options. You are not allowed to specify it in "
+                f"both for doctesting."
             )
 
     def run(self):
diff --git a/src/flake8/processor.py b/src/flake8/processor.py
index fe9113ce..6b7d3c4e 100644
--- a/src/flake8/processor.py
+++ b/src/flake8/processor.py
@@ -3,13 +3,16 @@
 import ast
 import contextlib
 import logging
-import sys
 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
 from flake8 import defaults
-from flake8 import exceptions
 from flake8 import utils
 
 LOG = logging.getLogger(__name__)
@@ -25,7 +28,7 @@
 _Logical = Tuple[List[str], List[str], _LogicalMapping]
 
 
-class FileProcessor(object):
+class FileProcessor:
     """Processes a file and holdes state.
 
     This processes a file by generating tokens, logical and physical lines,
@@ -56,8 +59,12 @@ class FileProcessor(object):
     #: always ``False``, included for compatibility
     noqa = False
 
-    def __init__(self, filename, options, lines=None):
-        # type: (str, argparse.Namespace, Optional[List[str]]) -> None
+    def __init__(
+        self,
+        filename: str,
+        options: argparse.Namespace,
+        lines: Optional[List[str]] = None,
+    ) -> None:
         """Initialice our file processor.
 
         :param str filename:
@@ -74,13 +81,13 @@ def __init__(self, filename, options, lines=None):
         #: Number of blank lines
         self.blank_lines = 0
         #: 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
-        self.checker_state = {}  # type: Dict[Any, Any]
+        self.checker_state: Dict[Any, Any] = {}
         #: User provided option for hang closing
         self.hang_closing = options.hang_closing
         #: Character used for indentation
-        self.indent_char = None  # type: Optional[str]
+        self.indent_char: Optional[str] = None
         #: Current level of indentation
         self.indent_level = 0
         #: Number of spaces used for indentation
@@ -104,59 +111,51 @@ def __init__(self, filename, options, lines=None):
         #: Previous unindented (i.e. top-level) logical line
         self.previous_unindented_logical_line = ""
         #: Current set of tokens
-        self.tokens = []  # type: List[_Token]
+        self.tokens: List[_Token] = []
         #: Total number of lines in the file
         self.total_lines = len(self.lines)
         #: Verbosity level of Flake8
         self.verbose = options.verbose
         #: Statistics dictionary
         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
-        self._noqa_line_mapping = None  # type: Optional[Dict[int, str]]
+        self._noqa_line_mapping: Optional[Dict[int, str]] = None
 
     @property
-    def file_tokens(self):  # type: () -> List[_Token]
-        """Return the complete set of tokens for a file.
-
-        Accessing this attribute *may* raise an InvalidSyntax exception.
-
-        :raises: flake8.exceptions.InvalidSyntax
-        """
+    def file_tokens(self) -> List[_Token]:
+        """Return the complete set of tokens for a file."""
         if self._file_tokens is None:
             line_iter = iter(self.lines)
-            try:
-                self._file_tokens = list(
-                    tokenize.generate_tokens(lambda: next(line_iter))
-                )
-            except (tokenize.TokenError, SyntaxError) as exc:
-                raise exceptions.InvalidSyntax(exception=exc)
+            self._file_tokens = list(
+                tokenize.generate_tokens(lambda: next(line_iter))
+            )
 
         return self._file_tokens
 
     @contextlib.contextmanager
-    def inside_multiline(self, line_number):
-        # type: (int) -> Generator[None, None, None]
+    def inside_multiline(
+        self, line_number: int
+    ) -> Generator[None, None, None]:
         """Context-manager to toggle the multiline attribute."""
         self.line_number = line_number
         self.multiline = True
         yield
         self.multiline = False
 
-    def reset_blank_before(self):  # type: () -> None
+    def reset_blank_before(self) -> None:
         """Reset the blank_before attribute to zero."""
         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."""
         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."""
         self.blank_lines += 1
 
-    def update_state(self, mapping):
-        # type: (_LogicalMapping) -> None
+    def update_state(self, mapping: _LogicalMapping) -> None:
         """Update the indent level based on the logical line mapping."""
         (start_row, start_col) = mapping[0][1]
         start_line = self.lines[start_row - 1]
@@ -164,15 +163,14 @@ def update_state(self, mapping):
         if self.blank_before < self.blank_lines:
             self.blank_before = self.blank_lines
 
-    def update_checker_state_for(self, plugin):
-        # type: (Dict[str, Any]) -> None
+    def update_checker_state_for(self, plugin: Dict[str, Any]) -> None:
         """Update the checker_state attribute for the plugin."""
         if "checker_state" in plugin["parameters"]:
             self.checker_state = self._checker_states.setdefault(
                 plugin["name"], {}
             )
 
-    def next_logical_line(self):  # type: () -> None
+    def next_logical_line(self) -> None:
         """Record the previous logical line.
 
         This also resets the tokens list and the blank_lines count.
@@ -185,11 +183,11 @@ def next_logical_line(self):  # type: () -> None
         self.blank_lines = 0
         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."""
         logical = []
         comments = []
-        mapping = []  # type: _LogicalMapping
+        mapping: _LogicalMapping = []
         length = 0
         previous_row = previous_column = None
         for token_type, text, start, end, line in self.tokens:
@@ -211,7 +209,7 @@ def build_logical_line_tokens(self):  # type: () -> _Logical
                     if previous_text == "," or (
                         previous_text not in "{[(" and text not in "}])"
                     ):
-                        text = " " + text
+                        text = f" {text}"
                 elif previous_column != start_column:
                     text = line[previous_column:start_column] + text
             logical.append(text)
@@ -220,12 +218,11 @@ def build_logical_line_tokens(self):  # type: () -> _Logical
             (previous_row, previous_column) = end
         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."""
         return ast.parse("".join(self.lines))
 
-    def build_logical_line(self):
-        # type: () -> Tuple[str, str, _LogicalMapping]
+    def build_logical_line(self) -> Tuple[str, str, _LogicalMapping]:
         """Build a logical line from the current tokens list."""
         comments, logical, mapping_list = self.build_logical_line_tokens()
         joined_comments = "".join(comments)
@@ -233,8 +230,7 @@ def build_logical_line(self):
         self.statistics["logical lines"] += 1
         return joined_comments, self.logical_line, mapping_list
 
-    def split_line(self, token):
-        # type: (_Token) -> Generator[str, None, None]
+    def split_line(self, token: _Token) -> Generator[str, None, None]:
         """Split a physical line's line based on new-lines.
 
         This also auto-increments the line number for the caller.
@@ -243,8 +239,11 @@ def split_line(self, token):
             yield line
             self.line_number += 1
 
-    def keyword_arguments_for(self, parameters, arguments=None):
-        # type: (Dict[str, bool], Optional[Dict[str, Any]]) -> Dict[str, Any]
+    def keyword_arguments_for(
+        self,
+        parameters: Dict[str, bool],
+        arguments: Optional[Dict[str, Any]] = None,
+    ) -> Dict[str, Any]:
         """Generate the keyword arguments for a list of parameters."""
         if arguments is None:
             arguments = {}
@@ -265,34 +264,25 @@ def keyword_arguments_for(self, parameters, arguments=None):
                     )
         return arguments
 
-    def generate_tokens(self):  # type: () -> Generator[_Token, None, None]
-        """Tokenize the file and yield the tokens.
+    def generate_tokens(self) -> Generator[_Token, None, None]:
+        """Tokenize the file and yield the tokens."""
+        for token in tokenize.generate_tokens(self.next_line):
+            if token[2][0] > self.total_lines:
+                break
+            self.tokens.append(token)
+            yield token
 
-        :raises flake8.exceptions.InvalidSyntax:
-            If a :class:`tokenize.TokenError` is raised while generating
-            tokens.
-        """
-        try:
-            for token in tokenize.generate_tokens(self.next_line):
-                if token[2][0] > self.total_lines:
-                    break
-                self.tokens.append(token)
-                yield token
-        except (tokenize.TokenError, SyntaxError) as exc:
-            raise exceptions.InvalidSyntax(exception=exc)
-
-    def _noqa_line_range(self, min_line, max_line):
-        # type: (int, int) -> Dict[int, str]
+    def _noqa_line_range(self, min_line: int, max_line: int) -> Dict[int, str]:
         line_range = range(min_line, max_line + 1)
         joined = "".join(self.lines[min_line - 1 : max_line])
         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."""
         if self._noqa_line_mapping is None:
             try:
                 file_tokens = self.file_tokens
-            except exceptions.InvalidSyntax:
+            except (tokenize.TokenError, SyntaxError):
                 # if we failed to parse the file tokens, we'll always fail in
                 # the future, so set this so the code does not try again
                 self._noqa_line_mapping = {}
@@ -327,7 +317,7 @@ def noqa_line_for(self, line_number):  # type: (int) -> Optional[str]
         # retrieve a physical line (since none exist).
         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."""
         if self.line_number >= self.total_lines:
             return ""
@@ -337,8 +327,7 @@ def next_line(self):  # type: () -> str
             self.indent_char = line[0]
         return line
 
-    def read_lines(self):
-        # type: () -> List[str]
+    def read_lines(self) -> List[str]:
         """Read the lines for this file checker."""
         if self.filename is None or self.filename == "-":
             self.filename = self.options.stdin_display_name or "stdin"
@@ -347,13 +336,8 @@ def read_lines(self):
             lines = self.read_lines_from_filename()
         return lines
 
-    def _readlines_py2(self):
-        # type: () -> List[str]
-        with open(self.filename, "rU") as fd:
-            return fd.readlines()
-
-    def _readlines_py3(self):
-        # type: () -> List[str]
+    def read_lines_from_filename(self) -> List[str]:
+        """Read the lines for a file."""
         try:
             with tokenize.open(self.filename) as fd:
                 return fd.readlines()
@@ -363,22 +347,11 @@ def _readlines_py3(self):
             with open(self.filename, encoding="latin-1") as fd:
                 return fd.readlines()
 
-    def read_lines_from_filename(self):
-        # 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]
+    def read_lines_from_stdin(self) -> List[str]:
         """Read the lines from standard in."""
         return utils.stdin_get_lines()
 
-    def should_ignore_file(self):
-        # type: () -> bool
+    def should_ignore_file(self) -> bool:
         """Check if ``flake8: noqa`` is in the file to be ignored.
 
         :returns:
@@ -400,8 +373,7 @@ def should_ignore_file(self):
         else:
             return False
 
-    def strip_utf_bom(self):
-        # type: () -> None
+    def strip_utf_bom(self) -> None:
         """Strip the UTF bom from the lines of the file."""
         if not self.lines:
             # If we have nothing to analyze quit early
@@ -418,23 +390,22 @@ def strip_utf_bom(self):
             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."""
     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."""
     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."""
     return token[0] in NEWLINE
 
 
-def count_parentheses(current_parentheses_count, token_text):
-    # type: (int, str) -> int
+def count_parentheses(current_parentheses_count: int, token_text: str) -> int:
     """Count the number of parentheses."""
     if token_text in "([{":  # nosec
         return current_parentheses_count + 1
@@ -443,12 +414,12 @@ def count_parentheses(current_parentheses_count, token_text):
     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."""
     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:
-        pos = "l.%s" % token[3][0]
+        pos = f"l.{token[3][0]}"
     log.log(
         flake8._EXTRA_VERBOSE,
         "l.%s\t%s\t%s\t%r"
@@ -456,9 +427,7 @@ def log_token(log, token):  # type: (logging.Logger, _Token) -> None
     )
 
 
-# NOTE(sigmavirus24): This was taken wholesale from
-# https://github.com/PyCQA/pycodestyle
-def expand_indent(line):  # type: (str) -> int
+def expand_indent(line: str) -> int:
     r"""Return the amount of indentation.
 
     Tabs are expanded to the next multiple of 8.
@@ -472,23 +441,13 @@ def expand_indent(line):  # type: (str) -> int
     >>> expand_indent('        \t')
     16
     """
-    if "\t" not in line:
-        return len(line) - len(line.lstrip())
-    result = 0
-    for char in line:
-        if char == "\t":
-            result = result // 8 * 8 + 8
-        elif char == " ":
-            result += 1
-        else:
-            break
-    return result
+    return len(line.expandtabs(8))
 
 
 # NOTE(sigmavirus24): This was taken wholesale from
 # https://github.com/PyCQA/pycodestyle. The in-line comments were edited to be
 # more descriptive.
-def mutate_string(text):  # type: (str) -> str
+def mutate_string(text: str) -> str:
     """Replace contents with 'xxx' to prevent syntax matching.
 
     >>> mutate_string('"abc"')
diff --git a/src/flake8/statistics.py b/src/flake8/statistics.py
index 100227fd..073bfe4d 100644
--- a/src/flake8/statistics.py
+++ b/src/flake8/statistics.py
@@ -1,19 +1,23 @@
 """Statistic collection logic for Flake8."""
 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
 
 
-class Statistics(object):
+class Statistics:
     """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."""
-        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.
 
         :returns:
@@ -23,7 +27,7 @@ def error_codes(self):  # type: () -> List[str]
         """
         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.
 
         :param error:
@@ -37,8 +41,9 @@ def record(self, error):  # type: (Violation) -> None
             self._store[key] = Statistic.create_from(error)
         self._store[key].increment()
 
-    def statistics_for(self, prefix, filename=None):
-        # type: (str, Optional[str]) -> Generator[Statistic, None, None]
+    def statistics_for(
+        self, prefix: str, filename: Optional[str] = None
+    ) -> Generator["Statistic", None, None]:
         """Generate statistics for the prefix and filename.
 
         If you have a :class:`Statistics` object that has recorded errors,
@@ -79,11 +84,11 @@ class Key(collections.namedtuple("Key", ["filename", "code"])):
     __slots__ = ()
 
     @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`."""
         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.
 
         :param str prefix:
@@ -102,7 +107,7 @@ def matches(self, prefix, filename):  # type: (str, Optional[str]) -> bool
         )
 
 
-class Statistic(object):
+class Statistic:
     """Simple wrapper around the logic of each statistic.
 
     Instead of maintaining a simple but potentially hard to reason about
@@ -110,8 +115,9 @@ class Statistic(object):
     convenience methods on it.
     """
 
-    def __init__(self, error_code, filename, message, count):
-        # type: (str, str, str, int) -> None
+    def __init__(
+        self, error_code: str, filename: str, message: str, count: int
+    ) -> None:
         """Initialize our Statistic."""
         self.error_code = error_code
         self.filename = filename
@@ -119,7 +125,7 @@ def __init__(self, error_code, filename, message, count):
         self.count = count
 
     @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`."""
         return cls(
             error_code=error.code,
@@ -128,6 +134,6 @@ def create_from(cls, error):  # type: (Violation) -> Statistic
             count=0,
         )
 
-    def increment(self):  # type: () -> None
+    def increment(self) -> None:
         """Increment the number of times we've seen this error in this file."""
         self.count += 1
diff --git a/src/flake8/style_guide.py b/src/flake8/style_guide.py
index 91d21ab4..aca743aa 100644
--- a/src/flake8/style_guide.py
+++ b/src/flake8/style_guide.py
@@ -4,16 +4,23 @@
 import contextlib
 import copy
 import enum
+import functools
 import itertools
 import linecache
 import logging
-from typing import Dict, Generator, List, Match, Optional, Sequence, Set
-from typing import Tuple, Union
+from typing import Dict
+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 statistics
 from flake8 import utils
-from flake8._compat import lru_cache
 from flake8.formatting import base as base_formatter
 
 __all__ = ("StyleGuide",)
@@ -42,29 +49,27 @@ class Decision(enum.Enum):
     Selected = "selected error"
 
 
-@lru_cache(maxsize=512)
-def find_noqa(physical_line):  # type: (str) -> Optional[Match[str]]
+@functools.lru_cache(maxsize=512)
+def find_noqa(physical_line: str) -> Optional[Match[str]]:
     return defaults.NOQA_INLINE_REGEXP.search(physical_line)
 
 
-_Violation = collections.namedtuple(
-    "Violation",
-    [
-        "code",
-        "filename",
-        "line_number",
-        "column_number",
-        "text",
-        "physical_line",
-    ],
-)
-
-
-class Violation(_Violation):
+class Violation(
+    collections.namedtuple(
+        "Violation",
+        [
+            "code",
+            "filename",
+            "line_number",
+            "column_number",
+            "text",
+            "physical_line",
+        ],
+    )
+):
     """Class representing a violation reported by Flake8."""
 
-    def is_inline_ignored(self, disable_noqa):
-        # type: (bool) -> bool
+    def is_inline_ignored(self, disable_noqa: bool) -> bool:
         """Determine if a comment has been added to ignore this line.
 
         :param bool disable_noqa:
@@ -105,8 +110,7 @@ def is_inline_ignored(self, disable_noqa):
         )
         return False
 
-    def is_in(self, diff):
-        # type: (Dict[str, Set[int]]) -> bool
+    def is_in(self, diff: Dict[str, Set[int]]) -> bool:
         """Determine if the violation is included in a diff's line ranges.
 
         This function relies on the parsed data added via
@@ -142,23 +146,30 @@ def is_in(self, diff):
         return self.line_number in line_numbers
 
 
-class DecisionEngine(object):
+class DecisionEngine:
     """A class for managing the decision process around violations.
 
     This contains the logic for whether a violation should be reported or
     ignored.
     """
 
-    def __init__(self, options):  # type: (argparse.Namespace) -> None
+    def __init__(self, options: argparse.Namespace) -> None:
         """Initialize the engine."""
-        self.cache = {}  # type: Dict[str, Decision]
+        self.cache: Dict[str, Decision] = {}
         self.selected = tuple(options.select)
         self.extended_selected = tuple(
             sorted(options.extended_default_select, reverse=True)
         )
         self.enabled_extensions = tuple(options.enable_extensions)
         self.all_selected = tuple(
-            sorted(self.selected + self.enabled_extensions, reverse=True)
+            sorted(
+                itertools.chain(
+                    self.selected,
+                    options.extend_select,
+                    self.enabled_extensions,
+                ),
+                reverse=True,
+            )
         )
         self.ignored = tuple(
             sorted(
@@ -171,16 +182,15 @@ def __init__(self, options):  # type: (argparse.Namespace) -> None
         ).union(options.extended_default_ignore)
         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)
 
-    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(
             self.extended_selected
         )
 
-    def was_selected(self, code):
-        # type: (str) -> Union[Selected, Ignored]
+    def was_selected(self, code: str) -> Union[Selected, Ignored]:
         """Determine if the code has been selected by the user.
 
         :param str code:
@@ -203,8 +213,7 @@ def was_selected(self, code):
 
         return Ignored.Implicitly
 
-    def was_ignored(self, code):
-        # type: (str) -> Union[Selected, Ignored]
+    def was_ignored(self, code: str) -> Union[Selected, Ignored]:
         """Determine if the code has been ignored by the user.
 
         :param str code:
@@ -221,8 +230,7 @@ def was_ignored(self, code):
 
         return Selected.Implicitly
 
-    def more_specific_decision_for(self, code):
-        # type: (str) -> Decision
+    def more_specific_decision_for(self, code: str) -> Decision:
         select = find_first_match(code, self.all_selected)
         extra_select = find_first_match(code, self.extended_selected)
         ignore = find_first_match(code, self.ignored)
@@ -270,8 +278,7 @@ def more_specific_decision_for(self, code):
             return Decision.Ignored
         return Decision.Selected
 
-    def make_decision(self, code):
-        # type: (str) -> Decision
+    def make_decision(self, code: str) -> Decision:
         """Decide if code should be ignored or selected."""
         LOG.debug('Deciding if "%s" should be reported', code)
         selected = self.was_selected(code)
@@ -297,8 +304,7 @@ def make_decision(self, code):
             decision = Decision.Ignored  # pylint: disable=R0204
         return decision
 
-    def decision_for(self, code):
-        # type: (str) -> Decision
+    def decision_for(self, code: str) -> Decision:
         """Return the decision for a specific code.
 
         This method caches the decisions for codes to avoid retracing the same
@@ -320,15 +326,15 @@ def decision_for(self, code):
         return decision
 
 
-class StyleGuideManager(object):
+class StyleGuideManager:
     """Manage multiple style guides for a single run."""
 
     def __init__(
         self,
-        options,  # type: argparse.Namespace
-        formatter,  # type: base_formatter.BaseFormatter
-        decider=None,  # type: Optional[DecisionEngine]
-    ):  # type: (...) -> None
+        options: argparse.Namespace,
+        formatter: base_formatter.BaseFormatter,
+        decider: Optional[DecisionEngine] = None,
+    ) -> None:
         """Initialize our StyleGuide.
 
         .. todo:: Add parameter documentation.
@@ -337,7 +343,7 @@ def __init__(
         self.formatter = formatter
         self.stats = statistics.Statistics()
         self.decider = decider or DecisionEngine(options)
-        self.style_guides = []  # type: List[StyleGuide]
+        self.style_guides: List[StyleGuide] = []
         self.default_style_guide = StyleGuide(
             options, formatter, self.stats, decider=decider
         )
@@ -348,8 +354,9 @@ def __init__(
             )
         )
 
-    def populate_style_guides_with(self, options):
-        # type: (argparse.Namespace) -> Generator[StyleGuide, None, None]
+    def populate_style_guides_with(
+        self, options: argparse.Namespace
+    ) -> Generator["StyleGuide", None, None]:
         """Generate style guides from the per-file-ignores option.
 
         :param options:
@@ -361,16 +368,14 @@ def populate_style_guides_with(self, options):
         :rtype:
             :class:`~flake8.style_guide.StyleGuide`
         """
-        per_file = utils.parse_files_to_codes_mapping(
-            options.per_file_ignores
-        )
+        per_file = utils.parse_files_to_codes_mapping(options.per_file_ignores)
         for filename, violations in per_file:
             yield self.default_style_guide.copy(
                 filename=filename, extend_ignore_with=violations
             )
 
-    @lru_cache(maxsize=None)
-    def style_guide_for(self, filename):  # type: (str) -> StyleGuide
+    @functools.lru_cache(maxsize=None)
+    def style_guide_for(self, filename: str) -> "StyleGuide":
         """Find the StyleGuide for the filename in particular."""
         guides = sorted(
             (g for g in self.style_guides if g.applies_to(filename)),
@@ -381,8 +386,9 @@ def style_guide_for(self, filename):  # type: (str) -> StyleGuide
         return guides[0]
 
     @contextlib.contextmanager
-    def processing_file(self, filename):
-        # type: (str) -> Generator[StyleGuide, None, None]
+    def processing_file(
+        self, filename: str
+    ) -> Generator["StyleGuide", None, None]:
         """Record the fact that we're processing the file's results."""
         guide = self.style_guide_for(filename)
         with guide.processing_file(filename):
@@ -390,14 +396,13 @@ def processing_file(self, filename):
 
     def handle_error(
         self,
-        code,
-        filename,
-        line_number,
-        column_number,
-        text,
-        physical_line=None,
-    ):
-        # type: (str, str, int, Optional[int], str, Optional[str]) -> int
+        code: str,
+        filename: str,
+        line_number: int,
+        column_number: Optional[int],
+        text: str,
+        physical_line: Optional[str] = None,
+    ) -> int:
         """Handle an error reported by a check.
 
         :param str code:
@@ -425,8 +430,7 @@ def handle_error(
             code, filename, line_number, column_number, text, physical_line
         )
 
-    def add_diff_ranges(self, diffinfo):
-        # type: (Dict[str, Set[int]]) -> None
+    def add_diff_ranges(self, diffinfo: Dict[str, Set[int]]) -> None:
         """Update the StyleGuides to filter out information not in the diff.
 
         This provides information to the underlying StyleGuides so that only
@@ -439,16 +443,16 @@ def add_diff_ranges(self, diffinfo):
             guide.add_diff_ranges(diffinfo)
 
 
-class StyleGuide(object):
+class StyleGuide:
     """Manage a Flake8 user's style guide."""
 
     def __init__(
         self,
-        options,  # type: argparse.Namespace
-        formatter,  # type: base_formatter.BaseFormatter
-        stats,  # type: statistics.Statistics
-        filename=None,  # type: Optional[str]
-        decider=None,  # type: Optional[DecisionEngine]
+        options: argparse.Namespace,
+        formatter: base_formatter.BaseFormatter,
+        stats: statistics.Statistics,
+        filename: Optional[str] = None,
+        decider: Optional[DecisionEngine] = None,
     ):
         """Initialize our StyleGuide.
 
@@ -461,14 +465,17 @@ def __init__(
         self.filename = filename
         if 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."""
-        return "<StyleGuide [{}]>".format(self.filename)
+        return f"<StyleGuide [{self.filename}]>"
 
-    def copy(self, filename=None, extend_ignore_with=None):
-        # type: (Optional[str], Optional[Sequence[str]]) -> StyleGuide
+    def copy(
+        self,
+        filename: Optional[str] = None,
+        extend_ignore_with: Optional[Sequence[str]] = None,
+    ) -> "StyleGuide":
         """Create a copy of this style guide with different values."""
         filename = filename or self.filename
         options = copy.deepcopy(self.options)
@@ -478,14 +485,15 @@ def copy(self, filename=None, extend_ignore_with=None):
         )
 
     @contextlib.contextmanager
-    def processing_file(self, filename):
-        # type: (str) -> Generator[StyleGuide, None, None]
+    def processing_file(
+        self, filename: str
+    ) -> Generator["StyleGuide", None, None]:
         """Record the fact that we're processing the file's results."""
         self.formatter.beginning(filename)
         yield self
         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.
 
         :param str filename:
@@ -501,12 +509,11 @@ def applies_to(self, filename):  # type: (str) -> bool
         return utils.matches_filename(
             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,
         )
 
-    def should_report_error(self, code):
-        # type: (str) -> Decision
+    def should_report_error(self, code: str) -> Decision:
         """Determine if the error code should be reported or ignored.
 
         This method only cares about the select and ignore rules as specified
@@ -522,14 +529,13 @@ def should_report_error(self, code):
 
     def handle_error(
         self,
-        code,
-        filename,
-        line_number,
-        column_number,
-        text,
-        physical_line=None,
-    ):
-        # type: (str, str, int, Optional[int], str, Optional[str]) -> int
+        code: str,
+        filename: str,
+        line_number: int,
+        column_number: Optional[int],
+        text: str,
+        physical_line: Optional[str] = None,
+    ) -> int:
         """Handle an error reported by a check.
 
         :param str code:
@@ -571,18 +577,13 @@ def handle_error(
         )
         is_not_inline_ignored = error.is_inline_ignored(disable_noqa) is False
         is_included_in_diff = error.is_in(self._parsed_diff)
-        if (
-            error_is_selected
-            and is_not_inline_ignored
-            and is_included_in_diff
-        ):
+        if error_is_selected and is_not_inline_ignored and is_included_in_diff:
             self.formatter.handle(error)
             self.stats.record(error)
             return 1
         return 0
 
-    def add_diff_ranges(self, diffinfo):
-        # type: (Dict[str, Set[int]]) -> None
+    def add_diff_ranges(self, diffinfo: Dict[str, Set[int]]) -> None:
         """Update the StyleGuide to filter out information not in the diff.
 
         This provides information to the StyleGuide so that only the errors
@@ -594,14 +595,15 @@ def add_diff_ranges(self, 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:
         return Decision.Selected
     return Decision.Ignored
 
 
-def find_first_match(error_code, code_list):
-    # type: (str, Tuple[str, ...]) -> Optional[str]
+def find_first_match(
+    error_code: str, code_list: Tuple[str, ...]
+) -> Optional[str]:
     startswith = error_code.startswith
     for code in code_list:
         if startswith(code):
diff --git a/src/flake8/utils.py b/src/flake8/utils.py
index 29dc5546..62c8ad41 100644
--- a/src/flake8/utils.py
+++ b/src/flake8/utils.py
@@ -1,6 +1,7 @@
 """Utility methods for flake8."""
 import collections
 import fnmatch as _fnmatch
+import functools
 import inspect
 import io
 import logging
@@ -8,24 +9,33 @@
 import platform
 import re
 import sys
+import textwrap
 import tokenize
-from typing import Callable, Dict, Generator, List, Optional, Pattern
-from typing import Sequence, Set, Tuple, Union
+from typing import Callable
+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._compat import lru_cache
 
-if False:  # `typing.TYPE_CHECKING` was introduced in 3.5.2
+if TYPE_CHECKING:
     from flake8.plugins.manager import Plugin
 
 DIFF_HUNK_REGEXP = re.compile(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@.*$")
 COMMA_SEPARATED_LIST_RE = re.compile(r"[,\s]")
 LOCAL_PLUGIN_LIST_RE = re.compile(r"[,\t\n\r\f\v]")
-string_types = (str, type(u""))
 
 
-def parse_comma_separated_list(value, regexp=COMMA_SEPARATED_LIST_RE):
-    # type: (str, Pattern[str]) -> List[str]
+def parse_comma_separated_list(
+    value: str, regexp: Pattern[str] = COMMA_SEPARATED_LIST_RE
+) -> List[str]:
     """Parse a comma-separated list.
 
     :param value:
@@ -40,14 +50,14 @@ def parse_comma_separated_list(value, regexp=COMMA_SEPARATED_LIST_RE):
     :rtype:
         list
     """
-    assert isinstance(value, string_types), value
+    assert isinstance(value, str), value
 
     separated = regexp.split(value)
     item_gen = (item.strip() for item in separated)
     return [item for item in item_gen if item]
 
 
-_Token = collections.namedtuple("Token", ("tp", "src"))
+_Token = collections.namedtuple("_Token", ("tp", "src"))
 _CODE, _FILE, _COLON, _COMMA, _WS = "code", "file", "colon", "comma", "ws"
 _EOF = "eof"
 _FILE_LIST_TOKEN_TYPES = [
@@ -59,8 +69,7 @@ def parse_comma_separated_list(value, regexp=COMMA_SEPARATED_LIST_RE):
 ]
 
 
-def _tokenize_files_to_codes_mapping(value):
-    # type: (str) -> List[_Token]
+def _tokenize_files_to_codes_mapping(value: str) -> List[_Token]:
     tokens = []
     i = 0
     while i < len(value):
@@ -77,8 +86,9 @@ def _tokenize_files_to_codes_mapping(value):
     return tokens
 
 
-def parse_files_to_codes_mapping(value_):  # noqa: C901
-    # type: (Union[Sequence[str], str]) -> List[Tuple[str, List[str]]]
+def parse_files_to_codes_mapping(  # noqa: C901
+    value_: Union[Sequence[str], str]
+) -> List[Tuple[str, List[str]]]:
     """Parse a files-to-codes mapping.
 
     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.
     :type value: str
     """
-    if not isinstance(value_, string_types):
+    if not isinstance(value_, str):
         value = "\n".join(value_)
     else:
         value = value_
 
-    ret = []  # type: List[Tuple[str, List[str]]]
+    ret: List[Tuple[str, List[str]]] = []
     if not value.strip():
         return ret
 
     class State:
         seen_sep = True
         seen_colon = False
-        filenames = []  # type: List[str]
-        codes = []  # type: List[str]
+        filenames: List[str] = []
+        codes: List[str] = []
 
-    def _reset():  # type: () -> None
+    def _reset() -> None:
         if State.codes:
             for filename in State.filenames:
                 ret.append((filename, State.codes))
@@ -112,16 +122,12 @@ def _reset():  # type: () -> None
         State.filenames = []
         State.codes = []
 
-    def _unexpected_token():  # type: () -> exceptions.ExecutionError
-        def _indent(s):  # type: (str) -> str
-            return "    " + s.strip().replace("\n", "\n    ")
-
+    def _unexpected_token() -> exceptions.ExecutionError:
         return exceptions.ExecutionError(
-            "Expected `per-file-ignores` to be a mapping from file exclude "
-            "patterns to ignore codes.\n\n"
-            "Configured `per-file-ignores` setting:\n\n{}".format(
-                _indent(value)
-            )
+            f"Expected `per-file-ignores` to be a mapping from file exclude "
+            f"patterns to ignore codes.\n\n"
+            f"Configured `per-file-ignores` setting:\n\n"
+            f"{textwrap.indent(value.strip(), '    ')}"
         )
 
     for token in _tokenize_files_to_codes_mapping(value):
@@ -155,8 +161,9 @@ def _indent(s):  # type: (str) -> str
     return ret
 
 
-def normalize_paths(paths, parent=os.curdir):
-    # type: (Sequence[str], str) -> List[str]
+def normalize_paths(
+    paths: Sequence[str], parent: str = os.curdir
+) -> List[str]:
     """Normalize a list of paths relative to a parent directory.
 
     :returns:
@@ -168,8 +175,7 @@ def normalize_paths(paths, parent=os.curdir):
     return [normalize_path(p, parent) for p in paths]
 
 
-def normalize_path(path, parent=os.curdir):
-    # type: (str, str) -> str
+def normalize_path(path: str, parent: str = os.curdir) -> str:
     """Normalize a single-path.
 
     :returns:
@@ -190,7 +196,9 @@ def normalize_path(path, parent=os.curdir):
     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()
     fd = io.BytesIO(stdin_value)
     try:
@@ -201,25 +209,12 @@ def _stdin_get_value_py3():  # type: () -> str
         return stdin_value.decode("utf-8")
 
 
-@lru_cache(maxsize=1)
-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]
+def stdin_get_lines() -> List[str]:
     """Return lines of stdin split according to file splitting."""
-    if sys.version_info < (3,):
-        return list(io.BytesIO(stdin_get_value()))
-    else:
-        return list(io.StringIO(stdin_get_value()))
+    return list(io.StringIO(stdin_get_value()))
 
 
-def parse_unified_diff(diff=None):
-    # type: (Optional[str]) -> Dict[str, Set[int]]
+def parse_unified_diff(diff: Optional[str] = None) -> Dict[str, Set[int]]:
     """Parse the unified diff passed on stdin.
 
     :returns:
@@ -233,12 +228,10 @@ def parse_unified_diff(diff=None):
 
     number_of_rows = 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():
         if number_of_rows:
-            # NOTE(sigmavirus24): Below we use a slice because stdin may be
-            # bytes instead of text on Python 3.
-            if line[:1] != "-":
+            if not line or line[0] != "-":
                 number_of_rows -= 1
             # We're in the part of the diff that has lines starting with +, -,
             # and ' ' to show context and the changes made. We skip these
@@ -277,22 +270,18 @@ def parse_unified_diff(diff=None):
         # So we can more simply check for a match instead of slicing and
         # comparing.
         if hunk_match:
-            (row, number_of_rows) = [
-                1 if not group else int(group)
-                for group in hunk_match.groups()
-            ]
-            assert current_path is not None
-            parsed_paths[current_path].update(
-                range(row, row + number_of_rows)
+            (row, number_of_rows) = (
+                1 if not group else int(group) for group in hunk_match.groups()
             )
+            assert current_path is not None
+            parsed_paths[current_path].update(range(row, row + number_of_rows))
 
     # We have now parsed our diff into a dictionary that looks like:
     #    {'file.py': set(range(10, 16), range(18, 20)), ...}
     return parsed_paths
 
 
-def is_windows():
-    # type: () -> bool
+def is_windows() -> bool:
     """Determine if we're running on Windows.
 
     :returns:
@@ -303,8 +292,7 @@ def is_windows():
     return os.name == "nt"
 
 
-def is_using_stdin(paths):
-    # type: (List[str]) -> bool
+def is_using_stdin(paths: List[str]) -> bool:
     """Determine if we're going to read from stdin.
 
     :param list paths:
@@ -317,12 +305,13 @@ def is_using_stdin(paths):
     return "-" in paths
 
 
-def _default_predicate(*args):  # type: (*str) -> bool
+def _default_predicate(*args: str) -> bool:
     return False
 
 
-def filenames_from(arg, predicate=None):
-    # type: (str, Optional[Callable[[str], bool]]) -> Generator[str, None, None]  # noqa: E501
+def filenames_from(
+    arg: str, predicate: Optional[Callable[[str], bool]] = None
+) -> Generator[str, None, None]:
     """Generate filenames from an argument.
 
     :param str arg:
@@ -362,8 +351,7 @@ def filenames_from(arg, predicate=None):
         yield arg
 
 
-def fnmatch(filename, patterns):
-    # type: (str, Sequence[str]) -> bool
+def fnmatch(filename: str, patterns: Sequence[str]) -> bool:
     """Wrap :func:`fnmatch.fnmatch` to add some functionality.
 
     :param str filename:
@@ -381,8 +369,7 @@ def fnmatch(filename, patterns):
     return any(_fnmatch.fnmatch(filename, pattern) for pattern in patterns)
 
 
-def parameters_for(plugin):
-    # type: (Plugin) -> Dict[str, bool]
+def parameters_for(plugin: "Plugin") -> Dict[str, bool]:
     """Return the parameters for the plugin.
 
     This will inspect the plugin and return either the function parameters
@@ -404,24 +391,11 @@ def parameters_for(plugin):
     if is_class:  # The plugin is a class
         func = plugin.plugin.__init__
 
-    if sys.version_info < (3, 3):
-        argspec = inspect.getargspec(func)
-        start_of_optional_args = len(argspec[0]) - len(argspec[-1] or [])
-        parameter_names = argspec[0]
-        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
-            ]
-        )
+    parameters = {
+        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:
         parameters.pop("self", None)
@@ -429,8 +403,12 @@ def parameters_for(plugin):
     return parameters
 
 
-def matches_filename(path, patterns, log_message, logger):
-    # type: (str, Sequence[str], str, logging.Logger) -> bool
+def matches_filename(
+    path: str,
+    patterns: Sequence[str],
+    log_message: str,
+    logger: logging.Logger,
+) -> bool:
     """Use fnmatch to discern if a path exists in patterns.
 
     :param str path:
@@ -462,7 +440,7 @@ def matches_filename(path, patterns, log_message, logger):
     return match
 
 
-def get_python_version():  # type: () -> str
+def get_python_version() -> str:
     """Find and format the python implementation and version.
 
     :returns:
@@ -470,7 +448,7 @@ def get_python_version():  # type: () -> str
     :rtype:
         str
     """
-    return "%s %s on %s" % (
+    return "{} {} on {}".format(
         platform.python_implementation(),
         platform.python_version(),
         platform.system(),
diff --git a/tests/conftest.py b/tests/conftest.py
index 9bf4f950..0f483094 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -3,4 +3,4 @@
 
 import flake8
 
-flake8.configure_logging(2, 'test-logs-%s.%s.log' % sys.version_info[0:2])
+flake8.configure_logging(2, "test-logs-%s.%s.log" % sys.version_info[0:2])
diff --git a/tests/fixtures/example-code/inline-ignores/E501.py b/tests/fixtures/example-code/inline-ignores/E501.py
index 62e5c0c2..64a5dcab 100644
--- a/tests/fixtures/example-code/inline-ignores/E501.py
+++ b/tests/fixtures/example-code/inline-ignores/E501.py
@@ -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()
diff --git a/tests/integration/subdir/aplugin.py b/tests/integration/subdir/aplugin.py
index bc5f812c..fde58906 100644
--- a/tests/integration/subdir/aplugin.py
+++ b/tests/integration/subdir/aplugin.py
@@ -1,11 +1,11 @@
 """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."""
 
-    name = 'ExtensionTestPlugin2'
-    version = '1.0.0'
+    name = "ExtensionTestPlugin2"
+    version = "1.0.0"
 
     def __init__(self, tree):
         """Construct an instance of test plugin."""
diff --git a/tests/integration/test_aggregator.py b/tests/integration/test_aggregator.py
index 2bdea704..ae752045 100644
--- a/tests/integration/test_aggregator.py
+++ b/tests/integration/test_aggregator.py
@@ -9,7 +9,7 @@
 from flake8.options import config
 from flake8.options import manager
 
-CLI_SPECIFIED_CONFIG = 'tests/fixtures/config_files/cli-specified.ini'
+CLI_SPECIFIED_CONFIG = "tests/fixtures/config_files/cli-specified.ini"
 
 
 @pytest.fixture
@@ -18,8 +18,8 @@ def optmanager():
     prelim_parser = argparse.ArgumentParser(add_help=False)
     options.register_preliminary_options(prelim_parser)
     option_manager = manager.OptionManager(
-        prog='flake8',
-        version='3.0.0',
+        prog="flake8",
+        version="3.0.0",
         parents=[prelim_parser],
     )
     options.register_default_options(option_manager)
@@ -28,31 +28,50 @@ def optmanager():
 
 def test_aggregate_options_with_config(optmanager):
     """Verify we aggregate options and config values appropriately."""
-    arguments = ['flake8', '--select',
-                 'E11,E34,E402,W,F', '--exclude', 'tests/*']
+    arguments = [
+        "flake8",
+        "--select",
+        "E11,E34,E402,W,F",
+        "--exclude",
+        "tests/*",
+    ]
     config_finder = config.ConfigFileFinder(
-        'flake8',
-        config_file=CLI_SPECIFIED_CONFIG)
+        "flake8", config_file=CLI_SPECIFIED_CONFIG
+    )
     options, args = aggregator.aggregate_options(
-        optmanager, config_finder, arguments)
+        optmanager, config_finder, arguments
+    )
 
-    assert options.select == ['E11', 'E34', 'E402', 'W', 'F']
-    assert options.ignore == ['E123', 'W234', 'E111']
-    assert options.exclude == [os.path.abspath('tests/*')]
+    assert options.select == ["E11", "E34", "E402", "W", "F"]
+    assert options.ignore == ["E123", "W234", "E111"]
+    assert options.exclude == [os.path.abspath("tests/*")]
 
 
 def test_aggregate_options_when_isolated(optmanager):
     """Verify we aggregate options and config values appropriately."""
-    arguments = ['flake8', '--select', 'E11,E34,E402,W,F',
-                 '--exclude', 'tests/*']
-    config_finder = config.ConfigFileFinder(
-        'flake8', ignore_config_files=True)
-    optmanager.extend_default_ignore(['E8'])
+    arguments = [
+        "flake8",
+        "--select",
+        "E11,E34,E402,W,F",
+        "--exclude",
+        "tests/*",
+    ]
+    config_finder = config.ConfigFileFinder("flake8", ignore_config_files=True)
+    optmanager.extend_default_ignore(["E8"])
     options, args = aggregator.aggregate_options(
-        optmanager, config_finder, arguments)
+        optmanager, config_finder, arguments
+    )
 
-    assert options.select == ['E11', 'E34', 'E402', 'W', 'F']
+    assert options.select == ["E11", "E34", "E402", "W", "F"]
     assert sorted(options.ignore) == [
-        'E121', 'E123', 'E126', 'E226', 'E24', 'E704', 'E8', 'W503', 'W504',
+        "E121",
+        "E123",
+        "E126",
+        "E226",
+        "E24",
+        "E704",
+        "E8",
+        "W503",
+        "W504",
     ]
-    assert options.exclude == [os.path.abspath('tests/*')]
+    assert options.exclude == [os.path.abspath("tests/*")]
diff --git a/tests/integration/test_api_legacy.py b/tests/integration/test_api_legacy.py
index 0ffaa220..efb0fc9e 100644
--- a/tests/integration/test_api_legacy.py
+++ b/tests/integration/test_api_legacy.py
@@ -5,8 +5,8 @@
 def test_legacy_api(tmpdir):
     """A basic end-to-end test for the legacy api reporting errors."""
     with tmpdir.as_cwd():
-        t_py = tmpdir.join('t.py')
-        t_py.write('import os  # unused import\n')
+        t_py = tmpdir.join("t.py")
+        t_py.write("import os  # unused import\n")
 
         style_guide = legacy.get_style_guide()
         report = style_guide.check_files([t_py.strpath])
diff --git a/tests/integration/test_checker.py b/tests/integration/test_checker.py
index 836b543d..7e0b975b 100644
--- a/tests/integration/test_checker.py
+++ b/tests/integration/test_checker.py
@@ -1,5 +1,7 @@
 """Integration tests for the checker submodule."""
-import mock
+import sys
+from unittest import mock
+
 import pytest
 
 from flake8 import checker
@@ -9,22 +11,22 @@
 
 PHYSICAL_LINE = "# Physical line content"
 
-EXPECTED_REPORT = (1, 1, 'T000 Expected Message')
-EXPECTED_REPORT_PHYSICAL_LINE = (1, 'T000 Expected Message')
+EXPECTED_REPORT = (1, 1, "T000 Expected Message")
+EXPECTED_REPORT_PHYSICAL_LINE = (1, "T000 Expected Message")
 EXPECTED_RESULT_PHYSICAL_LINE = (
-    'T000',
+    "T000",
     0,
     1,
-    'Expected Message',
+    "Expected Message",
     None,
 )
 
 
-class PluginClass(object):
+class PluginClass:
     """Simple file plugin class yielding the expected report."""
 
-    name = 'test'
-    version = '1.0.0'
+    name = "test"
+    version = "1.0.0"
 
     def __init__(self, tree):
         """Construct a dummy object to provide mandatory parameter."""
@@ -32,26 +34,26 @@ def __init__(self, tree):
 
     def run(self):
         """Run class yielding one element containing the expected report."""
-        yield EXPECTED_REPORT + (type(self), )
+        yield EXPECTED_REPORT + (type(self),)
 
 
 def plugin_func(func):
     """Decorate file plugins which are implemented as functions."""
-    func.name = 'test'
-    func.version = '1.0.0'
+    func.name = "test"
+    func.version = "1.0.0"
     return func
 
 
 @plugin_func
 def plugin_func_gen(tree):
     """Yield the expected report."""
-    yield EXPECTED_REPORT + (type(plugin_func_gen), )
+    yield EXPECTED_REPORT + (type(plugin_func_gen),)
 
 
 @plugin_func
 def plugin_func_list(tree):
     """Return a list of expected reports."""
-    return [EXPECTED_REPORT + (type(plugin_func_list), )]
+    return [EXPECTED_REPORT + (type(plugin_func_list),)]
 
 
 @plugin_func
@@ -97,35 +99,37 @@ def mock_file_checker_with_plugin(plugin_target):
     Useful as a starting point for mocking reports/results.
     """
     # Mock an entry point returning the plugin target
-    entry_point = mock.Mock(spec=['load'])
+    entry_point = mock.Mock(spec=["load"])
     entry_point.name = plugin_target.name
     entry_point.load.return_value = plugin_target
-    entry_point.value = 'mocked:value'
+    entry_point.value = "mocked:value"
 
     # Load the checker plugins using the entry point mock
     with mock.patch.object(
-            importlib_metadata,
-            'entry_points',
-            return_value={'flake8.extension': [entry_point]},
+        importlib_metadata,
+        "entry_points",
+        return_value={"flake8.extension": [entry_point]},
     ):
         checks = manager.Checkers()
 
     # Prevent it from reading lines from stdin or somewhere else
-    with mock.patch('flake8.processor.FileProcessor.read_lines',
-                    return_value=['Line 1']):
+    with mock.patch(
+        "flake8.processor.FileProcessor.read_lines", return_value=["Line 1"]
+    ):
         file_checker = checker.FileChecker(
-            '-',
-            checks.to_dictionary(),
-            mock.MagicMock()
+            "-", checks.to_dictionary(), mock.MagicMock()
         )
     return file_checker
 
 
-@pytest.mark.parametrize('plugin_target', [
-    PluginClass,
-    plugin_func_gen,
-    plugin_func_list,
-])
+@pytest.mark.parametrize(
+    "plugin_target",
+    [
+        PluginClass,
+        plugin_func_gen,
+        plugin_func_list,
+    ],
+)
 def test_handle_file_plugins(plugin_target):
     """Test the FileChecker class handling different file plugin types."""
     file_checker = mock_file_checker_with_plugin(plugin_target)
@@ -137,20 +141,25 @@ def test_handle_file_plugins(plugin_target):
     report = mock.Mock()
     file_checker.report = report
     file_checker.run_ast_checks()
-    report.assert_called_once_with(error_code=None,
-                                   line_number=EXPECTED_REPORT[0],
-                                   column=EXPECTED_REPORT[1],
-                                   text=EXPECTED_REPORT[2])
-
-
-@pytest.mark.parametrize('plugin_target,len_results', [
-    (plugin_func_physical_ret, 1),
-    (plugin_func_physical_none, 0),
-    (plugin_func_physical_list_single, 1),
-    (plugin_func_physical_list_multiple, 2),
-    (plugin_func_physical_gen_single, 1),
-    (plugin_func_physical_gen_multiple, 3),
-])
+    report.assert_called_once_with(
+        error_code=None,
+        line_number=EXPECTED_REPORT[0],
+        column=EXPECTED_REPORT[1],
+        text=EXPECTED_REPORT[2],
+    )
+
+
+@pytest.mark.parametrize(
+    "plugin_target,len_results",
+    [
+        (plugin_func_physical_ret, 1),
+        (plugin_func_physical_none, 0),
+        (plugin_func_physical_list_single, 1),
+        (plugin_func_physical_list_multiple, 2),
+        (plugin_func_physical_gen_single, 1),
+        (plugin_func_physical_gen_multiple, 3),
+    ],
+)
 def test_line_check_results(plugin_target, len_results):
     """Test the FileChecker class handling results from line checks."""
     file_checker = mock_file_checker_with_plugin(plugin_target)
@@ -166,54 +175,100 @@ def test_logical_line_offset_out_of_bounds():
 
     @plugin_func
     def _logical_line_out_of_bounds(logical_line):
-        yield 10000, 'L100 test'
+        yield 10000, "L100 test"
 
     file_checker = mock_file_checker_with_plugin(_logical_line_out_of_bounds)
 
     logical_ret = (
-        '',
+        "",
         'print("xxxxxxxxxxx")',
         [(0, (1, 0)), (5, (1, 5)), (6, (1, 6)), (19, (1, 19)), (20, (1, 20))],
     )
     with mock.patch.object(
-        FileProcessor, 'build_logical_line', return_value=logical_ret,
+        FileProcessor,
+        "build_logical_line",
+        return_value=logical_ret,
     ):
         file_checker.run_logical_checks()
-        assert file_checker.results == [('L100', 0, 0, 'test', None)]
+        assert file_checker.results == [("L100", 0, 0, "test", None)]
 
 
 PLACEHOLDER_CODE = 'some_line = "of" * code'
 
 
-@pytest.mark.parametrize('results, expected_order', [
-    # No entries should be added
-    ([], []),
-    # Results are correctly ordered
-    ([('A101', 1, 1, 'placeholder error', PLACEHOLDER_CODE),
-      ('A101', 2, 1, 'placeholder error', PLACEHOLDER_CODE)], [0, 1]),
-    # Reversed order of lines
-    ([('A101', 2, 1, 'placeholder error', PLACEHOLDER_CODE),
-      ('A101', 1, 1, 'placeholder error', PLACEHOLDER_CODE)], [1, 0]),
-    # Columns are not ordered correctly (when reports are ordered correctly)
-    ([('A101', 1, 2, 'placeholder error', PLACEHOLDER_CODE),
-      ('A101', 1, 1, 'placeholder error', PLACEHOLDER_CODE),
-      ('A101', 2, 1, 'placeholder error', PLACEHOLDER_CODE)], [1, 0, 2]),
-    ([('A101', 2, 1, 'placeholder error', PLACEHOLDER_CODE),
-      ('A101', 1, 1, 'placeholder error', PLACEHOLDER_CODE),
-      ('A101', 1, 2, 'placeholder error', PLACEHOLDER_CODE)], [1, 2, 0]),
-    ([('A101', 1, 2, 'placeholder error', PLACEHOLDER_CODE),
-      ('A101', 2, 2, 'placeholder error', PLACEHOLDER_CODE),
-      ('A101', 2, 1, 'placeholder error', PLACEHOLDER_CODE)], [0, 2, 1]),
-    ([('A101', 1, 3, 'placeholder error', PLACEHOLDER_CODE),
-      ('A101', 2, 2, 'placeholder error', PLACEHOLDER_CODE),
-      ('A101', 3, 1, 'placeholder error', PLACEHOLDER_CODE)], [0, 1, 2]),
-    ([('A101', 1, 1, 'placeholder error', PLACEHOLDER_CODE),
-      ('A101', 1, 3, 'placeholder error', PLACEHOLDER_CODE),
-      ('A101', 2, 2, 'placeholder error', PLACEHOLDER_CODE)], [0, 1, 2]),
-    # Previously sort column and message (so reversed) (see bug 196)
-    ([('A101', 1, 1, 'placeholder error', PLACEHOLDER_CODE),
-      ('A101', 2, 1, 'charlie error', PLACEHOLDER_CODE)], [0, 1]),
-])
+@pytest.mark.parametrize(
+    "results, expected_order",
+    [
+        # No entries should be added
+        ([], []),
+        # Results are correctly ordered
+        (
+            [
+                ("A101", 1, 1, "placeholder error", PLACEHOLDER_CODE),
+                ("A101", 2, 1, "placeholder error", PLACEHOLDER_CODE),
+            ],
+            [0, 1],
+        ),
+        # Reversed order of lines
+        (
+            [
+                ("A101", 2, 1, "placeholder error", PLACEHOLDER_CODE),
+                ("A101", 1, 1, "placeholder error", PLACEHOLDER_CODE),
+            ],
+            [1, 0],
+        ),
+        # Columns are not ordered correctly
+        # (when reports are ordered correctly)
+        (
+            [
+                ("A101", 1, 2, "placeholder error", PLACEHOLDER_CODE),
+                ("A101", 1, 1, "placeholder error", PLACEHOLDER_CODE),
+                ("A101", 2, 1, "placeholder error", PLACEHOLDER_CODE),
+            ],
+            [1, 0, 2],
+        ),
+        (
+            [
+                ("A101", 2, 1, "placeholder error", PLACEHOLDER_CODE),
+                ("A101", 1, 1, "placeholder error", PLACEHOLDER_CODE),
+                ("A101", 1, 2, "placeholder error", PLACEHOLDER_CODE),
+            ],
+            [1, 2, 0],
+        ),
+        (
+            [
+                ("A101", 1, 2, "placeholder error", PLACEHOLDER_CODE),
+                ("A101", 2, 2, "placeholder error", PLACEHOLDER_CODE),
+                ("A101", 2, 1, "placeholder error", PLACEHOLDER_CODE),
+            ],
+            [0, 2, 1],
+        ),
+        (
+            [
+                ("A101", 1, 3, "placeholder error", PLACEHOLDER_CODE),
+                ("A101", 2, 2, "placeholder error", PLACEHOLDER_CODE),
+                ("A101", 3, 1, "placeholder error", PLACEHOLDER_CODE),
+            ],
+            [0, 1, 2],
+        ),
+        (
+            [
+                ("A101", 1, 1, "placeholder error", PLACEHOLDER_CODE),
+                ("A101", 1, 3, "placeholder error", PLACEHOLDER_CODE),
+                ("A101", 2, 2, "placeholder error", PLACEHOLDER_CODE),
+            ],
+            [0, 1, 2],
+        ),
+        # Previously sort column and message (so reversed) (see bug 196)
+        (
+            [
+                ("A101", 1, 1, "placeholder error", PLACEHOLDER_CODE),
+                ("A101", 2, 1, "charlie error", PLACEHOLDER_CODE),
+            ],
+            [0, 1],
+        ),
+    ],
+)
 def test_report_order(results, expected_order):
     """
     Test in which order the results will be reported.
@@ -221,6 +276,7 @@ def test_report_order(results, expected_order):
     It gets a list of reports from the file checkers and verifies that the
     result will be ordered independent from the original report.
     """
+
     def count_side_effect(name, sorted_results):
         """Side effect for the result handler to tell all are reported."""
         return len(sorted_results)
@@ -229,11 +285,11 @@ def count_side_effect(name, sorted_results):
     # tuples to create the expected result lists from the indexes
     expected_results = [results[index] for index in expected_order]
 
-    file_checker = mock.Mock(spec=['results', 'display_name'])
+    file_checker = mock.Mock(spec=["results", "display_name"])
     file_checker.results = results
-    file_checker.display_name = 'placeholder'
+    file_checker.display_name = "placeholder"
 
-    style_guide = mock.MagicMock(spec=['options', 'processing_file'])
+    style_guide = mock.MagicMock(spec=["options", "processing_file"])
 
     # Create a placeholder manager without arguments or plugins
     # Just add one custom file checker which just provides the results
@@ -243,9 +299,9 @@ def count_side_effect(name, sorted_results):
     # _handle_results is the first place which gets the sorted result
     # Should something non-private be mocked instead?
     handler = mock.Mock(side_effect=count_side_effect)
-    with mock.patch.object(manager, '_handle_results', handler):
+    with mock.patch.object(manager, "_handle_results", handler):
         assert manager.report() == (len(results), len(results))
-        handler.assert_called_once_with('placeholder', expected_results)
+        handler.assert_called_once_with("placeholder", expected_results)
 
 
 def test_acquire_when_multiprocessing_pool_can_initialize():
@@ -281,3 +337,26 @@ def test_acquire_when_multiprocessing_pool_can_not_initialize():
 
     pool.assert_called_once_with(2, checker._pool_init)
     assert result is None
+
+
+def test_handling_syntaxerrors_across_pythons():
+    """Verify we properly handle exception argument tuples.
+
+    Python 3.10 added more information to the SyntaxError parse token tuple.
+    We need to handle that correctly to avoid crashing.
+    https://github.com/PyCQA/flake8/issues/1372
+    """
+    if sys.version_info < (3, 10):  # pragma: no cover (<3.10)
+        # Python 3.9 or older
+        err = SyntaxError(
+            "invalid syntax", ("<unknown>", 2, 5, "bad python:\n")
+        )
+        expected = (2, 4)
+    else:  # pragma: no cover (3.10+)
+        err = SyntaxError(
+            "invalid syntax", ("<unknown>", 2, 1, "bad python:\n", 2, 11)
+        )
+        expected = (2, 1)
+    file_checker = checker.FileChecker("-", {}, mock.MagicMock())
+    actual = file_checker._extract_syntax_information(err)
+    assert actual == expected
diff --git a/tests/integration/test_main.py b/tests/integration/test_main.py
index e2f58c5e..8ad76544 100644
--- a/tests/integration/test_main.py
+++ b/tests/integration/test_main.py
@@ -1,8 +1,9 @@
 """Integration tests for the main entrypoint of flake8."""
 import json
 import os
+import sys
+from unittest import mock
 
-import mock
 import pytest
 
 from flake8 import utils
@@ -17,7 +18,7 @@ def _call_main(argv, retv=0):
 
 def test_diff_option(tmpdir, capsys):
     """Ensure that `flake8 --diff` works."""
-    t_py_contents = '''\
+    t_py_contents = """\
 import os
 import sys  # unused but not part of diff
 
@@ -26,9 +27,9 @@ def test_diff_option(tmpdir, capsys):
 print(os.path.join('foo', 'bar'))
 
 y  # part of the diff and an error
-'''
+"""
 
-    diff = '''\
+    diff = """\
 diff --git a/t.py b/t.py
 index d64ac39..7d943de 100644
 --- a/t.py
@@ -39,39 +40,39 @@ def test_diff_option(tmpdir, capsys):
  print(os.path.join('foo', 'bar'))
 +
 +y  # part of the diff and an error
-'''
+"""
 
-    with mock.patch.object(utils, 'stdin_get_value', return_value=diff):
+    with mock.patch.object(utils, "stdin_get_value", return_value=diff):
         with tmpdir.as_cwd():
-            tmpdir.join('t.py').write(t_py_contents)
-            _call_main(['--diff'], retv=1)
+            tmpdir.join("t.py").write(t_py_contents)
+            _call_main(["--diff"], retv=1)
 
     out, err = capsys.readouterr()
     assert out == "t.py:8:1: F821 undefined name 'y'\n"
-    assert err == ''
+    assert err == ""
 
 
 def test_form_feed_line_split(tmpdir, capsys):
     """Test that form feed is treated the same for stdin."""
-    src = 'x=1\n\f\ny=1\n'
-    expected_out = '''\
+    src = "x=1\n\f\ny=1\n"
+    expected_out = """\
 t.py:1:2: E225 missing whitespace around operator
 t.py:3:2: E225 missing whitespace around operator
-'''
+"""
 
     with tmpdir.as_cwd():
-        tmpdir.join('t.py').write(src)
+        tmpdir.join("t.py").write(src)
 
-        with mock.patch.object(utils, 'stdin_get_value', return_value=src):
-            _call_main(['-', '--stdin-display-name=t.py'], retv=1)
+        with mock.patch.object(utils, "stdin_get_value", return_value=src):
+            _call_main(["-", "--stdin-display-name=t.py"], retv=1)
             out, err = capsys.readouterr()
             assert out == expected_out
-            assert err == ''
+            assert err == ""
 
-        _call_main(['t.py'], retv=1)
+        _call_main(["t.py"], retv=1)
         out, err = capsys.readouterr()
         assert out == expected_out
-        assert err == ''
+        assert err == ""
 
 
 def test_e101_indent_char_does_not_reset(tmpdir, capsys):
@@ -89,82 +90,79 @@ def test_e101_indent_char_does_not_reset(tmpdir, capsys):
 """
 
     with tmpdir.as_cwd():
-        tmpdir.join('t.py').write(t_py_contents)
-        _call_main(['t.py'])
+        tmpdir.join("t.py").write(t_py_contents)
+        _call_main(["t.py"])
 
 
 def test_statistics_option(tmpdir, capsys):
     """Ensure that `flake8 --statistics` works."""
     with tmpdir.as_cwd():
-        tmpdir.join('t.py').write('import os\nimport sys\n')
-        _call_main(['--statistics', 't.py'], retv=1)
+        tmpdir.join("t.py").write("import os\nimport sys\n")
+        _call_main(["--statistics", "t.py"], retv=1)
 
-    out, err = capsys.readouterr()
-    assert out == '''\
+    expected = """\
 t.py:1:1: F401 'os' imported but unused
 t.py:2:1: F401 'sys' imported but unused
 2     F401 'os' imported but unused
-'''
-    assert err == ''
+"""
+    out, err = capsys.readouterr()
+    assert out == expected
+    assert err == ""
 
 
 def test_show_source_option(tmpdir, capsys):
     """Ensure that --show-source and --no-show-source work."""
     with tmpdir.as_cwd():
-        tmpdir.join('tox.ini').write('[flake8]\nshow_source = true\n')
-        tmpdir.join('t.py').write('import os\n')
-        _call_main(['t.py'], retv=1)
+        tmpdir.join("tox.ini").write("[flake8]\nshow_source = true\n")
+        tmpdir.join("t.py").write("import os\n")
+        _call_main(["t.py"], retv=1)
 
-    out, err = capsys.readouterr()
-    assert out == '''\
+    expected = """\
 t.py:1:1: F401 'os' imported but unused
 import os
 ^
-'''
-    assert err == ''
+"""
+    out, err = capsys.readouterr()
+    assert out == expected
+    assert err == ""
 
     with tmpdir.as_cwd():
-        _call_main(['t.py', '--no-show-source'], retv=1)
+        _call_main(["t.py", "--no-show-source"], retv=1)
 
-    out, err = capsys.readouterr()
-    assert out == '''\
+    expected = """\
 t.py:1:1: F401 'os' imported but unused
-'''
-    assert err == ''
+"""
+    out, err = capsys.readouterr()
+    assert out == expected
+    assert err == ""
 
 
 def test_extend_exclude(tmpdir, capsys):
     """Ensure that `flake8 --extend-exclude` works."""
-    for d in ['project', 'vendor', 'legacy', '.git', '.tox', '.hg']:
-        tmpdir.mkdir(d).join('t.py').write('import os\nimport sys\n')
+    for d in ["project", "vendor", "legacy", ".git", ".tox", ".hg"]:
+        tmpdir.mkdir(d).join("t.py").write("import os\nimport sys\n")
 
     with tmpdir.as_cwd():
-        _call_main(['--extend-exclude=vendor,legacy/'], retv=1)
+        _call_main(["--extend-exclude=vendor,legacy/"], retv=1)
 
     out, err = capsys.readouterr()
-    expected_out = '''\
+    expected_out = """\
 ./project/t.py:1:1: F401 'os' imported but unused
 ./project/t.py:2:1: F401 'sys' imported but unused
-'''
-    assert out == expected_out.replace('/', os.sep)
-    assert err == ''
+"""
+    assert out == expected_out.replace("/", os.sep)
+    assert err == ""
 
 
 def test_malformed_per_file_ignores_error(tmpdir, capsys):
     """Test the error message for malformed `per-file-ignores`."""
-    setup_cfg = '''\
+    setup_cfg = """\
 [flake8]
 per-file-ignores =
     incorrect/*
     values/*
-'''
-
-    with tmpdir.as_cwd():
-        tmpdir.join('setup.cfg').write(setup_cfg)
-        _call_main(['.'], retv=1)
-
-    out, err = capsys.readouterr()
-    assert out == '''\
+"""
+    expected = """\
 There was a critical error during execution of Flake8:
 Expected `per-file-ignores` to be a mapping from file exclude patterns to ignore codes.
 
@@ -172,54 +170,78 @@ def test_malformed_per_file_ignores_error(tmpdir, capsys):
 
     incorrect/*
     values/*
-'''  # noqa: E501
+"""  # noqa: E501
+
+    with tmpdir.as_cwd():
+        tmpdir.join("setup.cfg").write(setup_cfg)
+        _call_main(["."], retv=1)
+
+    out, err = capsys.readouterr()
+    assert out == expected
 
 
 def test_tokenization_error_but_not_syntax_error(tmpdir, capsys):
     """Test that flake8 does not crash on tokenization errors."""
     with tmpdir.as_cwd():
         # this is a crash in the tokenizer, but not in the ast
-        tmpdir.join('t.py').write("b'foo' \\\n")
-        _call_main(['t.py'], retv=1)
+        tmpdir.join("t.py").write("b'foo' \\\n")
+        _call_main(["t.py"], retv=1)
+
+    if hasattr(sys, "pypy_version_info"):  # pragma: no cover (pypy)
+        expected = "t.py:2:1: E999 SyntaxError: end of file (EOF) in multi-line statement\n"  # noqa: E501
+    elif sys.version_info < (3, 8):  # pragma: no cover (<cp38)
+        expected = "t.py:2:1: E902 TokenError: EOF in multi-line statement\n"
+    elif sys.version_info < (3, 10):  # pragma: no cover (cp38+)
+        expected = "t.py:1:8: E999 SyntaxError: unexpected EOF while parsing\n"
+    else:  # pragma: no cover (cp310+)
+        expected = "t.py:1:10: E999 SyntaxError: unexpected EOF while parsing\n"  # noqa: E501
 
     out, err = capsys.readouterr()
-    assert out == 't.py:1:1: E902 TokenError: EOF in multi-line statement\n'
-    assert err == ''
+    assert out == expected
+    assert err == ""
 
 
 def test_tokenization_error_is_a_syntax_error(tmpdir, capsys):
     """Test when tokenize raises a SyntaxError."""
     with tmpdir.as_cwd():
-        tmpdir.join('t.py').write('if True:\n    pass\n pass\n')
-        _call_main(['t.py'], retv=1)
+        tmpdir.join("t.py").write("if True:\n    pass\n pass\n")
+        _call_main(["t.py"], retv=1)
+
+    if hasattr(sys, "pypy_version_info"):  # pragma: no cover (pypy)
+        expected = "t.py:3:2: E999 IndentationError: unindent does not match any outer indentation level\n"  # noqa: E501
+    elif sys.version_info < (3, 10):  # pragma: no cover (<cp310)
+        expected = "t.py:3:5: E999 IndentationError: unindent does not match any outer indentation level\n"  # noqa: E501
+    else:  # pragma: no cover (cp310+)
+        expected = "t.py:3:7: E999 IndentationError: unindent does not match any outer indentation level\n"  # noqa: E501
 
     out, err = capsys.readouterr()
-    assert out == 't.py:1:1: E902 IndentationError: unindent does not match any outer indentation level\n'  # noqa: E501
-    assert err == ''
+    assert out == expected
+    assert err == ""
 
 
 def test_bug_report_successful(capsys):
     """Test that --bug-report does not crash."""
-    _call_main(['--bug-report'])
+    _call_main(["--bug-report"])
     out, err = capsys.readouterr()
     assert json.loads(out)
-    assert err == ''
+    assert err == ""
 
 
 def test_specific_noqa_does_not_clobber_pycodestyle_noqa(tmpdir, capsys):
-    """See https://gitlab.com/pycqa/flake8/issues/552."""
+    """See https://github.com/pycqa/flake8/issues/1104."""
     with tmpdir.as_cwd():
-        tmpdir.join('t.py').write("test = ('ABC' == None)  # noqa: E501\n")
-        _call_main(['t.py'], retv=1)
+        tmpdir.join("t.py").write("test = ('ABC' == None)  # noqa: E501\n")
+        _call_main(["t.py"], retv=1)
 
-    out, err = capsys.readouterr()
-    assert out == '''\
+    expected = """\
 t.py:1:15: E711 comparison to None should be 'if cond is None:'
-'''
+"""
+    out, err = capsys.readouterr()
+    assert out == expected
 
 
 def test_specific_noqa_on_line_with_continuation(tmpdir, capsys):
-    """See https://gitlab.com/pycqa/flake8/issues/375."""
+    """See https://github.com/pycqa/flake8/issues/621."""
     t_py_src = '''\
 from os \\
     import path  # noqa: F401
@@ -230,63 +252,67 @@ def test_specific_noqa_on_line_with_continuation(tmpdir, capsys):
 '''
 
     with tmpdir.as_cwd():
-        tmpdir.join('t.py').write(t_py_src)
-        _call_main(['t.py'], retv=0)
+        tmpdir.join("t.py").write(t_py_src)
+        _call_main(["t.py"], retv=0)
 
     out, err = capsys.readouterr()
-    assert out == err == ''
+    assert out == err == ""
 
 
 def test_physical_line_file_not_ending_in_newline(tmpdir, capsys):
     """See https://github.com/PyCQA/pycodestyle/issues/960."""
-    t_py_src = 'def f():\n\tpass'
+    t_py_src = "def f():\n\tpass"
 
     with tmpdir.as_cwd():
-        tmpdir.join('t.py').write(t_py_src)
-        _call_main(['t.py'], retv=1)
+        tmpdir.join("t.py").write(t_py_src)
+        _call_main(["t.py"], retv=1)
 
-    out, err = capsys.readouterr()
-    assert out == '''\
+    expected = """\
 t.py:2:1: W191 indentation contains tabs
 t.py:2:6: W292 no newline at end of file
-'''
+"""
+    out, err = capsys.readouterr()
+    assert out == expected
 
 
 def test_physical_line_file_not_ending_in_newline_trailing_ws(tmpdir, capsys):
     """See https://github.com/PyCQA/pycodestyle/issues/960."""
-    t_py_src = 'x = 1   '
+    t_py_src = "x = 1   "
 
     with tmpdir.as_cwd():
-        tmpdir.join('t.py').write(t_py_src)
-        _call_main(['t.py'], retv=1)
+        tmpdir.join("t.py").write(t_py_src)
+        _call_main(["t.py"], retv=1)
 
-    out, err = capsys.readouterr()
-    assert out == '''\
+    expected = """\
 t.py:1:6: W291 trailing whitespace
 t.py:1:9: W292 no newline at end of file
-'''
+"""
+    out, err = capsys.readouterr()
+    assert out == expected
 
 
 def test_obtaining_args_from_sys_argv_when_not_explicity_provided(capsys):
     """Test that arguments are obtained from 'sys.argv'."""
-    with mock.patch('sys.argv', ['flake8', '--help']):
+    with mock.patch("sys.argv", ["flake8", "--help"]):
         _call_main(None)
 
     out, err = capsys.readouterr()
-    assert out.startswith('usage: flake8 [options] file file ...\n')
-    assert err == ''
+    assert out.startswith("usage: flake8 [options] file file ...\n")
+    assert err == ""
 
 
 def test_cli_config_option_respected(tmp_path):
     """Test --config is used."""
     config = tmp_path / "flake8.ini"
-    config.write_text(u"""\
+    config.write_text(
+        """\
 [flake8]
 ignore = F401
-""")
+"""
+    )
 
     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)])
 
@@ -294,13 +320,15 @@ def test_cli_config_option_respected(tmp_path):
 def test_cli_isolated_overrides_config_option(tmp_path):
     """Test --isolated overrides --config."""
     config = tmp_path / "flake8.ini"
-    config.write_text(u"""\
+    config.write_text(
+        """\
 [flake8]
 ignore = F401
-""")
+"""
+    )
 
     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)
 
@@ -316,13 +344,13 @@ def test_file_not_found(tmpdir, capsys):
 
 def test_output_file(tmpdir, capsys):
     """Ensure that --output-file is honored."""
-    tmpdir.join('t.py').write('import os\n')
+    tmpdir.join("t.py").write("import os\n")
 
     with tmpdir.as_cwd():
-        _call_main(['t.py', '--output-file=f'], retv=1)
+        _call_main(["t.py", "--output-file=a/b/f"], retv=1)
 
     out, err = capsys.readouterr()
     assert out == err == ""
 
     expected = "t.py:1:1: F401 'os' imported but unused\n"
-    assert tmpdir.join('f').read() == expected
+    assert tmpdir.join("a/b/f").read() == expected
diff --git a/tests/integration/test_plugins.py b/tests/integration/test_plugins.py
index 859fb698..7fff9dff 100644
--- a/tests/integration/test_plugins.py
+++ b/tests/integration/test_plugins.py
@@ -1,16 +1,15 @@
 """Integration tests for plugin loading."""
 from flake8.main import application
 
+LOCAL_PLUGIN_CONFIG = "tests/fixtures/config_files/local-plugin.ini"
+LOCAL_PLUGIN_PATH_CONFIG = "tests/fixtures/config_files/local-plugin-path.ini"
 
-LOCAL_PLUGIN_CONFIG = 'tests/fixtures/config_files/local-plugin.ini'
-LOCAL_PLUGIN_PATH_CONFIG = 'tests/fixtures/config_files/local-plugin-path.ini'
 
-
-class ExtensionTestPlugin(object):
+class ExtensionTestPlugin:
     """Extension test plugin."""
 
-    name = 'ExtensionTestPlugin'
-    version = '1.0.0'
+    name = "ExtensionTestPlugin"
+    version = "1.0.0"
 
     def __init__(self, tree):
         """Construct an instance of test plugin."""
@@ -21,14 +20,14 @@ def run(self):
     @classmethod
     def add_options(cls, parser):
         """Register options."""
-        parser.add_option('--anopt')
+        parser.add_option("--anopt")
 
 
-class ReportTestPlugin(object):
+class ReportTestPlugin:
     """Report test plugin."""
 
-    name = 'ReportTestPlugin'
-    version = '1.0.0'
+    name = "ReportTestPlugin"
+    version = "1.0.0"
 
     def __init__(self, tree):
         """Construct an instance of test plugin."""
@@ -40,24 +39,29 @@ def run(self):
 def test_enable_local_plugin_from_config():
     """App can load a local plugin from config file."""
     app = application.Application()
-    app.initialize(['flake8', '--config', LOCAL_PLUGIN_CONFIG])
+    app.initialize(["flake8", "--config", LOCAL_PLUGIN_CONFIG])
 
-    assert app.check_plugins['XE'].plugin is ExtensionTestPlugin
-    assert app.formatting_plugins['XR'].plugin is ReportTestPlugin
+    assert app.check_plugins is not None
+    assert app.check_plugins["XE"].plugin is ExtensionTestPlugin
+    assert app.formatting_plugins is not None
+    assert app.formatting_plugins["XR"].plugin is ReportTestPlugin
 
 
 def test_local_plugin_can_add_option():
     """A local plugin can add a CLI option."""
     app = application.Application()
     app.initialize(
-        ['flake8', '--config', LOCAL_PLUGIN_CONFIG, '--anopt', 'foo'])
+        ["flake8", "--config", LOCAL_PLUGIN_CONFIG, "--anopt", "foo"]
+    )
 
-    assert app.options.anopt == 'foo'
+    assert app.options is not None
+    assert app.options.anopt == "foo"
 
 
 def test_enable_local_plugin_at_non_installed_path():
     """Can add a paths option in local-plugins config section for finding."""
     app = application.Application()
-    app.initialize(['flake8', '--config', LOCAL_PLUGIN_PATH_CONFIG])
+    app.initialize(["flake8", "--config", LOCAL_PLUGIN_PATH_CONFIG])
 
-    assert app.check_plugins['XE'].plugin.name == 'ExtensionTestPlugin2'
+    assert app.check_plugins is not None
+    assert app.check_plugins["XE"].plugin.name == "ExtensionTestPlugin2"
diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py
index a407b507..28083879 100644
--- a/tests/unit/conftest.py
+++ b/tests/unit/conftest.py
@@ -6,13 +6,13 @@
 
 def options_from(**kwargs):
     """Generate a Values instances with our kwargs."""
-    kwargs.setdefault('hang_closing', True)
-    kwargs.setdefault('max_line_length', 79)
-    kwargs.setdefault('max_doc_length', None)
-    kwargs.setdefault('indent_size', 4)
-    kwargs.setdefault('verbose', False)
-    kwargs.setdefault('stdin_display_name', 'stdin')
-    kwargs.setdefault('disable_noqa', False)
+    kwargs.setdefault("hang_closing", True)
+    kwargs.setdefault("max_line_length", 79)
+    kwargs.setdefault("max_doc_length", None)
+    kwargs.setdefault("indent_size", 4)
+    kwargs.setdefault("verbose", False)
+    kwargs.setdefault("stdin_display_name", "stdin")
+    kwargs.setdefault("disable_noqa", False)
     return argparse.Namespace(**kwargs)
 
 
diff --git a/tests/unit/test_application.py b/tests/unit/test_application.py
index 51adefba..b95e3832 100644
--- a/tests/unit/test_application.py
+++ b/tests/unit/test_application.py
@@ -1,8 +1,8 @@
 """Tests for the Application class."""
 import argparse
 import sys
+from unittest import mock
 
-import mock
 import pytest
 
 from flake8.main import application as app
@@ -10,10 +10,10 @@
 
 def options(**kwargs):
     """Generate argparse.Namespace for our Application."""
-    kwargs.setdefault('verbose', 0)
-    kwargs.setdefault('output_file', None)
-    kwargs.setdefault('count', False)
-    kwargs.setdefault('exit_zero', False)
+    kwargs.setdefault("verbose", 0)
+    kwargs.setdefault("output_file", None)
+    kwargs.setdefault("count", False)
+    kwargs.setdefault("exit_zero", False)
     return argparse.Namespace(**kwargs)
 
 
@@ -24,19 +24,20 @@ def application():
 
 
 @pytest.mark.parametrize(
-    'result_count, catastrophic, exit_zero, value', [
+    "result_count, catastrophic, exit_zero, value",
+    [
         (0, False, False, False),
         (0, True, False, True),
         (2, False, False, True),
         (2, True, False, True),
-
         (0, True, True, True),
         (2, False, True, False),
         (2, True, True, True),
-    ]
+    ],
 )
-def test_exit_does_raise(result_count, catastrophic, exit_zero, value,
-                         application):
+def test_exit_does_raise(
+    result_count, catastrophic, exit_zero, value, application
+):
     """Verify Application.exit doesn't raise SystemExit."""
     application.result_count = result_count
     application.catastrophic_failure = catastrophic
@@ -53,10 +54,10 @@ def test_warns_on_unknown_formatter_plugin_name(application):
     default = mock.Mock()
     execute = default.execute
     application.formatting_plugins = {
-        'default': default,
+        "default": default,
     }
-    with mock.patch.object(app.LOG, 'warning') as warning:
-        assert execute is application.formatter_for('fake-plugin-name')
+    with mock.patch.object(app.LOG, "warning") as warning:
+        assert execute is application.formatter_for("fake-plugin-name")
 
     assert warning.called is True
     assert warning.call_count == 1
@@ -67,12 +68,12 @@ def test_returns_specified_plugin(application):
     desired = mock.Mock()
     execute = desired.execute
     application.formatting_plugins = {
-        'default': mock.Mock(),
-        'desired': desired,
+        "default": mock.Mock(),
+        "desired": desired,
     }
 
-    with mock.patch.object(app.LOG, 'warning') as warning:
-        assert execute is application.formatter_for('desired')
+    with mock.patch.object(app.LOG, "warning") as warning:
+        assert execute is application.formatter_for("desired")
 
     assert warning.called is False
 
@@ -80,10 +81,11 @@ def test_returns_specified_plugin(application):
 def test_prelim_opts_args(application):
     """Verify we get sensible prelim opts and args."""
     opts, args = application.parse_preliminary_options(
-        ['--foo', '--verbose', 'src', 'setup.py', '--statistics', '--version'])
+        ["--foo", "--verbose", "src", "setup.py", "--statistics", "--version"]
+    )
 
     assert opts.verbose
-    assert args == ['--foo', 'src', 'setup.py', '--statistics', '--version']
+    assert args == ["--foo", "src", "setup.py", "--statistics", "--version"]
 
 
 def test_prelim_opts_ignore_help(application):
@@ -91,16 +93,16 @@ def test_prelim_opts_ignore_help(application):
     # GIVEN
 
     # WHEN
-    _, args = application.parse_preliminary_options(['--help', '-h'])
+    _, args = application.parse_preliminary_options(["--help", "-h"])
 
     # THEN
-    assert args == ['--help', '-h']
+    assert args == ["--help", "-h"]
 
 
 def test_prelim_opts_handles_empty(application):
     """Verify empty argv lists are handled correctly."""
-    irrelevant_args = ['myexe', '/path/to/foo']
-    with mock.patch.object(sys, 'argv', irrelevant_args):
+    irrelevant_args = ["myexe", "/path/to/foo"]
+    with mock.patch.object(sys, "argv", irrelevant_args):
         opts, args = application.parse_preliminary_options([])
 
         assert args == []
diff --git a/tests/unit/test_base_formatter.py b/tests/unit/test_base_formatter.py
index ff2df7c4..89589031 100644
--- a/tests/unit/test_base_formatter.py
+++ b/tests/unit/test_base_formatter.py
@@ -1,7 +1,7 @@
 """Tests for the BaseFormatter object."""
 import argparse
+from unittest import mock
 
-import mock
 import pytest
 
 from flake8 import style_guide
@@ -10,23 +10,23 @@
 
 def options(**kwargs):
     """Create an argparse.Namespace instance."""
-    kwargs.setdefault('output_file', None)
-    kwargs.setdefault('tee', False)
+    kwargs.setdefault("output_file", None)
+    kwargs.setdefault("tee", False)
     return argparse.Namespace(**kwargs)
 
 
-@pytest.mark.parametrize('filename', [None, 'out.txt'])
+@pytest.mark.parametrize("filename", [None, "out.txt"])
 def test_start(filename):
     """Verify we open a new file in the start method."""
     mock_open = mock.mock_open()
     formatter = base.BaseFormatter(options(output_file=filename))
-    with mock.patch('flake8.formatting.base.open', mock_open):
+    with mock.patch("flake8.formatting.base.open", mock_open):
         formatter.start()
 
     if filename is None:
         assert mock_open.called is False
     else:
-        mock_open.assert_called_once_with(filename, 'a')
+        mock_open.assert_called_once_with(filename, "a")
 
 
 def test_stop():
@@ -45,71 +45,77 @@ def test_format_needs_to_be_implemented():
     formatter = base.BaseFormatter(options())
     with pytest.raises(NotImplementedError):
         formatter.format(
-            style_guide.Violation('A000', 'file.py', 1, 1, 'error text', None)
+            style_guide.Violation("A000", "file.py", 1, 1, "error text", None)
         )
 
 
 def test_show_source_returns_nothing_when_not_showing_source():
     """Ensure we return nothing when users want nothing."""
     formatter = base.BaseFormatter(options(show_source=False))
-    assert formatter.show_source(
-        style_guide.Violation('A000', 'file.py', 1, 1, 'error text', 'line')
-    ) == ''
+    assert (
+        formatter.show_source(
+            style_guide.Violation(
+                "A000", "file.py", 1, 1, "error text", "line"
+            )
+        )
+        == ""
+    )
 
 
 def test_show_source_returns_nothing_when_there_is_source():
     """Ensure we return nothing when there is no line."""
     formatter = base.BaseFormatter(options(show_source=True))
-    assert formatter.show_source(
-        style_guide.Violation('A000', 'file.py', 1, 1, 'error text', None)
-    ) == ''
-
-
-@pytest.mark.parametrize(('line1', 'line2', 'column'), [
-    (
-        'x=1\n',
-        ' ^',
-        2,
-    ),
-    (
-        '    x=(1\n       +2)\n',
-        '    ^',
-        5,
-    ),
-    (
-        '\tx\t=\ty\n',
-        '\t \t \t^',
-        6,
-    ),
-])
+    assert (
+        formatter.show_source(
+            style_guide.Violation("A000", "file.py", 1, 1, "error text", None)
+        )
+        == ""
+    )
+
+
+@pytest.mark.parametrize(
+    ("line1", "line2", "column"),
+    [
+        (
+            "x=1\n",
+            " ^",
+            2,
+        ),
+        (
+            "    x=(1\n       +2)\n",
+            "    ^",
+            5,
+        ),
+        (
+            "\tx\t=\ty\n",
+            "\t \t \t^",
+            6,
+        ),
+    ],
+)
 def test_show_source_updates_physical_line_appropriately(line1, line2, column):
     """Ensure the error column is appropriately indicated."""
     formatter = base.BaseFormatter(options(show_source=True))
-    error = style_guide.Violation('A000', 'file.py', 1, column, 'error', line1)
+    error = style_guide.Violation("A000", "file.py", 1, column, "error", line1)
     output = formatter.show_source(error)
     assert output == line1 + line2
 
 
-@pytest.mark.parametrize('tee', [False, True])
-def test_write_uses_an_output_file(tee):
+@pytest.mark.parametrize("tee", [False, True])
+def test_write_uses_an_output_file(tee, capsys):
     """Verify that we use the output file when it's present."""
-    line = 'Something to write'
-    source = 'source'
+    line = "Something to write"
+    source = "source"
     filemock = mock.Mock()
 
     formatter = base.BaseFormatter(options(tee=tee))
     formatter.output_fd = filemock
 
-    with mock.patch('flake8.formatting.base.print') as print_func:
-        formatter.write(line, source)
-        if tee:
-            assert print_func.called
-            assert print_func.mock_calls == [
-                mock.call(line, end='\n'),
-                mock.call(source, end='\n'),
-            ]
-        else:
-            assert not print_func.called
+    formatter.write(line, source)
+    if tee:
+        assert capsys.readouterr().out == f"{line}\n{source}\n"
+    else:
+        assert capsys.readouterr().out == ""
 
     assert filemock.write.called is True
     assert filemock.write.call_count == 2
@@ -119,21 +125,15 @@ def test_write_uses_an_output_file(tee):
     ]
 
 
-@mock.patch('flake8.formatting.base.print')
-def test_write_uses_print(print_function):
-    """Verify that we use the print function without an output file."""
-    line = 'Something to write'
-    source = 'source'
+def test_write_produces_stdout(capsys):
+    """Verify that we write to stdout without an output file."""
+    line = "Something to write"
+    source = "source"
 
     formatter = base.BaseFormatter(options())
     formatter.write(line, source)
 
-    assert print_function.called is True
-    assert print_function.call_count == 2
-    assert print_function.mock_calls == [
-        mock.call(line, end='\n'),
-        mock.call(source, end='\n'),
-    ]
+    assert capsys.readouterr().out == f"{line}\n{source}\n"
 
 
 class AfterInitFormatter(base.BaseFormatter):
@@ -163,14 +163,14 @@ def test_handle_formats_the_error():
     formatter = FormatFormatter(options(show_source=False))
     filemock = formatter.output_fd = mock.Mock()
     error = style_guide.Violation(
-        code='A001',
-        filename='example.py',
+        code="A001",
+        filename="example.py",
         line_number=1,
         column_number=1,
-        text='Fake error',
-        physical_line='a = 1',
+        text="Fake error",
+        physical_line="a = 1",
     )
 
     formatter.handle(error)
 
-    filemock.write.assert_called_once_with(repr(error) + '\n')
+    filemock.write.assert_called_once_with(repr(error) + "\n")
diff --git a/tests/unit/test_checker_manager.py b/tests/unit/test_checker_manager.py
index d3e7e619..f82dc49c 100644
--- a/tests/unit/test_checker_manager.py
+++ b/tests/unit/test_checker_manager.py
@@ -1,7 +1,7 @@
 """Tests for the Manager object for FileCheckers."""
 import errno
+from unittest import mock
 
-import mock
 import pytest
 
 from flake8 import checker
@@ -10,10 +10,12 @@
 
 def style_guide_mock():
     """Create a mock StyleGuide object."""
-    return mock.MagicMock(**{
-        'options.diff': False,
-        'options.jobs': JobsArgument("4"),
-    })
+    return mock.MagicMock(
+        **{
+            "options.diff": False,
+            "options.jobs": JobsArgument("4"),
+        }
+    )
 
 
 def _parallel_checker_manager():
@@ -27,21 +29,21 @@ def _parallel_checker_manager():
 
 def test_oserrors_cause_serial_fall_back():
     """Verify that OSErrors will cause the Manager to fallback to serial."""
-    err = OSError(errno.ENOSPC, 'Ominous message about spaceeeeee')
-    with mock.patch('_multiprocessing.SemLock', side_effect=err):
+    err = OSError(errno.ENOSPC, "Ominous message about spaceeeeee")
+    with mock.patch("_multiprocessing.SemLock", side_effect=err):
         manager = _parallel_checker_manager()
-        with mock.patch.object(manager, 'run_serial') as serial:
+        with mock.patch.object(manager, "run_serial") as serial:
             manager.run()
     assert serial.call_count == 1
 
 
-@mock.patch('flake8.checker._multiprocessing_is_fork', return_value=True)
+@mock.patch("flake8.checker._multiprocessing_is_fork", return_value=True)
 def test_oserrors_are_reraised(is_windows):
     """Verify that unexpected OSErrors will cause the Manager to reraise."""
-    err = OSError(errno.EAGAIN, 'Ominous message')
-    with mock.patch('_multiprocessing.SemLock', side_effect=err):
+    err = OSError(errno.EAGAIN, "Ominous message")
+    with mock.patch("_multiprocessing.SemLock", side_effect=err):
         manager = _parallel_checker_manager()
-        with mock.patch.object(manager, 'run_serial') as serial:
+        with mock.patch.object(manager, "run_serial") as serial:
             with pytest.raises(OSError):
                 manager.run()
     assert serial.call_count == 0
@@ -50,7 +52,7 @@ def test_oserrors_are_reraised(is_windows):
 def test_multiprocessing_is_disabled():
     """Verify not being able to import multiprocessing forces jobs to 0."""
     style_guide = style_guide_mock()
-    with mock.patch('flake8.checker.multiprocessing', None):
+    with mock.patch("flake8.checker.multiprocessing", None):
         manager = checker.Manager(style_guide, [], [])
         assert manager.jobs == 0
 
@@ -58,20 +60,20 @@ def test_multiprocessing_is_disabled():
 def test_make_checkers():
     """Verify that we create a list of FileChecker instances."""
     style_guide = style_guide_mock()
-    files = ['file1', 'file2']
+    files = ["file1", "file2"]
     checkplugins = mock.Mock()
     checkplugins.to_dictionary.return_value = {
-        'ast_plugins': [],
-        'logical_line_plugins': [],
-        'physical_line_plugins': [],
+        "ast_plugins": [],
+        "logical_line_plugins": [],
+        "physical_line_plugins": [],
     }
-    with mock.patch('flake8.checker.multiprocessing', None):
+    with mock.patch("flake8.checker.multiprocessing", None):
         manager = checker.Manager(style_guide, files, checkplugins)
 
-    with mock.patch('flake8.utils.filenames_from') as filenames_from:
-        filenames_from.side_effect = [['file1'], ['file2']]
-        with mock.patch('flake8.utils.fnmatch', return_value=True):
-            with mock.patch('flake8.processor.FileProcessor'):
+    with mock.patch("flake8.utils.filenames_from") as filenames_from:
+        filenames_from.side_effect = [["file1"], ["file2"]]
+        with mock.patch("flake8.utils.fnmatch", return_value=True):
+            with mock.patch("flake8.processor.FileProcessor"):
                 manager.make_checkers()
 
     assert manager._all_checkers
diff --git a/tests/unit/test_config_file_finder.py b/tests/unit/test_config_file_finder.py
index d21b03d8..51167962 100644
--- a/tests/unit/test_config_file_finder.py
+++ b/tests/unit/test_config_file_finder.py
@@ -1,80 +1,99 @@
-# -*- coding: utf-8 -*-
 """Tests for the ConfigFileFinder."""
 import configparser
 import os
+from unittest import mock
 
-import mock
 import pytest
 
 from flake8.options import config
 
-CLI_SPECIFIED_FILEPATH = 'tests/fixtures/config_files/cli-specified.ini'
-BROKEN_CONFIG_PATH = 'tests/fixtures/config_files/broken.ini'
+CLI_SPECIFIED_FILEPATH = "tests/fixtures/config_files/cli-specified.ini"
+BROKEN_CONFIG_PATH = "tests/fixtures/config_files/broken.ini"
 
 
 def test_cli_config():
     """Verify opening and reading the file specified via the cli."""
     cli_filepath = CLI_SPECIFIED_FILEPATH
-    finder = config.ConfigFileFinder('flake8')
+    finder = config.ConfigFileFinder("flake8")
 
     parsed_config = finder.cli_config(cli_filepath)
-    assert parsed_config.has_section('flake8')
-
-
-@pytest.mark.parametrize('cwd,expected', [
-    # Root directory of project
-    (os.path.abspath('.'),
-        [os.path.abspath('setup.cfg'),
-            os.path.abspath('tox.ini')]),
-    # Subdirectory of project directory
-    (os.path.abspath('src'),
-        [os.path.abspath('setup.cfg'),
-            os.path.abspath('tox.ini')]),
-    # Outside of project directory
-    (os.path.abspath('/'),
-        []),
-])
+    assert parsed_config.has_section("flake8")
+
+
+@pytest.mark.parametrize(
+    "cwd,expected",
+    [
+        # Root directory of project
+        (
+            os.path.abspath("."),
+            [os.path.abspath("setup.cfg"), os.path.abspath("tox.ini")],
+        ),
+        # Subdirectory of project directory
+        (
+            os.path.abspath("src"),
+            [os.path.abspath("setup.cfg"), os.path.abspath("tox.ini")],
+        ),
+        # Outside of project directory
+        (os.path.abspath("/"), []),
+    ],
+)
 def test_generate_possible_local_files(cwd, expected):
     """Verify generation of all possible config paths."""
-    finder = config.ConfigFileFinder('flake8')
+    finder = config.ConfigFileFinder("flake8")
 
-    with mock.patch.object(os, 'getcwd', return_value=cwd):
+    with mock.patch.object(os, "getcwd", return_value=cwd):
         config_files = list(finder.generate_possible_local_files())
 
     assert config_files == expected
 
 
-@pytest.mark.parametrize('extra_config_files,expected', [
-    # Extra config files specified
-    ([CLI_SPECIFIED_FILEPATH],
-        [os.path.abspath('setup.cfg'),
-            os.path.abspath('tox.ini'),
-            os.path.abspath(CLI_SPECIFIED_FILEPATH)]),
-    # Missing extra config files specified
-    ([CLI_SPECIFIED_FILEPATH,
-        'tests/fixtures/config_files/missing.ini'],
-        [os.path.abspath('setup.cfg'),
-            os.path.abspath('tox.ini'),
-            os.path.abspath(CLI_SPECIFIED_FILEPATH)]),
-])
+@pytest.mark.parametrize(
+    "extra_config_files,expected",
+    [
+        # Extra config files specified
+        (
+            [CLI_SPECIFIED_FILEPATH],
+            [
+                os.path.abspath("setup.cfg"),
+                os.path.abspath("tox.ini"),
+                os.path.abspath(CLI_SPECIFIED_FILEPATH),
+            ],
+        ),
+        # Missing extra config files specified
+        (
+            [
+                CLI_SPECIFIED_FILEPATH,
+                "tests/fixtures/config_files/missing.ini",
+            ],
+            [
+                os.path.abspath("setup.cfg"),
+                os.path.abspath("tox.ini"),
+                os.path.abspath(CLI_SPECIFIED_FILEPATH),
+            ],
+        ),
+    ],
+)
 def test_local_config_files(extra_config_files, expected):
     """Verify discovery of local config files."""
-    finder = config.ConfigFileFinder('flake8', extra_config_files)
+    finder = config.ConfigFileFinder("flake8", extra_config_files)
 
     assert list(finder.local_config_files()) == expected
 
 
 def test_local_configs():
     """Verify we return a ConfigParser."""
-    finder = config.ConfigFileFinder('flake8')
+    finder = config.ConfigFileFinder("flake8")
 
     assert isinstance(finder.local_configs(), configparser.RawConfigParser)
 
 
-@pytest.mark.parametrize('files', [
-    [BROKEN_CONFIG_PATH],
-    [CLI_SPECIFIED_FILEPATH, BROKEN_CONFIG_PATH],
-])
+@pytest.mark.parametrize(
+    "files",
+    [
+        [BROKEN_CONFIG_PATH],
+        [CLI_SPECIFIED_FILEPATH, BROKEN_CONFIG_PATH],
+    ],
+)
 def test_read_config_catches_broken_config_files(files):
     """Verify that we do not allow the exception to bubble up."""
     _, parsed = config.ConfigFileFinder._read_config(*files)
@@ -83,40 +102,42 @@ def test_read_config_catches_broken_config_files(files):
 
 def test_read_config_catches_decoding_errors(tmpdir):
     """Verify that we do not allow the exception to bubble up."""
-    setup_cfg = tmpdir.join('setup.cfg')
+    setup_cfg = tmpdir.join("setup.cfg")
     # pick bytes that are unlikely to decode
-    setup_cfg.write_binary(b'[x]\ny = \x81\x8d\x90\x9d')
+    setup_cfg.write_binary(b"[x]\ny = \x81\x8d\x90\x9d")
     _, parsed = config.ConfigFileFinder._read_config(setup_cfg.strpath)
     assert parsed == []
 
 
 def test_config_file_default_value():
     """Verify the default 'config_file' attribute value."""
-    finder = config.ConfigFileFinder('flake8')
+    finder = config.ConfigFileFinder("flake8")
     assert finder.config_file is None
 
 
 def test_setting_config_file_value():
     """Verify the 'config_file' attribute matches constructed value."""
-    config_file_value = 'flake8.ini'
-    finder = config.ConfigFileFinder('flake8', config_file=config_file_value)
+    config_file_value = "flake8.ini"
+    finder = config.ConfigFileFinder("flake8", config_file=config_file_value)
     assert finder.config_file == config_file_value
 
 
 def test_ignore_config_files_default_value():
     """Verify the default 'ignore_config_files' attribute value."""
-    finder = config.ConfigFileFinder('flake8')
+    finder = config.ConfigFileFinder("flake8")
     assert finder.ignore_config_files is False
 
 
-@pytest.mark.parametrize('ignore_config_files_arg', [
-    False,
-    True,
-])
+@pytest.mark.parametrize(
+    "ignore_config_files_arg",
+    [
+        False,
+        True,
+    ],
+)
 def test_setting_ignore_config_files_value(ignore_config_files_arg):
     """Verify the 'ignore_config_files' attribute matches constructed value."""
     finder = config.ConfigFileFinder(
-        'flake8',
-        ignore_config_files=ignore_config_files_arg
+        "flake8", ignore_config_files=ignore_config_files_arg
     )
     assert finder.ignore_config_files is ignore_config_files_arg
diff --git a/tests/unit/test_config_parser.py b/tests/unit/test_config_parser.py
new file mode 100644
index 00000000..0baa1083
--- /dev/null
+++ b/tests/unit/test_config_parser.py
@@ -0,0 +1,188 @@
+"""Unit tests for flake8.options.config.ConfigParser."""
+import os
+from unittest import mock
+
+import pytest
+
+from flake8.options import config
+from flake8.options import manager
+
+
+@pytest.fixture
+def optmanager():
+    """Generate an OptionManager with simple values."""
+    return manager.OptionManager(prog="flake8", version="3.0.0a1")
+
+
+@pytest.fixture
+def config_finder():
+    """Generate a simple ConfigFileFinder."""
+    return config.ConfigFileFinder("flake8")
+
+
+def test_parse_cli_config(optmanager, config_finder):
+    """Parse the specified config file as a cli config file."""
+    optmanager.add_option(
+        "--exclude",
+        parse_from_config=True,
+        comma_separated_list=True,
+        normalize_paths=True,
+    )
+    optmanager.add_option(
+        "--ignore", parse_from_config=True, comma_separated_list=True
+    )
+    optmanager.add_option("--quiet", parse_from_config=True, action="count")
+    parser = config.ConfigParser(optmanager, config_finder)
+
+    config_file = "tests/fixtures/config_files/cli-specified.ini"
+    parsed_config = parser.parse_cli_config(config_file)
+
+    config_dir = os.path.dirname(config_file)
+    assert parsed_config == {
+        "ignore": ["E123", "W234", "E111"],
+        "exclude": [
+            os.path.abspath(os.path.join(config_dir, "foo/")),
+            os.path.abspath(os.path.join(config_dir, "bar/")),
+            os.path.abspath(os.path.join(config_dir, "bogus/")),
+        ],
+        "quiet": 1,
+    }
+
+
+@pytest.mark.parametrize(
+    "filename,is_configured_by",
+    [
+        ("tests/fixtures/config_files/cli-specified.ini", True),
+        ("tests/fixtures/config_files/no-flake8-section.ini", False),
+    ],
+)
+def test_is_configured_by(
+    filename, is_configured_by, optmanager, config_finder
+):
+    """Verify the behaviour of the is_configured_by method."""
+    parsed_config, _ = config.ConfigFileFinder._read_config(filename)
+    parser = config.ConfigParser(optmanager, config_finder)
+
+    assert parser.is_configured_by(parsed_config) is is_configured_by
+
+
+def test_parse_local_config(optmanager, config_finder):
+    """Verify parsing of local config files."""
+    optmanager.add_option(
+        "--exclude",
+        parse_from_config=True,
+        comma_separated_list=True,
+        normalize_paths=True,
+    )
+    optmanager.add_option(
+        "--ignore", parse_from_config=True, comma_separated_list=True
+    )
+    optmanager.add_option("--quiet", parse_from_config=True, action="count")
+    parser = config.ConfigParser(optmanager, config_finder)
+
+    with mock.patch.object(config_finder, "local_config_files") as localcfs:
+        localcfs.return_value = [
+            "tests/fixtures/config_files/cli-specified.ini"
+        ]
+        parsed_config = parser.parse_local_config()
+
+    assert parsed_config == {
+        "ignore": ["E123", "W234", "E111"],
+        "exclude": [
+            os.path.abspath("foo/"),
+            os.path.abspath("bar/"),
+            os.path.abspath("bogus/"),
+        ],
+        "quiet": 1,
+    }
+
+
+def test_parse_isolates_config(optmanager):
+    """Verify behaviour of the parse method with isolated=True."""
+    config_finder = mock.MagicMock()
+    config_finder.ignore_config_files = True
+    parser = config.ConfigParser(optmanager, config_finder)
+
+    assert parser.parse() == {}
+    assert config_finder.local_configs.called is False
+
+
+def test_parse_uses_cli_config(optmanager):
+    """Verify behaviour of the parse method with a specified config."""
+    config_file_value = "foo.ini"
+    config_finder = mock.MagicMock()
+    config_finder.config_file = config_file_value
+    config_finder.ignore_config_files = False
+    parser = config.ConfigParser(optmanager, config_finder)
+
+    parser.parse()
+    config_finder.cli_config.assert_called_once_with(config_file_value)
+
+
+@pytest.mark.parametrize(
+    "config_fixture_path",
+    [
+        "tests/fixtures/config_files/cli-specified.ini",
+        "tests/fixtures/config_files/cli-specified-with-inline-comments.ini",
+        "tests/fixtures/config_files/cli-specified-without-inline-comments.ini",  # noqa: E501
+    ],
+)
+def test_parsed_configs_are_equivalent(
+    optmanager, config_finder, config_fixture_path
+):
+    """Verify the each file matches the expected parsed output.
+
+    This is used to ensure our documented behaviour does not regress.
+    """
+    optmanager.add_option(
+        "--exclude",
+        parse_from_config=True,
+        comma_separated_list=True,
+        normalize_paths=True,
+    )
+    optmanager.add_option(
+        "--ignore", parse_from_config=True, comma_separated_list=True
+    )
+    parser = config.ConfigParser(optmanager, config_finder)
+
+    with mock.patch.object(config_finder, "local_config_files") as localcfs:
+        localcfs.return_value = [config_fixture_path]
+        parsed_config = parser.parse()
+
+    assert parsed_config["ignore"] == ["E123", "W234", "E111"]
+    assert parsed_config["exclude"] == [
+        os.path.abspath("foo/"),
+        os.path.abspath("bar/"),
+        os.path.abspath("bogus/"),
+    ]
+
+
+@pytest.mark.parametrize(
+    "config_file",
+    ["tests/fixtures/config_files/config-with-hyphenated-options.ini"],
+)
+def test_parsed_hyphenated_and_underscored_names(
+    optmanager, config_finder, config_file
+):
+    """Verify we find hyphenated option names as well as underscored.
+
+    This tests for options like --max-line-length and --enable-extensions
+    which are able to be specified either as max-line-length or
+    max_line_length in our config files.
+    """
+    optmanager.add_option(
+        "--max-line-length", parse_from_config=True, type=int
+    )
+    optmanager.add_option(
+        "--enable-extensions",
+        parse_from_config=True,
+        comma_separated_list=True,
+    )
+    parser = config.ConfigParser(optmanager, config_finder)
+
+    with mock.patch.object(config_finder, "local_config_files") as localcfs:
+        localcfs.return_value = [config_file]
+        parsed_config = parser.parse()
+
+    assert parsed_config["max_line_length"] == 110
+    assert parsed_config["enable_extensions"] == ["H101", "H235"]
diff --git a/tests/unit/test_debug.py b/tests/unit/test_debug.py
index 6398cf9a..2da4bf89 100644
--- a/tests/unit/test_debug.py
+++ b/tests/unit/test_debug.py
@@ -1,5 +1,6 @@
 """Tests for our debugging module."""
-import mock
+from unittest import mock
+
 import pytest
 
 from flake8.main import debug
@@ -11,51 +12,82 @@ def test_dependencies():
     assert [] == debug.dependencies()
 
 
-@pytest.mark.parametrize('plugins, expected', [
-    ([], []),
-    ([manager.PluginVersion('pycodestyle', '2.0.0', False)],
-        [{'plugin': 'pycodestyle', 'version': '2.0.0', 'is_local': False}]),
-    ([manager.PluginVersion('pycodestyle', '2.0.0', False),
-      manager.PluginVersion('mccabe', '0.5.9', False)],
-        [{'plugin': 'mccabe', 'version': '0.5.9', 'is_local': False},
-         {'plugin': 'pycodestyle', 'version': '2.0.0', 'is_local': False}]),
-    ([manager.PluginVersion('pycodestyle', '2.0.0', False),
-      manager.PluginVersion('my-local', '0.0.1', True),
-      manager.PluginVersion('mccabe', '0.5.9', False)],
-        [{'plugin': 'mccabe', 'version': '0.5.9', 'is_local': False},
-         {'plugin': 'my-local', 'version': '0.0.1', 'is_local': True},
-         {'plugin': 'pycodestyle', 'version': '2.0.0', 'is_local': False}]),
-])
+@pytest.mark.parametrize(
+    "plugins, expected",
+    [
+        ([], []),
+        (
+            [manager.PluginVersion("pycodestyle", "2.0.0", False)],
+            [
+                {
+                    "plugin": "pycodestyle",
+                    "version": "2.0.0",
+                    "is_local": False,
+                }
+            ],
+        ),
+        (
+            [
+                manager.PluginVersion("pycodestyle", "2.0.0", False),
+                manager.PluginVersion("mccabe", "0.5.9", False),
+            ],
+            [
+                {"plugin": "mccabe", "version": "0.5.9", "is_local": False},
+                {
+                    "plugin": "pycodestyle",
+                    "version": "2.0.0",
+                    "is_local": False,
+                },
+            ],
+        ),
+        (
+            [
+                manager.PluginVersion("pycodestyle", "2.0.0", False),
+                manager.PluginVersion("my-local", "0.0.1", True),
+                manager.PluginVersion("mccabe", "0.5.9", False),
+            ],
+            [
+                {"plugin": "mccabe", "version": "0.5.9", "is_local": False},
+                {"plugin": "my-local", "version": "0.0.1", "is_local": True},
+                {
+                    "plugin": "pycodestyle",
+                    "version": "2.0.0",
+                    "is_local": False,
+                },
+            ],
+        ),
+    ],
+)
 def test_plugins_from(plugins, expected):
     """Test that we format plugins appropriately."""
     option_manager = mock.Mock(registered_plugins=set(plugins))
     assert expected == debug.plugins_from(option_manager)
 
 
-@mock.patch('platform.python_implementation', return_value='CPython')
-@mock.patch('platform.python_version', return_value='3.5.3')
-@mock.patch('platform.system', return_value='Linux')
+@mock.patch("platform.python_implementation", return_value="CPython")
+@mock.patch("platform.python_version", return_value="3.5.3")
+@mock.patch("platform.system", return_value="Linux")
 def test_information(system, pyversion, pyimpl):
     """Verify that we return all the information we care about."""
     expected = {
-        'version': '3.1.0',
-        'plugins': [{'plugin': 'mccabe', 'version': '0.5.9',
-                     'is_local': False},
-                    {'plugin': 'pycodestyle', 'version': '2.0.0',
-                     'is_local': False}],
-        'dependencies': [],
-        'platform': {
-            'python_implementation': 'CPython',
-            'python_version': '3.5.3',
-            'system': 'Linux',
+        "version": "3.1.0",
+        "plugins": [
+            {"plugin": "mccabe", "version": "0.5.9", "is_local": False},
+            {"plugin": "pycodestyle", "version": "2.0.0", "is_local": False},
+        ],
+        "dependencies": [],
+        "platform": {
+            "python_implementation": "CPython",
+            "python_version": "3.5.3",
+            "system": "Linux",
         },
     }
     option_manager = mock.Mock(
         registered_plugins={
-            manager.PluginVersion('pycodestyle', '2.0.0', False),
-            manager.PluginVersion('mccabe', '0.5.9', False),
+            manager.PluginVersion("pycodestyle", "2.0.0", False),
+            manager.PluginVersion("mccabe", "0.5.9", False),
         },
-        version='3.1.0',
+        version="3.1.0",
     )
     assert expected == debug.information(option_manager)
     pyimpl.assert_called_once_with()
@@ -63,14 +95,16 @@ def test_information(system, pyversion, pyimpl):
     system.assert_called_once_with()
 
 
-@mock.patch('flake8.main.debug.print')
-@mock.patch('flake8.main.debug.information', return_value={})
-@mock.patch('json.dumps', return_value='{}')
+@mock.patch("flake8.main.debug.print")
+@mock.patch("flake8.main.debug.information", return_value={})
+@mock.patch("json.dumps", return_value="{}")
 def test_print_information_no_plugins(dumps, information, print_mock):
     """Verify we print and exit only when we have plugins."""
     option_manager = mock.Mock(registered_plugins=set())
     action = debug.DebugAction(
-        "--bug-report", dest="bug_report", option_manager=option_manager,
+        "--bug-report",
+        dest="bug_report",
+        option_manager=option_manager,
     )
     assert action(None, None, None, None) is None
     assert dumps.called is False
@@ -78,21 +112,23 @@ def test_print_information_no_plugins(dumps, information, print_mock):
     assert print_mock.called is False
 
 
-@mock.patch('flake8.main.debug.print')
-@mock.patch('flake8.main.debug.information', return_value={})
-@mock.patch('json.dumps', return_value='{}')
+@mock.patch("flake8.main.debug.print")
+@mock.patch("flake8.main.debug.information", return_value={})
+@mock.patch("json.dumps", return_value="{}")
 def test_print_information(dumps, information, print_mock):
     """Verify we print and exit only when we have plugins."""
     plugins = [
-        manager.PluginVersion('pycodestyle', '2.0.0', False),
-        manager.PluginVersion('mccabe', '0.5.9', False),
+        manager.PluginVersion("pycodestyle", "2.0.0", False),
+        manager.PluginVersion("mccabe", "0.5.9", False),
     ]
     option_manager = mock.Mock(registered_plugins=set(plugins))
     action = debug.DebugAction(
-        "--bug-report", dest="bug_report", option_manager=option_manager,
+        "--bug-report",
+        dest="bug_report",
+        option_manager=option_manager,
     )
     with pytest.raises(SystemExit):
         action(None, None, None, None)
-    print_mock.assert_called_once_with('{}')
+    print_mock.assert_called_once_with("{}")
     dumps.assert_called_once_with({}, indent=2, sort_keys=True)
     information.assert_called_once_with(option_manager)
diff --git a/tests/unit/test_decision_engine.py b/tests/unit/test_decision_engine.py
index ef77324d..213dd84f 100644
--- a/tests/unit/test_decision_engine.py
+++ b/tests/unit/test_decision_engine.py
@@ -9,65 +9,86 @@
 
 def create_options(**kwargs):
     """Create and return an instance of argparse.Namespace."""
-    kwargs.setdefault('select', [])
-    kwargs.setdefault('extended_default_ignore', [])
-    kwargs.setdefault('extended_default_select', [])
-    kwargs.setdefault('ignore', [])
-    kwargs.setdefault('extend_ignore', [])
-    kwargs.setdefault('disable_noqa', False)
-    kwargs.setdefault('enable_extensions', [])
+    kwargs.setdefault("select", [])
+    kwargs.setdefault("extended_default_ignore", [])
+    kwargs.setdefault("extended_default_select", [])
+    kwargs.setdefault("extend_select", [])
+    kwargs.setdefault("ignore", [])
+    kwargs.setdefault("extend_ignore", [])
+    kwargs.setdefault("disable_noqa", False)
+    kwargs.setdefault("enable_extensions", [])
     return argparse.Namespace(**kwargs)
 
 
-@pytest.mark.parametrize('ignore_list,extend_ignore,error_code', [
-    (['E111', 'E121'], [], 'E111'),
-    (['E111', 'E121'], [], 'E121'),
-    (['E111'], ['E121'], 'E121'),
-    (['E11', 'E12'], [], 'E121'),
-    (['E2', 'E12'], [], 'E121'),
-    (['E2', 'E12'], [], 'E211'),
-    (['E2', 'E3'], ['E12'], 'E211'),
-])
+@pytest.mark.parametrize(
+    "ignore_list,extend_ignore,error_code",
+    [
+        (["E111", "E121"], [], "E111"),
+        (["E111", "E121"], [], "E121"),
+        (["E111"], ["E121"], "E121"),
+        (["E11", "E12"], [], "E121"),
+        (["E2", "E12"], [], "E121"),
+        (["E2", "E12"], [], "E211"),
+        (["E2", "E3"], ["E12"], "E211"),
+    ],
+)
 def test_was_ignored_ignores_errors(ignore_list, extend_ignore, error_code):
     """Verify we detect users explicitly ignoring an error."""
     decider = style_guide.DecisionEngine(
-        create_options(ignore=ignore_list, extend_ignore=extend_ignore))
+        create_options(ignore=ignore_list, extend_ignore=extend_ignore)
+    )
 
     assert decider.was_ignored(error_code) is style_guide.Ignored.Explicitly
 
 
-@pytest.mark.parametrize('ignore_list,extend_ignore,error_code', [
-    (['E111', 'E121'], [], 'E112'),
-    (['E111', 'E121'], [], 'E122'),
-    (['E11', 'E12'], ['E121'], 'W121'),
-    (['E2', 'E12'], [], 'E112'),
-    (['E2', 'E12'], [], 'E111'),
-    (['E2', 'E12'], ['W11', 'E3'], 'E111'),
-])
-def test_was_ignored_implicitly_selects_errors(ignore_list, extend_ignore,
-                                               error_code):
+@pytest.mark.parametrize(
+    "ignore_list,extend_ignore,error_code",
+    [
+        (["E111", "E121"], [], "E112"),
+        (["E111", "E121"], [], "E122"),
+        (["E11", "E12"], ["E121"], "W121"),
+        (["E2", "E12"], [], "E112"),
+        (["E2", "E12"], [], "E111"),
+        (["E2", "E12"], ["W11", "E3"], "E111"),
+    ],
+)
+def test_was_ignored_implicitly_selects_errors(
+    ignore_list, extend_ignore, error_code
+):
     """Verify we detect users does not explicitly ignore an error."""
     decider = style_guide.DecisionEngine(
-        create_options(ignore=ignore_list, extend_ignore=extend_ignore))
+        create_options(ignore=ignore_list, extend_ignore=extend_ignore)
+    )
 
     assert decider.was_ignored(error_code) is style_guide.Selected.Implicitly
 
 
-@pytest.mark.parametrize('select_list,enable_extensions,error_code', [
-    (['E111', 'E121'], [], 'E111'),
-    (['E111', 'E121'], [], 'E121'),
-    (['E11', 'E12'], [], 'E121'),
-    (['E2', 'E12'], [], 'E121'),
-    (['E2', 'E12'], [], 'E211'),
-    (['E1'], ['E2'], 'E211'),
-    ([], ['E2'], 'E211'),
-])
-def test_was_selected_selects_errors(select_list, enable_extensions,
-                                     error_code):
+@pytest.mark.parametrize(
+    "select_list,extend_select,enable_extensions,error_code",
+    [
+        (["E111", "E121"], [], [], "E111"),
+        (["E111", "E121"], [], [], "E121"),
+        (["E11", "E12"], [], [], "E121"),
+        (["E2", "E12"], [], [], "E121"),
+        (["E2", "E12"], [], [], "E211"),
+        (["E1"], ["E2"], [], "E211"),
+        (["E1"], [], ["E2"], "E211"),
+        ([], ["E2"], [], "E211"),
+        ([], [], ["E2"], "E211"),
+        (["E1"], ["E2"], [], "E211"),
+        (["E111"], ["E121"], ["E2"], "E121"),
+    ],
+)
+def test_was_selected_selects_errors(
+    select_list, extend_select, enable_extensions, error_code
+):
     """Verify we detect users explicitly selecting an error."""
     decider = style_guide.DecisionEngine(
-        options=create_options(select=select_list,
-                               enable_extensions=enable_extensions),
+        options=create_options(
+            select=select_list,
+            extend_select=extend_select,
+            enable_extensions=enable_extensions,
+        ),
     )
 
     assert decider.was_selected(error_code) is style_guide.Selected.Explicitly
@@ -75,24 +96,27 @@ def test_was_selected_selects_errors(select_list, enable_extensions,
 
 def test_was_selected_implicitly_selects_errors():
     """Verify we detect users implicitly selecting an error."""
-    error_code = 'E121'
+    error_code = "E121"
     decider = style_guide.DecisionEngine(
         create_options(
             select=[],
-            extended_default_select=['E'],
+            extended_default_select=["E"],
         ),
     )
 
     assert decider.was_selected(error_code) is style_guide.Selected.Implicitly
 
 
-@pytest.mark.parametrize('select_list,error_code', [
-    (['E111', 'E121'], 'E112'),
-    (['E111', 'E121'], 'E122'),
-    (['E11', 'E12'], 'E132'),
-    (['E2', 'E12'], 'E321'),
-    (['E2', 'E12'], 'E410'),
-])
+@pytest.mark.parametrize(
+    "select_list,error_code",
+    [
+        (["E111", "E121"], "E112"),
+        (["E111", "E121"], "E122"),
+        (["E11", "E12"], "E132"),
+        (["E2", "E12"], "E321"),
+        (["E2", "E12"], "E410"),
+    ],
+)
 def test_was_selected_excludes_errors(select_list, error_code):
     """Verify we detect users implicitly excludes an error."""
     decider = style_guide.DecisionEngine(create_options(select=select_list))
@@ -101,104 +125,247 @@ def test_was_selected_excludes_errors(select_list, error_code):
 
 
 @pytest.mark.parametrize(
-    'select_list,ignore_list,extend_ignore,error_code,expected', [
-        (['E111', 'E121'], [], [], 'E111', style_guide.Decision.Selected),
-        (['E111', 'E121'], [], [], 'E112', style_guide.Decision.Ignored),
-        (['E111', 'E121'], [], [], 'E121', style_guide.Decision.Selected),
-        (['E111', 'E121'], [], [], 'E122', style_guide.Decision.Ignored),
-        (['E11', 'E12'], [], [], 'E132', style_guide.Decision.Ignored),
-        (['E2', 'E12'], [], [], 'E321', style_guide.Decision.Ignored),
-        (['E2', 'E12'], [], [], 'E410', style_guide.Decision.Ignored),
-        (['E11', 'E121'], ['E1'], [], 'E112', style_guide.Decision.Selected),
-        (['E11', 'E121'], [], ['E1'], 'E112', style_guide.Decision.Selected),
-        (['E111', 'E121'], ['E2'], ['E3'], 'E122',
-         style_guide.Decision.Ignored),
-        (['E11', 'E12'], ['E13'], [], 'E132', style_guide.Decision.Ignored),
-        (['E1', 'E3'], ['E32'], [], 'E321', style_guide.Decision.Ignored),
-        ([], ['E2', 'E12'], [], 'E410', style_guide.Decision.Ignored),
-        (['E4'], ['E2', 'E12', 'E41'], [], 'E410',
-         style_guide.Decision.Ignored),
-        (['E41'], ['E2', 'E12', 'E4'], [], 'E410',
-         style_guide.Decision.Selected),
-        (['E'], ['F'], [], 'E410', style_guide.Decision.Selected),
-        (['F'], [], [], 'E410', style_guide.Decision.Ignored),
-        (['E'], defaults.IGNORE, [], 'E126', style_guide.Decision.Selected),
-        (['W'], defaults.IGNORE, [], 'E126', style_guide.Decision.Ignored),
-        (['E'], defaults.IGNORE, [], 'W391', style_guide.Decision.Ignored),
-        (['E', 'W'], ['E13'], [], 'E131', style_guide.Decision.Ignored),
-        (defaults.SELECT, ['E13'], [], 'E131', style_guide.Decision.Ignored),
-        (defaults.SELECT, defaults.IGNORE, ['W391'], 'E126',
-         style_guide.Decision.Ignored),
-        (defaults.SELECT, defaults.IGNORE, [], 'W391',
-         style_guide.Decision.Selected),
-    ]
+    "select_list,ignore_list,extend_ignore,error_code,expected",
+    [
+        (["E111", "E121"], [], [], "E111", style_guide.Decision.Selected),
+        (["E111", "E121"], [], [], "E112", style_guide.Decision.Ignored),
+        (["E111", "E121"], [], [], "E121", style_guide.Decision.Selected),
+        (["E111", "E121"], [], [], "E122", style_guide.Decision.Ignored),
+        (["E11", "E12"], [], [], "E132", style_guide.Decision.Ignored),
+        (["E2", "E12"], [], [], "E321", style_guide.Decision.Ignored),
+        (["E2", "E12"], [], [], "E410", style_guide.Decision.Ignored),
+        (["E11", "E121"], ["E1"], [], "E112", style_guide.Decision.Selected),
+        (["E11", "E121"], [], ["E1"], "E112", style_guide.Decision.Selected),
+        (
+            ["E111", "E121"],
+            ["E2"],
+            ["E3"],
+            "E122",
+            style_guide.Decision.Ignored,
+        ),
+        (["E11", "E12"], ["E13"], [], "E132", style_guide.Decision.Ignored),
+        (["E1", "E3"], ["E32"], [], "E321", style_guide.Decision.Ignored),
+        ([], ["E2", "E12"], [], "E410", style_guide.Decision.Ignored),
+        (
+            ["E4"],
+            ["E2", "E12", "E41"],
+            [],
+            "E410",
+            style_guide.Decision.Ignored,
+        ),
+        (
+            ["E41"],
+            ["E2", "E12", "E4"],
+            [],
+            "E410",
+            style_guide.Decision.Selected,
+        ),
+        (["E"], ["F"], [], "E410", style_guide.Decision.Selected),
+        (["F"], [], [], "E410", style_guide.Decision.Ignored),
+        (["E"], defaults.IGNORE, [], "E126", style_guide.Decision.Selected),
+        (["W"], defaults.IGNORE, [], "E126", style_guide.Decision.Ignored),
+        (["E"], defaults.IGNORE, [], "W391", style_guide.Decision.Ignored),
+        (["E", "W"], ["E13"], [], "E131", style_guide.Decision.Ignored),
+        (defaults.SELECT, ["E13"], [], "E131", style_guide.Decision.Ignored),
+        (
+            defaults.SELECT,
+            defaults.IGNORE,
+            ["W391"],
+            "E126",
+            style_guide.Decision.Ignored,
+        ),
+        (
+            defaults.SELECT,
+            defaults.IGNORE,
+            [],
+            "W391",
+            style_guide.Decision.Selected,
+        ),
+    ],
 )
-def test_decision_for(select_list, ignore_list, extend_ignore, error_code,
-                      expected):
+def test_decision_for(
+    select_list, ignore_list, extend_ignore, error_code, expected
+):
     """Verify we decide when to report an error."""
     decider = style_guide.DecisionEngine(
-        create_options(select=select_list,
-                       ignore=ignore_list,
-                       extend_ignore=extend_ignore))
+        create_options(
+            select=select_list,
+            ignore=ignore_list,
+            extend_ignore=extend_ignore,
+        )
+    )
 
     assert decider.decision_for(error_code) is expected
 
 
 @pytest.mark.parametrize(
-    'select,ignore,extended_default_ignore,extend_select,enabled_extensions,'
-    'error_code,expected', [
-        (defaults.SELECT, [], [], ['I1'], [], 'I100',
-            style_guide.Decision.Selected),
-        (defaults.SELECT, [], [], ['I1'], [], 'I201',
-            style_guide.Decision.Ignored),
-        (defaults.SELECT, ['I2'], [], ['I1'], [], 'I101',
-            style_guide.Decision.Selected),
-        (defaults.SELECT, ['I2'], [], ['I1'], [], 'I201',
-            style_guide.Decision.Ignored),
-        (defaults.SELECT, ['I1'], [], ['I10'], [], 'I101',
-            style_guide.Decision.Selected),
-        (defaults.SELECT, ['I10'], [], ['I1'], [], 'I101',
-            style_guide.Decision.Ignored),
-        (defaults.SELECT, [], [], [], ['U4'], 'U401',
-            style_guide.Decision.Selected),
-        (defaults.SELECT, ['U401'], [], [], ['U4'], 'U401',
-            style_guide.Decision.Ignored),
-        (defaults.SELECT, ['U401'], [], [], ['U4'], 'U402',
-            style_guide.Decision.Selected),
-        (
-            ['E', 'W'],
-            ['E13'],
-            [],
-            [],
-            [],
-            'E131',
+    "select,ignore,extended_default_ignore,extended_default_select,"
+    "enabled_extensions,error_code,expected",
+    [
+        (
+            defaults.SELECT,
+            [],
+            [],
+            ["I1"],
+            [],
+            "I100",
+            style_guide.Decision.Selected,
+        ),
+        (
+            defaults.SELECT,
+            [],
+            [],
+            ["I1"],
+            [],
+            "I201",
+            style_guide.Decision.Ignored,
+        ),
+        (
+            defaults.SELECT,
+            ["I2"],
+            [],
+            ["I1"],
+            [],
+            "I101",
+            style_guide.Decision.Selected,
+        ),
+        (
+            defaults.SELECT,
+            ["I2"],
+            [],
+            ["I1"],
+            [],
+            "I201",
+            style_guide.Decision.Ignored,
+        ),
+        (
+            defaults.SELECT,
+            ["I1"],
+            [],
+            ["I10"],
+            [],
+            "I101",
+            style_guide.Decision.Selected,
+        ),
+        (
+            defaults.SELECT,
+            ["I10"],
+            [],
+            ["I1"],
+            [],
+            "I101",
             style_guide.Decision.Ignored,
         ),
         (
-            ['E', 'W'],
-            ['E13'],
+            defaults.SELECT,
             [],
             [],
             [],
-            'E126',
+            ["U4"],
+            "U401",
             style_guide.Decision.Selected,
         ),
-        (['E2'], ['E21'], [], [], [], 'E221', style_guide.Decision.Selected),
-        (['E2'], ['E21'], [], [], [], 'E212', style_guide.Decision.Ignored),
-        (['F', 'W'], ['C90'], [], ['I1'], [], 'C901',
-            style_guide.Decision.Ignored),
-        (['E', 'W'], ['C'], [], [], [], 'E131',
-            style_guide.Decision.Selected),
-        (defaults.SELECT, defaults.IGNORE, [], [], ['I'], 'I101',
-            style_guide.Decision.Selected),
-        (defaults.SELECT, defaults.IGNORE, [], ['G'], ['I'], 'G101',
-            style_guide.Decision.Selected),
-        (defaults.SELECT, ['G1'], [], ['G'], ['I'], 'G101',
-            style_guide.Decision.Ignored),
-        (defaults.SELECT, ['E126'], [], [], ['I'], 'I101',
-            style_guide.Decision.Selected),
-        (['E', 'W'], defaults.IGNORE, [], ['I'], [], 'I101',
-            style_guide.Decision.Ignored),
+        (
+            defaults.SELECT,
+            ["U401"],
+            [],
+            [],
+            ["U4"],
+            "U401",
+            style_guide.Decision.Ignored,
+        ),
+        (
+            defaults.SELECT,
+            ["U401"],
+            [],
+            [],
+            ["U4"],
+            "U402",
+            style_guide.Decision.Selected,
+        ),
+        (
+            ["E", "W"],
+            ["E13"],
+            [],
+            [],
+            [],
+            "E131",
+            style_guide.Decision.Ignored,
+        ),
+        (
+            ["E", "W"],
+            ["E13"],
+            [],
+            [],
+            [],
+            "E126",
+            style_guide.Decision.Selected,
+        ),
+        (["E2"], ["E21"], [], [], [], "E221", style_guide.Decision.Selected),
+        (["E2"], ["E21"], [], [], [], "E212", style_guide.Decision.Ignored),
+        (
+            ["F", "W"],
+            ["C90"],
+            [],
+            ["I1"],
+            [],
+            "C901",
+            style_guide.Decision.Ignored,
+        ),
+        (
+            ["E", "W"],
+            ["C"],
+            [],
+            [],
+            [],
+            "E131",
+            style_guide.Decision.Selected,
+        ),
+        (
+            defaults.SELECT,
+            defaults.IGNORE,
+            [],
+            [],
+            ["I"],
+            "I101",
+            style_guide.Decision.Selected,
+        ),
+        (
+            defaults.SELECT,
+            defaults.IGNORE,
+            [],
+            ["G"],
+            ["I"],
+            "G101",
+            style_guide.Decision.Selected,
+        ),
+        (
+            defaults.SELECT,
+            ["G1"],
+            [],
+            ["G"],
+            ["I"],
+            "G101",
+            style_guide.Decision.Ignored,
+        ),
+        (
+            defaults.SELECT,
+            ["E126"],
+            [],
+            [],
+            ["I"],
+            "I101",
+            style_guide.Decision.Selected,
+        ),
+        (
+            ["E", "W"],
+            defaults.IGNORE,
+            [],
+            ["I"],
+            [],
+            "I101",
+            style_guide.Decision.Ignored,
+        ),
         (
             ["E", "W", "I101"],
             defaults.IGNORE + ("I101",),
@@ -219,19 +386,24 @@ def test_decision_for(select_list, ignore_list, extend_ignore, error_code,
         ),
         # TODO(sigmavirus24) Figure out how to exercise the final catch-all
         # return statement
-    ]
+    ],
 )
 def test_more_specific_decision_for_logic(
-    select, ignore, extended_default_ignore, extend_select,
-    enabled_extensions, error_code,
+    select,
+    ignore,
+    extended_default_ignore,
+    extended_default_select,
+    enabled_extensions,
+    error_code,
     expected,
 ):
     """Verify the logic of DecisionEngine.more_specific_decision_for."""
     decider = style_guide.DecisionEngine(
         create_options(
-            select=select, ignore=ignore,
+            select=select,
+            ignore=ignore,
+            extended_default_select=extended_default_select,
             extended_default_ignore=extended_default_ignore,
-            extended_default_select=extend_select,
             enable_extensions=enabled_extensions,
         ),
     )
diff --git a/tests/unit/test_exceptions.py b/tests/unit/test_exceptions.py
index 0254cb25..6be1ebd2 100644
--- a/tests/unit/test_exceptions.py
+++ b/tests/unit/test_exceptions.py
@@ -1,48 +1,33 @@
 """Tests for the flake8.exceptions module."""
 import pickle
 
-from flake8 import exceptions
-
-
-class _ExceptionTest:
-    def test_pickleable(self):
-        """Test that the exception is round-trip pickleable."""
-        for proto in range(pickle.HIGHEST_PROTOCOL + 1):
-            new_err = pickle.loads(pickle.dumps(self.err, protocol=proto))
-            assert str(self.err) == str(new_err)
-            orig_e = self.err.original_exception
-            new_e = new_err.original_exception
-            assert (type(orig_e), orig_e.args) == (type(new_e), new_e.args)
-
-
-class TestFailedToLoadPlugin(_ExceptionTest):
-    """Tests for the FailedToLoadPlugin exception."""
-
-    err = exceptions.FailedToLoadPlugin(
-        plugin_name='plugin_name',
-        exception=ValueError('boom!'),
-    )
-
-
-class TestInvalidSyntax(_ExceptionTest):
-    """Tests for the InvalidSyntax exception."""
-
-    err = exceptions.InvalidSyntax(exception=ValueError('Unexpected token: $'))
-
-
-class TestPluginRequestedUnknownParameters(_ExceptionTest):
-    """Tests for the PluginRequestedUnknownParameters exception."""
-
-    err = exceptions.PluginRequestedUnknownParameters(
-        plugin={'plugin_name': 'plugin_name'},
-        exception=ValueError('boom!'),
-    )
+import pytest
 
+from flake8 import exceptions
 
-class TestPluginExecutionFailed(_ExceptionTest):
-    """Tests for the PluginExecutionFailed exception."""
 
-    err = exceptions.PluginExecutionFailed(
-        plugin={'plugin_name': 'plugin_name'},
-        exception=ValueError('boom!'),
-    )
+@pytest.mark.parametrize(
+    "err",
+    (
+        exceptions.FailedToLoadPlugin(
+            plugin_name="plugin_name",
+            exception=ValueError("boom!"),
+        ),
+        exceptions.PluginRequestedUnknownParameters(
+            plugin={"plugin_name": "plugin_name"},
+            exception=ValueError("boom!"),
+        ),
+        exceptions.PluginExecutionFailed(
+            plugin={"plugin_name": "plugin_name"},
+            exception=ValueError("boom!"),
+        ),
+    ),
+)
+def test_pickleable(err):
+    """Ensure that our exceptions can cross pickle boundaries."""
+    for proto in range(pickle.HIGHEST_PROTOCOL + 1):
+        new_err = pickle.loads(pickle.dumps(err, protocol=proto))
+        assert str(err) == str(new_err)
+        orig_e = err.original_exception
+        new_e = new_err.original_exception
+        assert (type(orig_e), orig_e.args) == (type(new_e), new_e.args)
diff --git a/tests/unit/test_file_checker.py b/tests/unit/test_file_checker.py
index c4ee2bf9..bcc8b324 100644
--- a/tests/unit/test_file_checker.py
+++ b/tests/unit/test_file_checker.py
@@ -1,39 +1,21 @@
 """Unit tests for the FileChecker class."""
-import mock
+from unittest import mock
+
 import pytest
 
 import flake8
 from flake8 import checker
 
 
-@mock.patch('flake8.processor.FileProcessor')
-def test_run_ast_checks_handles_SyntaxErrors(FileProcessor):  # noqa: N802,N803
-    """Stress our SyntaxError handling.
-
-    Related to: https://gitlab.com/pycqa/flake8/issues/237
-    """
-    processor = mock.Mock(lines=[])
-    FileProcessor.return_value = processor
-    processor.build_ast.side_effect = SyntaxError('Failed to build ast',
-                                                  ('', 1, 5, 'foo(\n'))
-    file_checker = checker.FileChecker(__file__, checks={}, options=object())
-
-    with mock.patch.object(file_checker, 'report') as report:
-        file_checker.run_ast_checks()
-
-        report.assert_called_once_with(
-            'E999', 1, 3,
-            'SyntaxError: Failed to build ast',
-        )
-
-
-@mock.patch('flake8.checker.FileChecker._make_processor', return_value=None)
+@mock.patch("flake8.checker.FileChecker._make_processor", return_value=None)
 def test_repr(*args):
     """Verify we generate a correct repr."""
     file_checker = checker.FileChecker(
-        'example.py', checks={}, options=object(),
+        "example.py",
+        checks={},
+        options=object(),
     )
-    assert repr(file_checker) == 'FileChecker for example.py'
+    assert repr(file_checker) == "FileChecker for example.py"
 
 
 def test_nonexistent_file():
@@ -49,8 +31,8 @@ def test_nonexistent_file():
 
 def test_raises_exception_on_failed_plugin(tmp_path, default_options):
     """Checks that a failing plugin results in PluginExecutionFailed."""
-    foobar = tmp_path / 'foobar.py'
-    foobar.write_text(u"I exist!")  # Create temp file
+    foobar = tmp_path / "foobar.py"
+    foobar.write_text("I exist!")  # Create temp file
     plugin = {
         "name": "failure",
         "plugin_name": "failure",  # Both are necessary
@@ -59,6 +41,7 @@ def test_raises_exception_on_failed_plugin(tmp_path, default_options):
     }
     """Verify a failing plugin results in an plugin error"""
     fchecker = checker.FileChecker(
-        str(foobar), checks=[], options=default_options)
+        str(foobar), checks=[], options=default_options
+    )
     with pytest.raises(flake8.exceptions.PluginExecutionFailed):
         fchecker.run_check(plugin)
diff --git a/tests/unit/test_file_processor.py b/tests/unit/test_file_processor.py
index 0215ddf3..789135ab 100644
--- a/tests/unit/test_file_processor.py
+++ b/tests/unit/test_file_processor.py
@@ -1,8 +1,8 @@
 """Tests for the FileProcessor class."""
 import ast
 import tokenize
+from unittest import mock
 
-import mock
 import pytest
 
 from flake8 import processor
@@ -17,7 +17,7 @@ def test_read_lines_splits_lines(default_options):
 
 
 def _lines_from_file(tmpdir, contents, options):
-    f = tmpdir.join('f.py')
+    f = tmpdir.join("f.py")
     # be careful to write the bytes exactly to avoid newline munging
     f.write_binary(contents)
     return processor.FileProcessor(f.strpath, options).lines
@@ -26,111 +26,125 @@ def _lines_from_file(tmpdir, contents, options):
 def test_read_lines_universal_newlines(tmpdir, default_options):
     r"""Verify that line endings are translated to \n."""
     lines = _lines_from_file(
-        tmpdir, b'# coding: utf-8\r\nx = 1\r\n', default_options)
-    assert lines == ['# coding: utf-8\n', 'x = 1\n']
+        tmpdir, b"# coding: utf-8\r\nx = 1\r\n", default_options
+    )
+    assert lines == ["# coding: utf-8\n", "x = 1\n"]
 
 
 def test_read_lines_incorrect_utf_16(tmpdir, default_options):
     """Verify that an incorrectly encoded file is read as latin-1."""
     lines = _lines_from_file(
-        tmpdir, b'# coding: utf16\nx = 1\n', default_options)
-    assert lines == ['# coding: utf16\n', 'x = 1\n']
+        tmpdir, b"# coding: utf16\nx = 1\n", default_options
+    )
+    assert lines == ["# coding: utf16\n", "x = 1\n"]
 
 
 def test_read_lines_unknown_encoding(tmpdir, default_options):
     """Verify that an unknown encoding is still read as latin-1."""
     lines = _lines_from_file(
-        tmpdir, b'# coding: fake-encoding\nx = 1\n', default_options)
-    assert lines == ['# coding: fake-encoding\n', 'x = 1\n']
+        tmpdir, b"# coding: fake-encoding\nx = 1\n", default_options
+    )
+    assert lines == ["# coding: fake-encoding\n", "x = 1\n"]
 
 
-@pytest.mark.parametrize('first_line', [
-    '\xEF\xBB\xBF"""Module docstring."""\n',
-    u'\uFEFF"""Module docstring."""\n',
-])
+@pytest.mark.parametrize(
+    "first_line",
+    [
+        '\xEF\xBB\xBF"""Module docstring."""\n',
+        '\uFEFF"""Module docstring."""\n',
+    ],
+)
 def test_strip_utf_bom(first_line, default_options):
     r"""Verify that we strip '\xEF\xBB\xBF' from the first line."""
     lines = [first_line]
-    file_processor = processor.FileProcessor('-', default_options, lines[:])
+    file_processor = processor.FileProcessor("-", default_options, lines[:])
     assert file_processor.lines != lines
     assert file_processor.lines[0] == '"""Module docstring."""\n'
 
 
-@pytest.mark.parametrize('lines, expected', [
-    (['\xEF\xBB\xBF"""Module docstring."""\n'], False),
-    ([u'\uFEFF"""Module docstring."""\n'], 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),
-    (['# flake8: noqa', '#!/usr/bin/python', 'a = 1'], True),
-    (['# flake8:noqa', '#!/usr/bin/python', 'a = 1'], True),
-    (['#!/usr/bin/python', 'a = 1', '# flake8: noqa'], True),
-    (['#!/usr/bin/python', 'a = 1', '# flake8:noqa'], True),
-    (['#!/usr/bin/python', 'a = 1  # flake8: noqa'], False),
-    (['#!/usr/bin/python', 'a = 1  # flake8:noqa'], False),
-])
+@pytest.mark.parametrize(
+    "lines, expected",
+    [
+        (['\xEF\xBB\xBF"""Module docstring."""\n'], False),
+        (['\uFEFF"""Module docstring."""\n'], 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),
+        (["# flake8: noqa", "#!/usr/bin/python", "a = 1"], True),
+        (["# flake8:noqa", "#!/usr/bin/python", "a = 1"], True),
+        (["#!/usr/bin/python", "a = 1", "# flake8: noqa"], True),
+        (["#!/usr/bin/python", "a = 1", "# flake8:noqa"], True),
+        (["#!/usr/bin/python", "a = 1  # flake8: noqa"], False),
+        (["#!/usr/bin/python", "a = 1  # flake8:noqa"], False),
+    ],
+)
 def test_should_ignore_file(lines, expected, default_options):
     """Verify that we ignore a file if told to."""
-    file_processor = processor.FileProcessor('-', default_options, lines)
+    file_processor = processor.FileProcessor("-", default_options, lines)
     assert file_processor.should_ignore_file() is expected
 
 
 def test_should_ignore_file_to_handle_disable_noqa(default_options):
     """Verify that we ignore a file if told to."""
-    lines = ['# flake8: noqa']
-    file_processor = processor.FileProcessor('-', default_options, lines)
+    lines = ["# flake8: noqa"]
+    file_processor = processor.FileProcessor("-", default_options, lines)
     assert file_processor.should_ignore_file() is True
     default_options.disable_noqa = True
-    file_processor = processor.FileProcessor('-', default_options, lines)
+    file_processor = processor.FileProcessor("-", default_options, lines)
     assert file_processor.should_ignore_file() is False
 
 
-@mock.patch('flake8.utils.stdin_get_value')
+@mock.patch("flake8.utils.stdin_get_value")
 def test_read_lines_from_stdin(stdin_get_value, default_options):
     """Verify that we use our own utility function to retrieve stdin."""
-    stdin_get_value.return_value = ''
-    processor.FileProcessor('-', default_options)
+    stdin_get_value.return_value = ""
+    processor.FileProcessor("-", default_options)
     stdin_get_value.assert_called_once_with()
 
 
-@mock.patch('flake8.utils.stdin_get_value')
+@mock.patch("flake8.utils.stdin_get_value")
 def test_stdin_filename_attribute(stdin_get_value, default_options):
     """Verify that we update the filename attribute."""
-    stdin_get_value.return_value = ''
-    file_processor = processor.FileProcessor('-', default_options)
-    assert file_processor.filename == 'stdin'
+    stdin_get_value.return_value = ""
+    file_processor = processor.FileProcessor("-", default_options)
+    assert file_processor.filename == "stdin"
 
 
-@mock.patch('flake8.utils.stdin_get_value')
+@mock.patch("flake8.utils.stdin_get_value")
 def test_read_lines_uses_display_name(stdin_get_value, default_options):
     """Verify that when processing stdin we use a display name if present."""
-    default_options.stdin_display_name = 'display_name.py'
-    stdin_get_value.return_value = ''
-    file_processor = processor.FileProcessor('-', default_options)
-    assert file_processor.filename == 'display_name.py'
+    default_options.stdin_display_name = "display_name.py"
+    stdin_get_value.return_value = ""
+    file_processor = processor.FileProcessor("-", default_options)
+    assert file_processor.filename == "display_name.py"
 
 
-@mock.patch('flake8.utils.stdin_get_value')
+@mock.patch("flake8.utils.stdin_get_value")
 def test_read_lines_ignores_empty_display_name(
-        stdin_get_value, default_options,
+    stdin_get_value,
+    default_options,
 ):
     """Verify that when processing stdin we use a display name if present."""
-    stdin_get_value.return_value = ''
-    default_options.stdin_display_name = ''
-    file_processor = processor.FileProcessor('-', default_options)
-    assert file_processor.filename == 'stdin'
+    stdin_get_value.return_value = ""
+    default_options.stdin_display_name = ""
+    file_processor = processor.FileProcessor("-", default_options)
+    assert file_processor.filename == "stdin"
 
 
 def test_noqa_line_for(default_options):
     """Verify we grab the correct line from the cached lines."""
-    file_processor = processor.FileProcessor('-', default_options, lines=[
-        'Line 1\n',
-        'Line 2\n',
-        'Line 3\n',
-    ])
+    file_processor = processor.FileProcessor(
+        "-",
+        default_options,
+        lines=[
+            "Line 1\n",
+            "Line 2\n",
+            "Line 3\n",
+        ],
+    )
 
     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):
@@ -145,15 +159,15 @@ def test_noqa_line_for_continuation(default_options):
 """  # 7
 '''
     lines = src.splitlines(True)
-    file_processor = processor.FileProcessor('-', default_options, lines=lines)
+    file_processor = processor.FileProcessor("-", default_options, lines=lines)
 
     assert file_processor.noqa_line_for(0) is None
 
-    l_1_2 = 'from foo \\\n    import bar  # 2\n'
+    l_1_2 = "from foo \\\n    import bar  # 2\n"
     assert file_processor.noqa_line_for(1) == l_1_2
     assert file_processor.noqa_line_for(2) == l_1_2
 
-    assert file_processor.noqa_line_for(3) == '\n'
+    assert file_processor.noqa_line_for(3) == "\n"
 
     l_4_7 = 'x = """\nhello\nworld\n"""  # 7\n'
     for i in (4, 5, 6, 7):
@@ -164,76 +178,104 @@ def test_noqa_line_for_continuation(default_options):
 
 def test_noqa_line_for_no_eol_at_end_of_file(default_options):
     """Verify that we properly handle noqa line at the end of the file."""
-    src = 'from foo \\\nimport bar'  # no end of file newline
+    src = "from foo \\\nimport bar"  # no end of file newline
     lines = src.splitlines(True)
-    file_processor = processor.FileProcessor('-', default_options, lines=lines)
+    file_processor = processor.FileProcessor("-", default_options, lines=lines)
 
-    l_1_2 = 'from foo \\\nimport bar'
+    l_1_2 = "from foo \\\nimport bar"
     assert file_processor.noqa_line_for(1) == l_1_2
     assert file_processor.noqa_line_for(2) == l_1_2
 
 
 def test_next_line(default_options):
     """Verify we update the file_processor state for each new line."""
-    file_processor = processor.FileProcessor('-', default_options, lines=[
-        'Line 1',
-        'Line 2',
-        'Line 3',
-    ])
+    file_processor = processor.FileProcessor(
+        "-",
+        default_options,
+        lines=[
+            "Line 1",
+            "Line 2",
+            "Line 3",
+        ],
+    )
 
     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
 
 
-@pytest.mark.parametrize('params, args, expected_kwargs', [
-    ({'blank_before': True, 'blank_lines': True},
-        None,
-        {'blank_before': 0, 'blank_lines': 0}),
-    ({'noqa': True, 'fake': True},
-        {'fake': 'foo'},
-        {'noqa': False, 'fake': 'foo'}),
-    ({'blank_before': True, 'blank_lines': True, 'noqa': True},
-        {'blank_before': 10, 'blank_lines': 5, 'noqa': True},
-        {'blank_before': 10, 'blank_lines': 5, 'noqa': True}),
-    ({}, {'fake': 'foo'}, {'fake': 'foo'}),
-    ({'non-existent': False}, {'fake': 'foo'}, {'fake': 'foo'}),
-])
+@pytest.mark.parametrize(
+    "params, args, expected_kwargs",
+    [
+        (
+            {"blank_before": True, "blank_lines": True},
+            None,
+            {"blank_before": 0, "blank_lines": 0},
+        ),
+        (
+            {"noqa": True, "fake": True},
+            {"fake": "foo"},
+            {"noqa": False, "fake": "foo"},
+        ),
+        (
+            {"blank_before": True, "blank_lines": True, "noqa": True},
+            {"blank_before": 10, "blank_lines": 5, "noqa": True},
+            {"blank_before": 10, "blank_lines": 5, "noqa": True},
+        ),
+        ({}, {"fake": "foo"}, {"fake": "foo"}),
+        ({"non-existent": False}, {"fake": "foo"}, {"fake": "foo"}),
+    ],
+)
 def test_keyword_arguments_for(params, args, expected_kwargs, default_options):
     """Verify the keyword args are generated properly."""
-    file_processor = processor.FileProcessor('-', default_options, lines=[
-        'Line 1',
-    ])
+    file_processor = processor.FileProcessor(
+        "-",
+        default_options,
+        lines=[
+            "Line 1",
+        ],
+    )
     kwargs_for = file_processor.keyword_arguments_for
 
     assert kwargs_for(params, args) == expected_kwargs
 
 
 def test_keyword_arguments_for_does_not_handle_attribute_errors(
-        default_options,
+    default_options,
 ):
     """Verify we re-raise AttributeErrors."""
-    file_processor = processor.FileProcessor('-', default_options, lines=[
-        'Line 1',
-    ])
+    file_processor = processor.FileProcessor(
+        "-",
+        default_options,
+        lines=[
+            "Line 1",
+        ],
+    )
 
     with pytest.raises(AttributeError):
-        file_processor.keyword_arguments_for({'fake': True})
-
-
-@pytest.mark.parametrize('unsplit_line, expected_lines', [
-    ('line', []),
-    ('line 1\n', ['line 1']),
-    ('line 1\nline 2\n', ['line 1', 'line 2']),
-    ('line 1\n\nline 2\n', ['line 1', '', 'line 2']),
-])
+        file_processor.keyword_arguments_for({"fake": True})
+
+
+@pytest.mark.parametrize(
+    "unsplit_line, expected_lines",
+    [
+        ("line", []),
+        ("line 1\n", ["line 1"]),
+        ("line 1\nline 2\n", ["line 1", "line 2"]),
+        ("line 1\n\nline 2\n", ["line 1", "", "line 2"]),
+    ],
+)
 def test_split_line(unsplit_line, expected_lines, default_options):
     """Verify the token line splitting."""
-    file_processor = processor.FileProcessor('-', default_options, lines=[
-        'Line 1',
-    ])
+    file_processor = processor.FileProcessor(
+        "-",
+        default_options,
+        lines=[
+            "Line 1",
+        ],
+    )
 
-    token = (1, unsplit_line, (0, 0), (0, 0), '')
+    token = (1, unsplit_line, (0, 0), (0, 0), "")
     actual_lines = list(file_processor.split_line(token))
     assert expected_lines == actual_lines
 
@@ -242,9 +284,9 @@ def test_split_line(unsplit_line, expected_lines, default_options):
 
 def test_build_ast(default_options):
     """Verify the logic for how we build an AST for plugins."""
-    file_processor = processor.FileProcessor('-', default_options, lines=[
-        'a = 1\n'
-    ])
+    file_processor = processor.FileProcessor(
+        "-", default_options, lines=["a = 1\n"]
+    )
 
     module = file_processor.build_ast()
     assert isinstance(module, ast.Module)
@@ -252,25 +294,25 @@ def test_build_ast(default_options):
 
 def test_next_logical_line_updates_the_previous_logical_line(default_options):
     """Verify that we update our tracking of the previous logical line."""
-    file_processor = processor.FileProcessor('-', default_options, lines=[
-        'a = 1\n'
-    ])
+    file_processor = processor.FileProcessor(
+        "-", default_options, lines=["a = 1\n"]
+    )
 
     file_processor.indent_level = 1
-    file_processor.logical_line = 'a = 1'
-    assert file_processor.previous_logical == ''
+    file_processor.logical_line = "a = 1"
+    assert file_processor.previous_logical == ""
     assert file_processor.previous_indent_level == 0
 
     file_processor.next_logical_line()
-    assert file_processor.previous_logical == 'a = 1'
+    assert file_processor.previous_logical == "a = 1"
     assert file_processor.previous_indent_level == 1
 
 
 def test_visited_new_blank_line(default_options):
     """Verify we update the number of blank lines seen."""
-    file_processor = processor.FileProcessor('-', default_options, lines=[
-        'a = 1\n'
-    ])
+    file_processor = processor.FileProcessor(
+        "-", default_options, lines=["a = 1\n"]
+    )
 
     assert file_processor.blank_lines == 0
     file_processor.visited_new_blank_line()
@@ -279,9 +321,9 @@ def test_visited_new_blank_line(default_options):
 
 def test_inside_multiline(default_options):
     """Verify we update the line number and reset multiline."""
-    file_processor = processor.FileProcessor('-', default_options, lines=[
-        'a = 1\n'
-    ])
+    file_processor = processor.FileProcessor(
+        "-", default_options, lines=["a = 1\n"]
+    )
 
     assert file_processor.multiline is False
     assert file_processor.line_number == 0
@@ -292,63 +334,87 @@ def test_inside_multiline(default_options):
     assert file_processor.multiline is False
 
 
-@pytest.mark.parametrize('string, expected', [
-    ('""', '""'),
-    ("''", "''"),
-    ('"a"', '"x"'),
-    ("'a'", "'x'"),
-    ('"x"', '"x"'),
-    ("'x'", "'x'"),
-    ('"abcdef"', '"xxxxxx"'),
-    ("'abcdef'", "'xxxxxx'"),
-    ('""""""', '""""""'),
-    ("''''''", "''''''"),
-    ('"""a"""', '"""x"""'),
-    ("'''a'''", "'''x'''"),
-    ('"""x"""', '"""x"""'),
-    ("'''x'''", "'''x'''"),
-    ('"""abcdef"""', '"""xxxxxx"""'),
-    ("'''abcdef'''", "'''xxxxxx'''"),
-    ('"""xxxxxx"""', '"""xxxxxx"""'),
-    ("'''xxxxxx'''", "'''xxxxxx'''"),
-])
+@pytest.mark.parametrize(
+    "string, expected",
+    [
+        ('""', '""'),
+        ("''", "''"),
+        ('"a"', '"x"'),
+        ("'a'", "'x'"),
+        ('"x"', '"x"'),
+        ("'x'", "'x'"),
+        ('"abcdef"', '"xxxxxx"'),
+        ("'abcdef'", "'xxxxxx'"),
+        ('""""""', '""""""'),
+        ("''''''", "''''''"),
+        ('"""a"""', '"""x"""'),
+        ("'''a'''", "'''x'''"),
+        ('"""x"""', '"""x"""'),
+        ("'''x'''", "'''x'''"),
+        ('"""abcdef"""', '"""xxxxxx"""'),
+        ("'''abcdef'''", "'''xxxxxx'''"),
+        ('"""xxxxxx"""', '"""xxxxxx"""'),
+        ("'''xxxxxx'''", "'''xxxxxx'''"),
+    ],
+)
 def test_mutate_string(string, expected, default_options):
     """Verify we appropriately mutate the string to sanitize it."""
     actual = processor.mutate_string(string)
     assert expected == actual
 
 
-@pytest.mark.parametrize('string, expected', [
-    ('    ', 4),
-    ('      ', 6),
-    ('\t', 8),
-    ('\t\t', 16),
-    ('       \t', 8),
-    ('        \t', 16),
-])
+@pytest.mark.parametrize(
+    "string, expected",
+    [
+        ("    ", 4),
+        ("      ", 6),
+        ("\t", 8),
+        ("\t\t", 16),
+        ("       \t", 8),
+        ("        \t", 16),
+    ],
+)
 def test_expand_indent(string, expected):
     """Verify we correctly measure the amount of indentation."""
     actual = processor.expand_indent(string)
     assert expected == actual
 
 
-@pytest.mark.parametrize('token, log_string', [
-    [(tokenize.COMMENT, '# this is a comment',
-      (1, 0),  # (start_row, start_column)
-      (1, 19),  # (end_ro, end_column)
-      '# this is a comment',),
-     "l.1\t[:19]\tCOMMENT\t'# this is a comment'"],
-    [(tokenize.COMMENT, '# this is a comment',
-      (1, 5),  # (start_row, start_column)
-      (1, 19),  # (end_ro, end_column)
-      '# this is a comment',),
-     "l.1\t[5:19]\tCOMMENT\t'# this is a comment'"],
-    [(tokenize.COMMENT, '# this is a comment',
-      (1, 0),  # (start_row, start_column)
-      (2, 19),  # (end_ro, end_column)
-      '# this is a comment',),
-     "l.1\tl.2\tCOMMENT\t'# this is a comment'"],
-])
+@pytest.mark.parametrize(
+    "token, log_string",
+    [
+        [
+            (
+                tokenize.COMMENT,
+                "# this is a comment",
+                (1, 0),  # (start_row, start_column)
+                (1, 19),  # (end_ro, end_column)
+                "# this is a comment",
+            ),
+            "l.1\t[:19]\tCOMMENT\t'# this is a comment'",
+        ],
+        [
+            (
+                tokenize.COMMENT,
+                "# this is a comment",
+                (1, 5),  # (start_row, start_column)
+                (1, 19),  # (end_ro, end_column)
+                "# this is a comment",
+            ),
+            "l.1\t[5:19]\tCOMMENT\t'# this is a comment'",
+        ],
+        [
+            (
+                tokenize.COMMENT,
+                "# this is a comment",
+                (1, 0),  # (start_row, start_column)
+                (2, 19),  # (end_ro, end_column)
+                "# this is a comment",
+            ),
+            "l.1\tl.2\tCOMMENT\t'# this is a comment'",
+        ],
+    ],
+)
 def test_log_token(token, log_string):
     """Verify we use the log object passed in."""
     log = mock.Mock()
@@ -359,15 +425,18 @@ def test_log_token(token, log_string):
     )
 
 
-@pytest.mark.parametrize('current_count, token_text, expected', [
-    (0, '(', 1),
-    (0, '[', 1),
-    (0, '{', 1),
-    (1, ')', 0),
-    (1, ']', 0),
-    (1, '}', 0),
-    (10, '+', 10),
-])
+@pytest.mark.parametrize(
+    "current_count, token_text, expected",
+    [
+        (0, "(", 1),
+        (0, "[", 1),
+        (0, "{", 1),
+        (1, ")", 0),
+        (1, "]", 0),
+        (1, "}", 0),
+        (10, "+", 10),
+    ],
+)
 def test_count_parentheses(current_count, token_text, expected):
     """Verify our arithmetic is correct."""
     assert processor.count_parentheses(current_count, token_text) == expected
diff --git a/tests/unit/test_filenameonly_formatter.py b/tests/unit/test_filenameonly_formatter.py
index 8d0c88f5..7dda50b4 100644
--- a/tests/unit/test_filenameonly_formatter.py
+++ b/tests/unit/test_filenameonly_formatter.py
@@ -7,8 +7,8 @@
 
 def options(**kwargs):
     """Create an argparse.Namespace instance."""
-    kwargs.setdefault('output_file', None)
-    kwargs.setdefault('tee', False)
+    kwargs.setdefault("output_file", None)
+    kwargs.setdefault("tee", False)
     return argparse.Namespace(**kwargs)
 
 
@@ -18,22 +18,23 @@ def test_caches_filenames_already_printed():
     assert formatter.filenames_already_printed == set()
 
     formatter.format(
-        style_guide.Violation('code', 'file.py', 1, 1, 'text', 'l'))
-    assert formatter.filenames_already_printed == {'file.py'}
+        style_guide.Violation("code", "file.py", 1, 1, "text", "l")
+    )
+    assert formatter.filenames_already_printed == {"file.py"}
 
 
 def test_only_returns_a_string_once_from_format():
     """Verify format ignores the second error with the same filename."""
     formatter = default.FilenameOnly(options())
-    error = style_guide.Violation('code', 'file.py', 1, 1, 'text', '1')
+    error = style_guide.Violation("code", "file.py", 1, 1, "text", "1")
 
-    assert formatter.format(error) == 'file.py'
+    assert formatter.format(error) == "file.py"
     assert formatter.format(error) is None
 
 
 def test_show_source_returns_nothing():
     """Verify show_source returns nothing."""
     formatter = default.FilenameOnly(options())
-    error = style_guide.Violation('code', 'file.py', 1, 1, 'text', '1')
+    error = style_guide.Violation("code", "file.py", 1, 1, "text", "1")
 
     assert formatter.show_source(error) is None
diff --git a/tests/unit/test_get_local_plugins.py b/tests/unit/test_get_local_plugins.py
index ec11998a..7e7b318c 100644
--- a/tests/unit/test_get_local_plugins.py
+++ b/tests/unit/test_get_local_plugins.py
@@ -1,5 +1,5 @@
 """Tests for get_local_plugins."""
-import mock
+from unittest import mock
 
 from flake8.options import config
 
@@ -23,8 +23,8 @@ def test_get_local_plugins_uses_cli_config():
     config_finder = mock.MagicMock()
     config_finder.cli_config.return_value = config_obj
     config_finder.ignore_config_files = False
-    config_obj.get.return_value = ''
-    config_file_value = 'foo.ini'
+    config_obj.get.return_value = ""
+    config_file_value = "foo.ini"
     config_finder.config_file = config_file_value
 
     config.get_local_plugins(config_finder)
@@ -34,12 +34,12 @@ def test_get_local_plugins_uses_cli_config():
 
 def test_get_local_plugins():
     """Verify get_local_plugins returns expected plugins."""
-    config_fixture_path = 'tests/fixtures/config_files/local-plugin.ini'
-    config_finder = config.ConfigFileFinder('flake8')
+    config_fixture_path = "tests/fixtures/config_files/local-plugin.ini"
+    config_finder = config.ConfigFileFinder("flake8")
 
-    with mock.patch.object(config_finder, 'local_config_files') as localcfs:
+    with mock.patch.object(config_finder, "local_config_files") as localcfs:
         localcfs.return_value = [config_fixture_path]
         local_plugins = config.get_local_plugins(config_finder)
 
-    assert local_plugins.extension == ['XE = test_plugins:ExtensionTestPlugin']
-    assert local_plugins.report == ['XR = test_plugins:ReportTestPlugin']
+    assert local_plugins.extension == ["XE = test_plugins:ExtensionTestPlugin"]
+    assert local_plugins.report == ["XR = test_plugins:ReportTestPlugin"]
diff --git a/tests/unit/test_git.py b/tests/unit/test_git.py
deleted file mode 100644
index 24d4b5be..00000000
--- a/tests/unit/test_git.py
+++ /dev/null
@@ -1,29 +0,0 @@
-"""Tests around functionality in the git integration."""
-import mock
-import pytest
-
-from flake8.main import git
-
-
-@pytest.mark.parametrize('lazy', [True, False])
-def test_find_modified_files(lazy):
-    """Confirm our logic for listing modified files."""
-    if lazy:
-        # Here --cached is missing
-        call = [
-            'git', 'diff-index', '--name-only', '--diff-filter=ACMRTUXB',
-            'HEAD'
-        ]
-    else:
-        call = [
-            'git', 'diff-index', '--cached', '--name-only',
-            '--diff-filter=ACMRTUXB', 'HEAD'
-        ]
-    mocked_popen = mock.Mock()
-    mocked_popen.communicate.return_value = ('', '')
-
-    with mock.patch('flake8.main.git.piped_process') as piped_process:
-        piped_process.return_value = mocked_popen
-        git.find_modified_files(lazy)
-
-    piped_process.assert_called_once_with(call)
diff --git a/tests/unit/test_legacy_api.py b/tests/unit/test_legacy_api.py
index 0ec839ff..1dcdeb66 100644
--- a/tests/unit/test_legacy_api.py
+++ b/tests/unit/test_legacy_api.py
@@ -1,8 +1,8 @@
 """Tests for Flake8's legacy API."""
 import argparse
 import os.path
+from unittest import mock
 
-import mock
 import pytest
 
 from flake8.api import legacy as api
@@ -21,12 +21,14 @@ def test_get_style_guide():
     )
     mockedapp = mock.Mock()
     mockedapp.parse_preliminary_options.return_value = (prelim_opts, [])
-    mockedapp.program = 'flake8'
-    with mock.patch('flake8.api.legacy.config.ConfigFileFinder') as mock_config_finder:  # noqa: E501
+    mockedapp.program = "flake8"
+    with mock.patch(
+        "flake8.api.legacy.config.ConfigFileFinder"
+    ) as mock_config_finder:  # noqa: E501
         config_finder = ConfigFileFinder(mockedapp.program)
         mock_config_finder.return_value = config_finder
 
-        with mock.patch('flake8.main.application.Application') as application:
+        with mock.patch("flake8.main.application.Application") as application:
             application.return_value = mockedapp
             style_guide = api.get_style_guide()
 
@@ -35,7 +37,8 @@ def test_get_style_guide():
     mockedapp.find_plugins.assert_called_once_with(config_finder)
     mockedapp.register_plugin_options.assert_called_once_with()
     mockedapp.parse_configuration_and_cli.assert_called_once_with(
-        config_finder, [])
+        config_finder, []
+    )
     mockedapp.make_formatter.assert_called_once_with()
     mockedapp.make_guide.assert_called_once_with()
     mockedapp.make_file_checker_manager.assert_called_once_with()
@@ -45,22 +48,22 @@ def test_get_style_guide():
 def test_styleguide_options():
     """Show that we proxy the StyleGuide.options attribute."""
     app = mock.Mock()
-    app.options = 'options'
+    app.options = "options"
     style_guide = api.StyleGuide(app)
-    assert style_guide.options == 'options'
+    assert style_guide.options == "options"
 
 
 def test_styleguide_paths():
     """Show that we proxy the StyleGuide.paths attribute."""
     app = mock.Mock()
-    app.paths = 'paths'
+    app.paths = "paths"
     style_guide = api.StyleGuide(app)
-    assert style_guide.paths == 'paths'
+    assert style_guide.paths == "paths"
 
 
 def test_styleguide_check_files():
     """Verify we call the right application methods."""
-    paths = ['foo', 'bar']
+    paths = ["foo", "bar"]
     app = mock.Mock()
     style_guide = api.StyleGuide(app)
     report = style_guide.check_files(paths)
@@ -80,8 +83,8 @@ def test_styleguide_excluded():
     file_checker_manager = app.file_checker_manager = mock.Mock()
     style_guide = api.StyleGuide(app)
 
-    style_guide.excluded('file.py')
-    file_checker_manager.is_path_excluded.assert_called_once_with('file.py')
+    style_guide.excluded("file.py")
+    file_checker_manager.is_path_excluded.assert_called_once_with("file.py")
 
 
 def test_styleguide_excluded_with_parent():
@@ -95,10 +98,10 @@ def test_styleguide_excluded_with_parent():
     file_checker_manager.is_path_excluded.return_value = False
     style_guide = api.StyleGuide(app)
 
-    style_guide.excluded('file.py', 'parent')
+    style_guide.excluded("file.py", "parent")
     assert file_checker_manager.is_path_excluded.call_args_list == [
-        mock.call('file.py'),
-        mock.call(os.path.join('parent', 'file.py')),
+        mock.call("file.py"),
+        mock.call(os.path.join("parent", "file.py")),
     ]
 
 
@@ -123,7 +126,7 @@ def test_styleguide_init_report_with_non_subclass():
 
 def test_styleguide_init_report():
     """Verify we do the right incantation for the Application."""
-    app = mock.Mock(guide='fake')
+    app = mock.Mock(guide="fake")
     style_guide = api.StyleGuide(app)
 
     class FakeFormatter(formatter.BaseFormatter):
@@ -140,16 +143,16 @@ def test_styleguide_input_file():
     """Verify we call StyleGuide.check_files with the filename."""
     app = mock.Mock()
     style_guide = api.StyleGuide(app)
-    with mock.patch.object(style_guide, 'check_files') as check_files:
-        style_guide.input_file('file.py')
-    check_files.assert_called_once_with(['file.py'])
+    with mock.patch.object(style_guide, "check_files") as check_files:
+        style_guide.input_file("file.py")
+    check_files.assert_called_once_with(["file.py"])
 
 
 def test_report_total_errors():
     """Verify total errors is just a proxy attribute."""
-    app = mock.Mock(result_count='Fake count')
+    app = mock.Mock(result_count="Fake count")
     report = api.Report(app)
-    assert report.total_errors == 'Fake count'
+    assert report.total_errors == "Fake count"
 
 
 def test_report_get_statistics():
@@ -160,5 +163,5 @@ def test_report_get_statistics():
     app = mock.Mock(guide=style_guide)
 
     report = api.Report(app)
-    assert report.get_statistics('E') == []
-    stats.statistics_for.assert_called_once_with('E')
+    assert report.get_statistics("E") == []
+    stats.statistics_for.assert_called_once_with("E")
diff --git a/tests/unit/test_merged_config_parser.py b/tests/unit/test_merged_config_parser.py
deleted file mode 100644
index d446ad80..00000000
--- a/tests/unit/test_merged_config_parser.py
+++ /dev/null
@@ -1,225 +0,0 @@
-"""Unit tests for flake8.options.config.MergedConfigParser."""
-import os
-
-import mock
-import pytest
-
-from flake8.options import config
-from flake8.options import manager
-
-
-@pytest.fixture
-def optmanager():
-    """Generate an OptionManager with simple values."""
-    return manager.OptionManager(prog='flake8', version='3.0.0a1')
-
-
-@pytest.fixture
-def config_finder():
-    """Generate a simple ConfigFileFinder."""
-    return config.ConfigFileFinder('flake8')
-
-
-def test_parse_cli_config(optmanager, config_finder):
-    """Parse the specified config file as a cli config file."""
-    optmanager.add_option('--exclude', parse_from_config=True,
-                          comma_separated_list=True,
-                          normalize_paths=True)
-    optmanager.add_option('--ignore', parse_from_config=True,
-                          comma_separated_list=True)
-    optmanager.add_option('--quiet', parse_from_config=True,
-                          action='count')
-    parser = config.MergedConfigParser(optmanager, config_finder)
-
-    config_file = 'tests/fixtures/config_files/cli-specified.ini'
-    parsed_config = parser.parse_cli_config(config_file)
-
-    config_dir = os.path.dirname(config_file)
-    assert parsed_config == {
-        'ignore': ['E123', 'W234', 'E111'],
-        'exclude': [
-            os.path.abspath(os.path.join(config_dir, 'foo/')),
-            os.path.abspath(os.path.join(config_dir, 'bar/')),
-            os.path.abspath(os.path.join(config_dir, 'bogus/')),
-        ],
-        'quiet': 1,
-    }
-
-
-@pytest.mark.parametrize('filename,is_configured_by', [
-    ('tests/fixtures/config_files/cli-specified.ini', True),
-    ('tests/fixtures/config_files/no-flake8-section.ini', False),
-])
-def test_is_configured_by(
-        filename, is_configured_by, optmanager, config_finder):
-    """Verify the behaviour of the is_configured_by method."""
-    parsed_config, _ = config.ConfigFileFinder._read_config(filename)
-    parser = config.MergedConfigParser(optmanager, config_finder)
-
-    assert parser.is_configured_by(parsed_config) is is_configured_by
-
-
-def test_parse_user_config(optmanager, config_finder):
-    """Verify parsing of user config files."""
-    optmanager.add_option('--exclude', parse_from_config=True,
-                          comma_separated_list=True,
-                          normalize_paths=True)
-    optmanager.add_option('--ignore', parse_from_config=True,
-                          comma_separated_list=True)
-    optmanager.add_option('--quiet', parse_from_config=True,
-                          action='count')
-    parser = config.MergedConfigParser(optmanager, config_finder)
-
-    config_finder.user_config_file = ('tests/fixtures/config_files/'
-                                      'cli-specified.ini')
-    parsed_config = parser.parse_user_config()
-
-    assert parsed_config == {
-        'ignore': ['E123', 'W234', 'E111'],
-        'exclude': [
-            os.path.abspath('foo/'),
-            os.path.abspath('bar/'),
-            os.path.abspath('bogus/'),
-        ],
-        'quiet': 1,
-    }
-
-
-def test_parse_local_config(optmanager, config_finder):
-    """Verify parsing of local config files."""
-    optmanager.add_option('--exclude', parse_from_config=True,
-                          comma_separated_list=True,
-                          normalize_paths=True)
-    optmanager.add_option('--ignore', parse_from_config=True,
-                          comma_separated_list=True)
-    optmanager.add_option('--quiet', parse_from_config=True,
-                          action='count')
-    parser = config.MergedConfigParser(optmanager, config_finder)
-
-    with mock.patch.object(config_finder, 'local_config_files') as localcfs:
-        localcfs.return_value = [
-            'tests/fixtures/config_files/cli-specified.ini'
-        ]
-        parsed_config = parser.parse_local_config()
-
-    assert parsed_config == {
-        'ignore': ['E123', 'W234', 'E111'],
-        'exclude': [
-            os.path.abspath('foo/'),
-            os.path.abspath('bar/'),
-            os.path.abspath('bogus/'),
-        ],
-        'quiet': 1,
-    }
-
-
-def test_merge_user_and_local_config(optmanager, config_finder):
-    """Verify merging of parsed user and local config files."""
-    optmanager.add_option('--exclude', parse_from_config=True,
-                          comma_separated_list=True,
-                          normalize_paths=True)
-    optmanager.add_option('--ignore', parse_from_config=True,
-                          comma_separated_list=True)
-    optmanager.add_option('--select', parse_from_config=True,
-                          comma_separated_list=True)
-    parser = config.MergedConfigParser(optmanager, config_finder)
-
-    with mock.patch.object(config_finder, 'local_config_files') as localcfs:
-        localcfs.return_value = [
-            'tests/fixtures/config_files/local-config.ini'
-        ]
-        config_finder.user_config_file = ('tests/fixtures/config_files/'
-                                          'user-config.ini')
-        parsed_config = parser.merge_user_and_local_config()
-
-    assert parsed_config == {
-        'exclude': [
-            os.path.abspath('docs/')
-        ],
-        'ignore': ['D203'],
-        'select': ['E', 'W', 'F'],
-    }
-
-
-def test_parse_isolates_config(optmanager):
-    """Verify behaviour of the parse method with isolated=True."""
-    config_finder = mock.MagicMock()
-    config_finder.ignore_config_files = True
-    parser = config.MergedConfigParser(optmanager, config_finder)
-
-    assert parser.parse() == {}
-    assert config_finder.local_configs.called is False
-    assert config_finder.user_config.called is False
-
-
-def test_parse_uses_cli_config(optmanager):
-    """Verify behaviour of the parse method with a specified config."""
-    config_file_value = 'foo.ini'
-    config_finder = mock.MagicMock()
-    config_finder.config_file = config_file_value
-    config_finder.ignore_config_files = False
-    parser = config.MergedConfigParser(optmanager, config_finder)
-
-    parser.parse()
-    config_finder.cli_config.assert_called_once_with(config_file_value)
-
-
-@pytest.mark.parametrize('config_fixture_path', [
-    'tests/fixtures/config_files/cli-specified.ini',
-    'tests/fixtures/config_files/cli-specified-with-inline-comments.ini',
-    'tests/fixtures/config_files/cli-specified-without-inline-comments.ini',
-])
-def test_parsed_configs_are_equivalent(
-        optmanager, config_finder, config_fixture_path):
-    """Verify the each file matches the expected parsed output.
-
-    This is used to ensure our documented behaviour does not regress.
-    """
-    optmanager.add_option('--exclude', parse_from_config=True,
-                          comma_separated_list=True,
-                          normalize_paths=True)
-    optmanager.add_option('--ignore', parse_from_config=True,
-                          comma_separated_list=True)
-    parser = config.MergedConfigParser(optmanager, config_finder)
-
-    with mock.patch.object(config_finder, 'local_config_files') as localcfs:
-        localcfs.return_value = [config_fixture_path]
-        with mock.patch.object(config_finder,
-                               'user_config_file') as usercf:
-            usercf.return_value = ''
-            parsed_config = parser.merge_user_and_local_config()
-
-    assert parsed_config['ignore'] == ['E123', 'W234', 'E111']
-    assert parsed_config['exclude'] == [
-        os.path.abspath('foo/'),
-        os.path.abspath('bar/'),
-        os.path.abspath('bogus/'),
-    ]
-
-
-@pytest.mark.parametrize('config_file', [
-    'tests/fixtures/config_files/config-with-hyphenated-options.ini'
-])
-def test_parsed_hyphenated_and_underscored_names(
-        optmanager, config_finder, config_file):
-    """Verify we find hyphenated option names as well as underscored.
-
-    This tests for options like --max-line-length and --enable-extensions
-    which are able to be specified either as max-line-length or
-    max_line_length in our config files.
-    """
-    optmanager.add_option('--max-line-length', parse_from_config=True,
-                          type=int)
-    optmanager.add_option('--enable-extensions', parse_from_config=True,
-                          comma_separated_list=True)
-    parser = config.MergedConfigParser(optmanager, config_finder)
-
-    with mock.patch.object(config_finder, 'local_config_files') as localcfs:
-        localcfs.return_value = [config_file]
-        with mock.patch.object(config_finder,
-                               'user_config_file') as usercf:
-            usercf.return_value = ''
-            parsed_config = parser.merge_user_and_local_config()
-
-    assert parsed_config['max_line_length'] == 110
-    assert parsed_config['enable_extensions'] == ['H101', 'H235']
diff --git a/tests/unit/test_nothing_formatter.py b/tests/unit/test_nothing_formatter.py
index 85a2e76e..d7cbea6f 100644
--- a/tests/unit/test_nothing_formatter.py
+++ b/tests/unit/test_nothing_formatter.py
@@ -7,15 +7,15 @@
 
 def options(**kwargs):
     """Create an argparse.Namespace instance."""
-    kwargs.setdefault('output_file', None)
-    kwargs.setdefault('tee', False)
+    kwargs.setdefault("output_file", None)
+    kwargs.setdefault("tee", False)
     return argparse.Namespace(**kwargs)
 
 
 def test_format_returns_nothing():
     """Verify Nothing.format returns None."""
     formatter = default.Nothing(options())
-    error = style_guide.Violation('code', 'file.py', 1, 1, 'text', '1')
+    error = style_guide.Violation("code", "file.py", 1, 1, "text", "1")
 
     assert formatter.format(error) is None
 
@@ -23,6 +23,6 @@ def test_format_returns_nothing():
 def test_show_source_returns_nothing():
     """Verify Nothing.show_source returns None."""
     formatter = default.Nothing(options())
-    error = style_guide.Violation('code', 'file.py', 1, 1, 'text', '1')
+    error = style_guide.Violation("code", "file.py", 1, 1, "text", "1")
 
     assert formatter.show_source(error) is None
diff --git a/tests/unit/test_option.py b/tests/unit/test_option.py
index ba6b672c..52aef272 100644
--- a/tests/unit/test_option.py
+++ b/tests/unit/test_option.py
@@ -1,7 +1,7 @@
 """Unit tests for flake8.options.manager.Option."""
 import functools
+from unittest import mock
 
-import mock
 import pytest
 
 from flake8.options import manager
@@ -10,9 +10,9 @@
 def test_to_argparse():
     """Test conversion to an argparse arguments."""
     opt = manager.Option(
-        short_option_name='-t',
-        long_option_name='--test',
-        action='count',
+        short_option_name="-t",
+        long_option_name="--test",
+        action="count",
         parse_from_config=True,
         normalize_paths=True,
     )
@@ -20,42 +20,44 @@ def test_to_argparse():
     assert opt.parse_from_config is True
 
     args, kwargs = opt.to_argparse()
-    assert args == ['-t', '--test']
-    assert kwargs == {'action': 'count', 'type': mock.ANY}
-    assert isinstance(kwargs['type'], functools.partial)
+    assert args == ["-t", "--test"]
+    assert kwargs == {"action": "count", "type": mock.ANY}
+    assert isinstance(kwargs["type"], functools.partial)
 
 
 def test_to_optparse():
     """Test that .to_optparse() produces a useful error message."""
     with pytest.raises(AttributeError) as excinfo:
-        manager.Option('--foo').to_optparse
-    msg, = excinfo.value.args
-    assert msg == 'to_optparse: flake8 now uses argparse'
+        manager.Option("--foo").to_optparse
+    (msg,) = excinfo.value.args
+    assert msg == "to_optparse: flake8 now uses argparse"
 
 
 def test_to_argparse_creates_an_option_as_we_expect():
     """Show that we pass all keyword args to argparse."""
-    opt = manager.Option('-t', '--test', action='count')
+    opt = manager.Option("-t", "--test", action="count")
     args, kwargs = opt.to_argparse()
-    assert args == ['-t', '--test']
-    assert kwargs == {'action': 'count'}
+    assert args == ["-t", "--test"]
+    assert kwargs == {"action": "count"}
 
 
 def test_config_name_generation():
     """Show that we generate the config name deterministically."""
-    opt = manager.Option(long_option_name='--some-very-long-option-name',
-                         parse_from_config=True)
+    opt = manager.Option(
+        long_option_name="--some-very-long-option-name",
+        parse_from_config=True,
+    )
 
-    assert opt.config_name == 'some_very_long_option_name'
+    assert opt.config_name == "some_very_long_option_name"
 
 
 def test_config_name_needs_long_option_name():
     """Show that we error out if the Option should be parsed from config."""
     with pytest.raises(ValueError):
-        manager.Option('-s', parse_from_config=True)
+        manager.Option("-s", parse_from_config=True)
 
 
 def test_dest_is_not_overridden():
     """Show that we do not override custom destinations."""
-    opt = manager.Option('-s', '--short', dest='something_not_short')
-    assert opt.dest == 'something_not_short'
+    opt = manager.Option("-s", "--short", dest="something_not_short")
+    assert opt.dest == "something_not_short"
diff --git a/tests/unit/test_option_manager.py b/tests/unit/test_option_manager.py
index 09714b9a..93f94e94 100644
--- a/tests/unit/test_option_manager.py
+++ b/tests/unit/test_option_manager.py
@@ -1,21 +1,21 @@
 """Unit tests for flake.options.manager.OptionManager."""
 import argparse
 import os
+from unittest import mock
 
-import mock
 import pytest
 
 from flake8 import utils
 from flake8.main.options import JobsArgument
 from flake8.options import manager
 
-TEST_VERSION = '3.0.0b1'
+TEST_VERSION = "3.0.0b1"
 
 
 @pytest.fixture
 def optmanager():
     """Generate a simple OptionManager with default test arguments."""
-    return manager.OptionManager(prog='flake8', version=TEST_VERSION)
+    return manager.OptionManager(prog="flake8", version=TEST_VERSION)
 
 
 def test_option_manager_creates_option_parser(optmanager):
@@ -27,30 +27,29 @@ def test_option_manager_including_parent_options():
     """Verify parent options are included in the parsed options."""
     # GIVEN
     parent_parser = argparse.ArgumentParser(add_help=False)
-    parent_parser.add_argument('--parent')
+    parent_parser.add_argument("--parent")
 
     # WHEN
     optmanager = manager.OptionManager(
-        prog='flake8',
-        version=TEST_VERSION,
-        parents=[parent_parser])
-    option, _ = optmanager.parse_args(['--parent', 'foo'])
+        prog="flake8", version=TEST_VERSION, parents=[parent_parser]
+    )
+    option, _ = optmanager.parse_args(["--parent", "foo"])
 
     # THEN
-    assert option.parent == 'foo'
+    assert option.parent == "foo"
 
 
 def test_parse_args_forwarding_default_values(optmanager):
     """Verify default provided values are present in the final result."""
-    namespace = argparse.Namespace(foo='bar')
+    namespace = argparse.Namespace(foo="bar")
     options, args = optmanager.parse_args([], namespace)
-    assert options.foo == 'bar'
+    assert options.foo == "bar"
 
 
 def test_parse_args_forwarding_type_coercion(optmanager):
     """Verify default provided values are type converted from add_option."""
-    optmanager.add_option('--foo', type=int)
-    namespace = argparse.Namespace(foo='5')
+    optmanager.add_option("--foo", type=int)
+    namespace = argparse.Namespace(foo="5")
     options, args = optmanager.parse_args([], namespace)
     assert options.foo == 5
 
@@ -60,8 +59,8 @@ def test_add_option_short_option_only(optmanager):
     assert optmanager.options == []
     assert optmanager.config_options_dict == {}
 
-    optmanager.add_option('-s', help='Test short opt')
-    assert optmanager.options[0].short_option_name == '-s'
+    optmanager.add_option("-s", help="Test short opt")
+    assert optmanager.options[0].short_option_name == "-s"
 
 
 def test_add_option_long_option_only(optmanager):
@@ -69,9 +68,9 @@ def test_add_option_long_option_only(optmanager):
     assert optmanager.options == []
     assert optmanager.config_options_dict == {}
 
-    optmanager.add_option('--long', help='Test long opt')
+    optmanager.add_option("--long", help="Test long opt")
     assert optmanager.options[0].short_option_name is manager._ARG.NO
-    assert optmanager.options[0].long_option_name == '--long'
+    assert optmanager.options[0].long_option_name == "--long"
 
 
 def test_add_short_and_long_option_names(optmanager):
@@ -79,9 +78,9 @@ def test_add_short_and_long_option_names(optmanager):
     assert optmanager.options == []
     assert optmanager.config_options_dict == {}
 
-    optmanager.add_option('-b', '--both', help='Test both opts')
-    assert optmanager.options[0].short_option_name == '-b'
-    assert optmanager.options[0].long_option_name == '--both'
+    optmanager.add_option("-b", "--both", help="Test both opts")
+    assert optmanager.options[0].short_option_name == "-b"
+    assert optmanager.options[0].long_option_name == "--both"
 
 
 def test_add_option_with_custom_args(optmanager):
@@ -89,11 +88,11 @@ def test_add_option_with_custom_args(optmanager):
     assert optmanager.options == []
     assert optmanager.config_options_dict == {}
 
-    optmanager.add_option('--parse', parse_from_config=True)
-    optmanager.add_option('--commas', comma_separated_list=True)
-    optmanager.add_option('--files', normalize_paths=True)
+    optmanager.add_option("--parse", parse_from_config=True)
+    optmanager.add_option("--commas", comma_separated_list=True)
+    optmanager.add_option("--files", normalize_paths=True)
 
-    attrs = ['parse_from_config', 'comma_separated_list', 'normalize_paths']
+    attrs = ["parse_from_config", "comma_separated_list", "normalize_paths"]
     for option, attr in zip(optmanager.options, attrs):
         assert getattr(option, attr) is True
 
@@ -103,10 +102,10 @@ def test_parse_args_normalize_path(optmanager):
     assert optmanager.options == []
     assert optmanager.config_options_dict == {}
 
-    optmanager.add_option('--config', normalize_paths=True)
+    optmanager.add_option("--config", normalize_paths=True)
 
-    options, args = optmanager.parse_args(['--config', '../config.ini'])
-    assert options.config == os.path.abspath('../config.ini')
+    options, args = optmanager.parse_args(["--config", "../config.ini"])
+    assert options.config == os.path.abspath("../config.ini")
 
 
 def test_parse_args_handles_comma_separated_defaults(optmanager):
@@ -114,11 +113,12 @@ def test_parse_args_handles_comma_separated_defaults(optmanager):
     assert optmanager.options == []
     assert optmanager.config_options_dict == {}
 
-    optmanager.add_option('--exclude', default='E123,W234',
-                          comma_separated_list=True)
+    optmanager.add_option(
+        "--exclude", default="E123,W234", comma_separated_list=True
+    )
 
     options, args = optmanager.parse_args([])
-    assert options.exclude == ['E123', 'W234']
+    assert options.exclude == ["E123", "W234"]
 
 
 def test_parse_args_handles_comma_separated_lists(optmanager):
@@ -126,11 +126,12 @@ def test_parse_args_handles_comma_separated_lists(optmanager):
     assert optmanager.options == []
     assert optmanager.config_options_dict == {}
 
-    optmanager.add_option('--exclude', default='E123,W234',
-                          comma_separated_list=True)
+    optmanager.add_option(
+        "--exclude", default="E123,W234", comma_separated_list=True
+    )
 
-    options, args = optmanager.parse_args(['--exclude', 'E201,W111,F280'])
-    assert options.exclude == ['E201', 'W111', 'F280']
+    options, args = optmanager.parse_args(["--exclude", "E201,W111,F280"])
+    assert options.exclude == ["E201", "W111", "F280"]
 
 
 def test_parse_args_normalize_paths(optmanager):
@@ -138,57 +139,61 @@ def test_parse_args_normalize_paths(optmanager):
     assert optmanager.options == []
     assert optmanager.config_options_dict == {}
 
-    optmanager.add_option('--extra-config', normalize_paths=True,
-                          comma_separated_list=True)
+    optmanager.add_option(
+        "--extra-config", normalize_paths=True, comma_separated_list=True
+    )
 
-    options, args = optmanager.parse_args([
-        '--extra-config', '../config.ini,tox.ini,flake8/some-other.cfg'
-    ])
+    options, args = optmanager.parse_args(
+        ["--extra-config", "../config.ini,tox.ini,flake8/some-other.cfg"]
+    )
     assert options.extra_config == [
-        os.path.abspath('../config.ini'),
-        'tox.ini',
-        os.path.abspath('flake8/some-other.cfg'),
+        os.path.abspath("../config.ini"),
+        "tox.ini",
+        os.path.abspath("flake8/some-other.cfg"),
     ]
 
 
 def test_generate_versions(optmanager):
     """Verify a comma-separated string is generated of registered plugins."""
     optmanager.registered_plugins = [
-        manager.PluginVersion('Testing 100', '0.0.0', False),
-        manager.PluginVersion('Testing 101', '0.0.0', False),
-        manager.PluginVersion('Testing 300', '0.0.0', True),
+        manager.PluginVersion("Testing 100", "0.0.0", False),
+        manager.PluginVersion("Testing 101", "0.0.0", False),
+        manager.PluginVersion("Testing 300", "0.0.0", True),
     ]
-    assert (optmanager.generate_versions()
-            == 'Testing 100: 0.0.0, Testing 101: 0.0.0, Testing 300: 0.0.0')
+    assert (
+        optmanager.generate_versions()
+        == "Testing 100: 0.0.0, Testing 101: 0.0.0, Testing 300: 0.0.0"
+    )
 
 
 def test_plugins_are_sorted_in_generate_versions(optmanager):
     """Verify we sort before joining strings in generate_versions."""
     optmanager.registered_plugins = [
-        manager.PluginVersion('pyflakes', '1.5.0', False),
-        manager.PluginVersion('mccabe', '0.7.0', False),
-        manager.PluginVersion('pycodestyle', '2.2.0', False),
-        manager.PluginVersion('flake8-docstrings', '0.6.1', False),
-        manager.PluginVersion('flake8-bugbear', '2016.12.1', False),
+        manager.PluginVersion("pyflakes", "1.5.0", False),
+        manager.PluginVersion("mccabe", "0.7.0", False),
+        manager.PluginVersion("pycodestyle", "2.2.0", False),
+        manager.PluginVersion("flake8-docstrings", "0.6.1", False),
+        manager.PluginVersion("flake8-bugbear", "2016.12.1", False),
     ]
-    assert (optmanager.generate_versions()
-            == 'flake8-bugbear: 2016.12.1, '
-               'flake8-docstrings: 0.6.1, '
-               'mccabe: 0.7.0, '
-               'pycodestyle: 2.2.0, '
-               'pyflakes: 1.5.0')
+    assert (
+        optmanager.generate_versions() == "flake8-bugbear: 2016.12.1, "
+        "flake8-docstrings: 0.6.1, "
+        "mccabe: 0.7.0, "
+        "pycodestyle: 2.2.0, "
+        "pyflakes: 1.5.0"
+    )
 
 
 def test_generate_versions_with_format_string(optmanager):
     """Verify a comma-separated string is generated of registered plugins."""
-    optmanager.registered_plugins.update([
-        manager.PluginVersion('Testing', '0.0.0', False),
-        manager.PluginVersion('Testing', '0.0.0', False),
-        manager.PluginVersion('Testing', '0.0.0', False),
-    ])
-    assert (
-        optmanager.generate_versions() == 'Testing: 0.0.0'
+    optmanager.registered_plugins.update(
+        [
+            manager.PluginVersion("Testing", "0.0.0", False),
+            manager.PluginVersion("Testing", "0.0.0", False),
+            manager.PluginVersion("Testing", "0.0.0", False),
+        ]
     )
+    assert optmanager.generate_versions() == "Testing: 0.0.0"
 
 
 def test_update_version_string(optmanager):
@@ -197,17 +202,20 @@ def test_update_version_string(optmanager):
     assert optmanager.version_action.version == TEST_VERSION
 
     optmanager.registered_plugins = [
-        manager.PluginVersion('Testing 100', '0.0.0', False),
-        manager.PluginVersion('Testing 101', '0.0.0', False),
-        manager.PluginVersion('Testing 300', '0.0.0', False),
+        manager.PluginVersion("Testing 100", "0.0.0", False),
+        manager.PluginVersion("Testing 101", "0.0.0", False),
+        manager.PluginVersion("Testing 300", "0.0.0", False),
     ]
 
     optmanager.update_version_string()
 
     assert optmanager.version == TEST_VERSION
-    assert (optmanager.version_action.version == TEST_VERSION
-            + ' (Testing 100: 0.0.0, Testing 101: 0.0.0, Testing 300: 0.0.0) '
-            + utils.get_python_version())
+    assert (
+        optmanager.version_action.version
+        == TEST_VERSION
+        + " (Testing 100: 0.0.0, Testing 101: 0.0.0, Testing 300: 0.0.0) "
+        + utils.get_python_version()
+    )
 
 
 def test_generate_epilog(optmanager):
@@ -215,14 +223,14 @@ def test_generate_epilog(optmanager):
     assert optmanager.parser.epilog is None
 
     optmanager.registered_plugins = [
-        manager.PluginVersion('Testing 100', '0.0.0', False),
-        manager.PluginVersion('Testing 101', '0.0.0', False),
-        manager.PluginVersion('Testing 300', '0.0.0', False),
+        manager.PluginVersion("Testing 100", "0.0.0", False),
+        manager.PluginVersion("Testing 101", "0.0.0", False),
+        manager.PluginVersion("Testing 300", "0.0.0", False),
     ]
 
     expected_value = (
-        'Installed plugins: Testing 100: 0.0.0, Testing 101: 0.0.0, Testing'
-        ' 300: 0.0.0'
+        "Installed plugins: Testing 100: 0.0.0, Testing 101: 0.0.0, Testing"
+        " 300: 0.0.0"
     )
 
     optmanager.generate_epilog()
@@ -233,14 +241,14 @@ def test_extend_default_ignore(optmanager):
     """Verify that we update the extended default ignore list."""
     assert optmanager.extended_default_ignore == set()
 
-    optmanager.extend_default_ignore(['T100', 'T101', 'T102'])
-    assert optmanager.extended_default_ignore == {'T100', 'T101', 'T102'}
+    optmanager.extend_default_ignore(["T100", "T101", "T102"])
+    assert optmanager.extended_default_ignore == {"T100", "T101", "T102"}
 
 
 def test_parse_known_args(optmanager):
     """Verify we ignore unknown options."""
-    with mock.patch('sys.exit') as sysexit:
-        optmanager.parse_known_args(['--max-complexity', '5'])
+    with mock.patch("sys.exit") as sysexit:
+        optmanager.parse_known_args(["--max-complexity", "5"])
 
     assert sysexit.called is False
 
@@ -249,101 +257,101 @@ def test_optparse_normalize_callback_option_legacy(optmanager):
     """Test the optparse shim for `callback=`."""
     callback_foo = mock.Mock()
     optmanager.add_option(
-        '--foo',
-        action='callback',
+        "--foo",
+        action="callback",
         callback=callback_foo,
         callback_args=(1, 2),
-        callback_kwargs={'a': 'b'},
+        callback_kwargs={"a": "b"},
     )
     callback_bar = mock.Mock()
     optmanager.add_option(
-        '--bar',
-        action='callback',
-        type='string',
+        "--bar",
+        action="callback",
+        type="string",
         callback=callback_bar,
     )
     callback_baz = mock.Mock()
     optmanager.add_option(
-        '--baz',
-        action='callback',
-        type='string',
+        "--baz",
+        action="callback",
+        type="string",
         nargs=2,
         callback=callback_baz,
     )
 
-    optmanager.parse_args(['--foo', '--bar', 'bararg', '--baz', '1', '2'])
+    optmanager.parse_args(["--foo", "--bar", "bararg", "--baz", "1", "2"])
 
     callback_foo.assert_called_once_with(
         mock.ANY,  # the option / action instance
-        '--foo',
+        "--foo",
         None,
         mock.ANY,  # the OptionParser / ArgumentParser
         1,
         2,
-        a='b',
+        a="b",
     )
     callback_bar.assert_called_once_with(
         mock.ANY,  # the option / action instance
-        '--bar',
-        'bararg',
+        "--bar",
+        "bararg",
         mock.ANY,  # the OptionParser / ArgumentParser
     )
     callback_baz.assert_called_once_with(
         mock.ANY,  # the option / action instance
-        '--baz',
-        ('1', '2'),
+        "--baz",
+        ("1", "2"),
         mock.ANY,  # the OptionParser / ArgumentParser
     )
 
 
 @pytest.mark.parametrize(
-    ('type_s', 'input_val', 'expected'),
+    ("type_s", "input_val", "expected"),
     (
-        ('int', '5', 5),
-        ('long', '6', 6),
-        ('string', 'foo', 'foo'),
-        ('float', '1.5', 1.5),
-        ('complex', '1+5j', 1 + 5j),
+        ("int", "5", 5),
+        ("long", "6", 6),
+        ("string", "foo", "foo"),
+        ("float", "1.5", 1.5),
+        ("complex", "1+5j", 1 + 5j),
         # optparse allows this but does not document it
-        ('str', 'foo', 'foo'),
+        ("str", "foo", "foo"),
     ),
 )
 def test_optparse_normalize_types(optmanager, type_s, input_val, expected):
     """Test the optparse shim for type="typename"."""
-    optmanager.add_option('--foo', type=type_s)
-    opts, args = optmanager.parse_args(['--foo', input_val])
+    optmanager.add_option("--foo", type=type_s)
+    opts, args = optmanager.parse_args(["--foo", input_val])
     assert opts.foo == expected
 
 
 def test_optparse_normalize_choice_type(optmanager):
     """Test the optparse shim for type="choice"."""
-    optmanager.add_option('--foo', type='choice', choices=('1', '2', '3'))
-    opts, args = optmanager.parse_args(['--foo', '1'])
-    assert opts.foo == '1'
+    optmanager.add_option("--foo", type="choice", choices=("1", "2", "3"))
+    opts, args = optmanager.parse_args(["--foo", "1"])
+    assert opts.foo == "1"
     # fails to parse
     with pytest.raises(SystemExit):
-        optmanager.parse_args(['--foo', '4'])
+        optmanager.parse_args(["--foo", "4"])
 
 
 def test_optparse_normalize_help(optmanager, capsys):
     """Test the optparse shim for %default in help text."""
-    optmanager.add_option('--foo', default='bar', help='default: %default')
+    optmanager.add_option("--foo", default="bar", help="default: %default")
     with pytest.raises(SystemExit):
-        optmanager.parse_args(['--help'])
+        optmanager.parse_args(["--help"])
     out, err = capsys.readouterr()
     output = out + err
-    assert 'default: bar' in output
+    assert "default: bar" in output
 
 
 def test_optmanager_group(optmanager, capsys):
     """Test that group(...) causes options to be assigned to a group."""
-    with optmanager.group('groupname'):
-        optmanager.add_option('--foo')
+    with optmanager.group("groupname"):
+        optmanager.add_option("--foo")
     with pytest.raises(SystemExit):
-        optmanager.parse_args(['--help'])
+        optmanager.parse_args(["--help"])
     out, err = capsys.readouterr()
     output = out + err
-    assert '\ngroupname:\n' in output
+    assert "\ngroupname:\n" in output
 
 
 @pytest.mark.parametrize(
diff --git a/tests/unit/test_plugin.py b/tests/unit/test_plugin.py
index 402eac81..c41198e4 100644
--- a/tests/unit/test_plugin.py
+++ b/tests/unit/test_plugin.py
@@ -1,7 +1,7 @@
 """Tests for flake8.plugins.manager.Plugin."""
 import argparse
+from unittest import mock
 
-import mock
 import pytest
 
 from flake8 import exceptions
@@ -11,8 +11,8 @@
 
 def test_load_plugin_fallsback_on_old_setuptools():
     """Verify we fallback gracefully to on old versions of setuptools."""
-    entry_point = mock.Mock(spec=['load'])
-    plugin = manager.Plugin('T000', entry_point)
+    entry_point = mock.Mock(spec=["load"])
+    plugin = manager.Plugin("T000", entry_point)
 
     plugin.load_plugin()
     entry_point.load.assert_called_once_with()
@@ -20,8 +20,8 @@ def test_load_plugin_fallsback_on_old_setuptools():
 
 def test_load_plugin_is_idempotent():
     """Verify we use the preferred methods on new versions of setuptools."""
-    entry_point = mock.Mock(spec=['load'])
-    plugin = manager.Plugin('T000', entry_point)
+    entry_point = mock.Mock(spec=["load"])
+    plugin = manager.Plugin("T000", entry_point)
 
     plugin.load_plugin()
     plugin.load_plugin()
@@ -31,9 +31,9 @@ def test_load_plugin_is_idempotent():
 
 def test_load_plugin_catches_and_reraises_exceptions():
     """Verify we raise our own FailedToLoadPlugin."""
-    entry_point = mock.Mock(spec=['load'])
-    entry_point.load.side_effect = ValueError('Test failure')
-    plugin = manager.Plugin('T000', entry_point)
+    entry_point = mock.Mock(spec=["load"])
+    entry_point.load.side_effect = ValueError("Test failure")
+    plugin = manager.Plugin("T000", entry_point)
 
     with pytest.raises(exceptions.FailedToLoadPlugin):
         plugin.load_plugin()
@@ -41,9 +41,9 @@ def test_load_plugin_catches_and_reraises_exceptions():
 
 def test_load_noncallable_plugin():
     """Verify that we do not load a non-callable plugin."""
-    entry_point = mock.Mock(spec=['load'])
+    entry_point = mock.Mock(spec=["load"])
     entry_point.load.return_value = mock.NonCallableMock()
-    plugin = manager.Plugin('T000', entry_point)
+    plugin = manager.Plugin("T000", entry_point)
 
     with pytest.raises(exceptions.FailedToLoadPlugin):
         plugin.load_plugin()
@@ -52,8 +52,8 @@ def test_load_noncallable_plugin():
 
 def test_plugin_property_loads_plugin_on_first_use():
     """Verify that we load our plugin when we first try to use it."""
-    entry_point = mock.Mock(spec=['load'])
-    plugin = manager.Plugin('T000', entry_point)
+    entry_point = mock.Mock(spec=["load"])
+    plugin = manager.Plugin("T000", entry_point)
 
     assert plugin.plugin is not None
     entry_point.load.assert_called_once_with()
@@ -61,14 +61,14 @@ def test_plugin_property_loads_plugin_on_first_use():
 
 def test_execute_calls_plugin_with_passed_arguments():
     """Verify that we pass arguments directly to the plugin."""
-    entry_point = mock.Mock(spec=['load'])
+    entry_point = mock.Mock(spec=["load"])
     plugin_obj = mock.Mock()
-    plugin = manager.Plugin('T000', entry_point)
+    plugin = manager.Plugin("T000", entry_point)
     plugin._plugin = plugin_obj
 
-    plugin.execute('arg1', 'arg2', kwarg1='value1', kwarg2='value2')
+    plugin.execute("arg1", "arg2", kwarg1="value1", kwarg2="value2")
     plugin_obj.assert_called_once_with(
-        'arg1', 'arg2', kwarg1='value1', kwarg2='value2'
+        "arg1", "arg2", kwarg1="value1", kwarg2="value2"
     )
 
     # Extra assertions
@@ -77,23 +77,24 @@ def test_execute_calls_plugin_with_passed_arguments():
 
 def test_version_proxies_to_the_plugin():
     """Verify that we pass arguments directly to the plugin."""
-    entry_point = mock.Mock(spec=['load'])
-    plugin_obj = mock.Mock(spec_set=['version'])
-    plugin_obj.version = 'a.b.c'
-    plugin = manager.Plugin('T000', entry_point)
+    entry_point = mock.Mock(spec=["load"])
+    plugin_obj = mock.Mock(spec_set=["version"])
+    plugin_obj.version = "a.b.c"
+    plugin = manager.Plugin("T000", entry_point)
     plugin._plugin = plugin_obj
 
-    assert plugin.version == 'a.b.c'
+    assert plugin.version == "a.b.c"
 
 
 def test_register_options():
     """Verify we call add_options on the plugin only if it exists."""
     # Set up our mocks and Plugin object
-    entry_point = mock.Mock(spec=['load'])
-    plugin_obj = mock.Mock(spec_set=['name', 'version', 'add_options',
-                                     'parse_options'])
+    entry_point = mock.Mock(spec=["load"])
+    plugin_obj = mock.Mock(
+        spec_set=["name", "version", "add_options", "parse_options"]
+    )
     option_manager = mock.MagicMock(spec=options_manager.OptionManager)
-    plugin = manager.Plugin('T000', entry_point)
+    plugin = manager.Plugin("T000", entry_point)
     plugin._plugin = plugin_obj
 
     # Call the method we're testing.
@@ -106,10 +107,10 @@ def test_register_options():
 def test_register_options_checks_plugin_for_method():
     """Verify we call add_options on the plugin only if it exists."""
     # Set up our mocks and Plugin object
-    entry_point = mock.Mock(spec=['load'])
-    plugin_obj = mock.Mock(spec_set=['name', 'version', 'parse_options'])
-    option_manager = mock.Mock(spec=['register_plugin'])
-    plugin = manager.Plugin('T000', entry_point)
+    entry_point = mock.Mock(spec=["load"])
+    plugin_obj = mock.Mock(spec_set=["name", "version", "parse_options"])
+    option_manager = mock.Mock(spec=["register_plugin"])
+    plugin = manager.Plugin("T000", entry_point)
     plugin._plugin = plugin_obj
 
     # Call the method we're testing.
@@ -122,12 +123,13 @@ def test_register_options_checks_plugin_for_method():
 def test_provide_options():
     """Verify we call add_options on the plugin only if it exists."""
     # Set up our mocks and Plugin object
-    entry_point = mock.Mock(spec=['load'])
-    plugin_obj = mock.Mock(spec_set=['name', 'version', 'add_options',
-                                     'parse_options'])
+    entry_point = mock.Mock(spec=["load"])
+    plugin_obj = mock.Mock(
+        spec_set=["name", "version", "add_options", "parse_options"]
+    )
     option_values = argparse.Namespace(enable_extensions=[])
     option_manager = mock.Mock()
-    plugin = manager.Plugin('T000', entry_point)
+    plugin = manager.Plugin("T000", entry_point)
     plugin._plugin = plugin_obj
 
     # Call the method we're testing.
@@ -139,10 +141,13 @@ def test_provide_options():
     )
 
 
-@pytest.mark.parametrize('ignore_list, code, expected_list', [
-    (['E', 'W', 'F', 'C9'], 'W', ['E', 'F', 'C9']),
-    (['E', 'W', 'F'], 'C9', ['E', 'W', 'F']),
-])
+@pytest.mark.parametrize(
+    "ignore_list, code, expected_list",
+    [
+        (["E", "W", "F", "C9"], "W", ["E", "F", "C9"]),
+        (["E", "W", "F"], "C9", ["E", "W", "F"]),
+    ],
+)
 def test_enable(ignore_list, code, expected_list):
     """Verify that enabling a plugin removes it from the ignore list."""
     options = mock.Mock(ignore=ignore_list)
@@ -157,8 +162,8 @@ def test_enable(ignore_list, code, expected_list):
 def test_enable_without_providing_parsed_options():
     """Verify that enabling a plugin removes it from the ignore list."""
     optmanager = mock.Mock()
-    plugin = manager.Plugin('U4', mock.Mock())
+    plugin = manager.Plugin("U4", mock.Mock())
 
     plugin.enable(optmanager)
 
-    optmanager.remove_from_default_ignore.assert_called_once_with(['U4'])
+    optmanager.remove_from_default_ignore.assert_called_once_with(["U4"])
diff --git a/tests/unit/test_plugin_manager.py b/tests/unit/test_plugin_manager.py
index 9ad6abaa..5a38a388 100644
--- a/tests/unit/test_plugin_manager.py
+++ b/tests/unit/test_plugin_manager.py
@@ -1,58 +1,57 @@
 """Tests for flake8.plugins.manager.PluginManager."""
-import mock
+from unittest import mock
 
 from flake8._compat import importlib_metadata
 from flake8.plugins import manager
 
 
-@mock.patch.object(importlib_metadata, 'entry_points')
+@mock.patch.object(importlib_metadata, "entry_points")
 def test_calls_entrypoints_on_instantiation(entry_points_mck):
     """Verify that we call entry_points() when we create a manager."""
     entry_points_mck.return_value = {}
-    manager.PluginManager(namespace='testing.entrypoints')
+    manager.PluginManager(namespace="testing.entrypoints")
     entry_points_mck.assert_called_once_with()
 
 
-@mock.patch.object(importlib_metadata, 'entry_points')
+@mock.patch.object(importlib_metadata, "entry_points")
 def test_calls_entrypoints_creates_plugins_automaticaly(entry_points_mck):
     """Verify that we create Plugins on instantiation."""
     entry_points_mck.return_value = {
-        'testing.entrypoints': [
-            importlib_metadata.EntryPoint('T100', '', None),
-            importlib_metadata.EntryPoint('T200', '', None),
+        "testing.entrypoints": [
+            importlib_metadata.EntryPoint("T100", "", "testing.entrypoints"),
+            importlib_metadata.EntryPoint("T200", "", "testing.entrypoints"),
         ],
     }
-    plugin_mgr = manager.PluginManager(namespace='testing.entrypoints')
+    plugin_mgr = manager.PluginManager(namespace="testing.entrypoints")
 
     entry_points_mck.assert_called_once_with()
-    assert 'T100' in plugin_mgr.plugins
-    assert 'T200' in plugin_mgr.plugins
-    assert isinstance(plugin_mgr.plugins['T100'], manager.Plugin)
-    assert isinstance(plugin_mgr.plugins['T200'], manager.Plugin)
+    assert "T100" in plugin_mgr.plugins
+    assert "T200" in plugin_mgr.plugins
+    assert isinstance(plugin_mgr.plugins["T100"], manager.Plugin)
+    assert isinstance(plugin_mgr.plugins["T200"], manager.Plugin)
 
 
-@mock.patch.object(importlib_metadata, 'entry_points')
+@mock.patch.object(importlib_metadata, "entry_points")
 def test_handles_mapping_functions_across_plugins(entry_points_mck):
     """Verify we can use the PluginManager call functions on all plugins."""
     entry_points_mck.return_value = {
-        'testing.entrypoints': [
-            importlib_metadata.EntryPoint('T100', '', None),
-            importlib_metadata.EntryPoint('T200', '', None),
+        "testing.entrypoints": [
+            importlib_metadata.EntryPoint("T100", "", "testing.entrypoints"),
+            importlib_metadata.EntryPoint("T200", "", "testing.entrypoints"),
         ],
     }
-    plugin_mgr = manager.PluginManager(namespace='testing.entrypoints')
+    plugin_mgr = manager.PluginManager(namespace="testing.entrypoints")
     plugins = [plugin_mgr.plugins[name] for name in plugin_mgr.names]
 
     assert list(plugin_mgr.map(lambda x: x)) == plugins
 
 
-@mock.patch.object(importlib_metadata, 'entry_points')
+@mock.patch.object(importlib_metadata, "entry_points")
 def test_local_plugins(entry_points_mck):
     """Verify PluginManager can load given local plugins."""
     entry_points_mck.return_value = {}
     plugin_mgr = manager.PluginManager(
-        namespace='testing.entrypoints',
-        local_plugins=['X = path.to:Plugin']
+        namespace="testing.entrypoints", local_plugins=["X = path.to:Plugin"]
     )
 
-    assert plugin_mgr.plugins['X'].entry_point.value == 'path.to:Plugin'
+    assert plugin_mgr.plugins["X"].entry_point.value == "path.to:Plugin"
diff --git a/tests/unit/test_plugin_type_manager.py b/tests/unit/test_plugin_type_manager.py
index 939aa8c2..1b823af6 100644
--- a/tests/unit/test_plugin_type_manager.py
+++ b/tests/unit/test_plugin_type_manager.py
@@ -1,5 +1,6 @@
 """Tests for flake8.plugins.manager.PluginTypeManager."""
-import mock
+from unittest import mock
+
 import pytest
 
 from flake8 import exceptions
@@ -13,8 +14,8 @@ def create_plugin_mock(raise_exception=False):
     plugin = mock.create_autospec(manager.Plugin, instance=True)
     if raise_exception:
         plugin.load_plugin.side_effect = exceptions.FailedToLoadPlugin(
-            plugin_name='T101',
-            exception=ValueError('Test failure'),
+            plugin_name="T101",
+            exception=ValueError("Test failure"),
         )
     return plugin
 
@@ -27,26 +28,19 @@ def fake_map(func):
             yield func(plugin)
 
     # Mock out the PluginManager instance
-    manager_mock = mock.Mock(spec=['map'])
+    manager_mock = mock.Mock(spec=["map"])
     # Replace the map method
     manager_mock.map = fake_map
     return manager_mock
 
 
-def create_manager_with_plugins(plugins):
-    """Create a fake PluginManager with a plugins dictionary."""
-    manager_mock = mock.create_autospec(manager.PluginManager)
-    manager_mock.plugins = plugins
-    return manager_mock
-
-
 class FakeTestType(manager.PluginTypeManager):
     """Fake PluginTypeManager."""
 
     namespace = TEST_NAMESPACE
 
 
-@mock.patch('flake8.plugins.manager.PluginManager')
+@mock.patch("flake8.plugins.manager.PluginManager", autospec=True)
 def test_instantiates_a_manager(PluginManager):  # noqa: N803
     """Verify we create a PluginManager on instantiation."""
     FakeTestType()
@@ -54,26 +48,22 @@ def test_instantiates_a_manager(PluginManager):  # noqa: N803
     PluginManager.assert_called_once_with(TEST_NAMESPACE, local_plugins=None)
 
 
-@mock.patch('flake8.plugins.manager.PluginManager')
+@mock.patch("flake8.plugins.manager.PluginManager", autospec=True)
 def test_proxies_names_to_manager(PluginManager):  # noqa: N803
     """Verify we proxy the names attribute."""
-    PluginManager.return_value = mock.Mock(names=[
-        'T100', 'T200', 'T300'
-    ])
+    PluginManager.return_value = mock.Mock(names=["T100", "T200", "T300"])
     type_mgr = FakeTestType()
 
-    assert type_mgr.names == ['T100', 'T200', 'T300']
+    assert type_mgr.names == ["T100", "T200", "T300"]
 
 
-@mock.patch('flake8.plugins.manager.PluginManager')
+@mock.patch("flake8.plugins.manager.PluginManager", autospec=True)
 def test_proxies_plugins_to_manager(PluginManager):  # noqa: N803
     """Verify we proxy the plugins attribute."""
-    PluginManager.return_value = mock.Mock(plugins=[
-        'T100', 'T200', 'T300'
-    ])
+    PluginManager.return_value = mock.Mock(plugins=["T100", "T200", "T300"])
     type_mgr = FakeTestType()
 
-    assert type_mgr.plugins == ['T100', 'T200', 'T300']
+    assert type_mgr.plugins == ["T100", "T200", "T300"]
 
 
 def test_generate_call_function():
@@ -81,21 +71,28 @@ def test_generate_call_function():
     optmanager = object()
     plugin = mock.Mock(method_name=lambda x: x)
     func = manager.PluginTypeManager._generate_call_function(
-        'method_name', optmanager,
+        "method_name",
+        optmanager,
     )
 
     assert callable(func)
     assert func(plugin) is optmanager
 
 
-@mock.patch('flake8.plugins.manager.PluginManager')
+@mock.patch("flake8.plugins.manager.PluginManager", autospec=True)
 def test_load_plugins(PluginManager):  # noqa: N803
     """Verify load plugins loads *every* plugin."""
     # Create a bunch of fake plugins
-    plugins = [create_plugin_mock(), create_plugin_mock(),
-               create_plugin_mock(), create_plugin_mock(),
-               create_plugin_mock(), create_plugin_mock(),
-               create_plugin_mock(), create_plugin_mock()]
+    plugins = [
+        create_plugin_mock(),
+        create_plugin_mock(),
+        create_plugin_mock(),
+        create_plugin_mock(),
+        create_plugin_mock(),
+        create_plugin_mock(),
+        create_plugin_mock(),
+        create_plugin_mock(),
+    ]
     # Return our PluginManager mock
     PluginManager.return_value = create_mapping_manager_mock(plugins)
 
@@ -108,13 +105,19 @@ def test_load_plugins(PluginManager):  # noqa: N803
     assert type_mgr.plugins_loaded is True
 
 
-@mock.patch('flake8.plugins.manager.PluginManager')
+@mock.patch("flake8.plugins.manager.PluginManager")
 def test_load_plugins_fails(PluginManager):  # noqa: N803
     """Verify load plugins bubbles up exceptions."""
-    plugins = [create_plugin_mock(), create_plugin_mock(True),
-               create_plugin_mock(), create_plugin_mock(),
-               create_plugin_mock(), create_plugin_mock(),
-               create_plugin_mock(), create_plugin_mock()]
+    plugins = [
+        create_plugin_mock(),
+        create_plugin_mock(True),
+        create_plugin_mock(),
+        create_plugin_mock(),
+        create_plugin_mock(),
+        create_plugin_mock(),
+        create_plugin_mock(),
+        create_plugin_mock(),
+    ]
     # Return our PluginManager mock
     PluginManager.return_value = create_mapping_manager_mock(plugins)
 
@@ -132,13 +135,19 @@ def test_load_plugins_fails(PluginManager):  # noqa: N803
         assert plugin.load_plugin.called is False
 
 
-@mock.patch('flake8.plugins.manager.PluginManager')
+@mock.patch("flake8.plugins.manager.PluginManager")
 def test_register_options(PluginManager):  # noqa: N803
     """Test that we map over every plugin to register options."""
-    plugins = [create_plugin_mock(), create_plugin_mock(),
-               create_plugin_mock(), create_plugin_mock(),
-               create_plugin_mock(), create_plugin_mock(),
-               create_plugin_mock(), create_plugin_mock()]
+    plugins = [
+        create_plugin_mock(),
+        create_plugin_mock(),
+        create_plugin_mock(),
+        create_plugin_mock(),
+        create_plugin_mock(),
+        create_plugin_mock(),
+        create_plugin_mock(),
+        create_plugin_mock(),
+    ]
     # Return our PluginManager mock
     PluginManager.return_value = create_mapping_manager_mock(plugins)
     optmanager = object()
@@ -150,13 +159,19 @@ def test_register_options(PluginManager):  # noqa: N803
         plugin.register_options.assert_called_with(optmanager)
 
 
-@mock.patch('flake8.plugins.manager.PluginManager')
+@mock.patch("flake8.plugins.manager.PluginManager")
 def test_provide_options(PluginManager):  # noqa: N803
     """Test that we map over every plugin to provide parsed options."""
-    plugins = [create_plugin_mock(), create_plugin_mock(),
-               create_plugin_mock(), create_plugin_mock(),
-               create_plugin_mock(), create_plugin_mock(),
-               create_plugin_mock(), create_plugin_mock()]
+    plugins = [
+        create_plugin_mock(),
+        create_plugin_mock(),
+        create_plugin_mock(),
+        create_plugin_mock(),
+        create_plugin_mock(),
+        create_plugin_mock(),
+        create_plugin_mock(),
+        create_plugin_mock(),
+    ]
     # Return our PluginManager mock
     PluginManager.return_value = create_mapping_manager_mock(plugins)
     optmanager = object()
@@ -166,32 +181,30 @@ def test_provide_options(PluginManager):  # noqa: N803
     type_mgr.provide_options(optmanager, options, [])
 
     for plugin in plugins:
-        plugin.provide_options.assert_called_with(optmanager,
-                                                  options,
-                                                  [])
+        plugin.provide_options.assert_called_with(optmanager, options, [])
 
 
-@mock.patch('flake8.plugins.manager.PluginManager')
+@mock.patch("flake8.plugins.manager.PluginManager", autospec=True)
 def test_proxy_contains_to_managers_plugins_dict(PluginManager):  # noqa: N803
     """Verify that we proxy __contains__ to the manager's dictionary."""
-    plugins = {'T10%i' % i: create_plugin_mock() for i in range(8)}
+    plugins = {"T10%i" % i: create_plugin_mock() for i in range(8)}
     # Return our PluginManager mock
-    PluginManager.return_value = create_manager_with_plugins(plugins)
+    PluginManager.return_value.plugins = plugins
 
     type_mgr = FakeTestType()
     for i in range(8):
-        key = 'T10%i' % i
+        key = "T10%i" % i
         assert key in type_mgr
 
 
-@mock.patch('flake8.plugins.manager.PluginManager')
+@mock.patch("flake8.plugins.manager.PluginManager")
 def test_proxies_getitem_to_managers_plugins_dict(PluginManager):  # noqa: N803
     """Verify that we can use the PluginTypeManager like a dictionary."""
-    plugins = {'T10%i' % i: create_plugin_mock() for i in range(8)}
+    plugins = {"T10%i" % i: create_plugin_mock() for i in range(8)}
     # Return our PluginManager mock
-    PluginManager.return_value = create_manager_with_plugins(plugins)
+    PluginManager.return_value.plugins = plugins
 
     type_mgr = FakeTestType()
     for i in range(8):
-        key = 'T10%i' % i
+        key = "T10%i" % i
         assert type_mgr[key] is plugins[key]
diff --git a/tests/unit/test_pyflakes_codes.py b/tests/unit/test_pyflakes_codes.py
index 77f3e568..526832e1 100644
--- a/tests/unit/test_pyflakes_codes.py
+++ b/tests/unit/test_pyflakes_codes.py
@@ -18,14 +18,14 @@ def test_all_pyflakes_messages_have_flake8_codes_assigned():
 
 def test_undefined_local_code():
     """In pyflakes 2.1.0 this code's string formatting was changed."""
-    src = '''\
+    src = """\
 import sys
 
 def f():
     sys = sys
-'''
+"""
     tree = ast.parse(src)
-    checker = pyflakes_shim.FlakesChecker(tree, (), 't.py')
+    checker = pyflakes_shim.FlakesChecker(tree, (), "t.py")
     message_texts = [s for _, _, s, _ in checker.run()]
     assert message_texts == [
         "F823 local variable 'sys' defined in enclosing scope on line 1 referenced before assignment",  # noqa: E501
diff --git a/tests/unit/test_setuptools_command.py b/tests/unit/test_setuptools_command.py
deleted file mode 100644
index 1c52b2a7..00000000
--- a/tests/unit/test_setuptools_command.py
+++ /dev/null
@@ -1,33 +0,0 @@
-"""Module containing tests for the setuptools command integration."""
-import pytest
-from setuptools import dist
-
-from flake8.main import setuptools_command
-
-
-@pytest.fixture
-def distribution():
-    """Create a setuptools Distribution object."""
-    return dist.Distribution({
-        'name': 'foo',
-        'packages': [
-            'foo',
-            'foo.bar',
-            'foo_biz',
-        ],
-    })
-
-
-@pytest.fixture
-def command(distribution):
-    """Create an instance of Flake8's setuptools command."""
-    return setuptools_command.Flake8(distribution)
-
-
-def test_package_files_removes_submodules(command):
-    """Verify that we collect all package files."""
-    package_files = list(command.package_files())
-    assert sorted(package_files) == [
-        'foo',
-        'foo_biz',
-    ]
diff --git a/tests/unit/test_statistics.py b/tests/unit/test_statistics.py
index 16896f03..66565c31 100644
--- a/tests/unit/test_statistics.py
+++ b/tests/unit/test_statistics.py
@@ -4,19 +4,19 @@
 from flake8 import statistics as stats
 from flake8 import style_guide
 
-DEFAULT_ERROR_CODE = 'E100'
-DEFAULT_FILENAME = 'file.py'
-DEFAULT_TEXT = 'Default text'
+DEFAULT_ERROR_CODE = "E100"
+DEFAULT_FILENAME = "file.py"
+DEFAULT_TEXT = "Default text"
 
 
 def make_error(**kwargs):
     """Create errors with a bunch of default values."""
     return style_guide.Violation(
-        code=kwargs.pop('code', DEFAULT_ERROR_CODE),
-        filename=kwargs.pop('filename', DEFAULT_FILENAME),
-        line_number=kwargs.pop('line_number', 1),
-        column_number=kwargs.pop('column_number', 1),
-        text=kwargs.pop('text', DEFAULT_TEXT),
+        code=kwargs.pop("code", DEFAULT_ERROR_CODE),
+        filename=kwargs.pop("filename", DEFAULT_FILENAME),
+        line_number=kwargs.pop("line_number", 1),
+        column_number=kwargs.pop("column_number", 1),
+        text=kwargs.pop("text", DEFAULT_TEXT),
         physical_line=None,
     )
 
@@ -29,26 +29,29 @@ def test_key_creation():
     assert key.code == DEFAULT_ERROR_CODE
 
 
-@pytest.mark.parametrize('code, filename, args, expected_result', [
-    # Error prefix matches
-    ('E123', 'file000.py', ('E', None), True),
-    ('E123', 'file000.py', ('E1', None), True),
-    ('E123', 'file000.py', ('E12', None), True),
-    ('E123', 'file000.py', ('E123', None), True),
-    # Error prefix and filename match
-    ('E123', 'file000.py', ('E', 'file000.py'), True),
-    ('E123', 'file000.py', ('E1', 'file000.py'), True),
-    ('E123', 'file000.py', ('E12', 'file000.py'), True),
-    ('E123', 'file000.py', ('E123', 'file000.py'), True),
-    # Error prefix does not match
-    ('E123', 'file000.py', ('W', None), False),
-    # Error prefix matches but filename does not
-    ('E123', 'file000.py', ('E', 'file001.py'), False),
-    # Error prefix does not match but filename does
-    ('E123', 'file000.py', ('W', 'file000.py'), False),
-    # Neither error prefix match nor filename
-    ('E123', 'file000.py', ('W', 'file001.py'), False),
-])
+@pytest.mark.parametrize(
+    "code, filename, args, expected_result",
+    [
+        # Error prefix matches
+        ("E123", "file000.py", ("E", None), True),
+        ("E123", "file000.py", ("E1", None), True),
+        ("E123", "file000.py", ("E12", None), True),
+        ("E123", "file000.py", ("E123", None), True),
+        # Error prefix and filename match
+        ("E123", "file000.py", ("E", "file000.py"), True),
+        ("E123", "file000.py", ("E1", "file000.py"), True),
+        ("E123", "file000.py", ("E12", "file000.py"), True),
+        ("E123", "file000.py", ("E123", "file000.py"), True),
+        # Error prefix does not match
+        ("E123", "file000.py", ("W", None), False),
+        # Error prefix matches but filename does not
+        ("E123", "file000.py", ("E", "file001.py"), False),
+        # Error prefix does not match but filename does
+        ("E123", "file000.py", ("W", "file000.py"), False),
+        # Neither error prefix match nor filename
+        ("E123", "file000.py", ("W", "file001.py"), False),
+    ],
+)
 def test_key_matching(code, filename, args, expected_result):
     """Verify Key#matches behaves as we expect with fthe above input."""
     key = stats.Key.create_from(make_error(code=code, filename=filename))
@@ -75,7 +78,7 @@ def test_statistic_increment():
 def test_recording_statistics():
     """Verify that we appropriately create a new Statistic and store it."""
     aggregator = stats.Statistics()
-    assert list(aggregator.statistics_for('E')) == []
+    assert list(aggregator.statistics_for("E")) == []
     aggregator.record(make_error())
     storage = aggregator._store
     for key, value in storage.items():
@@ -88,9 +91,9 @@ def test_recording_statistics():
 def test_statistics_for_single_record():
     """Show we can retrieve the only statistic recorded."""
     aggregator = stats.Statistics()
-    assert list(aggregator.statistics_for('E')) == []
+    assert list(aggregator.statistics_for("E")) == []
     aggregator.record(make_error())
-    statistics = list(aggregator.statistics_for('E'))
+    statistics = list(aggregator.statistics_for("E"))
     assert len(statistics) == 1
     assert isinstance(statistics[0], stats.Statistic)
 
@@ -98,11 +101,11 @@ def test_statistics_for_single_record():
 def test_statistics_for_filters_by_filename():
     """Show we can retrieve the only statistic recorded."""
     aggregator = stats.Statistics()
-    assert list(aggregator.statistics_for('E')) == []
+    assert list(aggregator.statistics_for("E")) == []
     aggregator.record(make_error())
-    aggregator.record(make_error(filename='example.py'))
+    aggregator.record(make_error(filename="example.py"))
 
-    statistics = list(aggregator.statistics_for('E', DEFAULT_FILENAME))
+    statistics = list(aggregator.statistics_for("E", DEFAULT_FILENAME))
     assert len(statistics) == 1
     assert isinstance(statistics[0], stats.Statistic)
 
@@ -111,11 +114,11 @@ def test_statistic_for_retrieves_more_than_one_value():
     """Show this works for more than a couple statistic values."""
     aggregator = stats.Statistics()
     for i in range(50):
-        aggregator.record(make_error(code='E1{:02d}'.format(i)))
-        aggregator.record(make_error(code='W2{:02d}'.format(i)))
+        aggregator.record(make_error(code=f"E1{i:02d}"))
+        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
 
-    statistics = list(aggregator.statistics_for('W22'))
+    statistics = list(aggregator.statistics_for("W22"))
     assert len(statistics) == 10
diff --git a/tests/unit/test_style_guide.py b/tests/unit/test_style_guide.py
index c24472ad..da28355f 100644
--- a/tests/unit/test_style_guide.py
+++ b/tests/unit/test_style_guide.py
@@ -1,7 +1,7 @@
 """Tests for the flake8.style_guide.StyleGuide class."""
 import argparse
+from unittest import mock
 
-import mock
 import pytest
 
 from flake8 import statistics
@@ -12,14 +12,15 @@
 
 def create_options(**kwargs):
     """Create and return an instance of argparse.Namespace."""
-    kwargs.setdefault('select', [])
-    kwargs.setdefault('extended_default_ignore', [])
-    kwargs.setdefault('extended_default_select', [])
-    kwargs.setdefault('ignore', [])
-    kwargs.setdefault('extend_ignore', [])
-    kwargs.setdefault('disable_noqa', False)
-    kwargs.setdefault('enable_extensions', [])
-    kwargs.setdefault('per_file_ignores', [])
+    kwargs.setdefault("select", [])
+    kwargs.setdefault("extended_default_select", [])
+    kwargs.setdefault("extended_default_ignore", [])
+    kwargs.setdefault("extend_select", [])
+    kwargs.setdefault("ignore", [])
+    kwargs.setdefault("extend_ignore", [])
+    kwargs.setdefault("disable_noqa", False)
+    kwargs.setdefault("enable_extensions", [])
+    kwargs.setdefault("per_file_ignores", [])
     return argparse.Namespace(**kwargs)
 
 
@@ -27,13 +28,13 @@ def test_handle_error_does_not_raise_type_errors():
     """Verify that we handle our inputs better."""
     formatter = mock.create_autospec(base.BaseFormatter, instance=True)
     guide = style_guide.StyleGuide(
-        create_options(select=['T111'], ignore=[]),
+        create_options(select=["T111"], ignore=[]),
         formatter=formatter,
         stats=statistics.Statistics(),
     )
 
     assert 1 == guide.handle_error(
-        'T111', 'file.py', 1, None, 'error found', 'a = 1'
+        "T111", "file.py", 1, None, "error found", "a = 1"
     )
 
 
@@ -54,13 +55,16 @@ def test_style_guide_manager():
 ]
 
 
-@pytest.mark.parametrize('style_guide_file,filename,expected', [
-    ("first_file.py", "first_file.py", True),
-    ("first_file.py", "second_file.py", False),
-    ("sub_dir/*.py", "first_file.py", False),
-    ("sub_dir/*.py", "sub_dir/file.py", True),
-    ("sub_dir/*.py", "other_dir/file.py", False),
-])
+@pytest.mark.parametrize(
+    "style_guide_file,filename,expected",
+    [
+        ("first_file.py", "first_file.py", True),
+        ("first_file.py", "second_file.py", False),
+        ("sub_dir/*.py", "first_file.py", False),
+        ("sub_dir/*.py", "sub_dir/file.py", True),
+        ("sub_dir/*.py", "other_dir/file.py", False),
+    ],
+)
 def test_style_guide_applies_to(style_guide_file, filename, expected):
     """Verify that we match a file to its style guide."""
     formatter = mock.create_autospec(base.BaseFormatter, instance=True)
@@ -69,7 +73,8 @@ def test_style_guide_applies_to(style_guide_file, filename, expected):
         options,
         formatter=formatter,
         stats=statistics.Statistics(),
-        filename=style_guide_file)
+        filename=style_guide_file,
+    )
     assert guide.applies_to(filename) is expected
 
 
@@ -80,41 +85,56 @@ def test_style_guide_manager_pre_file_ignores_parsing():
     guide = style_guide.StyleGuideManager(options, formatter=formatter)
     assert len(guide.style_guides) == 5
     expected = [
-        utils.normalize_path(p) for p in [
-            "first_file.py", "second_file.py", "third_file.py", "sub_dir/*",
+        utils.normalize_path(p)
+        for p in [
+            "first_file.py",
+            "second_file.py",
+            "third_file.py",
+            "sub_dir/*",
         ]
     ]
     assert expected == [g.filename for g in guide.style_guides[1:]]
 
 
-@pytest.mark.parametrize('ignores,violation,filename,handle_error_return', [
-    (['E1', 'E2'], 'F401', 'first_file.py', 1),
-    (['E1', 'E2'], 'E121', 'first_file.py', 0),
-    (['E1', 'E2'], 'F401', 'second_file.py', 0),
-    (['E1', 'E2'], 'F401', 'third_file.py', 1),
-    (['E1', 'E2'], 'E311', 'third_file.py', 0),
-    (['E1', 'E2'], 'F401', 'sub_dir/file.py', 0),
-])
-def test_style_guide_manager_pre_file_ignores(ignores, violation, filename,
-                                              handle_error_return):
+@pytest.mark.parametrize(
+    "ignores,violation,filename,handle_error_return",
+    [
+        (["E1", "E2"], "F401", "first_file.py", 1),
+        (["E1", "E2"], "E121", "first_file.py", 0),
+        (["E1", "E2"], "F401", "second_file.py", 0),
+        (["E1", "E2"], "F401", "third_file.py", 1),
+        (["E1", "E2"], "E311", "third_file.py", 0),
+        (["E1", "E2"], "F401", "sub_dir/file.py", 0),
+    ],
+)
+def test_style_guide_manager_pre_file_ignores(
+    ignores, violation, filename, handle_error_return
+):
     """Verify how the StyleGuideManager creates a default style guide."""
     formatter = mock.create_autospec(base.BaseFormatter, instance=True)
-    options = create_options(ignore=ignores,
-                             select=['E', 'F', 'W'],
-                             per_file_ignores=PER_FILE_IGNORES_UNPARSED)
+    options = create_options(
+        ignore=ignores,
+        select=["E", "F", "W"],
+        per_file_ignores=PER_FILE_IGNORES_UNPARSED,
+    )
     guide = style_guide.StyleGuideManager(options, formatter=formatter)
-    assert (guide.handle_error(violation, filename, 1, 1, "Fake text")
-            == handle_error_return)
-
-
-@pytest.mark.parametrize('filename,expected', [
-    ('first_file.py', utils.normalize_path('first_file.py')),
-    ('second_file.py', utils.normalize_path('second_file.py')),
-    ('third_file.py', utils.normalize_path('third_file.py')),
-    ('fourth_file.py', None),
-    ('sub_dir/__init__.py', utils.normalize_path('sub_dir/*')),
-    ('other_dir/__init__.py', None),
-])
+    assert (
+        guide.handle_error(violation, filename, 1, 1, "Fake text")
+        == handle_error_return
+    )
+
+
+@pytest.mark.parametrize(
+    "filename,expected",
+    [
+        ("first_file.py", utils.normalize_path("first_file.py")),
+        ("second_file.py", utils.normalize_path("second_file.py")),
+        ("third_file.py", utils.normalize_path("third_file.py")),
+        ("fourth_file.py", None),
+        ("sub_dir/__init__.py", utils.normalize_path("sub_dir/*")),
+        ("other_dir/__init__.py", None),
+    ],
+)
 def test_style_guide_manager_style_guide_for(filename, expected):
     """Verify the style guide selection function."""
     formatter = mock.create_autospec(base.BaseFormatter, instance=True)
diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py
index 4b89484a..22bb44d3 100644
--- a/tests/unit/test_utils.py
+++ b/tests/unit/test_utils.py
@@ -3,8 +3,8 @@
 import logging
 import os
 import sys
+from unittest import mock
 
-import mock
 import pytest
 
 from flake8 import exceptions
@@ -14,86 +14,92 @@
 RELATIVE_PATHS = ["flake8", "pep8", "pyflakes", "mccabe"]
 
 
-@pytest.mark.parametrize("value,expected", [
-    ("E123,\n\tW234,\n    E206", ["E123", "W234", "E206"]),
-    ("E123,W234,E206", ["E123", "W234", "E206"]),
-    ("E123 W234 E206", ["E123", "W234", "E206"]),
-    ("E123\nW234 E206", ["E123", "W234", "E206"]),
-    ("E123\nW234\nE206", ["E123", "W234", "E206"]),
-    ("E123,W234,E206,", ["E123", "W234", "E206"]),
-    ("E123,W234,E206, ,\n", ["E123", "W234", "E206"]),
-    ("E123,W234,,E206,,", ["E123", "W234", "E206"]),
-    ("E123, W234,, E206,,", ["E123", "W234", "E206"]),
-    ("E123,,W234,,E206,,", ["E123", "W234", "E206"]),
-    ("", []),
-])
+@pytest.mark.parametrize(
+    "value,expected",
+    [
+        ("E123,\n\tW234,\n    E206", ["E123", "W234", "E206"]),
+        ("E123,W234,E206", ["E123", "W234", "E206"]),
+        ("E123 W234 E206", ["E123", "W234", "E206"]),
+        ("E123\nW234 E206", ["E123", "W234", "E206"]),
+        ("E123\nW234\nE206", ["E123", "W234", "E206"]),
+        ("E123,W234,E206,", ["E123", "W234", "E206"]),
+        ("E123,W234,E206, ,\n", ["E123", "W234", "E206"]),
+        ("E123,W234,,E206,,", ["E123", "W234", "E206"]),
+        ("E123, W234,, E206,,", ["E123", "W234", "E206"]),
+        ("E123,,W234,,E206,,", ["E123", "W234", "E206"]),
+        ("", []),
+    ],
+)
 def test_parse_comma_separated_list(value, expected):
     """Verify that similar inputs produce identical outputs."""
     assert utils.parse_comma_separated_list(value) == expected
 
 
 @pytest.mark.parametrize(
-    ('value', 'expected'),
+    ("value", "expected"),
     (
         # empty option configures nothing
-        ('', []), ('   ', []), ('\n\n\n', []),
+        ("", []),
+        ("   ", []),
+        ("\n\n\n", []),
         # basic case
         (
-            'f.py:E123',
-            [('f.py', ['E123'])],
+            "f.py:E123",
+            [("f.py", ["E123"])],
         ),
         # multiple filenames, multiple codes
         (
-            'f.py,g.py:E,F',
-            [('f.py', ['E', 'F']), ('g.py', ['E', 'F'])],
+            "f.py,g.py:E,F",
+            [("f.py", ["E", "F"]), ("g.py", ["E", "F"])],
         ),
         # demonstrate that whitespace is not important around tokens
         (
-            '   f.py  , g.py  : E  , F  ',
-            [('f.py', ['E', 'F']), ('g.py', ['E', 'F'])],
+            "   f.py  , g.py  : E  , F  ",
+            [("f.py", ["E", "F"]), ("g.py", ["E", "F"])],
         ),
         # whitespace can separate groups of configuration
         (
-            'f.py:E g.py:F',
-            [('f.py', ['E']), ('g.py', ['F'])],
+            "f.py:E g.py:F",
+            [("f.py", ["E"]), ("g.py", ["F"])],
         ),
         # newlines can separate groups of configuration
         (
-            'f.py: E\ng.py: F\n',
-            [('f.py', ['E']), ('g.py', ['F'])],
+            "f.py: E\ng.py: F\n",
+            [("f.py", ["E"]), ("g.py", ["F"])],
         ),
         # whitespace can be used in place of commas
         (
-            'f.py g.py: E F',
-            [('f.py', ['E', 'F']), ('g.py', ['E', 'F'])],
+            "f.py g.py: E F",
+            [("f.py", ["E", "F"]), ("g.py", ["E", "F"])],
         ),
         # go ahead, indent your codes
         (
-            'f.py:\n    E,F\ng.py:\n    G,H',
-            [('f.py', ['E', 'F']), ('g.py', ['G', 'H'])],
+            "f.py:\n    E,F\ng.py:\n    G,H",
+            [("f.py", ["E", "F"]), ("g.py", ["G", "H"])],
         ),
         # capitalized filenames are ok too
         (
-            'F.py,G.py: F,G',
-            [('F.py', ['F', 'G']), ('G.py', ['F', 'G'])],
+            "F.py,G.py: F,G",
+            [("F.py", ["F", "G"]), ("G.py", ["F", "G"])],
         ),
         #  it's easier to allow zero filenames or zero codes than forbid it
-        (':E', []), ('f.py:', []),
-        (':E f.py:F', [('f.py', ['F'])]),
-        ('f.py: g.py:F', [('g.py', ['F'])]),
-        ('f.py:E:', []),
-        ('f.py:E.py:', []),
-        ('f.py:Eg.py:F', [('Eg.py', ['F'])]),
+        (":E", []),
+        ("f.py:", []),
+        (":E f.py:F", [("f.py", ["F"])]),
+        ("f.py: g.py:F", [("g.py", ["F"])]),
+        ("f.py:E:", []),
+        ("f.py:E.py:", []),
+        ("f.py:Eg.py:F", [("Eg.py", ["F"])]),
         # sequences are also valid (?)
         (
-            ['f.py:E,F', 'g.py:G,H'],
-            [('f.py', ['E', 'F']), ('g.py', ['G', 'H'])],
+            ["f.py:E,F", "g.py:G,H"],
+            [("f.py", ["E", "F"]), ("g.py", ["G", "H"])],
         ),
         # six-digits codes are allowed
         (
-            'f.py: ABC123',
-            [('f.py', ['ABC123'])],
-        )
+            "f.py: ABC123",
+            [("f.py", ["ABC123"])],
+        ),
     ),
 )
 def test_parse_files_to_codes_mapping(value, expected):
@@ -102,16 +108,19 @@ def test_parse_files_to_codes_mapping(value, expected):
 
 
 @pytest.mark.parametrize(
-    'value',
+    "value",
     (
         # code while looking for filenames
-        'E123', 'f.py,E123', 'f.py E123',
+        "E123",
+        "f.py,E123",
+        "f.py E123",
         # eof while looking for filenames
-        'f.py', 'f.py:E,g.py'
+        "f.py",
+        "f.py:E,g.py"
         # colon while looking for codes
-        'f.py::',
+        "f.py::",
         # no separator between
-        'f.py:E1F1',
+        "f.py:E1F1",
     ),
 )
 def test_invalid_file_list(value):
@@ -120,22 +129,32 @@ def test_invalid_file_list(value):
         utils.parse_files_to_codes_mapping(value)
 
 
-@pytest.mark.parametrize("value,expected", [
-    ("flake8", "flake8"),
-    ("../flake8", os.path.abspath("../flake8")),
-    ("flake8/", os.path.abspath("flake8")),
-])
+@pytest.mark.parametrize(
+    "value,expected",
+    [
+        ("flake8", "flake8"),
+        ("../flake8", os.path.abspath("../flake8")),
+        ("flake8/", os.path.abspath("flake8")),
+    ],
+)
 def test_normalize_path(value, expected):
     """Verify that we normalize paths provided to the tool."""
     assert utils.normalize_path(value) == expected
 
 
-@pytest.mark.parametrize("value,expected", [
-    (["flake8", "pep8", "pyflakes", "mccabe"],
-        ["flake8", "pep8", "pyflakes", "mccabe"]),
-    (["../flake8", "../pep8", "../pyflakes", "../mccabe"],
-        [os.path.abspath("../" + p) for p in RELATIVE_PATHS]),
-])
+@pytest.mark.parametrize(
+    "value,expected",
+    [
+        (
+            ["flake8", "pep8", "pyflakes", "mccabe"],
+            ["flake8", "pep8", "pyflakes", "mccabe"],
+        ),
+        (
+            ["../flake8", "../pep8", "../pyflakes", "../mccabe"],
+            [os.path.abspath(f"../{p}") for p in RELATIVE_PATHS],
+        ),
+    ],
+)
 def test_normalize_paths(value, expected):
     """Verify we normalizes a sequence of paths provided to the tool."""
     assert utils.normalize_paths(value) == expected
@@ -143,19 +162,22 @@ def test_normalize_paths(value, expected):
 
 def test_is_windows_checks_for_nt():
     """Verify that we correctly detect Windows."""
-    with mock.patch.object(os, 'name', 'nt'):
+    with mock.patch.object(os, "name", "nt"):
         assert utils.is_windows() is True
 
-    with mock.patch.object(os, 'name', 'posix'):
+    with mock.patch.object(os, "name", "posix"):
         assert utils.is_windows() is False
 
 
-@pytest.mark.parametrize('filename,patterns,expected', [
-    ('foo.py', [], True),
-    ('foo.py', ['*.pyc'], False),
-    ('foo.pyc', ['*.pyc'], True),
-    ('foo.pyc', ['*.swp', '*.pyc', '*.py'], True),
-])
+@pytest.mark.parametrize(
+    "filename,patterns,expected",
+    [
+        ("foo.py", [], True),
+        ("foo.py", ["*.pyc"], False),
+        ("foo.pyc", ["*.pyc"], True),
+        ("foo.pyc", ["*.swp", "*.pyc", "*.py"], True),
+    ],
+)
 def test_fnmatch(filename, patterns, expected):
     """Verify that our fnmatch wrapper works as expected."""
     assert utils.fnmatch(filename, patterns) is expected
@@ -165,137 +187,146 @@ def test_fnmatch(filename, patterns, expected):
 def files_dir(tmpdir):
     """Create test dir for testing filenames_from."""
     with tmpdir.as_cwd():
-        tmpdir.join('a/b/c.py').ensure()
-        tmpdir.join('a/b/d.py').ensure()
-        tmpdir.join('a/b/e/f.py').ensure()
+        tmpdir.join("a/b/c.py").ensure()
+        tmpdir.join("a/b/d.py").ensure()
+        tmpdir.join("a/b/e/f.py").ensure()
         yield tmpdir
 
 
 def _normpath(s):
-    return s.replace('/', os.sep)
+    return s.replace("/", os.sep)
 
 
 def _normpaths(pths):
     return {_normpath(pth) for pth in pths}
 
 
-@pytest.mark.usefixtures('files_dir')
+@pytest.mark.usefixtures("files_dir")
 def test_filenames_from_a_directory():
     """Verify that filenames_from walks a directory."""
-    filenames = set(utils.filenames_from(_normpath('a/b/')))
+    filenames = set(utils.filenames_from(_normpath("a/b/")))
     # should include all files
-    expected = _normpaths(('a/b/c.py', 'a/b/d.py', 'a/b/e/f.py'))
+    expected = _normpaths(("a/b/c.py", "a/b/d.py", "a/b/e/f.py"))
     assert filenames == expected
 
 
-@pytest.mark.usefixtures('files_dir')
+@pytest.mark.usefixtures("files_dir")
 def test_filenames_from_a_directory_with_a_predicate():
     """Verify that predicates filter filenames_from."""
-    filenames = set(utils.filenames_from(
-        arg=_normpath('a/b/'),
-        predicate=lambda path: path.endswith(_normpath('b/c.py')),
-    ))
+    filenames = set(
+        utils.filenames_from(
+            arg=_normpath("a/b/"),
+            predicate=lambda path: path.endswith(_normpath("b/c.py")),
+        )
+    )
     # should not include c.py
-    expected = _normpaths(('a/b/d.py', 'a/b/e/f.py'))
+    expected = _normpaths(("a/b/d.py", "a/b/e/f.py"))
     assert filenames == expected
 
 
-@pytest.mark.usefixtures('files_dir')
+@pytest.mark.usefixtures("files_dir")
 def test_filenames_from_a_directory_with_a_predicate_from_the_current_dir():
     """Verify that predicates filter filenames_from."""
-    filenames = set(utils.filenames_from(
-        arg=_normpath('./a/b'),
-        predicate=lambda path: path == 'c.py',
-    ))
+    filenames = set(
+        utils.filenames_from(
+            arg=_normpath("./a/b"),
+            predicate=lambda path: path == "c.py",
+        )
+    )
     # none should have matched the predicate so all returned
-    expected = _normpaths(('./a/b/c.py', './a/b/d.py', './a/b/e/f.py'))
+    expected = _normpaths(("./a/b/c.py", "./a/b/d.py", "./a/b/e/f.py"))
     assert filenames == expected
 
 
-@pytest.mark.usefixtures('files_dir')
+@pytest.mark.usefixtures("files_dir")
 def test_filenames_from_a_single_file():
     """Verify that we simply yield that filename."""
-    filenames = set(utils.filenames_from(_normpath('a/b/c.py')))
-    assert filenames == {_normpath('a/b/c.py')}
+    filenames = set(utils.filenames_from(_normpath("a/b/c.py")))
+    assert filenames == {_normpath("a/b/c.py")}
 
 
 def test_filenames_from_a_single_file_does_not_exist():
     """Verify that a passed filename which does not exist is returned back."""
-    filenames = set(utils.filenames_from(_normpath('d/n/e.py')))
-    assert filenames == {_normpath('d/n/e.py')}
+    filenames = set(utils.filenames_from(_normpath("d/n/e.py")))
+    assert filenames == {_normpath("d/n/e.py")}
 
 
 def test_filenames_from_exclude_doesnt_exclude_directory_names(tmpdir):
     """Verify that we don't greedily exclude subdirs."""
-    tmpdir.join('1').ensure_dir().join('dont_return_me.py').ensure()
-    tmpdir.join('2').join('1').ensure_dir().join('return_me.py').ensure()
-    exclude = [tmpdir.join('1').strpath]
+    tmpdir.join("1").ensure_dir().join("dont_return_me.py").ensure()
+    tmpdir.join("2").join("1").ensure_dir().join("return_me.py").ensure()
+    exclude = [tmpdir.join("1").strpath]
 
     # This acts similar to src.flake8.checker.is_path_excluded
     def predicate(pth):
         return utils.fnmatch(os.path.abspath(pth), exclude)
 
     with tmpdir.as_cwd():
-        filenames = list(utils.filenames_from('.', predicate))
-    assert filenames == [os.path.join('.', '2', '1', 'return_me.py')]
+        filenames = list(utils.filenames_from(".", predicate))
+    assert filenames == [os.path.join(".", "2", "1", "return_me.py")]
 
 
 def test_parameters_for_class_plugin():
     """Verify that we can retrieve the parameters for a class plugin."""
-    class FakeCheck(object):
+
+    class FakeCheck:
         def __init__(self, tree):
             raise NotImplementedError
 
-    plugin = plugin_manager.Plugin('plugin-name', object())
+    plugin = plugin_manager.Plugin("plugin-name", object())
     plugin._plugin = FakeCheck
-    assert utils.parameters_for(plugin) == {'tree': True}
+    assert utils.parameters_for(plugin) == {"tree": True}
 
 
 def test_parameters_for_function_plugin():
     """Verify that we retrieve the parameters for a function plugin."""
+
     def fake_plugin(physical_line, self, tree, optional=None):
         raise NotImplementedError
 
-    plugin = plugin_manager.Plugin('plugin-name', object())
+    plugin = plugin_manager.Plugin("plugin-name", object())
     plugin._plugin = fake_plugin
     assert utils.parameters_for(plugin) == {
-        'physical_line': True,
-        'self': True,
-        'tree': True,
-        'optional': False,
+        "physical_line": True,
+        "self": True,
+        "tree": True,
+        "optional": False,
     }
 
 
 def read_diff_file(filename):
     """Read the diff file in its entirety."""
-    with open(filename, 'r') as fd:
+    with open(filename) as fd:
         content = fd.read()
     return content
 
 
-SINGLE_FILE_DIFF = read_diff_file('tests/fixtures/diffs/single_file_diff')
+SINGLE_FILE_DIFF = read_diff_file("tests/fixtures/diffs/single_file_diff")
 SINGLE_FILE_INFO = {
-    'flake8/utils.py': set(range(75, 83)).union(set(range(84, 94))),
+    "flake8/utils.py": set(range(75, 83)).union(set(range(84, 94))),
 }
-TWO_FILE_DIFF = read_diff_file('tests/fixtures/diffs/two_file_diff')
+TWO_FILE_DIFF = read_diff_file("tests/fixtures/diffs/two_file_diff")
 TWO_FILE_INFO = {
-    'flake8/utils.py': set(range(75, 83)).union(set(range(84, 94))),
-    'tests/unit/test_utils.py': set(range(115, 128)),
+    "flake8/utils.py": set(range(75, 83)).union(set(range(84, 94))),
+    "tests/unit/test_utils.py": set(range(115, 128)),
 }
-MULTI_FILE_DIFF = read_diff_file('tests/fixtures/diffs/multi_file_diff')
+MULTI_FILE_DIFF = read_diff_file("tests/fixtures/diffs/multi_file_diff")
 MULTI_FILE_INFO = {
-    'flake8/utils.py': set(range(75, 83)).union(set(range(84, 94))),
-    'tests/unit/test_utils.py': set(range(115, 129)),
-    'tests/fixtures/diffs/single_file_diff': set(range(1, 28)),
-    'tests/fixtures/diffs/two_file_diff': set(range(1, 46)),
+    "flake8/utils.py": set(range(75, 83)).union(set(range(84, 94))),
+    "tests/unit/test_utils.py": set(range(115, 129)),
+    "tests/fixtures/diffs/single_file_diff": set(range(1, 28)),
+    "tests/fixtures/diffs/two_file_diff": set(range(1, 46)),
 }
 
 
-@pytest.mark.parametrize("diff, parsed_diff", [
-    (SINGLE_FILE_DIFF, SINGLE_FILE_INFO),
-    (TWO_FILE_DIFF, TWO_FILE_INFO),
-    (MULTI_FILE_DIFF, MULTI_FILE_INFO),
-])
+@pytest.mark.parametrize(
+    "diff, parsed_diff",
+    [
+        (SINGLE_FILE_DIFF, SINGLE_FILE_INFO),
+        (TWO_FILE_DIFF, TWO_FILE_INFO),
+        (MULTI_FILE_DIFF, MULTI_FILE_INFO),
+    ],
+)
 def test_parse_unified_diff(diff, parsed_diff):
     """Verify that what we parse from a diff matches expectations."""
     assert utils.parse_unified_diff(diff) == parsed_diff
@@ -304,13 +335,19 @@ def test_parse_unified_diff(diff, parsed_diff):
 def test_matches_filename_for_excluding_dotfiles():
     """Verify that `.` and `..` are not matched by `.*`."""
     logger = logging.Logger(__name__)
-    assert not utils.matches_filename('.', ('.*',), '', logger)
-    assert not utils.matches_filename('..', ('.*',), '', logger)
+    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():
     """Ensure that stdin is normalized from crlf to lf."""
-    stdin = io.TextIOWrapper(io.BytesIO(b'1\r\n2\r\n'), 'UTF-8')
-    with mock.patch.object(sys, 'stdin', stdin):
-        assert utils.stdin_get_value.__wrapped__() == '1\n2\n'
+    stdin = io.TextIOWrapper(io.BytesIO(b"1\r\n2\r\n"), "UTF-8")
+    with mock.patch.object(sys, "stdin", stdin):
+        assert utils.stdin_get_value.__wrapped__() == "1\n2\n"
+
+
+def test_stdin_unknown_coding_token():
+    """Ensure we produce source even for unknown encodings."""
+    stdin = io.TextIOWrapper(io.BytesIO(b"# coding: unknown\n"), "UTF-8")
+    with mock.patch.object(sys, "stdin", stdin):
+        assert utils.stdin_get_value.__wrapped__() == "# coding: unknown\n"
diff --git a/tests/unit/test_violation.py b/tests/unit/test_violation.py
index a3a56f03..b9cf1a3a 100644
--- a/tests/unit/test_violation.py
+++ b/tests/unit/test_violation.py
@@ -1,65 +1,79 @@
 """Tests for the flake8.style_guide.Violation class."""
-import mock
+from unittest import mock
+
 import pytest
 
 from flake8 import style_guide
 
 
-@pytest.mark.parametrize('error_code,physical_line,expected_result', [
-    ('E111', 'a = 1', False),
-    ('E121', 'a = 1  # noqa: E111', False),
-    ('E121', 'a = 1  # noqa: E111,W123,F821', False),
-    ('E111', 'a = 1  # noqa: E111,W123,F821', True),
-    ('W123', 'a = 1  # noqa: E111,W123,F821', True),
-    ('W123', 'a = 1  # noqa: E111, W123,F821', True),
-    ('E111', 'a = 1  # noqa: E11,W123,F821', True),
-    ('E121', 'a = 1  # noqa:E111,W123,F821', False),
-    ('E111', 'a = 1  # noqa:E111,W123,F821', True),
-    ('W123', 'a = 1  # noqa:E111,W123,F821', True),
-    ('W123', 'a = 1  # noqa:E111, W123,F821', True),
-    ('E111', 'a = 1  # noqa:E11,W123,F821', True),
-    ('E111', 'a = 1  # noqa, analysis:ignore', True),
-    ('E111', 'a = 1  # noqa analysis:ignore', True),
-    ('E111', 'a = 1  # noqa - We do not care', True),
-    ('E111', 'a = 1  # noqa: We do not care', True),
-    ('E111', 'a = 1  # noqa:We do not care', True),
-    ('ABC123', 'a = 1  # noqa: ABC123', True),
-    ('E111', 'a = 1  # noqa: ABC123', False),
-    ('ABC123', 'a = 1  # noqa: ABC124', False),
-])
+@pytest.mark.parametrize(
+    "error_code,physical_line,expected_result",
+    [
+        ("E111", "a = 1", False),
+        ("E121", "a = 1  # noqa: E111", False),
+        ("E121", "a = 1  # noqa: E111,W123,F821", False),
+        ("E111", "a = 1  # noqa: E111,W123,F821", True),
+        ("W123", "a = 1  # noqa: E111,W123,F821", True),
+        ("W123", "a = 1  # noqa: E111, W123,F821", True),
+        ("E111", "a = 1  # noqa: E11,W123,F821", True),
+        ("E121", "a = 1  # noqa:E111,W123,F821", False),
+        ("E111", "a = 1  # noqa:E111,W123,F821", True),
+        ("W123", "a = 1  # noqa:E111,W123,F821", True),
+        ("W123", "a = 1  # noqa:E111, W123,F821", True),
+        ("E111", "a = 1  # noqa:E11,W123,F821", True),
+        ("E111", "a = 1  # noqa, analysis:ignore", True),
+        ("E111", "a = 1  # noqa analysis:ignore", True),
+        ("E111", "a = 1  # noqa - We do not care", True),
+        ("E111", "a = 1  # noqa: We do not care", True),
+        ("E111", "a = 1  # noqa:We do not care", True),
+        ("ABC123", "a = 1  # noqa: ABC123", True),
+        ("E111", "a = 1  # noqa: ABC123", False),
+        ("ABC123", "a = 1  # noqa: ABC124", False),
+    ],
+)
 def test_is_inline_ignored(error_code, physical_line, expected_result):
     """Verify that we detect inline usage of ``# noqa``."""
     error = style_guide.Violation(
-        error_code, 'filename.py', 1, 1, 'error text', None)
+        error_code, "filename.py", 1, 1, "error text", None
+    )
     # We want `None` to be passed as the physical line so we actually use our
     # monkey-patched linecache.getline value.
 
-    with mock.patch('linecache.getline', return_value=physical_line):
+    with mock.patch("linecache.getline", return_value=physical_line):
         assert error.is_inline_ignored(False) is expected_result
 
 
 def test_disable_is_inline_ignored():
     """Verify that is_inline_ignored exits immediately if disabling NoQA."""
     error = style_guide.Violation(
-        'E121', 'filename.py', 1, 1, 'error text', 'line')
+        "E121", "filename.py", 1, 1, "error text", "line"
+    )
 
-    with mock.patch('linecache.getline') as getline:
+    with mock.patch("linecache.getline") as getline:
         assert error.is_inline_ignored(True) is False
 
     assert getline.called is False
 
 
-@pytest.mark.parametrize('violation_file,violation_line,diff,expected', [
-    ('file.py', 10, {}, True),
-    ('file.py', 1, {'file.py': range(1, 2)}, True),
-    ('file.py', 10, {'file.py': range(1, 2)}, False),
-    ('file.py', 1, {'other.py': range(1, 2)}, False),
-    ('file.py', 10, {'other.py': range(1, 2)}, False),
-])
+@pytest.mark.parametrize(
+    "violation_file,violation_line,diff,expected",
+    [
+        ("file.py", 10, {}, True),
+        ("file.py", 1, {"file.py": range(1, 2)}, True),
+        ("file.py", 10, {"file.py": range(1, 2)}, False),
+        ("file.py", 1, {"other.py": range(1, 2)}, False),
+        ("file.py", 10, {"other.py": range(1, 2)}, False),
+    ],
+)
 def test_violation_is_in_diff(violation_file, violation_line, diff, expected):
     """Verify that we find violations within a diff."""
     violation = style_guide.Violation(
-        'E001', violation_file, violation_line, 1, 'warning', 'line',
+        "E001",
+        violation_file,
+        violation_line,
+        1,
+        "warning",
+        "line",
     )
 
     assert violation.is_in(diff) is expected
diff --git a/tox.ini b/tox.ini
index 85423ec3..246f914a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,10 +1,9 @@
 [tox]
 minversion=2.3.1
-envlist = py27,py35,py36,py37,py38,flake8,linters,docs
+envlist = py36,py37,py38,flake8,linters,docs
 
 [testenv]
 deps =
-    mock>=2.0.0
     pytest!=3.0.5,!=5.2.3
     coverage
 commands =
@@ -14,14 +13,8 @@ commands =
     # ensure 100% coverage of tests
     coverage report --fail-under 100 --include tests/*
 
-[testenv:venv]
-deps =
-    .
-commands = {posargs}
-
-# Dogfood our current master version
+# Dogfood our current main version
 [testenv:dogfood]
-basepython = python3
 skip_install = true
 deps =
     wheel
@@ -33,20 +26,17 @@ commands =
 
 # Linters
 [testenv:flake8]
-basepython = python3
 skip_install = true
 deps =
     flake8
     flake8-bugbear
     flake8-docstrings>=1.3.1
-    flake8-import-order>=0.9
     flake8-typing-imports>=1.1
     pep8-naming
 commands =
     flake8 src/flake8/ tests/ setup.py
 
 [testenv:pylint]
-basepython = python3
 skip_install = true
 deps =
     pyflakes
@@ -55,7 +45,6 @@ commands =
     pylint src/flake8
 
 [testenv:doc8]
-basepython = python3
 skip_install = true
 deps =
     sphinx
@@ -64,14 +53,12 @@ commands =
     doc8 docs/source/
 
 [testenv:pre-commit]
-basepython = python3
 skip_install = true
 deps = pre-commit
 commands =
     pre-commit run --all-files --show-diff-on-failure
 
 [testenv:bandit]
-basepython = python3
 skip_install = true
 deps =
     bandit
@@ -79,7 +66,6 @@ commands =
     bandit -r src/flake8/ -c .bandit.yml
 
 [testenv:linters]
-basepython = python3
 skip_install = true
 deps =
     {[testenv:flake8]deps}
@@ -96,7 +82,6 @@ commands =
 
 # Documentation
 [testenv:docs]
-basepython = python3
 deps =
     -rdocs/source/requirements.txt
 commands =
@@ -104,7 +89,6 @@ commands =
     sphinx-build -E -W -c docs/source/ -b man docs/source/ docs/build/man
 
 [testenv:serve-docs]
-basepython = python3
 skip_install = true
 changedir = docs/build/html
 deps =
@@ -112,7 +96,6 @@ commands =
     python -m http.server {posargs}
 
 [testenv:readme]
-basepython = python3
 deps =
     readme_renderer
 commands =
@@ -120,7 +103,6 @@ commands =
 
 # Release tooling
 [testenv:build]
-basepython = python3
 skip_install = true
 deps =
     wheel
@@ -129,7 +111,6 @@ commands =
     python setup.py -q sdist bdist_wheel
 
 [testenv:release]
-basepython = python3
 skip_install = true
 deps =
     {[testenv:build]deps}
@@ -145,7 +126,7 @@ commands =
 # defaults to selecting all other errors so we do not need select=E,F,W,I,D
 # Once Flake8 3.0 is released and in a good state, we can use both and it will
 # work well \o/
-ignore = D203, W503, E203
+ignore = D203, W503, E203, N818
 exclude =
     .tox,
     .git,
@@ -159,5 +140,3 @@ exclude =
     .cache,
     .eggs
 max-complexity = 10
-import-order-style = google
-application-import-names = flake8