third_party.pylibs.pylint.src/pylint/message/message_definition_store.py
2019-08-16 17:25:23 +02:00

200 lines
8.1 KiB
Python

# -*- coding: utf-8 -*-
# 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 __future__ import print_function
import collections
from pylint.exceptions import InvalidMessageError, UnknownMessageError
class MessageDefinitionStore:
"""The messages store knows information about every possible message but has
no particular state during analysis.
"""
def __init__(self):
# Primary registry for all active messages (i.e. all messages
# that can be emitted by pylint for the underlying Python
# version). It contains the 1:1 mapping from symbolic names
# to message definition objects.
# Keys are msg ids, values are a 2-uple with the msg type and the
# msg itself
self._messages_definitions = {}
# Maps alternative names (numeric IDs, deprecated names) to
# message definitions. May contain several names for each definition
# object.
self._alternative_names = {}
self._msgs_by_category = collections.defaultdict(list)
@property
def messages(self) -> list:
"""The list of all active messages."""
return self._messages_definitions.values()
def register_messages_from_checker(self, checker):
"""Register all messages from a checker.
:param BaseChecker checker:
"""
checker.check_consistency()
for message in checker.messages:
self.register_message(message)
def register_message(self, message):
"""Register a MessageDefinition with consistency in mind.
:param MessageDefinition message: The message definition being added.
"""
self._check_id_and_symbol_consistency(message.msgid, message.symbol)
self._check_symbol(message.msgid, message.symbol)
self._check_msgid(message.msgid, message.symbol)
for old_name in message.old_names:
self._check_symbol(message.msgid, old_name[1])
self._messages_definitions[message.symbol] = message
self._register_alternative_name(message, message.msgid, message.symbol)
for old_id, old_symbol in message.old_names:
self._register_alternative_name(message, old_id, old_symbol)
self._msgs_by_category[message.msgid[0]].append(message.msgid)
def _register_alternative_name(self, msg, msgid, symbol):
"""helper for register_message()"""
self._check_id_and_symbol_consistency(msgid, symbol)
self._alternative_names[msgid] = msg
self._alternative_names[symbol] = msg
def _check_symbol(self, msgid, symbol):
"""Check that a symbol is not already used. """
other_message = self._messages_definitions.get(symbol)
if other_message:
self._raise_duplicate_msgid(symbol, msgid, other_message.msgid)
else:
alternative_msgid = None
alternative_message = self._alternative_names.get(symbol)
if alternative_message:
if alternative_message.symbol == symbol:
alternative_msgid = alternative_message.msgid
else:
for old_msgid, old_symbol in alternative_message.old_names:
if old_symbol == symbol:
alternative_msgid = old_msgid
break
if msgid != alternative_msgid:
self._raise_duplicate_msgid(symbol, msgid, alternative_msgid)
def _check_msgid(self, msgid, symbol):
for message in self._messages_definitions.values():
if message.msgid == msgid:
self._raise_duplicate_symbol(msgid, symbol, message.symbol)
def _check_id_and_symbol_consistency(self, msgid, symbol):
try:
alternative = self._alternative_names[msgid]
except KeyError:
alternative = False
try:
if not alternative:
alternative = self._alternative_names[symbol]
except KeyError:
# There is no alternative names concerning this msgid/symbol.
# So nothing to check
return None
old_symbolic_name = None
old_symbolic_id = None
for alternate_msgid, alternate_symbol in alternative.old_names:
if alternate_msgid == msgid or alternate_symbol == symbol:
old_symbolic_id = alternate_msgid
old_symbolic_name = alternate_symbol
if symbol not in (alternative.symbol, old_symbolic_name):
if msgid == old_symbolic_id:
self._raise_duplicate_symbol(msgid, symbol, old_symbolic_name)
else:
self._raise_duplicate_symbol(msgid, symbol, alternative.symbol)
return None
@staticmethod
def _raise_duplicate_symbol(msgid, symbol, other_symbol):
"""Raise an error when a symbol is duplicated.
:param str msgid: The msgid corresponding to the symbols
:param str symbol: Offending symbol
:param str other_symbol: Other offending symbol
:raises InvalidMessageError:"""
symbols = [symbol, other_symbol]
symbols.sort()
error_message = "Message id '{msgid}' cannot have both ".format(msgid=msgid)
error_message += "'{other_symbol}' and '{symbol}' as symbolic name.".format(
other_symbol=symbols[0], symbol=symbols[1]
)
raise InvalidMessageError(error_message)
@staticmethod
def _raise_duplicate_msgid(symbol, msgid, other_msgid):
"""Raise an error when a msgid is duplicated.
:param str symbol: The symbol corresponding to the msgids
:param str msgid: Offending msgid
:param str other_msgid: Other offending msgid
:raises InvalidMessageError:"""
msgids = [msgid, other_msgid]
msgids.sort()
error_message = "Message symbol '{symbol}' cannot be used for ".format(
symbol=symbol
)
error_message += "'{other_msgid}' and '{msgid}' at the same time.".format(
other_msgid=msgids[0], msgid=msgids[1]
)
raise InvalidMessageError(error_message)
def get_message_definitions(self, msgid_or_symbol: str) -> list:
"""Returns the Message object for this message.
:param str msgid_or_symbol: msgid_or_symbol may be either a numeric or symbolic id.
:raises UnknownMessageError: if the message id is not defined.
:rtype: List of MessageDefinition
:return: A message definition corresponding to msgid_or_symbol
"""
# Only msgid can have a digit as second letter
is_msgid = msgid_or_symbol[1:].isdigit()
if is_msgid:
msgid_or_symbol = msgid_or_symbol.upper()
for source in (self._alternative_names, self._messages_definitions):
try:
return [source[msgid_or_symbol]]
except KeyError:
pass
error_msg = "No such message id or symbol '{msgid_or_symbol}'.".format(
msgid_or_symbol=msgid_or_symbol
)
raise UnknownMessageError(error_msg)
def get_msg_display_string(self, msgid):
"""Generates a user-consumable representation of a message. """
message_definitions = self.get_message_definitions(msgid)
if len(message_definitions) == 1:
return repr(message_definitions[0].symbol)
return repr([md.symbol for md in message_definitions])
def help_message(self, msgids):
"""Display help messages for the given message identifiers"""
for msgid in msgids:
try:
for message_definition in self.get_message_definitions(msgid):
print(message_definition.format_help(checkerref=True))
print("")
except UnknownMessageError as ex:
print(ex)
print("")
continue
def list_messages(self):
"""Output full messages list documentation in ReST format. """
messages = sorted(self._messages_definitions.values(), key=lambda m: m.msgid)
for message in messages:
if not message.may_be_emitted():
continue
print(message.format_help(checkerref=False))
print("")