mirror of
https://fuchsia.googlesource.com/third_party/github.com/pylint-dev/pylint
synced 2024-09-21 07:58:04 +00:00
200 lines
8.1 KiB
Python
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("")
|