Permalink
Browse files

Rework inline options processing and report filtering

Instead of only listing all inline options, pushes, and pops
in inline options during checking, compile all of them
into more compact instructions describing how option stack
should be updated for each line when filtering.

Option stacks are the same within each line. On each line there
can be a push of a new option table (only one now that inline options
are not allowed in long comments), and/or a pop of a given number of
inline options.

Inline options module becomes just another check stage, filtering no
longer interacts with it as the provided check result is easy enough
to work with.

Organize report filtering more efficiently, doing all the work in just
two passes.

Also, fix a bug with line length warnings always appearing in the output
earlier than other warnings on the same line because it used 1 as column
before warnings were sorted.
  • Loading branch information...
mpeterv committed Sep 13, 2018
1 parent ddbd1cd commit 2027a23e5d628353559bd55da44525d70f3c6559
View
@@ -29,6 +29,7 @@
negative step (#160).
* Added support for LuaRocks 3 module autodetection when checking
rockspecs (#176).
* Reduced amout of RAM used, particularly when checking many files.
### Changes
@@ -41,6 +42,8 @@
* Added missing globals to `rockspec` std: `hooks`, `deploy`,
`build_dependencies`, `test_dependencies`, and `test`.
* Fixed line lengths appearing in the output before other warnings on the same
line even if their column numbers are smaller.
### Miscellaneous
View
@@ -34,7 +34,6 @@ build = {
["luacheck.format"] = "src/luacheck/format.lua",
["luacheck.fs"] = "src/luacheck/fs.lua",
["luacheck.globbing"] = "src/luacheck/globbing.lua",
["luacheck.inline_options"] = "src/luacheck/inline_options.lua",
["luacheck.lexer"] = "src/luacheck/lexer.lua",
["luacheck.love_standard"] = "src/luacheck/love_standard.lua",
["luacheck.main"] = "src/luacheck/main.lua",
@@ -59,6 +58,7 @@ build = {
["luacheck.stages.linearize"] = "src/luacheck/stages/linearize.lua",
["luacheck.stages.name_functions"] = "src/luacheck/stages/name_functions.lua",
["luacheck.stages.parse"] = "src/luacheck/stages/parse.lua",
["luacheck.stages.parse_inline_options"] = "src/luacheck/stages/parse_inline_options.lua",
["luacheck.stages.resolve_locals"] = "src/luacheck/stages/resolve_locals.lua",
["luacheck.stages.unwrap_parens"] = "src/luacheck/stages/unwrap_parens.lua",
["luacheck.standards"] = "src/luacheck/standards.lua",
View
@@ -20,12 +20,12 @@ describe("cache", function()
assert.same(
[[return {{{"111",5,100,102,"foo",{"faa"}},{"211",4,1,3,"bar",nil,true},{"011",nil,100000,nil,"near '\"'"}},{}}]],
cache.serialize({
events = {
warnings = {
{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 '\"'"}
},
per_line_options = {}
inline_options = {}
})
)
end)
@@ -34,35 +34,35 @@ describe("cache", function()
assert.same(
[[local A,B="111","foo";return {{{A,5,100,nil,B},{A,6,100,nil,B},{"011",nil,100000,nil,"near '\"'"}},{},{}}]],
cache.serialize({
events = {
warnings = {
{code = "111", name = "foo", line = 5, column = 100},
{code = "111", name = "foo", line = 6, column = 100, secondary = true},
{code = "011", column = 100000, msg = "near '\"'"}
},
per_line_options = {},
inline_options = {},
line_lengths = {}
})
)
end)
it("uses at most 52 locals", function()
local events = {}
local warnings = {}
local expected_parts1 = {"local A"}
local expected_parts2 = {'="111"'}
local expected_parts3 = {";return {{"}
local function add_char(b)
local c = string.char(b)
table.insert(events, {code = "111", name = c})
table.insert(events, {code = "111", name = c})
table.insert(warnings, {code = "111", name = c})
table.insert(warnings, {code = "111", name = c})
table.insert(expected_parts1, "," .. c)
table.insert(expected_parts2, ',"' .. c .. '"')
table.insert(expected_parts3, ('{A,nil,nil,nil,%s},{A,nil,nil,nil,%s},'):format(c, c))
end
local function add_extra(name)
table.insert(events, {code = "111", name = name})
table.insert(events, {code = "111", name = name})
table.insert(warnings, {code = "111", name = name})
table.insert(warnings, {code = "111", name = name})
table.insert(expected_parts3, ('{A,nil,nil,nil,"%s"},{A,nil,nil,nil,"%s"},'):format(name, name))
end
@@ -84,19 +84,19 @@ describe("cache", function()
assert.same(expected,
cache.serialize({
events = events,
per_line_options = {},
warnings = warnings,
inline_options = {},
line_lengths = {}
})
)
end)
it("handles error result", function()
assert.same('return {{{"011",2,4,nil,"message"}},{},{}}', cache.serialize({
events = {
warnings = {
{code = "011", line = 2, column = 4, msg = "message"}
},
per_line_options = {},
inline_options = {},
line_lengths = {}
}))
end)
@@ -120,10 +120,10 @@ describe("cache", function()
local function report(code)
return {
events = {
warnings = {
code and {code = code}
},
per_line_options = {},
inline_options = {},
line_lengths = {}
}
end
@@ -214,31 +214,28 @@ return {{{"122"}},{},{}}
local tmpname
local foo_report = {
events = {
warnings = {
{code = "111", name = "not_print", line = 1, column = 1},
{push = true, line = 2, column = 1},
{options = {std = "none"}, line = 3, column = 1},
{code = "111", name = "not_print", line = 4, column = 1},
{code = "111", name = "print", line = 5, column = 1},
{pop = true, line = 6, column = 1},
{code = "111", name = "print", line = 7, column = 1},
{options = {std = "bad_std"}, line = 8, column = 1}
},
per_line_options = {
[4] = {
{options = {ignore = {",*"}}, line = 4, column = 10}
},
[1000] = {
{options = {std = "max"}, line = 1000, column = 1},
{options = {std = "another_bad_std"}, line = 1000, column = 20}
}
inline_options = {
{options = {std = "none"}, line = 3, column = 1},
{options = {ignore = {",*"}}, line = 4, column = 10},
{pop_count = 1, line = 5},
{pop_count = 1, line = 6},
{options = {std = "bad_std"}, line = 8, column = 1},
{options = {std = "max"}, line = 1000, column = 1},
{pop_count = 1, options = {std = "another_bad_std"}, line = 1001, column = 20},
{pop_count = 1, line = 1002},
},
line_lengths = {10, 20, 30}
}
local bar_report = {
events = {{code = "011", line = 2, column = 4, msg = "message"}},
per_line_options = {},
warnings = {{code = "011", line = 2, column = 4, msg = "message"}},
inline_options = {},
line_lengths = {40, 50}
}
View
@@ -1,21 +1,21 @@
local raw_check = require "luacheck.check"
local function remove_cyclomatic_complexity_warnings(events)
for i = #events, 1, -1 do
if events[i].code == "561" then
table.remove(events, i)
local function remove_cyclomatic_complexity_warnings(warnings)
for i = #warnings, 1, -1 do
if warnings[i].code == "561" then
table.remove(warnings, i)
end
end
end
local function check_full(src)
local report = raw_check(src)
remove_cyclomatic_complexity_warnings(report.events)
remove_cyclomatic_complexity_warnings(report.warnings)
return report
end
local function check(src)
return check_full(src).events
return check_full(src).warnings
end
describe("check", function()
@@ -249,26 +249,31 @@ return foo;
}, check("-- \240\144\128\128\224\166\152\nlocal --[[\204\128]] a;math['\204\130']()\n"))
end)
it("emits events, per-line options, and line lengths", function()
it("provides inline options, line lengths, and line endings", function()
assert.same({
events = {
{push = true, line = 1, column = 1, end_column = 28},
{options = {ignore = {"bar"}}, line = 1, column = 1, end_column = 28},
warnings = {
{code = "211", name = "foo", line = 2, column = 7, end_column = 9},
{code = "211", name = "bar", line = 2, column = 12, end_column = 14},
{pop = true, line = 3, column = 1, end_column = 16},
{push = true, closure = true, line = 4, column = 8},
{options = {ignore = {".*"}}, line = 5, column = 1, end_column = 19},
{code = "512", line = 7, column = 1, end_column = 32},
{code = "213", name = "_", line = 7, column = 5, end_column = 5},
{code = "113", name = "pairs", line = 7, column = 10, end_column = 14},
{pop = true, closure = true, line = 9, column = 1}
{code = "211", name = "f", func = true, line = 11, column = 16, end_column = 16}
},
per_line_options = {
[2] = {{options = {ignore = {"foo"}}, line = 2, column = 16, end_column = 38}}
inline_options = {
{options = {ignore = {"bar"}}, line = 1, column = 1, end_column = 28},
{options = {ignore = {"foo"}}, line = 2, column = 16, end_column = 38},
{pop_count = 1, line = 3},
{pop_count = 1, line = 4},
{options = {ignore = {".*"}}, line = 5, column = 1, end_column = 19},
{options = {ignore = {"f"}}, line = 11, column = 24, end_column = 44},
{pop_count = 1, options = {std = "max"}, line = 12, column = 1, end_column = 20},
{options = {std = "none"}, line = 13, column = 1, end_column = 21},
{pop_count = 2, line = 15},
{pop_count = 1, line = 16}
},
line_lengths = {28, 38, 16, 17, 19, 17, 32, 16, 3, 0},
line_endings = {"comment", "comment", "comment", nil, "comment", "comment", nil, "comment", nil}
line_lengths = {28, 38, 16, 17, 19, 17, 32, 16, 0, 17, 44, 20, 21, 16, 3, 0},
line_endings = {"comment", "comment", "comment", nil, "comment", "comment", nil, "comment", nil,
"comment", "comment", "comment", "comment", "comment"}
}, check_full[[
-- luacheck: push ignore bar
local foo, bar -- luacheck: ignore foo
@@ -278,6 +283,12 @@ return function()
-- luacheck: push
for _ in pairs({}) do return end
-- luacheck: pop
-- luacheck: push
local function f() end -- luacheck: ignore f
-- luacheck: std max
-- luacheck: std none
-- luacheck: pop
end
]])
end)
@@ -305,7 +316,7 @@ end
-- luacheck: no ignore anything please
-- luacheck:
-- luacheck: no unused, , no redefined
]].events)
]].warnings)
end)
it("handles argparse sample", function()
View
@@ -939,7 +939,7 @@ return {{{"011",1,6,15,"expected '=' near '__future__'"}},{},{},{}}
%s
abspath{spec/samples/python_code.lua}
%s
return {{{"111", 1, 1, nil, "global"}, {"321", 6, 8, nil, "uninit"}},{},{},{}}
return {{{"111", 1, 1, nil, "global"}, {"321", 6, 8, nil, "uninit"}},{},{1, 1, 1, 1, 1, 1},{}}
abspath{spec/samples/good_code.lua}
%s
return {{{"011",5,7,nil, "this code is actually bad"}},{},{},{}}
View
@@ -4,18 +4,23 @@ local function filter(issue_arrays, opts)
local report = {}
for i, issues in ipairs(issue_arrays) do
local line_lengths = {}
for issue_index, issue in ipairs(issues) do
issue.line = issue_index
issue.column = 1
line_lengths[issue_index] = 0
end
report[i] = {events = issues, per_line_options = {}, line_lengths = {}}
report[i] = {warnings = issues, inline_options = {}, line_lengths = line_lengths, line_endings = {}}
end
local result = filter_full(report, opts)
for _, file_report in ipairs(result) do
for _, issue in ipairs(file_report) do
issue.line = nil
issue.column = nil
end
end
@@ -531,30 +536,34 @@ describe("filter", function()
{code = "121", name = "print", line = 7, column = 1},
{code = "021", msg = "invalid value of option 'std': unknown std 'bad_std'", line = 8, column = 1},
{code = "021", msg = "invalid value of option 'std': unknown std 'another_bad_std'",
line = 1000,column = 20}
line = 11, column = 20},
{code = "211", name = "not_print", line = 14, column = 1}
}
}, filter_full({
{
events = {
warnings = {
{code = "111", name = "not_print", line = 1, column = 1},
{push = true, line = 2, column = 1},
{options = {std = "none"}, line = 3, column = 1},
{code = "111", name = "not_print", line = 4, column = 1},
{code = "111", name = "print", line = 5, column = 1},
{pop = true, line = 6, column = 1},
{code = "111", name = "print", line = 7, column = 1},
{options = {std = "bad_std"}, line = 8, column = 1}
{code = "111", name = "not_print", line = 12, column = 1},
{code = "211", name = "not_print", line = 14, column = 1},
{code = "311", name = "c", line = 14, column = 2}
},
per_line_options = {
[4] = {
{options = {ignore = {",*"}}, line = 4, column = 10}
},
[1000] = {
{options = {std = "max"}, line = 1000, column = 1},
{options = {std = "another_bad_std"}, line = 1000, column = 20}
}
},
line_lengths = {}
inline_options = {
{options = {std = "none"}, line = 3, column = 1},
{options = {ignore = {".*"}}, line = 4, column = 10},
{pop_count = 1, line = 5},
{pop_count = 1, line = 7},
{options = {std = "bad_std"}, line = 8, column = 1},
{options = {std = "max"}, line = 9, column = 1},
{options = {std = "another_bad_std"}, line = 11, column = 20},
{options = {ignore = {"not_print"}}, line = 12, column = 1},
{options = {ignore = {"211"}}, line = 13, column = 1},
{pop_count = 2, options = {ignore = {"c"}}, line = 14, column = 1}
},
line_lengths = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
line_endings = {}
}
}, {
{
@@ -571,12 +580,12 @@ describe("filter", function()
}
}, filter_full({
{
events = {
warnings = {},
inline_options = {
{options = {max_line_length = 20}, line = 3, column = 1},
{options = {max_string_line_length = 15}, line = 4, column = 1},
{options = {max_line_length = false}, line = 6, column = 1}
},
per_line_options = {},
line_lengths = {120, 121, 15, 20, 18, 15, 200},
line_endings = {[5] = "string"}
}
View
@@ -439,7 +439,7 @@ describe("get_report", function()
end)
it("returns a table with single error event on syntax error", function()
local report = strip_locations({luacheck.get_report("return return").events})[1]
local report = strip_locations({luacheck.get_report("return return").warnings})[1]
assert.same({code = "011", msg = "expected expression near 'return'"}, report[1])
end)
end)
Oops, something went wrong.

0 comments on commit 2027a23

Please sign in to comment.