local utils = require "luacheck.utils"
|
|
local stage = {}
|
|
local function cyclomatic_complexity_message_format(warning)
|
local template = "cyclomatic complexity of %s is too high ({complexity} > {max_complexity})"
|
|
local function_descr
|
|
if warning.function_type == "main_chunk" then
|
function_descr = "main chunk"
|
elseif warning.function_name then
|
function_descr = "{function_type} {function_name!}"
|
else
|
function_descr = "function"
|
end
|
|
return template:format(function_descr)
|
end
|
|
stage.warnings = {
|
["561"] = {message_format = cyclomatic_complexity_message_format,
|
fields = {"complexity", "function_type", "function_name"}}
|
}
|
|
local function warn_cyclomatic_complexity(chstate, line, complexity)
|
if line == chstate.top_line then
|
chstate:warn("561", 1, 1, 1, {
|
complexity = complexity,
|
function_type = "main_chunk"
|
})
|
else
|
local node = line.node
|
|
chstate:warn_range("561", node, {
|
complexity = complexity,
|
function_type = node[1][1] and node[1][1].implicit and "method" or "function",
|
function_name = node.name
|
})
|
end
|
end
|
|
local CyclomaticComplexityMetric = utils.class()
|
|
function CyclomaticComplexityMetric:incr_decisions(count)
|
self.count = self.count + count
|
end
|
|
function CyclomaticComplexityMetric:calc_expr(node)
|
if node.tag == "Op" and (node[1] == "and" or node[1] == "or") then
|
self:incr_decisions(1)
|
end
|
|
if node.tag ~= "Function" then
|
self:calc_exprs(node)
|
end
|
end
|
|
function CyclomaticComplexityMetric:calc_exprs(exprs)
|
for _, expr in ipairs(exprs) do
|
if type(expr) == "table" then
|
self:calc_expr(expr)
|
end
|
end
|
end
|
|
function CyclomaticComplexityMetric:calc_item_Eval(item)
|
self:calc_expr(item.node)
|
end
|
|
function CyclomaticComplexityMetric:calc_item_Local(item)
|
if item.rhs then
|
self:calc_exprs(item.rhs)
|
end
|
end
|
|
function CyclomaticComplexityMetric:calc_item_Set(item)
|
self:calc_exprs(item.rhs)
|
end
|
|
function CyclomaticComplexityMetric:calc_item(item)
|
local f = self["calc_item_" .. item.tag]
|
if f then
|
f(self, item)
|
end
|
end
|
|
function CyclomaticComplexityMetric:calc_items(items)
|
for _, item in ipairs(items) do
|
self:calc_item(item)
|
end
|
end
|
|
-- stmt if: {condition, block; condition, block; ... else_block}
|
function CyclomaticComplexityMetric:calc_stmt_If(node)
|
for i = 1, #node - 1, 2 do
|
self:incr_decisions(1)
|
self:calc_stmts(node[i+1])
|
end
|
|
if #node % 2 == 1 then
|
self:calc_stmts(node[#node])
|
end
|
end
|
|
-- stmt while: {condition, block}
|
function CyclomaticComplexityMetric:calc_stmt_While(node)
|
self:incr_decisions(1)
|
self:calc_stmts(node[2])
|
end
|
|
-- stmt repeat: {block, condition}
|
function CyclomaticComplexityMetric:calc_stmt_Repeat(node)
|
self:incr_decisions(1)
|
self:calc_stmts(node[1])
|
end
|
|
-- stmt forin: {iter_vars, expression_list, block}
|
function CyclomaticComplexityMetric:calc_stmt_Forin(node)
|
self:incr_decisions(1)
|
self:calc_stmts(node[3])
|
end
|
|
-- stmt fornum: {first_var, expression, expression, expression[optional], block}
|
function CyclomaticComplexityMetric:calc_stmt_Fornum(node)
|
self:incr_decisions(1)
|
self:calc_stmts(node[5] or node[4])
|
end
|
|
function CyclomaticComplexityMetric:calc_stmt(node)
|
local f = self["calc_stmt_" .. node.tag]
|
if f then
|
f(self, node)
|
end
|
end
|
|
function CyclomaticComplexityMetric:calc_stmts(stmts)
|
for _, stmt in ipairs(stmts) do
|
self:calc_stmt(stmt)
|
end
|
end
|
|
-- Cyclomatic complexity of a function equals to the number of decision points plus 1.
|
function CyclomaticComplexityMetric:report(chstate, line)
|
self.count = 1
|
self:calc_stmts(line.node[2])
|
self:calc_items(line.items)
|
warn_cyclomatic_complexity(chstate, line, self.count)
|
end
|
|
function stage.run(chstate)
|
local ccmetric = CyclomaticComplexityMetric()
|
|
for _, line in ipairs(chstate.lines) do
|
ccmetric:report(chstate, line)
|
end
|
end
|
|
return stage
|