message_id_store.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  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. symbol_a, symbol_b = sorted([symbol, other_symbol])
  89. raise InvalidMessageError(
  90. f"Message id '{msgid}' cannot have both "
  91. f"'{symbol_a}' and '{symbol_b}' as symbolic name."
  92. )
  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. msgid_a, msgid_b = sorted([msgid, other_msgid])
  97. raise InvalidMessageError(
  98. f"Message symbol '{symbol}' cannot be used for "
  99. f"'{msgid_a}' and '{msgid_b}' at the same time."
  100. f" If you're creating an 'old_names' use 'old-{symbol}' as the old symbol."
  101. )
  102. def get_active_msgids(self, msgid_or_symbol: str) -> list[str]:
  103. """Return msgids but the input can be a symbol.
  104. self.__active_msgids is used to implement a primitive cache for this function.
  105. """
  106. try:
  107. return self.__active_msgids[msgid_or_symbol]
  108. except KeyError:
  109. pass
  110. # If we don't have a cached value yet we compute it
  111. msgid: str | None
  112. deletion_reason = None
  113. moved_reason = None
  114. if msgid_or_symbol[1:].isdigit():
  115. # Only msgid can have a digit as second letter
  116. msgid = msgid_or_symbol.upper()
  117. symbol = self.__msgid_to_symbol.get(msgid)
  118. if not symbol:
  119. deletion_reason = is_deleted_msgid(msgid)
  120. if deletion_reason is None:
  121. moved_reason = is_moved_msgid(msgid)
  122. else:
  123. symbol = msgid_or_symbol
  124. msgid = self.__symbol_to_msgid.get(msgid_or_symbol)
  125. if not msgid:
  126. deletion_reason = is_deleted_symbol(symbol)
  127. if deletion_reason is None:
  128. moved_reason = is_moved_symbol(symbol)
  129. if not (msgid and symbol):
  130. if deletion_reason is not None:
  131. raise DeletedMessageError(msgid_or_symbol, deletion_reason)
  132. if moved_reason is not None:
  133. raise MessageBecameExtensionError(msgid_or_symbol, moved_reason)
  134. error_msg = f"No such message id or symbol '{msgid_or_symbol}'."
  135. raise UnknownMessageError(error_msg)
  136. ids = self.__old_names.get(msgid, [msgid])
  137. # Add to cache
  138. self.__active_msgids[msgid_or_symbol] = ids
  139. return ids