output_line.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  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 warnings
  4. from typing import Any, NamedTuple, Optional, Sequence, Tuple, TypeVar, Union
  5. from astroid import nodes
  6. from pylint.constants import PY38_PLUS
  7. from pylint.interfaces import UNDEFINED, Confidence
  8. from pylint.message.message import Message
  9. from pylint.testutils.constants import UPDATE_OPTION
  10. T = TypeVar("T")
  11. class MessageTest(NamedTuple):
  12. msg_id: str
  13. line: Optional[int] = None
  14. node: Optional[nodes.NodeNG] = None
  15. args: Optional[Any] = None
  16. confidence: Optional[Confidence] = UNDEFINED
  17. col_offset: Optional[int] = None
  18. """Used to test messages produced by pylint. Class name cannot start with Test as pytest doesn't allow constructors in test classes."""
  19. class MalformedOutputLineException(Exception):
  20. def __init__(
  21. self,
  22. row: Union[Sequence[str], str],
  23. exception: Exception,
  24. ) -> None:
  25. example = "msg-symbolic-name:42:27:MyClass.my_function:The message"
  26. other_example = "msg-symbolic-name:7:42::The message"
  27. expected = [
  28. "symbol",
  29. "line",
  30. "column",
  31. "end_line",
  32. "end_column",
  33. "MyClass.myFunction, (or '')",
  34. "Message",
  35. "confidence",
  36. ]
  37. reconstructed_row = ""
  38. i = 0
  39. try:
  40. for i, column in enumerate(row):
  41. reconstructed_row += f"\t{expected[i]}='{column}' ?\n"
  42. for missing in expected[i + 1 :]:
  43. reconstructed_row += f"\t{missing}= Nothing provided !\n"
  44. except IndexError:
  45. pass
  46. raw = ":".join(row)
  47. msg = f"""\
  48. {exception}
  49. Expected '{example}' or '{other_example}' but we got '{raw}':
  50. {reconstructed_row}
  51. Try updating it with: 'python tests/test_functional.py {UPDATE_OPTION}'"""
  52. super().__init__(msg)
  53. class OutputLine(NamedTuple):
  54. symbol: str
  55. lineno: int
  56. column: int
  57. end_lineno: Optional[int]
  58. end_column: Optional[int]
  59. object: str
  60. msg: str
  61. confidence: str
  62. @classmethod
  63. def from_msg(cls, msg: Message, check_endline: bool = True) -> "OutputLine":
  64. """Create an OutputLine from a Pylint Message"""
  65. column = cls._get_column(msg.column)
  66. end_line = cls._get_py38_none_value(msg.end_line, check_endline)
  67. end_column = cls._get_py38_none_value(msg.end_column, check_endline)
  68. return cls(
  69. msg.symbol,
  70. msg.line,
  71. column,
  72. end_line,
  73. end_column,
  74. msg.obj or "",
  75. msg.msg.replace("\r\n", "\n"),
  76. msg.confidence.name,
  77. )
  78. @staticmethod
  79. def _get_column(column: str) -> int:
  80. """Handle column numbers with the exception of pylint < 3.8 not having them
  81. in the ast parser.
  82. """
  83. if not PY38_PLUS:
  84. # We check the column only for the new better ast parser introduced in python 3.8
  85. return 0 # pragma: no cover
  86. return int(column)
  87. @staticmethod
  88. def _get_py38_none_value(value: T, check_endline: bool) -> Optional[T]:
  89. """Used to make end_line and end_column None as indicated by our version compared to
  90. `min_pyver_end_position`."""
  91. if not check_endline:
  92. return None # pragma: no cover
  93. return value
  94. @classmethod
  95. def from_csv(
  96. cls, row: Union[Sequence[str], str], check_endline: bool = True
  97. ) -> "OutputLine":
  98. """Create an OutputLine from a comma separated list (the functional tests expected
  99. output .txt files).
  100. """
  101. try:
  102. if isinstance(row, Sequence):
  103. column = cls._get_column(row[2])
  104. if len(row) == 5:
  105. warnings.warn(
  106. "In pylint 3.0 functional tests expected output should always include the "
  107. "expected confidence level, expected end_line and expected end_column. "
  108. "An OutputLine should thus have a length of 8.",
  109. DeprecationWarning,
  110. )
  111. return cls(
  112. row[0],
  113. int(row[1]),
  114. column,
  115. None,
  116. None,
  117. row[3],
  118. row[4],
  119. UNDEFINED.name,
  120. )
  121. if len(row) == 6:
  122. warnings.warn(
  123. "In pylint 3.0 functional tests expected output should always include the "
  124. "expected end_line and expected end_column. An OutputLine should thus have "
  125. "a length of 8.",
  126. DeprecationWarning,
  127. )
  128. return cls(
  129. row[0], int(row[1]), column, None, None, row[3], row[4], row[5]
  130. )
  131. if len(row) == 8:
  132. end_line = cls._get_py38_none_value(row[3], check_endline)
  133. end_column = cls._get_py38_none_value(row[4], check_endline)
  134. return cls(
  135. row[0],
  136. int(row[1]),
  137. column,
  138. cls._value_to_optional_int(end_line),
  139. cls._value_to_optional_int(end_column),
  140. row[5],
  141. row[6],
  142. row[7],
  143. )
  144. raise IndexError
  145. except Exception as e:
  146. raise MalformedOutputLineException(row, e) from e
  147. def to_csv(self) -> Tuple[str, str, str, str, str, str, str, str]:
  148. """Convert an OutputLine to a tuple of string to be written by a
  149. csv-writer.
  150. """
  151. return (
  152. str(self.symbol),
  153. str(self.lineno),
  154. str(self.column),
  155. str(self.end_lineno),
  156. str(self.end_column),
  157. str(self.object),
  158. str(self.msg),
  159. str(self.confidence),
  160. )
  161. @staticmethod
  162. def _value_to_optional_int(value: Optional[str]) -> Optional[int]:
  163. """Checks if a (stringified) value should be None or a Python integer"""
  164. if value == "None" or not value:
  165. return None
  166. return int(value)