Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds table field checks #65

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docsrc/warnings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ Code Description
312 Value of an argument is unused.
313 Value of a loop variable is unused.
314 Value of a field in a table literal is unused.
315 Unused table field
321 Accessing uninitialized local variable.
325 Accessing unitialized table field
331 Value assigned to a local variable is mutated but never accessed.
341 Mutating uninitialized local variable.
411 Redefining a local variable.
Expand Down
1 change: 1 addition & 0 deletions luacheck-dev-1.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ build = {
["luacheck.stages.detect_empty_blocks"] = "src/luacheck/stages/detect_empty_blocks.lua",
["luacheck.stages.detect_empty_statements"] = "src/luacheck/stages/detect_empty_statements.lua",
["luacheck.stages.detect_globals"] = "src/luacheck/stages/detect_globals.lua",
["luacheck.stages.check_table_fields"] = "src/luacheck/stages/check_table_fields.lua",
["luacheck.stages.detect_reversed_fornum_loops"] = "src/luacheck/stages/detect_reversed_fornum_loops.lua",
["luacheck.stages.detect_unbalanced_assignments"] = "src/luacheck/stages/detect_unbalanced_assignments.lua",
["luacheck.stages.detect_uninit_accesses"] = "src/luacheck/stages/detect_uninit_accesses.lua",
Expand Down
39 changes: 22 additions & 17 deletions spec/cli_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -296,32 +296,36 @@ Total: 5 warnings / 0 errors in 1 file
end)

it("raises critical errors on config without additional operators", function()
assert.equal([[Checking spec/samples/compound_operators.lua 4 errors
assert.equal([[Checking spec/samples/compound_operators.lua 1 warning / 4 errors

spec/samples/compound_operators.lua:2:1: assignment uses compound operator +=
spec/samples/compound_operators.lua:3:1: assignment uses compound operator -=
spec/samples/compound_operators.lua:5:2: assignment uses compound operator /=
spec/samples/compound_operators.lua:9:3: value assigned to table field 't'.'a' is unused
spec/samples/compound_operators.lua:10:1: assignment uses compound operator *=

Total: 0 warnings / 4 errors in 1 file
Total: 1 warning / 4 errors in 1 file
]], get_output "spec/samples/compound_operators.lua --no-config")
end)

it("raises critical errors for unfiltered additional operators", function()
assert.equal([[Checking spec/samples/compound_operators.lua 3 errors
assert.equal([[Checking spec/samples/compound_operators.lua 1 warning / 3 errors

spec/samples/compound_operators.lua:3:1: assignment uses compound operator -=
spec/samples/compound_operators.lua:5:2: assignment uses compound operator /=
spec/samples/compound_operators.lua:9:3: value assigned to table field 't'.'a' is unused
spec/samples/compound_operators.lua:10:1: assignment uses compound operator *=

Total: 0 warnings / 3 errors in 1 file
Total: 1 warning / 3 errors in 1 file
]], get_output "spec/samples/compound_operators.lua --no-config --operators +=")
end)

it("allows to define allowed compound operators", function()
assert.equal([[Checking spec/samples/compound_operators.lua OK
assert.equal([[Checking spec/samples/compound_operators.lua 1 warning

Total: 0 warnings / 0 errors in 1 file
spec/samples/compound_operators.lua:9:3: value assigned to table field 't'.'a' is unused

Total: 1 warning / 0 errors in 1 file
]], get_output "spec/samples/compound_operators.lua --config=spec/configs/compound_operators_config.luacheckrc")
end)

Expand Down Expand Up @@ -768,18 +772,19 @@ Total: 8 warnings / 3 errors in 2 files

it("shows correct ranges for files with utf8", function()
assert.equal([[
Checking spec/samples/utf8.lua 4 warnings
Checking spec/samples/utf8.lua 5 warnings

spec/samples/utf8.lua:2:1-4: setting undefined field '분야 명' of global 'math'
spec/samples/utf8.lua:2:16-19: accessing undefined field '値' of global 'math'
spec/samples/utf8.lua:3:25-25: unused variable 't'
spec/samples/utf8.lua:4:5-28: value assigned to field 'päällekkäinen nimi a\u{200B}b' is overwritten on line 5 before use
spec/samples/utf8.lua:5:5-28: value assigned to table field 't'.'päällekkäinen nimi a​b' is unused

Checking spec/samples/utf8_error.lua 1 error

spec/samples/utf8_error.lua:2:11-11: expected statement near 'о'

Total: 4 warnings / 1 error in 2 files
Total: 5 warnings / 1 error in 2 files
]], get_output "spec/samples/utf8.lua spec/samples/utf8_error.lua --ranges --no-config")
end)

Expand Down Expand Up @@ -1235,7 +1240,7 @@ Codes: true
assert.equal(([[
Checking spec/samples/argparse-0.2.0.lua 9 warnings
Checking spec/samples/compat.lua 4 warnings
Checking spec/samples/compound_operators.lua 4 errors
Checking spec/samples/compound_operators.lua 1 warning / 4 errors
Checking spec/samples/custom_std_inline_options.lua 3 warnings / 1 error
Checking spec/samples/global_inline_options.lua 3 warnings
Checking spec/samples/globals.lua 2 warnings
Expand All @@ -1250,10 +1255,10 @@ Checking spec/samples/redefined.lua 7 warnings
Checking spec/samples/reversed_fornum.lua 1 warning
Checking spec/samples/unused_code.lua 10 warnings
Checking spec/samples/unused_secondaries.lua 4 warnings
Checking spec/samples/utf8.lua 4 warnings
Checking spec/samples/utf8.lua 5 warnings
Checking spec/samples/utf8_error.lua 1 error

Total: 75 warnings / 9 errors in 21 files
Total: 77 warnings / 9 errors in 21 files
]]):gsub("(spec/samples)/", "%1"..package.config:sub(1, 1)),
get_output "spec/samples --config=spec/configs/exclude_files_config.luacheckrc -qq --exclude-files spec/samples/global_fields.lua")
end)
Expand All @@ -1262,7 +1267,7 @@ Total: 75 warnings / 9 errors in 21 files
assert.equal([[
Checking argparse-0.2.0.lua 9 warnings
Checking compat.lua 4 warnings
Checking compound_operators.lua 4 errors
Checking compound_operators.lua 1 warning / 4 errors
Checking custom_std_inline_options.lua 3 warnings / 1 error
Checking global_inline_options.lua 3 warnings
Checking globals.lua 2 warnings
Expand All @@ -1277,18 +1282,18 @@ Checking redefined.lua 7 warnings
Checking reversed_fornum.lua 1 warning
Checking unused_code.lua 10 warnings
Checking unused_secondaries.lua 4 warnings
Checking utf8.lua 4 warnings
Checking utf8.lua 5 warnings
Checking utf8_error.lua 1 error

Total: 75 warnings / 9 errors in 21 files
Total: 77 warnings / 9 errors in 21 files
]], get_output(". --config=spec/configs/exclude_files_config.luacheckrc -qq --exclude-files global_fields.lua", "spec/samples/"))
end)

it("combines excluded files from config and cli", function()
assert.equal([[
Checking argparse-0.2.0.lua 9 warnings
Checking compat.lua 4 warnings
Checking compound_operators.lua 4 errors
Checking compound_operators.lua 1 warning / 4 errors
Checking custom_std_inline_options.lua 3 warnings / 1 error
Checking global_inline_options.lua 3 warnings
Checking globals.lua 2 warnings
Expand All @@ -1301,10 +1306,10 @@ Checking redefined.lua 7 warnings
Checking reversed_fornum.lua 1 warning
Checking unused_code.lua 10 warnings
Checking unused_secondaries.lua 4 warnings
Checking utf8.lua 4 warnings
Checking utf8.lua 5 warnings
Checking utf8_error.lua 1 error

Total: 67 warnings / 9 errors in 19 files
Total: 69 warnings / 9 errors in 19 files
]], get_output(". --config=spec/configs/exclude_files_config.luacheckrc -qq --exclude-files global_fields.lua --exclude-files " .. quote("./read*"), "spec/samples/"))
end)

Expand Down
184 changes: 184 additions & 0 deletions spec/table_field_limitations_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
local helper = require "spec.helper"

local function assert_warnings(warnings, src)
assert.same(warnings, helper.get_stage_warnings("check_table_fields", src))
end

describe("table field todo tests", function()
it("does nothing for globals", function()
assert_warnings({}, [[
x = {}
x[1] = 1
x[2] = x.y
x[1] = 1

y[1] = 1
y[2] = x.y
y[1] = 1
]])
end)

it("can't parse complicated values out", function()
assert_warnings({}, [[
local val = nil
local t = {}
t[1] = val
print(t[1])
]])
end)

it("does nothing for nested tables", function()
assert_warnings({}, [[
local x = {}
x[1] = {}
x[1][1] = 1
x[1][1] = x[1][2]
return x
]])
end)

-- Because of possible multiple return
it("assumes tables initialized from functions can have arbitrary keys set", function()
assert_warnings({
{code = "315", line = 3, column = 3, end_column = 3, name = 'x', field = 'y', set_is_nil = ''},
}, [[
local function func() return 1 end
local x = {func()}
x.y = x[2]
]])
end)

it("does nothing for table parameters that aren't declared in scope", function()
assert_warnings({}, [[
function func(x)
x[1] = x.z
x[1] = 1
end
]])
end)

it("doesn't handle metatables", function()
assert_warnings({}, [[
local x = setmetatable({}, {})
x[1] = 1
print(x[2])
]])
end)

it("detects unused and undefined table fields inside control blocks, but not between them", function()
assert_warnings({
{line = 4, column = 13, name = 'x', end_column = 13, field = 'z', code = '325', },
{line = 10, column = 13, name = 'x', end_column = 13, field = 'z', code = '325', },
{line = 16, column = 13, name = 'x', end_column = 13, field = 'z', code = '325', },
{line = 22, column = 13, name = 'x', end_column = 13, field = 'z', code = '325', },
{line = 28, column = 13, name = 'x', end_column = 13, field = 'z', code = '325', },
{line = 34, column = 13, name = 'x', end_column = 13, field = 'z', code = '325', },
}, [[
do
local x = {}
x.y = 1
x[1] = x.z
end

if true then
local x = {}
x.y = 1
x[1] = x.z
end

while true do
local x = {}
x.y = 1
x[1] = x.z
end

repeat
local x = {}
x.y = 1
x[1] = x.z
until false

for i=1,2 do
local x = {}
x.y = 1
x[1] = x.z
end

for _,_ in pairs({}) do
local x = {}
x.y = 1
x[1] = x.z
end
]])
end)

it("stops checking referenced upvalues if function call is known to not have table as an upvalue", function()
assert_warnings({}, [[
local x = {}
x[1] = 1
local function printx() x = 1 end
local function ret2() return 2 end
ret2()
x[1] = 1

local y = {}
y[1] = 1
function y.printx() y = 1 end
function y.ret2() return 2 end
y.ret2()
y[1] = 1
]])
end)

it("halts checking at the end of control flow blocks with jumps", function()
assert_warnings({}, [[
local x = {1}
if math.rand(0,1) ~= 1 then
x = {}
end

x[1] = x[1]

local y = {1}
if math.random(0,1) == 1 then
y[1] = 2
else
y = {}
end

y[1] = y[1]

local a = {1}
while math.random(0,1) == 1 do
a = {}
end

a[1] = a[1]
]])
end)

it("stops checking if a function is called", function()
assert_warnings({
{line = 8, column = 3, name = 'y', end_column = 3, field = 'x', code = '315', set_is_nil = '' },
{line = 8, column = 9, name = 'y', end_column = 9, field = 'a', code = '325', },
{line = 14, column = 9, name = 't', end_column = 9, field = 'a', code = '325', },
}, [[
local x = {}
x.y = 1
print("Unrelated text")
x.y = 2
x[1] = x.z

local y = {}
y.x = y.a
y.x = 1
function y:func() return 1 end
y:func()

local t = {}
t.x = t.a
local var = 'func'
t.x = y[var]() + 1
]])
end)
end)
Loading
Loading