Source code for wemake_python_styleguide.checker

# -*- coding: utf-8 -*-

"""
Entry point to the app.

Represents a :term:`checker` business entity.
There's only a single checker instance
that runs a lot of :term:`visitors <visitor>`.

.. mermaid::
   :caption: Checker relation with visitors.

    graph TD
        C1[Checker] --> V1[Visitor 1]
        C1[Checker] --> V2[Visitor 2]
        C1[Checker] --> VN[Visitor N]

That's how all ``flake8`` plugins work:

.. mermaid::
   :caption: ``flake8`` API calls order.

    graph LR
        F1[flake8] --> F2[add_options]
        F2         --> F3[parse_options]
        F3         --> F4[__init__]
        F4	       --> F5[run]

.. _checker:

Checker API
-----------

.. autoclass:: Checker
   :no-undoc-members:
   :exclude-members: name, version, visitors, _run_checks
   :special-members: __init__

"""

import ast
import tokenize
import traceback
from typing import ClassVar, Iterator, Sequence, Type

from flake8.options.manager import OptionManager
from typing_extensions import final

from wemake_python_styleguide import constants, types
from wemake_python_styleguide import version as pkg_version
from wemake_python_styleguide.options.config import Configuration
from wemake_python_styleguide.options.validation import validate_options
from wemake_python_styleguide.presets.types import file_tokens as tokens_preset
from wemake_python_styleguide.presets.types import filename as filename_preset
from wemake_python_styleguide.presets.types import tree as tree_preset
from wemake_python_styleguide.transformations.ast_tree import transform
from wemake_python_styleguide.visitors import base

VisitorClass = Type[base.BaseVisitor]


[docs]@final class Checker(object): """ Implementation of :term:`checker`. See also: http://flake8.pycqa.org/en/latest/plugin-development/index.html Attributes: name: required by the ``flake8`` API, should match the package name. version: required by the ``flake8`` API, defined in the packaging file. config: custom configuration object used to provide and parse options: :class:`wemake_python_styleguide.options.config.Configuration`. options: option structure passed by ``flake8``: :class:`wemake_python_styleguide.types.ConfigurationOptions`. visitors: :term:`preset` of visitors that are run by this checker. """ name: ClassVar[str] = pkg_version.pkg_name version: ClassVar[str] = pkg_version.pkg_version options: types.ConfigurationOptions config = Configuration() _visitors: ClassVar[Sequence[VisitorClass]] = ( *filename_preset.PRESET, *tree_preset.PRESET, *tokens_preset.PRESET, )
[docs] def __init__( self, tree: ast.AST, file_tokens: Sequence[tokenize.TokenInfo], filename: str = constants.STDIN, ) -> None: """ Creates new checker instance. These parameter names should not be changed. ``flake8`` has special API that passes concrete parameters to the plugins that ask for them. ``flake8`` also decides how to execute this plugin based on its parameters. This one is executed once per module. Arguments: tree: ``ast`` parsed by ``flake8``. Differs from ``ast.parse`` since it is mutated by multiple ``flake8`` plugins. Why mutated? Since it is really expensive to copy all ``ast`` information in terms of memory. file_tokens: ``tokenize.tokenize`` parsed file tokens. filename: module file name, might be empty if piping is used. """ self.tree = transform(tree) self.filename = filename self.file_tokens = file_tokens
[docs] @classmethod def add_options(cls, parser: OptionManager) -> None: """ ``flake8`` api method to register new plugin options. See :class:`wemake_python_styleguide.options.config.Configuration` docs for detailed options reference. Arguments: parser: ``flake8`` option parser instance. """ cls.config.register_options(parser)
[docs] @classmethod def parse_options(cls, options: types.ConfigurationOptions) -> None: """Parses registered options for providing them to each visitor.""" cls.options = validate_options(options)
[docs] def run(self) -> Iterator[types.CheckResult]: """ Runs the checker. This method is used by ``flake8`` API. It is executed after all configuration is parsed. Yields: Violations that were found by the passed visitors. """ yield from self._run_checks(self._visitors)
def _run_checks( self, visitors: Sequence[VisitorClass], ) -> Iterator[types.CheckResult]: """Runs all passed visitors one by one.""" for visitor_class in visitors: visitor = visitor_class.from_checker(self) try: visitor.run() except Exception: # In case we fail misserably, we want users to see at # least something! Full stack trace # and some rules that still work. print(traceback.format_exc()) # noqa: T001 for error in visitor.violations: yield (*error.node_items(), type(self))