Skip to content

Commit 87333de

Browse files
committed
feat: warn on mutated loop control variables (#109)
1 parent 030ce6a commit 87333de

File tree

8 files changed

+45
-11
lines changed

8 files changed

+45
-11
lines changed

docsrc/cli.rst

+1
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ Option Meaning
120120
``--enable | -e <patt> [<patt>] ...`` Do not filter out warnings matching patterns.
121121
``--only | -o <patt> [<patt>] ...`` Filter out warnings not matching patterns.
122122
``--operators <patt> [<patt>] ...`` Allow compound operators matching patterns. (Multiple assignment not supported, as this is specifically for the Playdate SDK.)
123+
``--const-loop-control-vars`` Flag loop control variables as <const>.
123124
``--config <config>`` Path to custom configuration file (default: ``.luacheckrc``).
124125
``--no-config`` Do not look up custom configuration file.
125126
``--default-config <config>`` Default path to custom configuration file, to be used if ``--[no-]config`` is not used and ``.luacheckrc`` is not found.

docsrc/config.rst

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Option Type Default v
4343
``new_read_globals`` Array of strings or field definition map (Do not overwrite)
4444
``not_globals`` Array of strings ``{}``
4545
``operators`` Array of strings ``{}``
46+
``const_loop_control_vars`` Boolean ``false``
4647
``compat`` Boolean ``false``
4748
``allow_defined`` Boolean ``false``
4849
``allow_defined_top`` Boolean ``false``

docsrc/warnings.rst

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ Code Description
4545
432 Shadowing an upvalue argument.
4646
433 Shadowing an upvalue loop variable.
4747
441 Constant local variable is modified.
48+
442 Loop control variable is modified.
4849
511 Unreachable code.
4950
512 Loop can be executed at most once.
5051
521 Unused label.

spec/linearize_spec.lua

+19
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,25 @@ end
476476
477477
a, b = 3, 3
478478
print(a, b)
479+
]]))
480+
end)
481+
482+
it("handles loop control variables correctly", function()
483+
assert.same({
484+
{code = "442", line = 2, column = 3, end_column = 11, name = 'i',
485+
defined_line = 1},
486+
{code = "442", line = 7, column = 3, end_column = 13, name = 'k',
487+
defined_line = 6},
488+
}, helper.get_stage_warnings("linearize", [[
489+
for i = 1, 10 do
490+
i = i - 1
491+
print(i)
492+
end
493+
494+
for k,_ in pairs({ foo = "bar" }) do
495+
k = "k:"..k
496+
print(k)
497+
end
479498
]]))
480499
end)
481500
end)

spec/parser_spec.lua

+6-6
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ describe("parser", function()
242242
describe("when parsing for", function()
243243
it("parses fornum correctly", function()
244244
assert.same({tag = "Fornum",
245-
{tag = "Id", "i"},
245+
{tag = "Id", const_loop_var = true, "i"},
246246
{tag = "Number", "1"},
247247
{tag = "Op", "len", {tag = "Id", "t"}},
248248
{}
@@ -283,7 +283,7 @@ describe("parser", function()
283283

284284
it("parses fornum with step correctly", function()
285285
assert.same({tag = "Fornum",
286-
{tag = "Id", "i"},
286+
{tag = "Id", const_loop_var = true, "i"},
287287
{tag = "Number", "1"},
288288
{tag = "Op", "len", {tag = "Id", "t"}},
289289
{tag = "Number", "2"},
@@ -297,14 +297,14 @@ describe("parser", function()
297297

298298
it("parses forin correctly", function()
299299
assert.same({tag = "Forin", {
300-
{tag = "Id", "i"}
300+
{tag = "Id", const_loop_var = true, "i"}
301301
}, {
302302
{tag = "Id", "t"}
303303
},
304304
{}
305305
}, get_node("for i in t do end"))
306306
assert.same({tag = "Forin", {
307-
{tag = "Id", "i"},
307+
{tag = "Id", const_loop_var = true, "i"},
308308
{tag = "Id", "j"}
309309
}, {
310310
{tag = "Id", "t"},
@@ -987,7 +987,7 @@ describe("parser", function()
987987
},
988988
{tag = "Do",
989989
{tag = "Fornum",
990-
{tag = "Id", "i"},
990+
{tag = "Id", const_loop_var = true, "i"},
991991
{tag = "Number", "1"},
992992
{tag = "Number", "2"},
993993
{
@@ -998,7 +998,7 @@ describe("parser", function()
998998
},
999999
{tag = "Forin",
10001000
{
1001-
{tag = "Id", "k"},
1001+
{tag = "Id", const_loop_var = true, "k"},
10021002
{tag = "Id", "v"}
10031003
},
10041004
{

src/luacheck/parser.lua

+4-3
Original file line numberDiff line numberDiff line change
@@ -719,14 +719,15 @@ statements["for"] = function(state)
719719

720720
local ast_node = {}
721721
local tag
722-
local first_var = parse_id(state)
722+
local control_var = parse_id(state)
723+
control_var.const_loop_var = true
723724

724725
if state.token == "=" then
725726
-- Numeric "for" loop.
726727
tag = "Fornum"
727728
-- Skip "=".
728729
skip_token(state)
729-
ast_node[1] = first_var
730+
ast_node[1] = control_var
730731
ast_node[2] = parse_expression(state)
731732
check_and_skip_token(state, ",")
732733
ast_node[3] = parse_expression(state)
@@ -741,7 +742,7 @@ statements["for"] = function(state)
741742
-- Generic "for" loop.
742743
tag = "Forin"
743744

744-
local iter_vars = {first_var}
745+
local iter_vars = {control_var}
745746
while test_and_skip_token(state, ",") do
746747
iter_vars[#iter_vars + 1] = parse_id(state)
747748
end

src/luacheck/stages/linearize.lua

+11-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ stage.warnings = {
2424
message_format = "variable {name!} was defined as const on line {defined_line}",
2525
fields = {"name", "defined_line"}
2626
},
27+
["442"] = {
28+
message_format =
29+
"variable {name!}, defined on line {defined_line}, is a loop control variable and shouldn't be modified",
30+
fields = {"name", "defined_line"}
31+
},
2732
["521"] = {message_format = "unused label {label!}", fields = {"label"}}
2833
}
2934

@@ -47,7 +52,8 @@ local function warn_redefined(chstate, var, prev_var, is_same_scope)
4752
end
4853

4954
local function warn_modified_const_label(chstate, node, var)
50-
chstate:warn_range("441", node, {
55+
local code = "44" .. (var.node.const and "1" or "2")
56+
chstate:warn_range(code, node, {
5157
defined_line = var.node.line,
5258
name = var.name
5359
})
@@ -533,6 +539,10 @@ function LinState:emit_stmt_Set(node)
533539
if var.node.const then
534540
warn_modified_const_label(self.chstate, node, var)
535541
end
542+
543+
if var.node.const_loop_var then
544+
warn_modified_const_label(self.chstate, node, var)
545+
end
536546
end
537547
else
538548
assert(expr.tag == "Index")

src/luacheck/standards.lua

+2-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,8 @@ local function add_fields(def, fields, overwrite, ignore_array_part, default_rea
128128
return
129129
end
130130

131-
for field_name, field_def in pairs(fields) do
131+
for k, v in pairs(fields) do
132+
local field_name, field_def = k, v
132133
if type(field_name) == "string" or not ignore_array_part then
133134
if type(field_name) ~= "string" then
134135
field_name = field_def

0 commit comments

Comments
 (0)