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!