Skip to content

Latest commit

 

History

History
246 lines (171 loc) · 6.11 KB

README.md

File metadata and controls

246 lines (171 loc) · 6.11 KB

flake8-plugin-utils

pypi Python: 3.6+ Downloads Build Status Code coverage License: MIT Code style: black

The package provides base classes and utils for flake8 plugin writing.

Installation

pip install flake8-plugin-utils

Example

Write simple plugin

from flake8_plugin_utils import Error, Visitor, Plugin

class MyError(Error):
    code = 'X100'
    message = 'my error'

class MyVisitor(Visitor):
    def visit_ClassDef(self, node):
        self.error_from_node(MyError, node)

class MyPlugin(Plugin):
    name = 'MyPlugin'
    version = '0.1.0'
    visitors = [MyVisitor]

and test it with pytest

from flake8_plugin_utils import assert_error, assert_not_error

def test_code_with_error():
    assert_error(MyVisitor, 'class Y: pass', MyError)

def test_code_without_error():
    assert_not_error(MyVisitor, 'x = 1')

Configuration

To add configuration to a plugin, do the following:

  1. Implement classmethod add_options in your plugin class, as per the flake8 docs.
  2. Override classmethod parse_options_to_config in your plugin class to return any object holding the options you need.
  3. If you need a custom __init__ for your visitor, make sure it accepts a keyword argument named config and pass it to super().__init__
  4. Use self.config in visitor code.

Example:

from flake8_plugin_utils import Error, Visitor, Plugin, assert_error

class MyError(Error):
    code = 'X100'
    message = 'my error with {thing}'

class MyConfig:
    def __init__(self, config_option):
        self.config_option = config_option

class MyVisitorWithConfig(Visitor):
    def visit_ClassDef(self, node):
        self.error_from_node(
            MyError, node, thing=f'{node.name} {self.config.config_option}'
        )

class MyPluginWithConfig(Plugin):
    name = 'MyPluginWithConfig'
    version = '0.0.1'
    visitors = [MyVisitorWithConfig]

    @classmethod
    def add_options(cls, options_manager):
        options_manager.add_option('--config_option', parse_from_config=True, ...)

    @classmethod
    def parse_options_to_config(cls, option_manager, options, args):
        return MyConfig(config_option=options.config_option)


def test_code_with_error():
    assert_error(
        MyVisitorWithConfig,
        'class Y: pass',
        MyError,
        config=MyConfig(config_option='123'),
        thing='Y 123',
    )

Formatting

Your Errors can take formatting arguments in their message:

from flake8_plugin_utils import Error, Visitor, assert_error

class MyFormattedError(Error):
    code = 'X101'
    message = 'my error with {thing}'

class MyFormattedVisitor(Visitor):
    def visit_ClassDef(self, node):
        self.error_from_node(MyFormattedError, node, thing=node.name)

def test_code_with_error():
    assert_error(
        MyFormattedVisitor,
        'class Y: pass',
        MyFormattedError,
        thing='Y',
    )

Usage with typing/mypy

The Plugin and Visitor classes are generic with the config class as type parameter. If your plugin does not have any config, inherit it from Plugin[None] and the visitors from Visitor[None]. Otherwise, use the config class as the type parameter (e.g. Plugin[MyConfig] and Visitor[MyConfig] in the above example).

Utility functions

  • assert_error, assert_not_error Utilities for testing visitors (see examples above).

  • is_true, is_false, is_none Convenience functions to check if an AST node represents a True/False/None value.

  • check_equivalent_nodes Checks if two given AST nodes are equivalent. The nodes are considered equivalent in the following cases:

    • dicts -- if they contain same key-value pairs, possibly in different order, with duplicates and **expansions taken into account
    • sets -- if they contain same elements, possibly in different order, with duplicates taken into account
    • anything else -- if they represent the same AST, regardless of formatting (with any dicts in sets inside checked according to the rules above)

For developers

Show help

make help

Create venv and install deps

make init

Install git precommit hook

make precommit

Run linters, autoformat, tests etc.

make pretty lint test

Bump new version

make bump_major
make bump_minor
make bump_patch

Change Log

Unreleased

  • ...

1.3.3 - 2022-01-14

  • add py.typed file (#78)

1.3.2 - 2021-05-05

  • Drop noqa detection (#56)
  • docs: Add help for Makefile

1.3.1 - 2020-08-06

  • Fix handling of encoding when loading files (#37)

1.3.0 - 2020-03-26

  • add check_equivalent_nodes utility function

1.2.0 - 2020-03-06

  • add config argument to assert_error and assert_not_error

1.1.1 - 2020-03-02

  • ignore encoding errors when reading strings for noqa validation

1.1.0 - 2020-03-01

  • add ability for plugins to parse and use configuration NB: this change breaks type-checking if you use typing/mypy. Change your code to inherit from Plugin[None] and Visitor[None] to fix.

1.0.0 - 2019-05-23

  • add message formatting to Error

0.2.1 - 2019-04-01

  • don`t strip before src dedent in _error_from_src
  • add is_none, is_true, is_false util functions

0.2.0 - 2019.02.21

  • add assert methods

0.1.0 - 2019.02.09

  • initial