# Copyright (c) 2006-2014 LOGILAB S.A. (Paris, FRANCE) # Copyright (c) 2013-2014 Google, Inc. # Copyright (c) 2013 buck@yelp.com # Copyright (c) 2014-2017 Claudiu Popa # Copyright (c) 2014 Brett Cannon # Copyright (c) 2014 Arun Persaud # Copyright (c) 2015 Ionel Cristian Maries # Copyright (c) 2016 Moises Lopez # Copyright (c) 2017-2018 Bryce Guinta # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html # For details: https://github.com/PyCQA/pylint/blob/master/COPYING from typing import Any from pylint.config import OptionsProviderMixIn from pylint.constants import WarningScope from pylint.exceptions import InvalidMessageError from pylint.interfaces import UNDEFINED, IRawChecker, ITokenChecker, implements from pylint.message.message_definition import MessageDefinition class BaseChecker(OptionsProviderMixIn): # checker name (you may reuse an existing one) name = None # type: str # options level (0 will be displaying in --help, 1 in --long-help) level = 1 # ordered list of options to control the ckecker behaviour options = () # type: Any # messages issued by this checker msgs = {} # type: Any # reports issued by this checker reports = () # type: Any # mark this checker as enabled or not. enabled = True def __init__(self, linter=None): """checker instances should have the linter as argument :param ILinter linter: is an object implementing ILinter.""" if self.name is not None: self.name = self.name.lower() OptionsProviderMixIn.__init__(self) self.linter = linter def __gt__(self, other): """Permit to sort a list of Checker by name.""" return "{}{}".format(self.name, self.msgs).__gt__( "{}{}".format(other.name, other.msgs) ) def __repr__(self): status = "Checker" if self.enabled else "Disabled checker" msgids = [id for id in self.msgs] return "{} '{}' responsible for {}".format(status, self.name, ", ".join(msgids)) def add_message( self, msgid, line=None, node=None, args=None, confidence=None, col_offset=None ): if not confidence: confidence = UNDEFINED self.linter.add_message(msgid, line, node, args, confidence, col_offset) def check_consistency(self) -> None: """Check the consistency of msgid. msg ids for a checker should be a string of len 4, where the two first characters are the checker id and the two last the msg id in this checker. :raises InvalidMessageError: If the checker id in the messages are not always the same. """ checker_id = None existing_ids = [] for message in self.messages: if checker_id is not None and checker_id != message.msgid[1:3]: error_msg = "Inconsistent checker part in message id " error_msg += "'{}' (expected 'x{checker_id}xx' ".format( message.msgid, checker_id=checker_id ) error_msg += "because we already had {existing_ids}).".format( existing_ids=existing_ids ) raise InvalidMessageError(error_msg) checker_id = message.msgid[1:3] existing_ids.append(message.msgid) def create_message_definition_from_tuple(self, msgid, msg_tuple): if implements(self, (IRawChecker, ITokenChecker)): default_scope = WarningScope.LINE else: default_scope = WarningScope.NODE options = {} if len(msg_tuple) > 3: (msg, symbol, descr, options) = msg_tuple elif len(msg_tuple) > 2: (msg, symbol, descr) = msg_tuple else: error_msg = """Messages should have a msgid and a symbol. Something like this : "W1234": ( "message", "message-symbol", "Message description with detail.", ... ), """ raise InvalidMessageError(error_msg) options.setdefault("scope", default_scope) return MessageDefinition(self, msgid, msg, descr, symbol, **options) @property def messages(self) -> list: return [ self.create_message_definition_from_tuple(msgid, msg_tuple) for msgid, msg_tuple in sorted(self.msgs.items()) ] # dummy methods implementing the IChecker interface def get_message_definition(self, msgid): for message_definition in self.messages: if message_definition.msgid == msgid: return message_definition error_msg = "MessageDefinition for '{}' does not exists. ".format(msgid) error_msg += "Choose from {}.".format([m.msgid for m in self.messages]) raise InvalidMessageError(error_msg) def open(self): """called before visiting project (i.e set of modules)""" def close(self): """called after visiting project (i.e set of modules)""" class BaseTokenChecker(BaseChecker): """Base class for checkers that want to have access to the token stream.""" def process_tokens(self, tokens): """Should be overridden by subclasses.""" raise NotImplementedError()