@@ -22,6 +22,7 @@ class Severity(enum.Enum):
22
22
NOTE = 1
23
23
WARNING = 2
24
24
ERROR = 3
25
+ FATAL = 4
25
26
26
27
@classmethod
27
28
def from_string (cls , string : str ) -> "Severity" :
@@ -39,6 +40,7 @@ def __repr__(self) -> str:
39
40
"N" : Severity .NOTE ,
40
41
"W" : Severity .WARNING ,
41
42
"E" : Severity .ERROR ,
43
+ "F" : Severity .FATAL ,
42
44
}
43
45
44
46
_COMMENT_MESSAGES = frozenset (
@@ -55,11 +57,11 @@ def __repr__(self) -> str:
55
57
class Message :
56
58
"""Mypy message"""
57
59
58
- filename : str
59
- lineno : int
60
- colno : Optional [int ]
61
- severity : Severity
62
- message : str
60
+ filename : str = ""
61
+ lineno : int = 0
62
+ colno : Optional [int ] = None
63
+ severity : Severity = Severity . ERROR
64
+ message : str = ""
63
65
revealed_type : Optional [str ] = None
64
66
error_code : Optional [str ] = None
65
67
@@ -72,19 +74,22 @@ class Message:
72
74
COMMENT_RE = re .compile (
73
75
r"^(?:# *type: *ignore *)?(?:# *)?"
74
76
r"(?P<severity>[RENW]):"
75
- r"((?P<colno>\d+):)? * "
76
- r"(?P<message>[^#]*?) "
77
- r"(?: +\[(?P<error_code >[^\]]*)\])? "
77
+ r"((?P<colno>\d+):)?"
78
+ r" * "
79
+ r"(?P<message_and_error_code >[^#]*) "
78
80
r"(?:#.*?)?$"
79
81
)
80
82
83
+ MESSAGE_AND_ERROR_CODE = re .compile (
84
+ r"(?P<message>[^\[][^#]*?)" r" +" r"\[(?P<error_code>[^\]]*)\]"
85
+ )
86
+
81
87
OUTPUT_RE = re .compile (
82
88
r"^(?P<fname>([a-zA-Z]:)?[^:]+):"
83
89
r"(?P<lineno>[0-9]+):"
84
90
r"((?P<colno>[0-9]+):)?"
85
91
r" *(?P<severity>(error|note|warning)):"
86
- r"(?P<message>.*?)"
87
- r"(?: +\[(?P<error_code>[^\]]*)\])?"
92
+ r"(?P<message_and_error_code>.*?)"
88
93
r"$"
89
94
)
90
95
@@ -128,7 +133,7 @@ def astuple(self, *, normalized: bool = False) -> "Message.TupleType":
128
133
129
134
>>> m = Message("foo.py", 1, 1, Severity.NOTE, 'Revealed type is "float"')
130
135
>>> m.astuple()
131
- ('foo.py', 1, 1, Severity.NOTE, 'Revealed type is "float"', 'float')
136
+ ('foo.py', 1, 1, Severity.NOTE, 'Revealed type is "float"', 'float', None )
132
137
"""
133
138
return (
134
139
self .filename ,
@@ -144,7 +149,11 @@ def is_comment(self) -> bool:
144
149
return (self .severity , self .message ) in _COMMENT_MESSAGES
145
150
146
151
def _as_short_tuple (
147
- self , * , normalized : bool = False , default_error_code : Optional [str ] = None
152
+ self ,
153
+ * ,
154
+ normalized : bool = False ,
155
+ default_message : str = "" ,
156
+ default_error_code : Optional [str ] = None ,
148
157
) -> "Message.TupleType" :
149
158
if normalized :
150
159
message = self .normalized_message
@@ -155,32 +164,73 @@ def _as_short_tuple(
155
164
self .lineno ,
156
165
None ,
157
166
self .severity ,
158
- message ,
167
+ message or default_message ,
159
168
self .revealed_type ,
160
169
self .error_code or default_error_code ,
161
170
)
162
171
172
+ def __hash__ (self ) -> int :
173
+ t = (self .filename , self .lineno , self .severity , self .revealed_type )
174
+ return hash (t )
175
+
163
176
def __eq__ (self , other ):
177
+ """Compare if *self* and *other* are equal.
178
+
179
+ Returns `True` if *other* is a :obj:`Message:` object
180
+ considered to be equal to *self*.
181
+
182
+ >>> Message() == Message()
183
+ True
184
+ >>> Message(error_code="baz") == Message(message="some text", error_code="baz")
185
+ True
186
+ >>> Message(message="some text") == Message(message="some text", error_code="baz")
187
+ True
188
+
189
+ >>> Message() == Message(message="some text", error_code="baz")
190
+ False
191
+ >>> Message(error_code="baz") == Message(error_code="bax")
192
+ False
193
+ """
164
194
if isinstance (other , Message ):
165
195
default_error_code = self .error_code or other .error_code
166
- if self .colno is None or other .colno is None :
167
- a = self ._as_short_tuple (
168
- normalized = True , default_error_code = default_error_code
169
- )
170
- b = other ._as_short_tuple (
171
- normalized = True , default_error_code = default_error_code
196
+ if self .error_code and other .error_code :
197
+ default_message = self .normalized_message or other .normalized_message
198
+ else :
199
+ default_message = ""
200
+
201
+ def to_tuple (m : Message ):
202
+ return m ._as_short_tuple (
203
+ normalized = True ,
204
+ default_message = default_message ,
205
+ default_error_code = default_error_code ,
172
206
)
173
- return a == b
207
+
208
+ if self .colno is None or other .colno is None :
209
+ return to_tuple (self ) == to_tuple (other )
174
210
else :
175
211
return self .astuple (normalized = True ) == other .astuple (normalized = True )
176
212
else :
177
213
return NotImplemented
178
214
179
- def __hash__ (self ) -> int :
180
- return hash (self ._as_short_tuple (normalized = True ))
181
-
182
215
def __str__ (self ) -> str :
183
- return f"{ self ._prefix } { self .severity .name .lower ()} : { self .message } "
216
+ return self .to_string (prefix = f"{ self ._prefix } " )
217
+
218
+ def to_string (self , prefix : Optional [str ] = None ) -> str :
219
+ prefix = prefix or f"{ self ._prefix } "
220
+ error_code = f" [{ self .error_code } ]" if self .error_code else ""
221
+ return f"{ prefix } { self .severity .name .lower ()} : { self .message } { error_code } "
222
+
223
+ @classmethod
224
+ def __split_message_and_error_code (cls , msg : str ) -> Tuple [str , Optional [str ]]:
225
+ msg = msg .strip ()
226
+ if msg .startswith ("[" ) and msg .endswith ("]" ):
227
+ return "" , msg [1 :- 1 ]
228
+ else :
229
+ m = cls .MESSAGE_AND_ERROR_CODE .fullmatch (msg )
230
+ if m :
231
+ return m .group ("message" ), m .group ("error_code" )
232
+ else :
233
+ return msg , None
184
234
185
235
@classmethod
186
236
def from_comment (
@@ -189,13 +239,17 @@ def from_comment(
189
239
"""Create message object from Python *comment*.
190
240
191
241
>>> Message.from_comment("foo.py", 1, "R: foo")
192
- Message(filename='foo.py', lineno=1, colno=None, severity=Severity.NOTE, message="Revealed type is 'foo'", revealed_type='foo')
242
+ Message(filename='foo.py', lineno=1, colno=None, severity=Severity.NOTE, message="Revealed type is 'foo'", revealed_type='foo', error_code=None)
243
+ >>> Message.from_comment("foo.py", 1, "E: [assignment]")
244
+ Message(filename='foo.py', lineno=1, colno=None, severity=Severity.ERROR, message='', revealed_type=None, error_code='assignment')
193
245
"""
194
246
m = cls .COMMENT_RE .match (comment .strip ())
195
247
if not m :
196
248
raise ValueError ("Not a valid mypy message comment" )
197
249
colno = int (m .group ("colno" )) if m .group ("colno" ) else None
198
- message = m .group ("message" ).strip ()
250
+ message , error_code = cls .__split_message_and_error_code (
251
+ m .group ("message_and_error_code" )
252
+ )
199
253
if m .group ("severity" ) == "R" :
200
254
revealed_type = message
201
255
message = "Revealed type is {!r}" .format (message )
@@ -208,37 +262,45 @@ def from_comment(
208
262
severity = Severity .from_string (m .group ("severity" )),
209
263
message = message ,
210
264
revealed_type = revealed_type ,
211
- error_code = m . group ( " error_code" ) or None ,
265
+ error_code = error_code ,
212
266
)
213
267
214
268
@classmethod
215
269
def from_output (cls , line : str ) -> "Message" :
216
270
"""Create message object from mypy output line.
217
271
218
272
>>> m = Message.from_output("z.py:1: note: bar")
219
- >>> (m.lineno, m.colno, m.severity, m.message, m.revealed_type)
220
- (1, None, Severity.NOTE, 'bar', None)
273
+ >>> (m.lineno, m.colno, m.severity, m.message, m.revealed_type, m.error_code )
274
+ (1, None, Severity.NOTE, 'bar', None, None )
221
275
222
276
>>> m = Message.from_output("z.py:1:13: note: bar")
223
- >>> (m.lineno, m.colno, m.severity, m.message, m.revealed_type)
224
- (1, 13, Severity.NOTE, 'bar', None)
277
+ >>> (m.lineno, m.colno, m.severity, m.message, m.revealed_type, m.error_code )
278
+ (1, 13, Severity.NOTE, 'bar', None, None )
225
279
226
280
>>> m = Message.from_output("z.py:1: note: Revealed type is 'bar'")
227
- >>> (m.lineno, m.colno, m.severity, m.message, m.revealed_type)
228
- (1, None, Severity.NOTE, "Revealed type is 'bar'", 'bar')
281
+ >>> (m.lineno, m.colno, m.severity, m.message, m.revealed_type, m.error_code )
282
+ (1, None, Severity.NOTE, "Revealed type is 'bar'", 'bar', None )
229
283
230
284
>>> m = Message.from_output('z.py:1: note: Revealed type is "bar"')
231
- >>> (m.lineno, m.colno, m.severity, m.message, m.revealed_type)
232
- (1, None, Severity.NOTE, 'Revealed type is "bar"', 'bar')
285
+ >>> (m.lineno, m.colno, m.severity, m.message, m.revealed_type, m.error_code)
286
+ (1, None, Severity.NOTE, 'Revealed type is "bar"', 'bar', None)
287
+
288
+ >>> m = Message.from_output("z.py:1:13: error: bar [baz]")
289
+ >>> (m.lineno, m.colno, m.severity, m.message, m.revealed_type, m.error_code)
290
+ (1, 13, Severity.ERROR, 'bar', None, 'baz')
233
291
234
292
"""
235
293
m = cls .OUTPUT_RE .match (line )
236
294
if not m :
237
295
raise ValueError ("Not a valid mypy message" )
296
+ message , error_code = cls .__split_message_and_error_code (
297
+ m .group ("message_and_error_code" )
298
+ )
238
299
return cls (
239
300
os .path .abspath (m .group ("fname" )),
240
301
lineno = int (m .group ("lineno" )),
241
302
colno = int (m .group ("colno" )) if m .group ("colno" ) else None ,
242
303
severity = Severity [m .group ("severity" ).upper ()],
243
- message = m .group ("message" ).strip (),
304
+ message = message ,
305
+ error_code = error_code ,
244
306
)
0 commit comments