Tutorial¶
When you want to force someone to write the code the way you want: you need to create a rule for that.
There are multiple options of how this can be done. This guide will walk through all possible cases and cover every decision path.
Deciding what exactly to write¶
The most important thing is the question: what kind of rule do you want to create?
Depending on the answer you can end up with either:
Creating a new
flake8
pluginCreating a pair of new visitor and violation inside this plugin, and some checking logic to find problems with your code
Just a new violation and checking logic to find problems with your code
What does it depend on internally?
Writing new plugin¶
First of all, you have to decide:
Are you writing a separate plugin and adding it as a dependency?
Are you writing a built-in extension to this styleguide?
How to make a decision?
Will this plugin be useful to other developers without this styleguide?¶
If so, it would be wise to create a separate flake8
plugin.
Then you can add newly created plugin as a dependency.
Our rules do not make any sense without each other.
It is also useful when you try to wrap an existing tool into flake8
API.
Real world examples of tools that are useful by them self:
Can this plugin be used with the existing checker?¶
flake8
has a very strict API about plugins.
Here are some problems that you may encounter:
Some plugins are called once per file, some are called once per line
Plugins should define clear
violation code
/checker
relationIt is impossible to use the same letter violation codes for several checkers
So, if you want a plugin to work with each logical line - you have to create a custom plugin.
Real world examples of plugins unsuitable for this checker:
Is this rule out off scope?¶
There are awesome tools that cannot be added
because they are just simply out of scope.
This means that they cover very specific case or technology
and not just good-old python
.
Real world examples of plugins that are out of scope:
All these plugins should be installed individually to the end-user dependencies. And only when user really want it. So, it is up to the user to decide.
And these plugins while being awesome won’t be added to our project at all.
Conclusion¶
If you said “yes” to any of these question - write a plugin. Then possibly add it as a dependency to this project.
Writing new visitor¶
If you are still willing to write a builtin extension to this project, you will have to write a violation and/or visitor.
First of all, you have to decide what base class do you want to use?
There are several possibilities:
classDiagram ABC <|-- BaseFilenameVisitor ABC <|-- BaseViolation ABC <|-- BaseVisitor BaseVisitor <|-- BaseFilenameVisitor BaseVisitor <|-- BaseNodeTokenVisitor BaseVisitor <|-- BaseNodeVisitor BaseVisitor <|-- BaseTokenVisitor Collection <|-- Sequence NodeVisitor <|-- BaseNodeTokenVisitor NodeVisitor <|-- BaseNodeVisitor Reversible <|-- Sequence
When to choose what base class? Imagine that you have several ideas in mind:
I want to lint module names not to contain numbers
I want to lint code not to contain number
3
I want to lint code to disallow multiplication of exactly two number
Each of these tasks will require different approaches.
Will require to subclass a filename-based visitor
Will require to subclass a
tokenize
-based visitorWill require to subclass a
ast
-based visitor
How to differ these cases by yourself?
You need to read though the docs of
ast
andtokenize
modulesYou can have a look at the existing visitors
But, you might not want to write a new visitor. You can reuse existing ones and write only a violation and checking logic.
Technical documentation about the Visitors API is available.
Writing new violation¶
The only thing you should care about is to select the correct base class for new violation.
classDiagram ABC <|-- BaseViolation BaseViolation <|-- SimpleViolation BaseViolation <|-- TokenizeViolation BaseViolation <|-- _BaseASTViolation Enum <|-- ViolationPostfixes _BaseASTViolation <|-- ASTViolation _BaseASTViolation <|-- MaybeASTViolation
It only depends on already selected visitor type, so you won’t have to make this decision twice.
Technical documentation about the Violations API is available.
Writing business logic¶
When you will have your visitor and violation
it will be required to actually write
some logic to raise a violation
from visitor
.
We do this inside the visitor
,
but we create protected methods and place logic there.
Consider this example:
class WrongComprehensionVisitor(BaseNodeVisitor):
_max_ifs = 1
def _check_ifs(self, node: ast.comprehension) -> None:
if len(node.ifs) > self._max_ifs:
# This will restrict to have more than 1 `if`
# in your comprehensions:
self.add_violation(MultipleIfsInComprehensionViolation(node))
def visit_comprehension(self, node: ast.comprehension) -> None:
self._check_ifs(node)
self.generic_visit(node)
You may also end up using the same logic over and over again.
In this case we can decouple it and move to logics/
package.
Then it would be easy to reuse something.
Writing tests¶
Writing end-to-end tests¶
In end-to-end tests we check that our visitor, violation and business logic work correctly together all the way from flake8 config file to its output.
To check all supported violations, a module containing code which
raises them: noqa.py
. It is for all
possible violations.
The next thing is test itself which should reside in
tests/test_checker/test_noqa.py
module. The main test functions are
written already, so probably the only thing to do is to put the violation
code into either SHOULD_BE_RAISED
.