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