-
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.
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 call linter.load_plugin_modules('Checker')
- Calls module.register()
- Calls linter.register_checker(Checker(linter)) in pylint/lint.py
- linter.register_checker(Checker) appends Checker to self._checkers of Pylinter
After Checker is stored in Pylinter._checker,
- check() in pylint/lint.py calls _do_checks() if config.jobs == 1
- _do_checks() calls walker.add_checker(checker) where walker = utils.PyLintASTWalker(self)
- In pylint/utils.py, add_checker() collects visit methods. A checker's methods are named visit_* because in line 954 of pylint/util.py, there is an if statement that makes sure only methods that start with 'visit_' are added
- _do_checks() then calls self.check_astroid_module(ast_node, walker, rawcheckers, tokencheckers)
- check_astroid_module(ast_node, walker, rawcheckers, tokencheckers) then performs a walk
- walk() in pylint/util.py 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/utils.py
https://github.com/PyCQA/pylint/blob/master/pylint/lint.py - pylint/lint.py
https://github.com/PyCQA/pylint/blob/master/pylint/utils.py
##How an error detected by a checker is stored?
When 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 >= 0, register_checker() wouldn't run.
###Message dictionary
Each checker is being used for finding problems in your code, the problems being displayed to the user through messages. The message dictionary should specify what messages the said checker is going to emit. It 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 that is decorated by check_messages().
###Register method that registers the checker to Pylinter._checkers.
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
Issues: There are some typos and inconsistency in naming style in PyTA checker files.