5 from dataclasses
import dataclass
6 from typing
import IO
, List
, Tuple
24 class PositionedMessage
:
32 message
: PositionedMessage
33 reason
: List
[PositionedMessage
]
36 class ParseException(Exception):
44 reasons
: List
[PositionedMessage
],
46 return Error(errorCode
, PositionedMessage(position
, message
), reasons
)
49 def end_of_file(line
: str) -> bool:
50 return line
== "" or line
== "\n"
53 def parse_errors(output_file_name
: str) -> List
[Error
]:
54 with
open(output_file_name
) as output_file
:
56 return parse_error(output_file
, True)
57 except ParseException
:
59 return parse_error(output_file
, False)
60 except ParseException
as ex
:
61 raise ParseException(f
"at file {output_file_name}: {ex}")
64 def same_error(line
: str, multiple_error_file
: bool) -> bool:
65 if multiple_error_file
:
66 return starts_with_space(line
)
68 return not end_of_file(line
)
71 def parse_error(output_file
: IO
[str], multiple_error_file
: bool) -> List
[Error
]:
73 line
= output_file
.readline()
74 while not end_of_file(line
):
75 if line
== "No errors\n":
77 position
= parse_position(line
)
78 line
= output_file
.readline()
79 if starts_with_space(line
):
80 # this is part of an hh_show, so ignore and continue
81 while starts_with_space(line
):
82 line
= output_file
.readline()
84 (message
, errorCode
) = parse_message_and_code(output_file
, line
)
85 line
= output_file
.readline()
87 while same_error(line
, multiple_error_file
):
88 reasonPos
= parse_position(line
)
89 line
= output_file
.readline()
90 (line
, reasonMessage
) = parse_message(output_file
, line
)
91 reason
= PositionedMessage(reasonPos
, reasonMessage
)
92 reasons
.append(reason
)
93 errors
.append(make_error(errorCode
, position
, message
, reasons
))
97 position_regex
= r
'^\s*File "(.*)", line (\d+), characters (\d+)-(\d+):(\[\d+\])?\n'
100 def parse_position(line
: str) -> Position
:
101 match
= re
.match(position_regex
, line
)
103 raise ParseException(f
"Could not parse position line: {line}")
104 file = match
.group(1)
105 lineNum
= int(match
.group(2))
106 startCol
= int(match
.group(3))
107 endCol
= int(match
.group(4))
108 return Position(file, lineNum
, startCol
, endCol
)
111 def parse_message_and_code(file: IO
[str], line
: str) -> Tuple
[str, ErrorCode
]:
113 message_and_code_regex
= r
"^\s*(.*) \((.*)\[(\d+)\]\)\n"
114 match
= re
.match(message_and_code_regex
, line
)
116 match
= re
.match(r
"^\s*(.*)\n", line
)
118 raise ParseException(f
"Could not parse message line: {line}")
119 message_line
= match
.group(1)
120 message_chunks
.append(message_line
)
121 line
= file.readline()
122 match
= re
.match(message_and_code_regex
, line
)
123 assert match
is not None, "should have raised exception above"
124 message_line
= match
.group(1)
125 type = match
.group(2)
126 code
= int(match
.group(3))
127 message_chunks
.append(message_line
)
128 message
= "".join(message_chunks
)
129 return (message
, ErrorCode(type, code
))
132 def parse_message(file: IO
[str], line
: str) -> Tuple
[str, str]:
134 message_regex
= r
"^\s*(.*)\n"
136 match
= re
.match(message_regex
, line
)
138 raise ParseException(f
"Could not parse message line: {line}")
139 message_line
= match
.group(1)
140 message_chunks
.append(message_line
)
142 line
= file.readline()
143 match
= re
.match(position_regex
, line
)
144 while match
is None and not end_of_file(line
):
145 match
= re
.match(message_regex
, line
)
147 raise ParseException(f
"Could not parse message line: {line}")
148 message_line
= match
.group(1)
149 message_chunks
.append(message_line
)
151 line
= file.readline()
152 match
= re
.match(position_regex
, line
)
153 message
= "".join(message_chunks
)
154 return (line
, message
)
157 def starts_with_space(s
: str) -> bool:
158 return re
.match(r
"^\s", s
) is not None
161 def sprint_error(error
: Error
) -> str:
162 file = error
.message
.position
.fileName
163 line
= error
.message
.position
.line
164 startCol
= error
.message
.position
.startColumn
165 endCol
= error
.message
.position
.endColumn
166 message
= error
.message
.message
167 type = error
.code
.type
168 code
= error
.code
.code
170 f
"\033[91m{file}:{line}:{startCol},{endCol}:\033[0m "
171 f
"{message} ({type}[{code}])\n"
174 for reason
in error
.reason
:
175 file = reason
.position
.fileName
176 line
= reason
.position
.line
177 startCol
= reason
.position
.startColumn
178 endCol
= reason
.position
.endColumn
179 message
= reason
.message
180 out
.append(f
" \033[91m{file}:{line}:{startCol},{endCol}:\033[0m {message}\n")
184 def sprint_errors(errors
: List
[Error
]) -> str:
189 out
.append(sprint_error(error
))