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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
|
-- Formatting for the command-line interface of the static analyzer explcheck.
-- Transform a singular into plural if the count is zero or greater than two.
local function pluralize(singular, count)
if count == 1 then
return singular
else
return singular .. "s"
end
end
-- Shorten a pathname, so that it does not exceed maximum length.
local function format_pathname(pathname, max_length)
-- First, replace path segments with `/.../`, keeping other segments.
local first_iteration = true
while #pathname > max_length do
local pattern
if first_iteration then
pattern = "([^/]*)/[^/]*/(.*)"
else
pattern = "([^/]*)/%.%.%./[^/]*/(.*)"
end
local prefix_start, _, prefix, suffix = pathname:find(pattern)
if prefix_start == nil or prefix_start > 1 then
break
end
pathname = prefix .. "/.../" .. suffix
first_iteration = false
end
-- If this isn't enough, remove the initial path segment and prefix the filename with `...`.
if #pathname > max_length then
local pattern
if first_iteration then
pattern = "([^/]*/?)(.*)"
else
pattern = "([^/]*/%.%.%./)(.*)"
end
local prefix_start, _, _, suffix = pathname:find(pattern)
if prefix_start == 1 then
pathname = ".../" .. suffix
if #pathname > max_length then
pathname = "..." .. suffix:sub(-(max_length - #("...")))
end
end
end
return pathname
end
-- Colorize a string using ASCII color codes.
local function colorize(text, ...)
local buffer = {}
for _, color_code in ipairs({...}) do
table.insert(buffer, "\27[")
table.insert(buffer, tostring(color_code))
table.insert(buffer, "m")
end
table.insert(buffer, text)
table.insert(buffer, "\27[0m")
return table.concat(buffer, "")
end
-- Remove ASCII color codes from a string.
local function decolorize(text)
return text:gsub("\27%[[0-9]+m", "")
end
-- Convert a byte number in a file to a line and column number in a file.
local function convert_byte_to_line_and_column(line_starting_byte_numbers, byte_number)
local line_number = 0
for _, line_starting_byte_number in ipairs(line_starting_byte_numbers) do
if line_starting_byte_number > byte_number then
break
end
line_number = line_number + 1
end
assert(line_number > 0)
local line_starting_byte_number = line_starting_byte_numbers[line_number]
assert(line_starting_byte_number <= byte_number)
local column_number = byte_number - line_starting_byte_number + 1
return line_number, column_number
end
-- Print the results of analyzing a file.
local function print_results(pathname, issues, line_starting_byte_numbers, is_last_file)
-- Display an overview.
local all_issues = {}
local status
if(#issues.errors > 0) then
status = (
colorize(
(
tostring(#issues.errors)
.. " "
.. pluralize("error", #issues.errors)
), 1, 31
)
)
table.insert(all_issues, issues.errors)
if(#issues.warnings > 0) then
status = (
status
.. ", "
.. colorize(
(
tostring(#issues.warnings)
.. " "
.. pluralize("warning", #issues.warnings)
), 1, 33
)
)
table.insert(all_issues, issues.warnings)
end
elseif(#issues.warnings > 0) then
status = colorize(
(
tostring(#issues.warnings)
.. " "
.. pluralize("warning", #issues.warnings)
), 1, 33
)
table.insert(all_issues, issues.warnings)
else
status = colorize("OK", 1, 32)
end
local max_overview_length = 72
local prefix = "Checking "
local formatted_pathname = format_pathname(
pathname,
math.max(
(
max_overview_length
- #prefix
- #(" ")
- #decolorize(status)
), 1
)
)
local overview = (
prefix
.. formatted_pathname
.. (" "):rep(
math.max(
(
max_overview_length
- #prefix
- #decolorize(status)
- #formatted_pathname
), 1
)
)
.. status
)
io.write("\n" .. overview)
-- Display the errors, followed by warnings.
if #all_issues > 0 then
for _, warnings_or_errors in ipairs(all_issues) do
print()
-- Display the warnings/errors.
for _, issue in ipairs(issues.sort(warnings_or_errors)) do
local code = issue[1]
local message = issue[2]
local range = issue[3]
local position = ":"
if range ~= nil then
local line_number, column_number = convert_byte_to_line_and_column(line_starting_byte_numbers, range[1])
position = position .. tostring(line_number) .. ":" .. tostring(column_number) .. ":"
end
local max_line_length = 88
local reserved_position_length = 10
local reserved_suffix_length = 30
local label_indent = (" "):rep(4)
local suffix = code:upper() .. " " .. message
formatted_pathname = format_pathname(
pathname,
math.max(
(
max_line_length
- #label_indent
- reserved_position_length
- #(" ")
- math.max(#suffix, reserved_suffix_length)
), 1
)
)
local line = (
label_indent
.. formatted_pathname
.. position
.. (" "):rep(
math.max(
(
max_line_length
- #label_indent
- #formatted_pathname
- #decolorize(position)
- math.max(#suffix, reserved_suffix_length)
), 1
)
)
.. suffix
.. (" "):rep(math.max(reserved_suffix_length - #suffix, 0))
)
io.write("\n" .. line)
end
end
if(not is_last_file) then
print()
end
end
end
-- Print the summary results of analyzing multiple files.
local function print_summary(num_pathnames, num_warnings, num_errors)
io.write("\n\nTotal: ")
local errors_message = tostring(num_errors) .. " " .. pluralize("error", num_errors)
errors_message = colorize(errors_message, 1, (num_errors > 0 and 31) or 32)
io.write(errors_message .. ", ")
local warnings_message = tostring(num_warnings) .. " " .. pluralize("warning", num_warnings)
warnings_message = colorize(warnings_message, 1, (num_warnings > 0 and 33) or 32)
io.write(warnings_message .. " in ")
print(tostring(num_pathnames) .. " " .. pluralize("file", num_pathnames))
end
return {
convert_byte_to_line_and_column = convert_byte_to_line_and_column,
print_results = print_results,
print_summary = print_summary,
}
|