-
Notifications
You must be signed in to change notification settings - Fork 62
Pylint Checkers
Checkers check through the code and store errors as message objects.
More information about Pylint and checkers in Pylint:
- Pylint doc
https://pylint.readthedocs.io/en/latest/ - Pylint checkers files
https://github.com/PyCQA/pylint/tree/master/pylint/checkers
When a user calls python_ta.check_all() to check a piece of code, linter in method _check() will load the default checkers as well as PyTA's custom checkers.
Checker methods in checker will then be called to detect errors and store error messages for reporters.
More information about python_ta.check_all() and PyTA's custom checkers:
- python_ta
https://github.com/pyta-uoft/pyta/blob/master/python_ta/__init__.py - PyTA's custom checkers
https://github.com/pyta-uoft/pyta/tree/master/python_ta/checkers
There are several types of checker, but Pylint mainly uses AST checkers. An AST checker is a visitor, and should implement visit_ or leave_ methods for the nodes it’s interested in.
If we have a checker named Checker, its checker's methods are called through the following steps:
- In pylint/lint.py calls linter.load_plugin_modules('Checker')
- Calls the Checker's register method
- Calls linter.register_checker(Checker(linter))
- linter.register_checker(Checker) [appends Checker] (https://github.com/PyCQA/pylint/blob/master/pylint/lint.py#L546) to [self._checkers of Pylinter] (https://github.com/PyCQA/pylint/blob/master/pylint/lint.py#L410) if [the priority of Checker is less than 0] (https://github.com/PyCQA/pylint/blob/master/pylint/lint.py#L545)
After Checker is stored in Pylinter._checker,
- [check()] (https://github.com/PyCQA/pylint/blob/master/pylint/lint.py#L726) in pylint/lint.py calls _do_checks() if config.jobs == 1
- [_do_checks()] (https://github.com/PyCQA/pylint/blob/master/pylint/lint.py#L823) calls walker.add_checker(checker) where [walker = utils.PyLintASTWalker(self)] (https://github.com/PyCQA/pylint/blob/master/pylint/utils.py#L926)
- In pylint/utils.py, add_checker() collects visit methods. A checker's methods are named visit_* because there is an [if statement that makes sure only methods that start with 'visit_' are added] (https://github.com/PyCQA/pylint/blob/master/pylint/utils.py#L954)
- _do_checks() then calls [self.check_astroid_module(ast_node, walker, rawcheckers, tokencheckers)] (https://github.com/PyCQA/pylint/blob/master/pylint/lint.py#L855)
- check_astroid_module(ast_node, walker, rawcheckers, tokencheckers) then [performs a walk] (https://github.com/PyCQA/pylint/blob/master/pylint/lint.py#L934)
- [walk()] (https://github.com/PyCQA/pylint/blob/master/pylint/utils.py#L974) walks through the ast_node and perform visit_methods that add_checker() collected
The files mentioned above can be found in PyCQA's github pylint repository:
- pylint/lint.py
https://github.com/PyCQA/pylint/blob/master/pylint/lint.py - pylint/util.py
https://github.com/PyCQA/pylint/blob/master/pylint/utils.py
##How an error detected by a checker is stored?
When a visit method is called, the decorator check_messages() in pylint.checkers.utils.py adds the message symbol string to attribute checks_msgs of the visit method.
The attribute checks_msgs is then used by method _is_method_enabled() of class PyLintASTWalker(object), which is used by method add_checker() to check if the checker's method is enabled. Only methods that are enabled are added to self.visit_events() and therefore can be called.
In the following example, the message symbol 'assigning_to_self' is passed into check_messages() as a parameter. The function store_messages() adds 'assigning_to_self' to checks_msgs of visit_assign() and returns visit_assign(). visit_assign() then calls add_message() in the end.
Example:
from pylint.checkers.utils import check_messages
@check_messages('assigning_to_self')
def visit_assign(self, node):
target = node.targets[0]
if isinstance(target, astroid.AssignName) and target.name == 'self':
args = node.lineno
self.add_message('assigning_to_self', node=node, args=args)
The example above is equivalent to:
def check_messages('assigning_to_self'):
"""decorator to store messages that are handled by a checker method"""
def store_messages(visit_assign(self, node)):
visit_assign.checks_msgs = 'assigning_to_self'
return visit_assign()
return store_messages
If an error is detected in visit_assign(), add_message stores the message as a Message object to the reporter by calling self.reporter.handle_message(Message()) in the end. The method add_message() is inherited from class MessagesHandlerMixIn(object) in pylint/utils.py.
But where does add_message() get all the message information from?
Go back to section How a checker's methods are called? when linter.register_checker(Checker(linter)) is called.
linter.register_checker(Checker(linter)) not only appends Checker to Pylinter._checkers, it also calls self.msgs_store.register_messages(checker) when error messages are detected in a checker.
When self.msgs_store.register_messages(checker), where self.msgs_store = MessagesStore(), is called, register_messages() in class MessagesStore() registers the message information and store them in self.msgs_store._messages.
When add_message() is called, it gets the message information by calling self.msgs_store.check_message_id(), where check_message_id() gets its information from self.msgs_store._messages and outputs the message object.
For more information about the decorator check_messages():
- pylint/checkers/utils.py https://github.com/PyCQA/pylint/blob/master/pylint/checkers/utils.py
###Import statements
import astroid
from pylint.interfaces import IAstroidChecker
from pylint.checkers import BaseChecker
from pylint.checkers.utils import check_messages
-
An AST custom checker needs to get an AST representation of the module that is being checked, so we need to import astroid.
-
Different types of checkers need different interfaces. IAstroidChecker needs to be imported when implementing an AST custom checker.
-
Any custom checker is a subclass of pylint's BaseChecker.
-
check_messages() is a decorator that is mentioned in section How an error detected by a checker is stored?. It adds a message symbol to an attribute of visit method.
###Name
- The name is being used for generating a special configuration section of the checker, in case in has provided options.
###Priority
- The priority of the checker needs to be less than 0. Checkers are ordered by the priority, from the most negative to the most positive. If priority is greater than or equal to 0, register_checker() will raise an AssertionError.
###Message dictionary
-
The message dictionary should specify what messages the said checker is going to emit.
-
A message has the following format:
msgs = {'message-id': ('displayed-message', 'message-symbol', 'message-help')} -
The message id should be a 5-digits number, prefixed with a message category. There are multiple message categories, these being C, W, E, F, R, standing for Convention, Warning, Error, Fatal and Refactoring. The rest of the 5 digits should not conflict with existing checkers and they should be consistent across the checker. For instance, the first two digits should not be different across the checker.
-
The displayed message is used for displaying the message to the user, once it is emitted.
-
The message symbol is an alias of the message id and it can be used wherever the message id can be used.
-
The message help is used when calling pylint --help-msg.
###Visit method
- A visit method detects errors and is decorated by check_messages(). More information about visit method is covered in section How a checker's methods are called? Why are they named visit_*? and How an error detected by a checker is stored?
###Register method
- A register method registers the checker to Pylinter._checkers. How register method is called is covered in section How a checker's methods are called?.
Part of the components description are taken from Pylint's doc on how to write a custom checker: https://pylint.readthedocs.io/en/latest/reference_guide/custom_checkers.html