message_definition.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  2. # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
  3. import sys
  4. from typing import TYPE_CHECKING, List, Optional, Tuple
  5. from astroid import nodes
  6. from pylint.constants import _SCOPE_EXEMPT, MSG_TYPES, WarningScope
  7. from pylint.exceptions import InvalidMessageError
  8. from pylint.utils import normalize_text
  9. if TYPE_CHECKING:
  10. from pylint.checkers import BaseChecker
  11. class MessageDefinition:
  12. def __init__(
  13. self,
  14. checker: "BaseChecker",
  15. msgid: str,
  16. msg: str,
  17. description: str,
  18. symbol: str,
  19. scope: str,
  20. minversion: Optional[Tuple[int, int]] = None,
  21. maxversion: Optional[Tuple[int, int]] = None,
  22. old_names: Optional[List[Tuple[str, str]]] = None,
  23. ) -> None:
  24. self.checker_name = checker.name
  25. self.check_msgid(msgid)
  26. self.msgid = msgid
  27. self.symbol = symbol
  28. self.msg = msg
  29. self.description = description
  30. self.scope = scope
  31. self.minversion = minversion
  32. self.maxversion = maxversion
  33. self.old_names: List[Tuple[str, str]] = []
  34. if old_names:
  35. for old_msgid, old_symbol in old_names:
  36. self.check_msgid(old_msgid)
  37. self.old_names.append(
  38. (old_msgid, old_symbol),
  39. )
  40. @staticmethod
  41. def check_msgid(msgid: str) -> None:
  42. if len(msgid) != 5:
  43. raise InvalidMessageError(f"Invalid message id {msgid!r}")
  44. if msgid[0] not in MSG_TYPES:
  45. raise InvalidMessageError(f"Bad message type {msgid[0]} in {msgid!r}")
  46. def __repr__(self) -> str:
  47. return f"MessageDefinition:{self.symbol} ({self.msgid})"
  48. def __str__(self) -> str:
  49. return f"{repr(self)}:\n{self.msg} {self.description}"
  50. def may_be_emitted(self) -> bool:
  51. """return True if message may be emitted using the current interpreter"""
  52. if self.minversion is not None and self.minversion > sys.version_info:
  53. return False
  54. if self.maxversion is not None and self.maxversion <= sys.version_info:
  55. return False
  56. return True
  57. def format_help(self, checkerref: bool = False) -> str:
  58. """return the help string for the given message id"""
  59. desc = self.description
  60. if checkerref:
  61. desc += f" This message belongs to the {self.checker_name} checker."
  62. title = self.msg
  63. if self.minversion or self.maxversion:
  64. restr = []
  65. if self.minversion:
  66. restr.append(f"< {'.'.join(str(n) for n in self.minversion)}")
  67. if self.maxversion:
  68. restr.append(f">= {'.'.join(str(n) for n in self.maxversion)}")
  69. restriction = " or ".join(restr)
  70. if checkerref:
  71. desc += f" It can't be emitted when using Python {restriction}."
  72. else:
  73. desc += (
  74. f" This message can't be emitted when using Python {restriction}."
  75. )
  76. msg_help = normalize_text(" ".join(desc.split()), indent=" ")
  77. message_id = f"{self.symbol} ({self.msgid})"
  78. if title != "%s":
  79. title = title.splitlines()[0]
  80. return f":{message_id}: *{title.rstrip(' ')}*\n{msg_help}"
  81. return f":{message_id}:\n{msg_help}"
  82. def check_message_definition(
  83. self, line: Optional[int], node: Optional[nodes.NodeNG]
  84. ) -> None:
  85. """Check MessageDefinition for possible errors"""
  86. if self.msgid[0] not in _SCOPE_EXEMPT:
  87. # Fatal messages and reports are special, the node/scope distinction
  88. # does not apply to them.
  89. if self.scope == WarningScope.LINE:
  90. if line is None:
  91. raise InvalidMessageError(
  92. f"Message {self.msgid} must provide line, got None"
  93. )
  94. if node is not None:
  95. raise InvalidMessageError(
  96. f"Message {self.msgid} must only provide line, "
  97. f"got line={line}, node={node}"
  98. )
  99. elif self.scope == WarningScope.NODE:
  100. # Node-based warnings may provide an override line.
  101. if node is None:
  102. raise InvalidMessageError(
  103. f"Message {self.msgid} must provide Node, got None"
  104. )