Skip to content

Commit 542dc36

Browse files
Add sonar formatter
1 parent d54c9f3 commit 542dc36

File tree

7 files changed

+180
-0
lines changed

7 files changed

+180
-0
lines changed

.travis.yml

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ _lua: &lua
2222
- luarocks install cluacov
2323
- luarocks install luautf8
2424
- luarocks install luasocket
25+
- luarocks install dkjson
2526
- luarocks make
2627

2728
script:

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Unreleased
44

5+
* Add SonarQube formatter.
56
* Tarantool: Add rules for box.session.
67
Fixes warnings for writing to box.session.storage.
78

docsrc/cli.rst

+1
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ Option Meaning
139139
* ``JUnit`` - JUnit XML formatter;
140140
* ``visual_studio`` - MSBuild/Visual Studio aware formatter;
141141
* ``plain`` - simple warning-per-line formatter;
142+
* ``sonar`` - formatter for SonarQube;
142143
* ``default`` - standard formatter.
143144
``-q | --quiet`` Suppress report output for files without warnings.
144145

luacheck-scm-1.rockspec

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ build = {
3737
["luacheck.formatter.default"] = "src/luacheck/formatter/default.lua",
3838
["luacheck.formatter.junit"] = "src/luacheck/formatter/junit.lua",
3939
["luacheck.formatter.plain"] = "src/luacheck/formatter/plain.lua",
40+
["luacheck.formatter.sonar"] = "src/luacheck/formatter/sonar.lua",
4041
["luacheck.formatter.tap"] = "src/luacheck/formatter/tap.lua",
4142
["luacheck.formatter.visual_studio"] = "src/luacheck/formatter/visual_studio.lua",
4243
["luacheck.fs"] = "src/luacheck/fs.lua",

spec/formatters/sonar_spec.lua

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
local json_ok, json = pcall(require, "dkjson")
2+
json = json_ok and json or require("json")
3+
4+
local get_output = require("spec.helper").get_output
5+
6+
assert:set_parameter("TableFormatLevel", 6)
7+
8+
local function get_json_output(...)
9+
local output = get_output(...)
10+
local ok, result = pcall(json.decode, output)
11+
if ok then
12+
return result
13+
else
14+
return output
15+
end
16+
end
17+
18+
local function build_issue(path, line, col, code, severity, issue_type, message, secondaryLocations)
19+
return {
20+
effortMinutes = 2,
21+
engineId = "luacheck",
22+
primaryLocation = {
23+
message = message,
24+
filePath = path,
25+
textRange = {
26+
startLine = tonumber(line),
27+
startColumn = col and (tonumber(col) - 1),
28+
}
29+
},
30+
ruleId = code,
31+
secondaryLocations = secondaryLocations,
32+
severity = severity,
33+
type = issue_type,
34+
}
35+
end
36+
37+
-- luacheck: max line length 180
38+
describe("Sonar formatter", function()
39+
it("renders empty array when there is no issues", function()
40+
-- don't use get_json_output to ensure that there is array in json
41+
assert.equal('{"issues":[]}\n', get_output "spec/samples/good_code.lua --std=lua52 --formatter sonar --no-config")
42+
end)
43+
44+
it("renders issues", function()
45+
local expected = {
46+
build_issue("spec/samples/bad_code.lua", 3, 16, "211", "MAJOR", "CODE_SMELL", "unused function 'helper'"),
47+
build_issue("spec/samples/bad_code.lua", 3, 23, "212", "MAJOR", "CODE_SMELL", "unused variable length argument"),
48+
build_issue("spec/samples/bad_code.lua", 7, 10, "111", "MAJOR", "CODE_SMELL", "setting non-standard global variable 'embrace'"),
49+
build_issue("spec/samples/bad_code.lua", 8, 10, "412", "MAJOR", "CODE_SMELL", "variable 'opt' was previously defined as an argument on line 7", {{
50+
filePath = "spec/samples/bad_code.lua",
51+
message = "opt",
52+
textRange = {
53+
startLine = 7,
54+
startColumn = 17,
55+
endColumn = 19,
56+
},
57+
}}),
58+
build_issue("spec/samples/bad_code.lua", 9, 11, "113", "MAJOR", "CODE_SMELL", "accessing undefined variable 'hepler'"),
59+
build_issue("spec/samples/python_code.lua", 1, 6, "011", "BLOCKER", "BUG", "expected '=' near '__future__'"),
60+
}
61+
local output = get_json_output "spec/samples/good_code.lua spec/samples/bad_code.lua spec/samples/python_code.lua --std=lua52 --formatter sonar --no-config"
62+
for i, val in ipairs(output.issues) do
63+
assert.same(expected[i], val)
64+
end
65+
end)
66+
67+
it("renders fatal errors", function()
68+
assert.same({issues = {
69+
build_issue("spec/samples/404.lua", 1, nil, "FATAL", "BLOCKER", "BUG", "I/O error")
70+
}}, get_json_output "spec/samples/404.lua --formatter sonar --no-config")
71+
end)
72+
end)

src/luacheck/formatter/sonar.lua

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
local format = require("luacheck.format")
2+
3+
local json_ok, json = pcall(require, "dkjson")
4+
json = json_ok and json or require("json")
5+
6+
local sonar_error = {
7+
severity = "BLOCKER",
8+
type = "BUG",
9+
}
10+
local sonar_warning = {
11+
severity = "MAJOR",
12+
type = "CODE_SMELL",
13+
}
14+
15+
local function is_error(event)
16+
return event.code:sub(1, 1) == "0"
17+
end
18+
19+
local function build_primary_location(file_name, event)
20+
local has_end_column = event.endColumn and event.endColumn > event.column
21+
local message = format.get_message(event)
22+
return {
23+
message = message,
24+
filePath = file_name,
25+
textRange = {
26+
startLine = event.line,
27+
startColumn = event.column - 1,
28+
endColumn = has_end_column and (event.endColumn - 1) or nil,
29+
}
30+
}
31+
end
32+
33+
local function build_secondary_locations(file_name, event)
34+
local has_secondary_locations = event.prev_line ~= nil
35+
if not has_secondary_locations then
36+
return nil
37+
end
38+
39+
local has_prev_column = event.prev_column ~= nil
40+
local has_prev_end_column = has_prev_column and (event.prev_end_column and event.prev_end_column > event.prev_column)
41+
42+
return {
43+
{
44+
message = event.name,
45+
filePath = file_name,
46+
textRange = {
47+
startLine = event.prev_line,
48+
startColumn = has_prev_column and (event.prev_column - 1) or nil,
49+
endColumn = has_prev_end_column and (event.prev_end_column - 1) or nil,
50+
}
51+
}
52+
}
53+
end
54+
55+
local function sonar_issue(file_name, event)
56+
local category = is_error(event) and sonar_error or sonar_warning
57+
58+
return {
59+
engineId = "luacheck",
60+
ruleId = event.code,
61+
severity = category.severity,
62+
type = category.type,
63+
effortMinutes = 2,
64+
primaryLocation = build_primary_location(file_name, event),
65+
secondaryLocations = build_secondary_locations(file_name, event)
66+
}
67+
end
68+
69+
local function sonar_fatal(file_name, file_report)
70+
return {
71+
engineId = "luacheck",
72+
ruleId = "FATAL",
73+
severity = "BLOCKER",
74+
type = "BUG",
75+
effortMinutes = 2,
76+
primaryLocation = {
77+
message = format.fatal_type(file_report),
78+
filePath = file_name,
79+
textRange = {
80+
startLine = 1
81+
}
82+
}
83+
}
84+
end
85+
86+
return function(report, file_names)
87+
local issues = setmetatable({}, {__jsontype = "array"})
88+
89+
for i, file_report in ipairs(report) do
90+
local file_name = file_names[i]
91+
if file_report.fatal then
92+
table.insert(issues, sonar_fatal(file_name, file_report))
93+
else
94+
for _, event in ipairs(file_report) do
95+
table.insert(issues, sonar_issue(file_name, event))
96+
end
97+
end
98+
end
99+
100+
return json.encode({
101+
issues = issues
102+
})
103+
end

src/luacheck/main.lua

+1
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ Links:
237237
" JUnit - JUnit XML formatter;\n" ..
238238
" visual_studio - MSBuild/Visual Studio aware formatter;\n" ..
239239
" plain - simple warning-per-line formatter;\n" ..
240+
" sonar - formatter for SonarQube;\n" ..
240241
" default - standard formatter."),
241242
parser:flag("-q --quiet", "Suppress output for files without warnings.\n" ..
242243
"-qq: Suppress output of warnings.\n" ..

0 commit comments

Comments
 (0)