message_definition.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  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. import sys
  6. from typing import TYPE_CHECKING
  7. from astroid import nodes
  8. from pylint.constants import _SCOPE_EXEMPT, MSG_TYPES, WarningScope
  9. from pylint.exceptions import InvalidMessageError
  10. from pylint.utils import normalize_text
  11. if TYPE_CHECKING:
  12. from pylint.checkers import BaseChecker
  13. class MessageDefinition:
  14. # pylint: disable-next=too-many-arguments
  15. def __init__(
  16. self,
  17. checker: BaseChecker,
  18. msgid: str,
  19. msg: str,
  20. description: str,
  21. symbol: str,
  22. scope: str,
  23. minversion: tuple[int, int] | None = None,
  24. maxversion: tuple[int, int] | None = None,
  25. old_names: list[tuple[str, str]] | None = None,
  26. shared: bool = False,
  27. default_enabled: bool = True,
  28. ) -> None:
  29. self.checker_name = checker.name
  30. self.check_msgid(msgid)
  31. self.msgid = msgid
  32. self.symbol = symbol
  33. self.msg = msg
  34. self.description = description
  35. self.scope = scope
  36. self.minversion = minversion
  37. self.maxversion = maxversion
  38. self.shared = shared
  39. self.default_enabled = default_enabled
  40. self.old_names: list[tuple[str, str]] = []
  41. if old_names:
  42. for old_msgid, old_symbol in old_names:
  43. self.check_msgid(old_msgid)
  44. self.old_names.append(
  45. (old_msgid, old_symbol),
  46. )
  47. @staticmethod
  48. def check_msgid(msgid: str) -> None:
  49. if len(msgid) != 5:
  50. raise InvalidMessageError(f"Invalid message id {msgid!r}")
  51. if msgid[0] not in MSG_TYPES:
  52. raise InvalidMessageError(f"Bad message type {msgid[0]} in {msgid!r}")
  53. def __eq__(self, other: object) -> bool:
  54. return (
  55. isinstance(other, MessageDefinition)
  56. and self.msgid == other.msgid
  57. and self.symbol == other.symbol
  58. )
  59. def __repr__(self) -> str:
  60. return f"MessageDefinition:{self.symbol} ({self.msgid})"
  61. def __str__(self) -> str:
  62. return f"{self!r}:\n{self.msg} {self.description}"
  63. def may_be_emitted(self, py_version: tuple[int, ...] | sys._version_info) -> bool:
  64. """May the message be emitted using the configured py_version?"""
  65. if self.minversion is not None and self.minversion > py_version:
  66. return False
  67. if self.maxversion is not None and self.maxversion <= py_version:
  68. return False
  69. return True
  70. def format_help(self, checkerref: bool = False) -> str:
  71. """Return the help string for the given message id."""
  72. desc = self.description
  73. if checkerref:
  74. desc += f" This message belongs to the {self.checker_name} checker."
  75. title = self.msg
  76. if self.minversion or self.maxversion:
  77. restr = []
  78. if self.minversion:
  79. restr.append(f"< {'.'.join(str(n) for n in self.minversion)}")
  80. if self.maxversion:
  81. restr.append(f">= {'.'.join(str(n) for n in self.maxversion)}")
  82. restriction = " or ".join(restr)
  83. if checkerref:
  84. desc += f" It can't be emitted when using Python {restriction}."
  85. else:
  86. desc += (
  87. f" This message can't be emitted when using Python {restriction}."
  88. )
  89. msg_help = normalize_text(" ".join(desc.split()), indent=" ")
  90. message_id = f"{self.symbol} ({self.msgid})"
  91. if title != "%s":
  92. title = title.splitlines()[0]
  93. return f":{message_id}: *{title.rstrip(' ')}*\n{msg_help}"
  94. return f":{message_id}:\n{msg_help}"
  95. def check_message_definition(
  96. self, line: int | None, node: nodes.NodeNG | None
  97. ) -> None:
  98. """Check MessageDefinition for possible errors."""
  99. if self.msgid[0] not in _SCOPE_EXEMPT:
  100. # Fatal messages and reports are special, the node/scope distinction
  101. # does not apply to them.
  102. if self.scope == WarningScope.LINE:
  103. if line is None:
  104. raise InvalidMessageError(
  105. f"Message {self.msgid} must provide line, got None"
  106. )
  107. if node is not None:
  108. raise InvalidMessageError(
  109. f"Message {self.msgid} must only provide line, "
  110. f"got line={line}, node={node}"
  111. )
  112. elif self.scope == WarningScope.NODE:
  113. # Node-based warnings may provide an override line.
  114. if node is None:
  115. raise InvalidMessageError(
  116. f"Message {self.msgid} must provide Node, got None"
  117. )