• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lunarmodules / luacheck / 24662353255

20 Apr 2026 10:48AM UTC coverage: 97.095% (+0.07%) from 97.027%
24662353255

push

github

web-flow
fix(ci): build LuaJIT using system malloc for lualanes tests (#144)

6318 of 6507 relevant lines covered (97.1%)

26928.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/src/luacheck/format.lua
1
local stages = require "luacheck.stages"
780✔
2
local utils = require "luacheck.utils"
780✔
3

4
local format = {}
780✔
5

6
local color_support = not utils.is_windows or os.getenv("ANSICON")
780✔
7
-- Disable colors when NO_COLOR is set, see https://no-color.org/.
8
color_support = color_support and not os.getenv("NO_COLOR")
780✔
9

10
local function get_message_format(warning)
11
   local message_format = assert(stages.warnings[warning.code], "Unknown warning code " .. warning.code).message_format
2,526✔
12

13
   if type(message_format) == "function" then
2,526✔
14
      return message_format(warning)
822✔
15
   else
16
      return message_format
1,704✔
17
   end
18
end
19

20
local function plural(number)
21
   return (number == 1) and "" or "s"
3,030✔
22
end
23

24
local color_codes = {
780✔
25
   reset = 0,
520✔
26
   bright = 1,
520✔
27
   red = 31,
520✔
28
   green = 32
520✔
29
}
30

31
local function encode_color(c)
32
   return "\27[" .. tostring(color_codes[c]) .. "m"
1,716✔
33
end
34

35
local function colorize(str, ...)
36
   str = str .. encode_color("reset")
640✔
37

38
   for _, color in ipairs({...}) do
1,236✔
39
      str = encode_color(color) .. str
1,008✔
40
   end
41

42
   return encode_color("reset") .. str
640✔
43
end
44

45
local function format_color(str, color, ...)
46
   return color and colorize(str, ...) or str
2,674✔
47
end
48

49
local function format_number(number, color)
50
   return format_color(tostring(number), color, "bright", (number > 0) and "red" or "reset")
1,308✔
51
end
52

53
-- Substitutes markers within string format with values from a table.
54
-- "{field_name}" marker is replaced with `values.field_name`.
55
-- "{field_name!}" marker adds highlight or quoting depending on color
56
-- option.
57
local function substitute(string_format, values, color)
58
   return (string_format:gsub("{([_a-zA-Z0-9]+)(!?)}", function(field_name, highlight)
5,052✔
59
      local value = tostring(assert(values[field_name], "No field " .. field_name))
2,700✔
60

61
      if highlight == "!" then
2,700✔
62
         if color then
1,938✔
63
            return colorize(value, "bright")
180✔
64
         else
65
            return "'" .. value .. "'"
1,758✔
66
         end
67
      else
68
         return value
762✔
69
      end
70
   end))
71
end
72

73
local function format_message(event, color)
74
   return substitute(get_message_format(event), event, color)
3,368✔
75
end
76

77
-- Returns formatted message for an issue, without color.
78
function format.get_message(event)
780✔
79
   return format_message(event)
78✔
80
end
81

82
local function capitalize(str)
83
   return str:gsub("^.", string.upper)
72✔
84
end
85

86
local function fatal_type(file_report)
87
   return capitalize(file_report.fatal) .. " error"
96✔
88
end
89

90
local function count_warnings_errors(events)
91
   local warnings, errors = 0, 0
960✔
92

93
   for _, event in ipairs(events) do
4,818✔
94
      if event.code:sub(1, 1) == "0" then
5,144✔
95
         errors = errors + 1
288✔
96
      else
97
         warnings = warnings + 1
3,570✔
98
      end
99
   end
100

101
   return warnings, errors
960✔
102
end
103

104
local function format_file_report_header(report, file_name, opts)
105
   local label = "Checking " .. file_name
1,200✔
106
   local status
107

108
   if report.fatal then
1,200✔
109
      status = format_color(fatal_type(report), opts.color, "bright")
80✔
110
   elseif #report == 0 then
1,152✔
111
      status = format_color("OK", opts.color, "bright", "green")
256✔
112
   else
113
      local warnings, errors = count_warnings_errors(report)
960✔
114

115
      if warnings > 0 then
960✔
116
         status = format_color(tostring(warnings).." warning"..plural(warnings), opts.color, "bright", "red")
1,430✔
117
      end
118

119
      if errors > 0 then
960✔
120
         status = status and (status.." / ") or ""
168✔
121
         status = status..(format_color(tostring(errors).." error"..plural(errors), opts.color, "bright"))
280✔
122
      end
123
   end
124

125
   return label .. (" "):rep(math.max(50 - #label, 1)) .. status
1,200✔
126
end
127

128
local function format_location(file, location, opts)
129
   local res = ("%s:%d:%d"):format(file, location.line, location.column)
2,412✔
130

131
   if opts.ranges then
2,412✔
132
      res = ("%s-%d"):format(res, location.end_column)
156✔
133
   end
134

135
   return res
2,412✔
136
end
137

138
local function event_code(event)
139
   return (event.code:sub(1, 1) == "0" and "E" or "W")..event.code
280✔
140
end
141

142
local function format_event(file_name, event, opts)
143
   local message = format_message(event, opts.color)
2,412✔
144

145
   if opts.codes then
2,412✔
146
      message = ("(%s) %s"):format(event_code(event), message)
184✔
147
   end
148

149
   return format_location(file_name, event, opts) .. ": " .. message
3,216✔
150
end
151

152
local function format_file_report(report, file_name, opts)
153
   local buf = {format_file_report_header(report, file_name, opts)}
1,104✔
154

155
   if #report > 0 then
828✔
156
      table.insert(buf, "")
594✔
157

158
      for _, event in ipairs(report) do
2,826✔
159
         table.insert(buf, "    " .. format_event(file_name, event, opts))
2,976✔
160
      end
161

162
      table.insert(buf, "")
594✔
163
   elseif report.fatal then
234✔
164
      table.insert(buf, "")
42✔
165
      table.insert(buf, "    " .. file_name .. ": " .. report.msg)
42✔
166
      table.insert(buf, "")
42✔
167
   end
168

169
   return table.concat(buf, "\n")
828✔
170
end
171

172
local function escape_xml(str)
173
   str = str:gsub("&", "&")
174✔
174
   str = str:gsub('"', """)
174✔
175
   str = str:gsub("'", "'")
174✔
176
   str = str:gsub("<", "&lt;")
174✔
177
   str = str:gsub(">", "&gt;")
174✔
178
   return str
174✔
179
end
180

181
format.builtin_formatters = {}
780✔
182

183
function format.builtin_formatters.default(report, file_names, opts)
1,560✔
184
   local buf = {}
654✔
185

186
   if opts.quiet <= 2 then
654✔
187
      for i, file_report in ipairs(report) do
1,908✔
188
         if opts.quiet == 0 or file_report.fatal or #file_report > 0 then
1,278✔
189
            table.insert(buf, (opts.quiet == 2 and format_file_report_header or format_file_report) (
2,800✔
190
               file_report, file_names[i], opts))
1,200✔
191
         end
192
      end
193

194
      if #buf > 0 and buf[#buf]:sub(-1) ~= "\n" then
838✔
195
         table.insert(buf, "")
198✔
196
      end
197
   end
198

199
   local total = ("Total: %s warning%s / %s error%s in %d file%s"):format(
1,308✔
200
      format_number(report.warnings, opts.color), plural(report.warnings),
872✔
201
      format_number(report.errors, opts.color), plural(report.errors),
872✔
202
      #report - report.fatals, plural(#report - report.fatals))
654✔
203

204
   if report.fatals > 0 then
654✔
205
      total = total..(", couldn't check %s file%s"):format(
84✔
206
         report.fatals, plural(report.fatals))
70✔
207
   end
208

209
   table.insert(buf, total)
654✔
210
   return table.concat(buf, "\n")
654✔
211
end
212

213
function format.builtin_formatters.TAP(report, file_names, opts)
1,560✔
214
   opts.color = false
12✔
215
   local buf = {}
12✔
216

217
   for i, file_report in ipairs(report) do
60✔
218
      if file_report.fatal then
48✔
219
         table.insert(buf, ("not ok %d %s: %s"):format(#buf + 1, file_names[i], fatal_type(file_report)))
16✔
220
      elseif #file_report == 0 then
36✔
221
         table.insert(buf, ("ok %d %s"):format(#buf + 1, file_names[i]))
12✔
222
      else
223
         for _, warning in ipairs(file_report) do
96✔
224
            table.insert(buf, ("not ok %d %s"):format(#buf + 1, format_event(file_names[i], warning, opts)))
96✔
225
         end
226
      end
227
   end
228

229
   table.insert(buf, 1, "1.." .. tostring(#buf))
12✔
230
   return table.concat(buf, "\n")
12✔
231
end
232

233
function format.builtin_formatters.JUnit(report, file_names)
1,560✔
234
   -- JUnit formatter doesn't support any options.
235
   local opts = {}
6✔
236
   local buf = {[[<?xml version="1.0" encoding="UTF-8"?>]]}
6✔
237
   local num_testcases = 0
6✔
238

239
   for _, file_report in ipairs(report) do
30✔
240
      if file_report.fatal or #file_report == 0 then
24✔
241
         num_testcases = num_testcases + 1
12✔
242
      else
243
         num_testcases = num_testcases + #file_report
12✔
244
      end
245
   end
246

247
   table.insert(buf, ([[<testsuite name="Luacheck report" tests="%d">]]):format(num_testcases))
6✔
248

249
   for file_i, file_report in ipairs(report) do
30✔
250
      if file_report.fatal then
24✔
251
         table.insert(buf, ([[    <testcase name="%s" classname="%s">]]):format(
12✔
252
            escape_xml(file_names[file_i]), escape_xml(file_names[file_i])))
8✔
253
         table.insert(buf, ([[        <error type="%s"/>]]):format(escape_xml(fatal_type(file_report))))
10✔
254
         table.insert(buf, [[    </testcase>]])
6✔
255
      elseif #file_report == 0 then
18✔
256
         table.insert(buf, ([[    <testcase name="%s" classname="%s"/>]]):format(
12✔
257
            escape_xml(file_names[file_i]), escape_xml(file_names[file_i])))
14✔
258
      else
259
         for event_i, event in ipairs(file_report) do
48✔
260
            table.insert(buf, ([[    <testcase name="%s:%d" classname="%s">]]):format(
72✔
261
               escape_xml(file_names[file_i]), event_i, escape_xml(file_names[file_i])))
48✔
262
            table.insert(buf, ([[        <failure type="%s" message="%s"/>]]):format(
72✔
263
               escape_xml(event_code(event)), escape_xml(format_event(file_names[file_i], event, opts))))
72✔
264
            table.insert(buf, [[    </testcase>]])
36✔
265
         end
266
      end
267
   end
268

269
   table.insert(buf, [[</testsuite>]])
6✔
270
   return table.concat(buf, "\n")
6✔
271
end
272

273
local fatal_error_codes = {
780✔
274
   ["I/O"] = "F1",
520✔
275
   ["syntax"] = "F2",
520✔
276
   ["runtime"] = "F3"
520✔
277
}
278

279
function format.builtin_formatters.visual_studio(report, file_names)
1,560✔
280
   local buf = {}
6✔
281

282
   for i, file_report in ipairs(report) do
30✔
283
      if file_report.fatal then
24✔
284
         -- Older docs suggest that line number after a file name is optional; newer docs mark it as required.
285
         -- Just use tool name as origin and put file name into the message.
286
         table.insert(buf, ("luacheck : fatal error %s: couldn't check %s: %s"):format(
12✔
287
            fatal_error_codes[file_report.fatal], file_names[i], file_report.msg))
12✔
288
      else
289
         for _, event in ipairs(file_report) do
54✔
290
               -- Older documentation on the format suggests that it could support column range.
291
               -- Newer docs don't mention it. Don't use it for now.
292
               local event_type = event.code:sub(1, 1) == "0" and "error" or "warning"
48✔
293
               local message = format_message(event)
36✔
294
               table.insert(buf, ("%s(%d,%d) : %s %s: %s"):format(
72✔
295
                  file_names[i], event.line, event.column, event_type, event_code(event), message))
48✔
296
         end
297
      end
298
   end
299

300
   return table.concat(buf, "\n")
6✔
301
end
302

303
function format.builtin_formatters.plain(report, file_names, opts)
1,560✔
304
   opts.color = false
24✔
305
   local buf = {}
24✔
306

307
   for i, file_report in ipairs(report) do
72✔
308
      if file_report.fatal then
48✔
309
         table.insert(buf, ("%s: %s (%s)"):format(file_names[i], fatal_type(file_report), file_report.msg))
8✔
310
      else
311
         for _, event in ipairs(file_report) do
114✔
312
            table.insert(buf, format_event(file_names[i], event, opts))
96✔
313
         end
314
      end
315
   end
316

317
   return table.concat(buf, "\n")
24✔
318
end
319

320
--- Formats a report.
321
-- Recognized options:
322
--    `options.formatter`: name of used formatter. Default: "default".
323
--    `options.quiet`: integer in range 0-3. See CLI. Default: 0.
324
--    `options.color`: should use ansicolors? Default: true.
325
--    `options.codes`: should output warning codes? Default: false.
326
--    `options.ranges`: should output token end column? Default: false.
327
function format.format(report, file_names, options)
780✔
328
   return format.builtin_formatters[options.formatter or "default"](report, file_names, {
1,170✔
329
      quiet = options.quiet or 0,
702✔
330
      color = (options.color ~= false) and color_support,
702✔
331
      codes = options.codes,
702✔
332
      ranges = options.ranges
702✔
333
   })
234✔
334
end
335

336
return format
780✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc