message_id_store.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  2. # For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE
  3. # Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt
  4. from __future__ import annotations
  5. from typing import NoReturn
  6. from pylint.exceptions import (
  7. DeletedMessageError,
  8. InvalidMessageError,
  9. MessageBecameExtensionError,
  10. UnknownMessageError,
  11. )
  12. from pylint.message._deleted_message_ids import (
  13. is_deleted_msgid,
  14. is_deleted_symbol,
  15. is_moved_msgid,
  16. is_moved_symbol,
  17. )
  18. class MessageIdStore:
  19. """The MessageIdStore store MessageId and make sure that there is a 1-1 relation
  20. between msgid and symbol.
  21. """
  22. def __init__(self) -> None:
  23. self.__msgid_to_symbol: dict[str, str] = {}
  24. self.__symbol_to_msgid: dict[str, str] = {}
  25. self.__old_names: dict[str, list[str]] = {}
  26. self.__active_msgids: dict[str, list[str]] = {}
  27. def __len__(self) -> int:
  28. return len(self.__msgid_to_symbol)
  29. def __repr__(self) -> str:
  30. result = "MessageIdStore: [\n"
  31. for msgid, symbol in self.__msgid_to_symbol.items():
  32. result += f" - {msgid} ({symbol})\n"
  33. result += "]"
  34. return result
  35. def get_symbol(self, msgid: str) -> str:
  36. try:
  37. return self.__msgid_to_symbol[msgid.upper()]
  38. except KeyError as e:
  39. msg = f"'{msgid}' is not stored in the message store."
  40. raise UnknownMessageError(msg) from e
  41. def get_msgid(self, symbol: str) -> str:
  42. try:
  43. return self.__symbol_to_msgid[symbol]
  44. except KeyError as e:
  45. msg = f"'{symbol}' is not stored in the message store."
  46. raise UnknownMessageError(msg) from e
  47. def register_message_definition(
  48. self, msgid: str, symbol: str, old_names: list[tuple[str, str]]
  49. ) -> None:
  50. self.check_msgid_and_symbol(msgid, symbol)
  51. self.add_msgid_and_symbol(msgid, symbol)
  52. for old_msgid, old_symbol in old_names:
  53. self.check_msgid_and_symbol(old_msgid, old_symbol)
  54. self.add_legacy_msgid_and_symbol(old_msgid, old_symbol, msgid)
  55. def add_msgid_and_symbol(self, msgid: str, symbol: str) -> None:
  56. """Add valid message id.
  57. There is a little duplication with add_legacy_msgid_and_symbol to avoid a function call,
  58. this is called a lot at initialization.
  59. """
  60. self.__msgid_to_symbol[msgid] = symbol
  61. self.__symbol_to_msgid[symbol] = msgid
  62. def add_legacy_msgid_and_symbol(
  63. self, msgid: str, symbol: str, new_msgid: str
  64. ) -> None:
  65. """Add valid legacy message id.
  66. There is a little duplication with add_msgid_and_symbol to avoid a function call,
  67. this is called a lot at initialization.
  68. """
  69. self.__msgid_to_symbol[msgid] = symbol
  70. self.__symbol_to_msgid[symbol] = msgid
  71. existing_old_names = self.__old_names.get(msgid, [])
  72. existing_old_names.append(new_msgid)
  73. self.__old_names[msgid] = existing_old_names
  74. def check_msgid_and_symbol(self, msgid: str, symbol: str) -> None:
  75. existing_msgid: str | None = self.__symbol_to_msgid.get(symbol)
  76. existing_symbol: str | None = self.__msgid_to_symbol.get(msgid)
  77. if existing_symbol is None and existing_msgid is None:
  78. return # both symbol and msgid are usable
  79. if existing_msgid is not None:
  80. if existing_msgid != msgid:
  81. self._raise_duplicate_msgid(symbol, msgid, existing_msgid)
  82. if existing_symbol and existing_symbol != symbol:
  83. # See https://github.com/python/mypy/issues/10559
  84. self._raise_duplicate_symbol(msgid, symbol, existing_symbol)
  85. @staticmethod
  86. def _raise_duplicate_symbol(msgid: str, symbol: str, other_symbol: str) -> NoReturn:
  87. """Raise an error when a symbol is duplicated."""
  88. symbols = [symbol, other_symbol]
  89. symbols.sort()
  90. error_message = f"Message id '{msgid}' cannot have both "
  91. error_message += f"'{symbols[0]}' and '{symbols[1]}' as symbolic name."
  92. raise InvalidMessageError(error_message)
  93. @staticmethod
  94. def _raise_duplicate_msgid(symbol: str, msgid: str, other_msgid: str) -> NoReturn:
  95. """Raise an error when a msgid is duplicated."""
  96. msgids = [msgid, other_msgid]
  97. msgids.sort()
  98. error_message = (
  99. f"Message symbol '{symbol}' cannot be used for "
  100. f"'{msgids[0]}' and '{msgids[1]}' at the same time."
  101. f" If you're creating an 'old_names' use 'old-{symbol}' as the old symbol."
  102. )
  103. raise InvalidMessageError(error_message)
  104. def get_active_msgids(self, msgid_or_symbol: str) -> list[str]:
  105. """Return msgids but the input can be a symbol.
  106. self.__active_msgids is used to implement a primitive cache for this function.
  107. """
  108. try:
  109. return self.__active_msgids[msgid_or_symbol]
  110. except KeyError:
  111. pass
  112. # If we don't have a cached value yet we compute it
  113. msgid: str | None
  114. deletion_reason = None
  115. moved_reason = None
  116. if msgid_or_symbol[1:].isdigit():
  117. # Only msgid can have a digit as second letter
  118. msgid = msgid_or_symbol.upper()
  119. symbol = self.__msgid_to_symbol.get(msgid)
  120. if not symbol:
  121. deletion_reason = is_deleted_msgid(msgid)
  122. if deletion_reason is None:
  123. moved_reason = is_moved_msgid(msgid)
  124. else:
  125. symbol = msgid_or_symbol
  126. msgid = self.__symbol_to_msgid.get(msgid_or_symbol)
  127. if not msgid:
  128. deletion_reason = is_deleted_symbol(symbol)
  129. if deletion_reason is None:
  130. moved_reason = is_moved_symbol(symbol)
  131. if not msgid or not symbol:
  132. if deletion_reason is not None:
  133. raise DeletedMessageError(msgid_or_symbol, deletion_reason)
  134. if moved_reason is not None:
  135. raise MessageBecameExtensionError(msgid_or_symbol, moved_reason)
  136. error_msg = f"No such message id or symbol '{msgid_or_symbol}'."
  137. raise UnknownMessageError(error_msg)
  138. ids = self.__old_names.get(msgid, [msgid])
  139. # Add to cache
  140. self.__active_msgids[msgid_or_symbol] = ids
  141. return ids