• 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

99.55
/src/luacheck/options.lua
1
local options = {}
882✔
2

3
local builtin_standards = require "luacheck.builtin_standards"
882✔
4
local standards = require "luacheck.standards"
882✔
5
local utils = require "luacheck.utils"
882✔
6

7
local boolean = utils.has_type("boolean")
882✔
8
local number_or_false = utils.has_type_or_false("number")
882✔
9
local array_of_strings = utils.array_of("string")
882✔
10

11
-- Validates std string.
12
-- Returns an array of std names with `add` field if there is `+` at the beginning of the string.
13
-- On validation error returns `nil` and an error message.
14
local function split_std(std, stds)
15
   local parts = utils.split(std, "+")
6,778✔
16

17
   if parts[1]:match("^%s*$") then
6,778✔
18
      parts.add = true
4,642✔
19
      table.remove(parts, 1)
4,642✔
20
   end
21

22
   for i, part in ipairs(parts) do
13,580✔
23
      parts[i] = utils.strip(part)
9,130✔
24

25
      if not stds[parts[i]] then
6,850✔
26
         return nil, ("unknown std '%s'"):format(parts[i])
48✔
27
      end
28
   end
29

30
   return parts
6,730✔
31
end
32

33
local function std_or_array_of_strings(x, stds)
34
   if type(x) == "string" then
5,686✔
35
      local ok, err = split_std(x, stds)
5,674✔
36
      return not not ok, err
5,674✔
37
   elseif type(x) == "table" then
12✔
38
      return standards.validate_std_table(x)
6✔
39
   else
40
      return false, "string or table expected, got " .. type(x)
6✔
41
   end
42
end
43

44
local function field_map(x)
45
   if type(x) == "table" then
462✔
46
      return standards.validate_globals_table(x)
438✔
47
   else
48
      return false, "table expected, got " .. type(x)
24✔
49
   end
50
end
51

52
options.nullary_inline_options = {
882✔
53
   global = boolean,
882✔
54
   unused = boolean,
882✔
55
   redefined = boolean,
882✔
56
   unused_args = boolean,
882✔
57
   unused_secondaries = boolean,
882✔
58
   self = boolean,
882✔
59
   compat = boolean,
882✔
60
   allow_defined = boolean,
882✔
61
   allow_defined_top = boolean,
882✔
62
   module = boolean
882✔
63
}
882✔
64

65
options.variadic_inline_options = {
882✔
66
   globals = field_map,
882✔
67
   read_globals = field_map,
882✔
68
   new_globals = field_map,
882✔
69
   new_read_globals = field_map,
882✔
70
   not_globals = array_of_strings,
882✔
71
   ignore = array_of_strings,
882✔
72
   enable = array_of_strings,
882✔
73
   only = array_of_strings,
882✔
74
}
882✔
75

76
options.all_options = {
882✔
77
   std = std_or_array_of_strings,
882✔
78
   max_line_length = number_or_false,
882✔
79
   max_code_line_length = number_or_false,
882✔
80
   max_string_line_length = number_or_false,
882✔
81
   max_comment_line_length = number_or_false,
882✔
82
   max_cyclomatic_complexity = number_or_false,
882✔
83
   operators = array_of_strings
882✔
84
}
882✔
85

86
utils.update(options.all_options, options.nullary_inline_options)
882✔
87
utils.update(options.all_options, options.variadic_inline_options)
882✔
88

89
-- Returns true if opts is valid option_set or is nil.
90
-- Otherwise returns false and an error message.
91
function options.validate(option_set, opts, stds)
882✔
92
   if opts == nil then
14,632✔
93
      return true
1,050✔
94
   end
95

96
   if type(opts) ~= "table" then
13,582✔
97
      return false, "option table expected, got " .. type(opts)
24✔
98
   end
99

100
   stds = stds or builtin_standards
13,558✔
101

102
   for option, validator in utils.sorted_pairs(option_set) do
470,848✔
103
      if opts[option] ~= nil then
336,424✔
104
         local ok, err = validator(opts[option], stds)
11,548✔
105

106
         if not ok then
11,548✔
107
            return false, ("invalid value of option '%s': %s"):format(option, err)
168✔
108
         end
109
      end
110
   end
111

112
   return true
13,390✔
113
end
114

115
-- Option stack is an array of options with options closer to end
116
-- overriding options closer to beginning.
117

118
-- Extracts sequence of active std tables from an option stack.
119
local function get_std_tables(opts_stack, stds)
120
   local base_std
121
   local add_stds = {}
2,082✔
122
   local no_compat = false
2,082✔
123

124
   for _, opts in utils.ripairs(opts_stack) do
11,478✔
125
      if opts.compat and not no_compat then
7,050✔
126
         base_std = stds.max
12✔
127
         break
8✔
128
      elseif opts.compat == false then
7,038✔
129
         no_compat = true
6✔
130
      end
131

132
      if opts.std then
7,038✔
133
         if type(opts.std) == "table" then
1,110✔
134
            base_std = opts.std
6✔
135
            break
4✔
136
         else
137
            local parts = split_std(opts.std, stds)
1,104✔
138

139
            for _, part in ipairs(parts) do
2,250✔
140
               table.insert(add_stds, stds[part])
1,146✔
141
            end
142

143
            if not parts.add then
1,104✔
144
               base_std = {}
1,026✔
145
               break
684✔
146
            end
147
         end
148
      end
149
   end
150

151
   table.insert(add_stds, 1, base_std or stds.max)
2,082✔
152
   return add_stds
2,082✔
153
end
154

155
-- Returns index of the last option table in a stack that uses given option,
156
-- or zero if the option isn't used anywhere.
157
local function index_of_last_option_usage(opts_stack, option_name)
158
   for index, opts in utils.ripairs(opts_stack) do
29,628✔
159
      if opts[option_name] then
17,040✔
160
         return index
24✔
161
      end
162
   end
163

164
   return 0
4,140✔
165
end
166

167
local function split_field(field_name)
168
   return utils.split(field_name, "%.")
756✔
169
end
170

171
local function field_comparator(field1, field2)
172
   local parts1 = field1[1]
84✔
173
   local parts2 = field2[1]
84✔
174

175
   for i = 1, math.max(#parts1, #parts2) do
102✔
176
      local part1 = parts1[i]
96✔
177
      local part2 = parts2[i]
96✔
178

179
      if not part1 then
96✔
180
         return true
×
181
      elseif not part2 then
96✔
182
         return false
12✔
183
      end
184

185
      if part1 ~= part2 then
84✔
186
         return part1 < part2
66✔
187
      end
188
   end
189

190
   return false
6✔
191
end
192

193
-- Combine all stds and global related options into one final definition table.
194
-- A definition table may have fields `read_only` (boolean), `other_fields` (boolean),
195
-- and `fields` (maps field names to definition tables).
196
-- Std table format is similar, except at the top level there are two fields
197
-- `globals` and `read_globals` mapping to top-level field tables. Also in field tables
198
-- it's possible to use field names in array part as a shortcut:
199
-- `{fields = {"foo"}}` is equivalent to `{fields = {foo = {}}}` or `{fields = {foo = {other_fields = true}}}`
200
-- in top level fields tables.
201
local function get_final_std(opts_stack, stds)
202
   local final_std = {}
2,082✔
203
   local std_tables = get_std_tables(opts_stack, stds)
2,082✔
204

205
   for _, std_table in ipairs(std_tables) do
5,310✔
206
      standards.add_std_table(final_std, std_table)
3,228✔
207
   end
208

209
   local last_new_globals = index_of_last_option_usage(opts_stack, "new_globals")
2,082✔
210
   local last_new_read_globals = index_of_last_option_usage(opts_stack, "new_read_globals")
2,082✔
211

212
   for index, opts in ipairs(opts_stack) do
10,626✔
213
      local globals = (index >= last_new_globals) and (opts.new_globals or opts.globals)
8,544✔
214
      local read_globals = (index >= last_new_read_globals) and (opts.new_read_globals or opts.read_globals)
8,544✔
215

216
      local new_fields = {}
8,544✔
217

218
      if globals then
8,544✔
219
         for _, global in ipairs(globals) do
822✔
220
            table.insert(new_fields, {split_field(global), false})
616✔
221
         end
222
      end
223

224
      if read_globals then
8,544✔
225
         for _, read_global in ipairs(read_globals) do
396✔
226
            table.insert(new_fields, {split_field(read_global), true})
304✔
227
         end
228
      end
229

230
      if globals and read_globals then
8,544✔
231
         -- If there are both globals and read-only globals defined in one options table,
232
         -- it's important that more general definitions are applied first,
233
         -- otherwise they will completely overwrite more specific definitions.
234
         -- E.g. `globals x` should be applied before `read globals x.y`.
235
         table.sort(new_fields, field_comparator)
36✔
236
      end
237

238
      for _, field in ipairs(new_fields) do
9,234✔
239
         standards.overwrite_field(final_std, field[1], field[2])
690✔
240
      end
241

242
      standards.add_std_table(final_std, {globals = globals, read_globals = read_globals}, true, true)
8,544✔
243

244
      if opts.not_globals then
8,544✔
245
         for _, not_global in ipairs(opts.not_globals) do
114✔
246
            standards.remove_field(final_std, split_field(not_global))
88✔
247
         end
248
      end
249
   end
250

251
   standards.finalize(final_std)
2,082✔
252
   return final_std
2,082✔
253
end
254

255
local function get_scalar_opt(opts_stack, option, default)
256
   for _, opts in utils.ripairs(opts_stack) do
87,276✔
257
      if opts[option] ~= nil then
50,250✔
258
         return opts[option]
408✔
259
      end
260
   end
261

262
   return default
12,084✔
263
end
264

265
local line_length_suboptions = {"max_code_line_length", "max_string_line_length", "max_comment_line_length"}
882✔
266

267
local function get_max_line_opts(opts_stack)
268
   local res = {max_line_length = 120}
2,082✔
269

270
   for _, opt_name in ipairs(line_length_suboptions) do
8,328✔
271
      res[opt_name] = res.max_line_length
6,246✔
272
   end
273

274
   for _, opts in ipairs(opts_stack) do
10,626✔
275
      if opts.max_line_length ~= nil then
8,544✔
276
         res.max_line_length = opts.max_line_length
288✔
277

278
         for _, opt_name in ipairs(line_length_suboptions) do
1,152✔
279
            res[opt_name] = opts.max_line_length
864✔
280
         end
281
      end
282

283
      for _, opt_name in ipairs(line_length_suboptions) do
34,176✔
284
         if opts[opt_name] ~= nil then
25,632✔
285
            res[opt_name] = opts[opt_name]
288✔
286
         end
287
      end
288
   end
289

290
   return res
2,082✔
291
end
292

293
local function anchor_pattern(pattern, only_start)
294
   if not pattern then
4,776✔
295
      return
2,190✔
296
   end
297

298
   if pattern:sub(1, 1) == "^" or pattern:sub(-1) == "$" then
4,310✔
299
      return pattern
6✔
300
   else
301
      return "^" .. pattern .. (only_start and "" or "$")
2,580✔
302
   end
303
end
304

305
-- Returns {pair of normalized patterns for code and name}.
306
-- `pattern` can be:
307
--    string containing '/': first part matches warning code, second - variable name;
308
--    string containing letters: matches variable name;
309
--    otherwise: matches warning code.
310
-- Unless anchored by user, pattern for name is anchored from both sides
311
-- and pattern for code is only anchored at the beginning.
312
local function normalize_pattern(pattern)
313
   local code_pattern, name_pattern
314
   local slash_pos = pattern:find("/")
2,388✔
315

316
   if slash_pos then
2,388✔
317
      code_pattern = pattern:sub(1, slash_pos - 1)
264✔
318
      name_pattern = pattern:sub(slash_pos + 1)
264✔
319
   elseif pattern:find("[_a-zA-Z]") then
2,190✔
320
      name_pattern = pattern
882✔
321
   else
322
      code_pattern = pattern
1,308✔
323
   end
324

325
   return {anchor_pattern(code_pattern, true), anchor_pattern(name_pattern)}
3,980✔
326
end
327

328
-- From most specific to less specific, pairs {option, pattern}.
329
-- Applying macros in order is required to get deterministic results
330
-- and get sensible results when intersecting macros are used.
331
-- E.g. unused = false, unused_args = true should leave unused args enabled.
332
local macros = {
882✔
333
   {"unused_args", "21[23]"},
882✔
334
   {"global", "1"},
882✔
335
   {"unused", "[23]"},
882✔
336
   {"redefined", "4"}
882✔
337
}
588✔
338

339
-- Returns array of rules which should be applied in order.
340
-- A rule is a table {{pattern*}, type}.
341
-- `pattern` is a non-normalized pattern.
342
-- `type` can be "enable", "disable" or "only".
343
local function get_rules(opts_stack)
344
   local rules = {}
2,082✔
345
   local used_macros = {}
2,082✔
346

347
   for _, opts in utils.ripairs(opts_stack) do
14,862✔
348
      for _, macro_info in ipairs(macros) do
42,720✔
349
         local option, pattern = macro_info[1], macro_info[2]
34,176✔
350

351
         if not used_macros[option] then
34,176✔
352
            if opts[option] ~= nil then
33,738✔
353
               table.insert(rules, {{pattern}, opts[option] and "enable" or "disable"})
204✔
354
               used_macros[option] = true
204✔
355
            end
356
         end
357
      end
358

359
      if opts.ignore then
8,544✔
360
         table.insert(rules, {opts.ignore, "disable"})
1,350✔
361
      end
362

363
      if opts.only then
8,544✔
364
         table.insert(rules, {opts.only, "only"})
306✔
365
      end
366

367
      if opts.enable then
8,544✔
368
         table.insert(rules, {opts.enable, "enable"})
90✔
369
      end
370
   end
371

372
   return rules
2,082✔
373
end
374

375
local function normalize_patterns(rules)
376
   local res = {}
2,082✔
377

378
   for i, rule in ipairs(rules) do
4,032✔
379
      res[i] = {{}, rule[2]}
1,950✔
380

381
      for j, pattern in ipairs(rule[1]) do
4,338✔
382
         res[i][1][j] = normalize_pattern(pattern)
3,184✔
383
      end
384
   end
385

386
   return res
2,082✔
387
end
388

389
local function get_operators(opts_stack)
390
   local operators, operatorsMap = nil, nil
1,735✔
391

392
   for _, opts in ipairs(opts_stack) do
10,626✔
393
      if opts.operators then
8,544✔
394
         operators = operators or {}
12✔
395
         operatorsMap = operatorsMap or {}
12✔
396

397
         for _, op in ipairs(opts.operators) do
42✔
398
            if not operatorsMap[op] then
30✔
399
               table.insert(operators, op)
30✔
400
               operatorsMap[op] = true
30✔
401
            end
402
         end
403
      end
404
   end
405

406
   return operators
2,082✔
407
end
408

409
local scalar_options = {
882✔
410
   unused_secondaries = true,
588✔
411
   self = true,
588✔
412
   module = false,
588✔
413
   allow_defined = false,
588✔
414
   allow_defined_top = false,
588✔
415
   max_cyclomatic_complexity = false
588✔
416
}
417

418
-- Returns normalized options.
419
-- Normalized options have fields:
420
--    std: normalized std table, see `luacheck.standards` module;
421
--    unused_secondaries, self, module, allow_defined, allow_defined_top: booleans;
422
--    max_line_length: number or false;
423
--    rules: see get_rules.
424
function options.normalize(opts_stack, stds)
882✔
425
   local res = {}
2,082✔
426
   stds = stds or builtin_standards
2,082✔
427
   res.std = get_final_std(opts_stack, stds)
2,776✔
428
   res.operators = get_operators(opts_stack)
2,776✔
429

430
   for option, default in pairs(scalar_options) do
14,574✔
431
      res[option] = get_scalar_opt(opts_stack, option, default)
16,656✔
432
   end
433

434
   local max_line_opts = get_max_line_opts(opts_stack)
2,082✔
435
   utils.update(res, max_line_opts)
2,082✔
436
   res.rules = normalize_patterns(get_rules(opts_stack))
3,470✔
437

438
   return res
2,082✔
439
end
440

441
return options
882✔
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