Skip to content

Commit

Permalink
Implement new std format
Browse files Browse the repository at this point in the history
New std format can define nested trees of allowed fields within
globals and their read-only status. This tree can be alerted
by using field names such as `foo.bar` in all global-related options.

Warnings related to operations on unfefined fields
within defined globals use new warning codes 142 (set) and 143 (access).

TODO: precise builtin definitions of standard Lua libraries.
TODO: precise default std (use standard lib of Lua used to run Luacheck).
TODO: a new CLI-level test for all of this.
TODO: docs.
  • Loading branch information
mpeterv committed Feb 26, 2017
1 parent f2a35be commit a9e2e36
Show file tree
Hide file tree
Showing 20 changed files with 1,129 additions and 308 deletions.
5 changes: 3 additions & 2 deletions install.lua
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ for _, filename in ipairs {
"filter.lua",
"options.lua",
"inline_options.lua",
"stds.lua",
"builtin_standards.lua",
"expand_rockspec.lua",
"multithreading.lua",
"cache.lua",
Expand All @@ -119,7 +119,8 @@ for _, filename in ipairs {
"utils.lua",
"argparse.lua",
"whitespace.lua",
"detect_globals.lua"} do
"detect_globals.lua",
"standards.lua"} do
copy("src" .. dirsep .. "luacheck" .. dirsep .. filename, luacheck_lib_dir)
end

Expand Down
5 changes: 3 additions & 2 deletions luacheck-scm-1.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ build = {
["luacheck.filter"] = "src/luacheck/filter.lua",
["luacheck.options"] = "src/luacheck/options.lua",
["luacheck.inline_options"] = "src/luacheck/inline_options.lua",
["luacheck.stds"] = "src/luacheck/stds.lua",
["luacheck.builtin_standards"] = "src/luacheck/builtin_standards.lua",
["luacheck.expand_rockspec"] = "src/luacheck/expand_rockspec.lua",
["luacheck.multithreading"] = "src/luacheck/multithreading.lua",
["luacheck.cache"] = "src/luacheck/cache.lua",
Expand All @@ -42,7 +42,8 @@ build = {
["luacheck.utils"] = "src/luacheck/utils.lua",
["luacheck.argparse"] = "src/luacheck/argparse.lua",
["luacheck.whitespace"] = "src/luacheck/whitespace.lua",
["luacheck.detect_globals"] = "src/luacheck/detect_globals.lua"
["luacheck.detect_globals"] = "src/luacheck/detect_globals.lua",
["luacheck.standards"] = "src/luacheck/standards.lua"
},
install = {
bin = {
Expand Down
4 changes: 2 additions & 2 deletions spec/cache_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ describe("cache", function()

it("returns serialized result", function()
assert.same(
[[return {{{"111","foo",5,100,102},{"211","bar",4,1,3,[8]=true},{"011",[4]=100000,[12]="near '\"'"}},{}}]],
[[return {{{"111","foo",5,100,102,[23]={"faa"}},{"211","bar",4,1,3,[8]=true},{"011",[4]=100000,[12]="near '\"'"}},{}}]],
cache.serialize({
events = {
{code = "111", name = "foo", line = 5, column = 100, end_column = 102},
{code = "111", name = "foo", indexing = {"faa"}, line = 5, column = 100, end_column = 102},
{code = "211", name = "bar", line = 4, column = 1, end_column = 3, secondary = true},
{code = "011", column = 100000, msg = "near '\"'"}
},
Expand Down
115 changes: 91 additions & 24 deletions spec/check_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe("check", function()

it("does not find anything wrong in used locals", function()
assert.same({
{code = "113", name = "print", line = 5, column = 4, end_column = 8}
{code = "113", name = "print", indexing = {"print"}, line = 5, column = 4, end_column = 8}
}, check[[
local a
local b = 5
Expand All @@ -24,15 +24,15 @@ end

it("detects global set", function()
assert.same({
{code = "111", name = "foo", line = 1, column = 1, end_column = 3, top = true}
{code = "111", name = "foo", indexing = {"foo"}, line = 1, column = 1, end_column = 3, top = true}
}, check[[
foo = {}
]])
end)

it("detects global set in nested functions", function()
assert.same({
{code = "111", name = "foo", line = 2, column = 4, end_column = 6}
{code = "111", name = "foo", indexing = {"foo"}, line = 2, column = 4, end_column = 6}
}, check[[
local function bar()
foo = {}
Expand All @@ -43,9 +43,9 @@ bar()

it("detects global access in multi-assignments", function()
assert.same({
{code = "111", name = "y", line = 2, column = 4, end_column = 4, top = true},
{code = "111", name = "y", indexing = {"y"}, line = 2, column = 4, end_column = 4, top = true},
{code = "532", line = 2, column = 6, end_column = 6},
{code = "113", name = "print", line = 3, column = 1, end_column = 5}
{code = "113", name = "print", indexing = {"print"}, line = 3, column = 1, end_column = 5}
}, check[[
local x
x, y = 1
Expand All @@ -55,8 +55,8 @@ print(x)

it("detects global access in self swap", function()
assert.same({
{code = "113", name = "a", line = 1, column = 11, end_column = 11},
{code = "113", name = "print", line = 2, column = 1, end_column = 5}
{code = "113", name = "a", indexing = {"a"}, line = 1, column = 11, end_column = 11},
{code = "113", name = "print", indexing = {"print"}, line = 2, column = 1, end_column = 5}
}, check[[
local a = a
print(a)
Expand All @@ -65,16 +65,31 @@ print(a)

it("detects global mutation", function()
assert.same({
{code = "112", name = "a", line = 1, column = 1, end_column = 1}
{code = "112", name = "a", indexing = {"a", false}, line = 1, column = 1, end_column = 1}
}, check[[
a[1] = 6
]])
end)

it("detects indirect global field access", function()
assert.same({
{code = "113", name = "b", line = 2, column = 15, end_column = 15},
{code = "113", name = "b", line = 3, column = 8, end_column = 12, indirect = true}
{
code = "113",
name = "b",
indexing = {"b", false},
line = 2,
column = 15,
end_column = 15
}, {
code = "113",
name = "b",
indexing = {"b", false, false, "foo"},
previous_indexing_len = 2,
line = 3,
column = 8,
end_column = 12,
indirect = true
}
}, check[[
local c = "foo"
local alias = b[1]
Expand All @@ -84,19 +99,71 @@ return alias[2][c]

it("detects indirect global field mutation", function()
assert.same({
{code = "113", name = "b", line = 2, column = 15, end_column = 15},
{code = "112", name = "b", line = 3, column = 1, end_column = 5, indirect = true}
{
code = "113",
name = "b",
indexing = {"b", false},
line = 2,
column = 15,
end_column = 15
}, {
code = "112",
name = "b",
indexing = {"b", false, false, "foo"},
previous_indexing_len = 2,
line = 3,
column = 1,
end_column = 5,
indirect = true
}
}, check[[
local c = "foo"
local alias = b[1]
alias[2][c] = c
]])
end)

it("provides indexing information for warnings related to globals", function()
assert.same({
{
code = "113",
name = "global",
indexing = {"global"},
line = 2,
column = 11,
end_column = 16
}, {
code = "113",
name = "global",
indexing = {"global", "foo", "bar", false},
indirect = true,
previous_indexing_len = 1,
line = 3,
column = 15,
end_column = 15
}, {
code = "113",
name = "global",
indexing = {"global", "foo", "bar", false, true},
indirect = true,
previous_indexing_len = 4,
line = 5,
column = 8,
end_column = 13
}
}, check[[
local c = "foo"
local g = global
local alias = g[c].bar[1]
local alias2 = alias
return alias2[...]
]])
end)

it("detects unused locals", function()
assert.same({
{code = "211", name = "a", line = 1, column = 7, end_column = 7},
{code = "113", name = "print", line = 5, column = 4, end_column = 8}
{code = "113", name = "print", indexing = {"print"}, line = 5, column = 4, end_column = 8}
}, check[[
local a = 4
Expand Down Expand Up @@ -220,7 +287,7 @@ return a
assert.same({
{code = "213", name = "i", line = 1, column = 5, end_column = 5},
{code = "213", name = "i", line = 2, column = 5, end_column = 5},
{code = "113", name = "pairs", line = 2, column = 10, end_column = 14}
{code = "113", name = "pairs", indexing = {"pairs"}, line = 2, column = 10, end_column = 14}
}, check[[
for i=1, 2 do end
for i in pairs{} do end
Expand All @@ -231,7 +298,7 @@ for i in pairs{} do end
assert.same({
{code = "311", name = "a", line = 3, column = 4, end_column = 4},
{code = "311", name = "a", line = 5, column = 4, end_column = 4},
{code = "113", name = "print", line = 9, column = 1, end_column = 5}
{code = "113", name = "print", indexing = {"print"}, line = 9, column = 1, end_column = 5}
}, check[[
local a
if ... then
Expand All @@ -247,7 +314,7 @@ print(a)

it("does not detect unused value when it and a closure using it can live together", function()
assert.same({
{code = "113", name = "escape", line = 3, column = 4, end_column = 9}
{code = "113", name = "escape", indexing = {"escape"}, line = 3, column = 4, end_column = 9}
}, check[[
local a = 3
if true then
Expand Down Expand Up @@ -432,8 +499,8 @@ return foo

it("does not detect unused values in loops", function()
assert.same({
{code = "113", name = "print", line = 3, column = 4, end_column = 8},
{code = "113", name = "math", line = 4, column = 8, end_column = 11}
{code = "113", name = "print", indexing = {"print"}, line = 3, column = 4, end_column = 8},
{code = "113", name = "math", indexing = {"math", "floor"}, line = 4, column = 8, end_column = 11}
}, check[[
local a = 10
while a > 0 do
Expand All @@ -459,7 +526,7 @@ goto loop
assert.same({
{code = "211", name = "foo", line = 1, column = 7, end_column = 9},
{code = "411", name = "foo", line = 2, column = 7, end_column = 9, prev_line = 1, prev_column = 7},
{code = "113", name = "print", line = 3, column = 1, end_column = 5}
{code = "113", name = "print", indexing = {"print"}, line = 3, column = 1, end_column = 5}
}, check[[
local foo
local foo = "bar"
Expand Down Expand Up @@ -670,7 +737,7 @@ end

it("detects accessing uninitialized variables", function()
assert.same({
{code = "113", name = "get", line = 6, column = 8, end_column = 10},
{code = "113", name = "get", indexing = {"get"}, line = 6, column = 8, end_column = 10},
{code = "321", name = "a", line = 6, column = 12, end_column = 12}
}, check[[
local a
Expand All @@ -688,7 +755,7 @@ return a
it("detects mutating uninitialized variables", function()
assert.same({
{code = "341", name = "a", line = 4, column = 4, end_column = 4},
{code = "113", name = "get", line = 6, column = 8, end_column = 10}
{code = "113", name = "get", indexing = {"get"}, line = 6, column = 8, end_column = 10}
}, check[[
local a
Expand All @@ -704,7 +771,7 @@ return a

it("detects accessing uninitialized variables in nested functions", function()
assert.same({
{code = "113", name = "get", line = 7, column = 8, end_column = 10},
{code = "113", name = "get", indexing = {"get"}, line = 7, column = 8, end_column = 10},
{code = "321", name = "a", line = 7, column = 12, end_column = 12}
}, check[[
return function() return function(...)
Expand All @@ -723,7 +790,7 @@ end end

it("does not detect accessing unitialized variables incorrectly in loops", function()
assert.same({
{code = "113", name = "get", line = 4, column = 8, end_column = 10}
{code = "113", name = "get", indexing = {"get"}, line = 4, column = 8, end_column = 10}
}, check[[
local a
Expand Down Expand Up @@ -809,7 +876,7 @@ return foo;
{options = {ignore = {".*"}}, line = 5, column = 1, end_column = 19},
{code = "512", line = 7, column = 1, end_column = 3},
{code = "213", name = "_", line = 7, column = 5, end_column = 5},
{code = "113", name = "pairs", line = 7, column = 10, end_column = 14},
{code = "113", name = "pairs", indexing = {"pairs"}, line = 7, column = 10, end_column = 14},
{pop = true, closure = true, line = 9, column = 1}
},
per_line_options = {
Expand Down
23 changes: 17 additions & 6 deletions spec/cli_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,7 @@ Total: 1 warning / 0 errors in 1 file
Checking spec/samples/read_globals.lua 5 warnings
spec/samples/read_globals.lua:1:1: setting read-only global variable 'string'
spec/samples/read_globals.lua:2:1: mutating read-only global variable 'table'
spec/samples/read_globals.lua:2:1: setting read-only field 'append' of global 'table'
spec/samples/read_globals.lua:5:1: setting read-only global variable 'bar'
spec/samples/read_globals.lua:6:1: mutating non-standard global variable 'baz'
spec/samples/read_globals.lua:6:21: accessing undefined variable 'baz'
Expand All @@ -598,19 +598,30 @@ Total: 5 warnings / 0 errors in 1 file
Checking spec/samples/indirect_globals.lua 3 warnings
spec/samples/indirect_globals.lua:2:11-16: accessing undefined variable 'global'
spec/samples/indirect_globals.lua:5:1-8: indirectly mutating read-only global variable 'table'
spec/samples/indirect_globals.lua:5:1-8: indirectly setting read-only field 'concat.foo.bar' of global 'table'
spec/samples/indirect_globals.lua:5:32-37: accessing undefined variable 'global'
Total: 3 warnings / 0 errors in 1 file
]], get_output "spec/samples/indirect_globals.lua --std=min --ranges --no-config")
end)

it("allows defining fields", function()
assert.equal([[
Checking spec/samples/indirect_globals.lua 2 warnings
spec/samples/indirect_globals.lua:2:11-16: accessing undefined variable 'global'
spec/samples/indirect_globals.lua:5:32-37: accessing undefined variable 'global'
Total: 2 warnings / 0 errors in 1 file
]], get_output "spec/samples/indirect_globals.lua --std=min --globals table.concat.foo --ranges --no-config")
end)

it("allows showing warning codes", function()
assert.equal([[
Checking spec/samples/read_globals.lua 5 warnings
spec/samples/read_globals.lua:1:1: (W121) setting read-only global variable 'string'
spec/samples/read_globals.lua:2:1: (W122) mutating read-only global variable 'table'
spec/samples/read_globals.lua:2:1: (W122) setting read-only field 'append' of global 'table'
spec/samples/read_globals.lua:5:1: (W121) setting read-only global variable 'bar'
spec/samples/read_globals.lua:6:1: (W112) mutating non-standard global variable 'baz'
spec/samples/read_globals.lua:6:21: (W113) accessing undefined variable 'baz'
Expand Down Expand Up @@ -704,7 +715,7 @@ Total: 5 warnings / 0 errors in 1 file
Checking spec/samples/read_globals_inline_options.lua 3 warnings
spec/samples/read_globals_inline_options.lua:3:1: setting read-only global variable 'foo'
spec/samples/read_globals_inline_options.lua:3:16: mutating read-only global variable 'baz'
spec/samples/read_globals_inline_options.lua:3:16: setting read-only field '?' of global 'baz'
spec/samples/read_globals_inline_options.lua:5:1: setting read-only global variable 'foo'
Total: 3 warnings / 0 errors in 1 file
Expand Down Expand Up @@ -839,7 +850,7 @@ spec/samples/good_code.lua
return {{},{},{19,0,23,17,3,0,30,25,26,3,0,15}}
spec/samples/bad_code.lua
(%d+)
return {{{"112","package",1,1,7},{"211","helper",3,16,21,[10]=true},{"212","...",3,23,25},{"111","embrace",7,10,16,[11]=true},{"412","opt",8,10,12,7,18},{"113","hepler",9,11,16}},{},{24,0,26,9,3,0,21,31,26,3,0}}
local A,B,C="package","embrace","hepler";return {{{"112",A,1,1,7,[23]={A,"loaded",true}},{"211","helper",3,16,21,[10]=true},{"212","...",3,23,25},{"111",B,7,10,16,[11]=true,[23]={B}},{"412","opt",8,10,12,7,18},{"113",C,9,11,16,[23]={C}}},{},{24,0,26,9,3,0,21,31,26,3,0}}
spec/samples/python_code.lua
(%d+)
return {{{"011",[3]=1,[4]=6,[5]=15,[12]="expected '=' near '__future__'"}},{},{}}
Expand All @@ -861,7 +872,7 @@ return {{{"011",[3]=1,[4]=6,[5]=15,[12]="expected '=' near '__future__'"}},{},{}
%s
spec/samples/python_code.lua
%s
return {{{"111", "global", 1, 1}, {"321", "uninit", 6, 8}},{},{}}
return {{{"111", "global", 1, 1, [23]={"global"}}, {"321", "uninit", 6, 8}},{},{}}
spec/samples/good_code.lua
%s
return {{{"011",[3]=5,[4]=7,[12]="this code is actually bad"}},{},{}}
Expand Down
4 changes: 2 additions & 2 deletions spec/configs/custom_stds_config.luacheckrc
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
stds = {
my_std = {"print", "setfenv"},
other_std = {"tostring", "setfenv"}
my_std = {read_globals = {"print", "setfenv"}},
other_std = {read_globals = {"tostring", "setfenv"}}
}

std = "my_std"
Loading

0 comments on commit a9e2e36

Please sign in to comment.