Linter Bears¶
Welcome. This tutorial aims to show you how to use the Lint class in order to integrate linters in your bears.
Why is This Useful?¶
A lot of programming languages already have linters implemented, so if your project uses a language that does not already have a linter Bear you might need to implement it on your own. Don’t worry, it’s easy!
What do we Need?¶
First of all, we need the linter that we are going to use. In this tutorial we will build the HTMLTutorialBear so we need an html linter. This one will do. Since it is a python package we can go ahead and install it with
$ pip install html-linter
Writing the Bear¶
Since we are going to use the Lint class we should go ahead and import it together with LocalBear (all bears that handle only one file inherit from LocalBear). Also we will go ahead and write the class head. It should inherit both LocalBear and Lint.
from coalib.bearlib.abstractions.Lint import Lint
from coalib.bears.LocalBear import LocalBear
class HTMLTutorialBear(LocalBear, Lint):
To make our bear use a linter we will have to overwrite some of the
predefined values of the Lint class. Some of the most important are
executable
and output_regex
.
We use executable
to specify the linter executable. In our case it would
be
executable = 'html_lint.py'
The output_regex
is used to group parts of the output (such as lines
,
columns
, severity
and message
) so it can be used by the Lint
class to yield Results (more on communicating with the user
Writing Bears).
In order to figure out the output_regex
we have to first see how the
linter output looks. I will use this file as sample.html
<html>
<body>
<h1>Hello, world!</h1>
</body>
</html>
Now we can run
$ html_lint.py sample.html
1:1: Info: Optional Tags: Omit optional tags (optional): You may remove the opening "html" tag.
2:3: Info: Optional Tags: Omit optional tags (optional): You may remove the opening "body" tag.
4:3: Info: Optional Tags: Omit optional tags (optional): You may remove the closing "body" tag.
5:1: Info: Optional Tags: Omit optional tags (optional): You may remove the closing "html" tag.
Our output_regex
should look like this
(line):(column): (severity): (message)
Or in python regex
(?P<line>\d+):(?P<column>\d+):\s(?P<severity>Error|Warning|Info):\s(?P<message>.+)
Now that we found out our output_regex
(we might want to test it just
to be sure, https://regex101.com/#python is a great tool for this purpose).
We can now compile and add our regex using the re
python library like so
(the regex was split into 2 lines to focus on readability)
import re
from coalib.bearlib.abstractions.Lint import Lint
from coalib.bears.LocalBear import LocalBear
class HTMLTutorialBear(LocalBear, Lint):
executable = 'html_lint.py'
output_regex = re.compile(
r'(?P<line>\d+):(?P<column>\d+):\s'
r'(?P<severity>Error|Warning|Info):\s(?P<message>.+)'
)
coala uses three types of severities
- INFO
- NORMAL
- MAJOR
which are defined in coalib.results.RESULT_SEVERITY
. In order to use
the severity group from the regex we will first have to map the output
severities (as seen in the linter and as used in the regex above
Info
, Warning
, Error
) to the defined coala severities
(above stated INFO
, NORMAL
, MAJOR
).
Luckily for us the Lint class provides us with the severity_map
property. severity_map
is just a dictionary of strings that
should be mapped to the define coala severities. Let’s go ahead and import
coalib.results.RESULT_SEVERITY
and write our severity_map
. Our code
could look like this
import re
from coalib.bearlib.abstractions.Lint import Lint
from coalib.bears.LocalBear import LocalBear
from coalib.results.RESULT_SEVERITY import RESULT_SEVERITY
class HTMLTutorialBear(LocalBear, Lint):
executable = 'html_lint.py'
output_regex = re.compile(
r'(?P<line>\d+):(?P<column>\d+):\s'
r'(?P<severity>Error|Warning|Info):\s(?P<message>.+)'
)
severity_map = {
"Info": RESULT_SEVERITY.INFO,
"Warning": RESULT_SEVERITY.NORMAL,
"Error": RESULT_SEVERITY.MAJOR
}
As with every other bear (see Writing Bears) we have to define our run method.
def run(self, filename, file):
return self.lint(filename)
And that should be enough. The lint() method of the Lint class will do the rest for us.
We can test our bear like this
$ coala --bear-dirs=. --bears=HTMLTutorialBear --files=sample.html
Note
In order for the above command to work we should have 2 files in
our current dir: HTMLTutorialBear.py
and our sample.html
.
Naming is very important in coala. coala will look for bears
by their filename and display them based on their
classname.
Adding Settings to our Bear¶
If we run
$ html_lint.py -h
We can see that there is a --disable
option which lets us disable some
checks. Let’s add that functionality to our bear.
First of all we have to import the setting that we are going to use from
coalib. Since --disable
needs a comma separated list we can use a list
to keep our options. For that we will import typed_list
like so
from coalib.settings.Setting import typed_list
typed_list(item_type)
is a function that converts the given input
(which the user will pass to the Bear as a setting) into a list of
items and afterwards will apply a conversion to type item_type
to each
item in the list (you can also use basic types like int
, bool
, etc.
see Writing Bears)
Next, we have to add our setting as a parameter for the run()
method
of our bear.
We will give the param a sugestive name like htmllint_ignore
.
def run(self,
filename,
file,
htmllint_ignore: typed_list(str)=[]):
'''
Checks the code with `html_lint.py` on each file separately.
:param htmllint_ignore: List of checkers to ignore.
'''
Note
The documentation of the param is parsed by coala and it will be used as help to the user for that specific setting.
The last thing we need to do is join the strings in the html_ignore
,
append them to --disable=
and add it as an argument. There are alot
of ways of doing that.
ignore = ','.join(part.strip() for part in htmllint_ignore)
self.arguments = '--disable=' + ignore
return self.lint(filename)
Right place for ‘{filename}’¶
Depending on where the executable (html_lint.py
in this case) wants the
file-name (eg. sample.html
) to be present in the command which does the
linting, we add '{filename}'
to the arguments. When we run
html_lint.py -h
, we can see that the command signature is:
html5_lint.py [--disable=DISABLE] FILENAME...
So, we want '{filename}'
at the end of the arguments.
self.arguments = '--disable=' + ignore
self.arguments += ' {filename}'
return self.lint(filename)
Finished Bear¶
Well done, you made it this far! Now you should have built a fully functional HTML Lint Bear. If you followed the code from this tutorial it should look something like this
import re
from coalib.bearlib.abstractions.Lint import Lint
from coalib.bears.LocalBear import LocalBear
from coalib.settings.Setting import typed_list
from coalib.results.RESULT_SEVERITY import RESULT_SEVERITY
class HTMLTutorialBear(LocalBear, Lint):
executable = 'html_lint.py'
output_regex = re.compile(
r'(?P<line>\d+):(?P<column>\d+):\s'
r'(?P<severity>Error|Warning|Info):\s(?P<message>.+)'
)
severity_map = {
"Info": RESULT_SEVERITY.INFO,
"Warning": RESULT_SEVERITY.NORMAL,
"Error": RESULT_SEVERITY.MAJOR
}
def run(self,
filename,
file,
htmllint_ignore: typed_list(str)=[]):
'''
Checks the code with `html_lint.py` on each file separately.
:param htmllint_ignore: List of checkers to ignore.
'''
ignore = ','.join(part.strip() for part in htmllint_ignore)
self.arguments = '--disable=' + ignore
self.arguments += ' {filename}'
return self.lint(filename)
Running and Testing our Bear¶
By running
$ coala --bear-dirs=. --bears=HTMLTutorialBear -B
We can see that our Bear setting is documented properly. To use coala with our Bear on sample.html we run
$ coala --bear-dirs=. --bears=HTMLTutorialBear --files=sample.html
To use our htmllint_ignore setting we can do
$ coala --bear-dirs=. --bears=HTMLTutorialBear \
> -S htmllint_ignore=optional_tag --files=sample.html
This will not output anything because all the messages had the optional_tag.
You now know how to write a linter Bear and also how to use it in your project. Congratulations!