5 from dataclasses
import dataclass
7 from typing
import IO
, List
, Tuple
25 class PositionedMessage
:
30 class Severity(str, Enum
):
39 message
: PositionedMessage
40 reason
: List
[PositionedMessage
]
43 class ParseException(Exception):
52 reasons
: List
[PositionedMessage
],
54 return Error(severity
, errorCode
, PositionedMessage(position
, message
), reasons
)
57 def end_of_file(line
: str) -> bool:
58 return line
== "" or line
== "\n"
61 def parse_errors(output_file_name
: str) -> List
[Error
]:
62 with
open(output_file_name
) as output_file
:
64 return parse_error(output_file
, True)
65 except ParseException
:
67 return parse_error(output_file
, False)
68 except ParseException
as ex
:
69 raise ParseException(f
"at file {output_file_name}: {ex}")
72 def same_error(line
: str, multiple_error_file
: bool) -> bool:
73 if multiple_error_file
:
74 return starts_with_space(line
)
76 return not end_of_file(line
)
79 # parse a "plain" formatted error. The expected format is emittted by
80 # Errors.to_string (OCaml) or Error::display_plain() (Rust).
81 def parse_error(output_file
: IO
[str], multiple_error_file
: bool) -> List
[Error
]:
83 line
= output_file
.readline()
84 while not end_of_file(line
):
85 if line
== "No errors\n":
87 severity_string
, position_string
= line
.split(": ", 1)
88 severity
= Severity(severity_string
)
89 position
= parse_position(line
)
90 line
= output_file
.readline()
91 if starts_with_space(line
):
92 # this is part of an hh_show, so ignore and continue
93 while starts_with_space(line
):
94 line
= output_file
.readline()
96 (message
, errorCode
) = parse_message_and_code(output_file
, line
)
97 line
= output_file
.readline()
99 while same_error(line
, multiple_error_file
):
100 reasonPos
= parse_position(line
)
101 line
= output_file
.readline()
102 (line
, reasonMessage
) = parse_message(output_file
, line
)
103 reason
= PositionedMessage(reasonPos
, reasonMessage
)
104 reasons
.append(reason
)
105 errors
.append(make_error(severity
, errorCode
, position
, message
, reasons
))
109 position_regex
= r
'^\s*File "(.*)", line (\d+), characters (\d+)-(\d+):(\[\d+\])?\n'
112 def parse_position(line
: str) -> Position
:
113 match
= re
.match(position_regex
, line
)
115 raise ParseException(f
"Could not parse position line: {line}")
116 file = match
.group(1)
117 lineNum
= int(match
.group(2))
118 startCol
= int(match
.group(3))
119 endCol
= int(match
.group(4))
120 return Position(file, lineNum
, startCol
, endCol
)
123 def parse_message_and_code(file: IO
[str], line
: str) -> Tuple
[str, ErrorCode
]:
125 message_and_code_regex
= r
"^\s*(.*) \((.*)\[(\d+)\]\)\n"
126 match
= re
.match(message_and_code_regex
, line
)
128 match
= re
.match(r
"^\s*(.*)\n", line
)
130 raise ParseException(f
"Could not parse message line: {line}")
131 message_line
= match
.group(1)
132 message_chunks
.append(message_line
)
133 line
= file.readline()
134 match
= re
.match(message_and_code_regex
, line
)
135 assert match
is not None, "should have raised exception above"
136 message_line
= match
.group(1)
137 type = match
.group(2)
138 code
= int(match
.group(3))
139 message_chunks
.append(message_line
)
140 message
= "".join(message_chunks
)
141 return (message
, ErrorCode(type, code
))
144 def parse_message(file: IO
[str], line
: str) -> Tuple
[str, str]:
146 message_regex
= r
"^\s*(.*)\n"
148 match
= re
.match(message_regex
, line
)
150 raise ParseException(f
"Could not parse message line: {line}")
151 message_line
= match
.group(1)
152 message_chunks
.append(message_line
)
154 line
= file.readline()
155 match
= re
.match(position_regex
, line
)
156 while match
is None and not end_of_file(line
):
157 match
= re
.match(message_regex
, line
)
159 raise ParseException(f
"Could not parse message line: {line}")
160 message_line
= match
.group(1)
161 message_chunks
.append(message_line
)
163 line
= file.readline()
164 match
= re
.match(position_regex
, line
)
165 message
= "".join(message_chunks
)
166 return (line
, message
)
169 def starts_with_space(s
: str) -> bool:
170 return re
.match(r
"^\s", s
) is not None
173 def sprint_error(error
: Error
) -> str:
174 file = error
.message
.position
.fileName
175 line
= error
.message
.position
.line
176 startCol
= error
.message
.position
.startColumn
177 endCol
= error
.message
.position
.endColumn
178 message
= error
.message
.message
179 type = error
.code
.type
180 code
= error
.code
.code
182 f
"\033[91m{file}:{line}:{startCol},{endCol}:\033[0m "
183 f
"{message} ({type}[{code}])\n"
186 for reason
in error
.reason
:
187 file = reason
.position
.fileName
188 line
= reason
.position
.line
189 startCol
= reason
.position
.startColumn
190 endCol
= reason
.position
.endColumn
191 message
= reason
.message
192 out
.append(f
" \033[91m{file}:{line}:{startCol},{endCol}:\033[0m {message}\n")
196 def sprint_errors(errors
: List
[Error
]) -> str:
201 out
.append(sprint_error(error
))