Module:ClangDiags

From emmtrix Wiki
Jump to navigation Jump to search

local p = {}

local clangVersion = '17.0.6'

local diagsData = require('Module:ClangDiags/DiagsData')
local groupsData = require('Module:ClangDiags/GroupsData')
local diagsLongDataIdx = require('Module:ClangDiags/DiagsLongDataIdx')

local function getLongDiagData(id)
	for _, idx in ipairs(diagsLongDataIdx) do
		if idx.start <= id and id <= idx['end'] then
			--error("getLongDiagData " .. id .. ' Module:ClangDiags/DiagsLongData' .. idx.file)
			local data = require('Module:ClangDiags/DiagsLongData' .. idx.file)
			local diag = data[id]
			
			for k, v in pairs(diagsData[id]) do
				diag[k] = v
			end
			
			return diag
		end
	end
end

local function escapeSpecialChars(text)
    local replacements = {
        ['%['] = "&#91;",
        ['%]'] = "&#93;",
        ['%%'] = "&#37;",
		['{'] = "&#123;",
    	['}'] = "&#125;",
    	['%|'] = "&#124;",
    }

    -- Iterate over all replacements and perform the substitutions
    for original, replacement in pairs(replacements) do
        text = text:gsub(original, replacement)
    end

    return text
end

local function formatInlineCode(text)
	return '<code>' .. escapeSpecialChars(text) .. '</code>'
end

local function formatGroupLink(g, defaultactive)
	local eltext = 'element'
	if (g.count ~= 1) then
		eltext = eltext .. 's'
	end
	
	local flag = g.flag
	if defaultactive then
		flag = g.flagoff
	end
	
	return string.format('[[Clang:Flag/%s|%s]] (%d %s)', g.flag, flag, g.count, eltext)
end

local function getStyle(prefix)
	local style = {};
	
	style.prefix = "color: #007bfd; font-weight: bolder;"
	style.message = "color: #007bfd; font-weight: bolder;"
    if prefix == "error: " or prefix == "fatal error: " then
		style.prefix = "color: #A00; font-weight: bolder;"
    elseif prefix == "warning: " then
		style.prefix = "color: #A0A; font-weight: bolder;"
    elseif prefix == "note: " then
		style.prefix = "color: #000; font-weight: bolder;"
		style.message = "color: #007bfd;"
    elseif prefix == "remark: " then
		style.prefix = "color: #00A; font-weight: bolder;"
    end
    
    return style
end

-- Function to detect diagnostic type and format message
local function formatMessage(message)
    local formattedMessage = message
	local keywords = {"fatal error: ", "error: ", "warning: ", "note: ", "remark: "}

	for _, type in ipairs(keywords) do
		local startIdx, endIdx = string.find(message, type)

		if endIdx then
			local prefix = string.sub(message, 1, startIdx-1)
			local mainText = string.sub(message, endIdx)
			
			local style = getStyle(type)

	        formattedMessage = string.format("<span style=\"%s\">%s<span style=\"%s\">%s</span>%s</span>", style.message, prefix, style.prefix, type, escapeSpecialChars(mainText))
		
			break	
		end
		
	end

    return formattedMessage
end

local function formatHierarchical(message, vert)
	if type(message) == 'string' then
		if message == "" then
			return "&nbsp;\n"
		else
			return escapeSpecialChars(message) .. '\n'
		end
	end
	
	wiki = ''	
	if vert then
		wiki = wiki .. '\n{| border=0 style="margin: -3px;"\n'
		for no, part in ipairs(message) do
			if no ~= 1 then
				wiki = wiki .. '|-\n'
				wiki = wiki .. '| style="padding: 0 5px; border-top: 2px solid;" | ' .. formatHierarchical(part, false)
			else
				wiki = wiki .. '| style="padding: 0 5px;" | ' .. formatHierarchical(part, false)
			end
		end
		wiki = wiki .. '|}\n'
	else
		wiki = wiki .. '\n{| border=0 style="margin: -3px;"\n'
		for _, part in ipairs(message) do
			wiki = wiki .. '| ' .. formatHierarchical(part, true)	
		end
		wiki = wiki .. '|}\n'
	end

	return wiki
end

local function formatHierarchicalPrefix(prefix, message, prefix2)
	local style = getStyle(prefix)
	
	prefix = (prefix2 or "") .. string.format('<span style="%s">%s</span>', style.prefix, prefix)
	
	if type(message) == 'string' then
		message = prefix .. message
	else
		table.insert(message, 1, prefix)

		message = formatHierarchical(message)
	end

	return string.format('<span style="%s">%s</span>', style.message, message)
end

local function formatHierarchicalPrefix2(val)
	return formatHierarchicalPrefix('error: ', val)
end

local function formatMultiVersion(last, first)
	local str
	if first == nil and last == nil then
		return ''
	elseif first == nil and last ~= nil then
		str = string.format('until %s', last)
	elseif first ~= nil and last == nil then
		str = string.format('since %s', first)
	elseif first == last then
		str = string.format('%s', first)
	else
		str = string.format('%s - %s', first, last)
	end
	
	return string.format(' <span style="color: green; font-size: 0.8em;">(%s)</span>', str)
end

local function formatMultiVersionValues(vervals, func)
	if type(vervals) == 'string' then
		return func(vervals)
	end
	
	wiki = ''
	for no,verval in ipairs(vervals) do
		if no ~= 1 then
			wiki = wiki .. "<br>"
		end
		wiki = wiki .. func(verval[3]) .. formatMultiVersion(verval[1], verval[2])
	end
	
	return wiki
end

local function getLatestMultiVersionValue(vervals)
	if type(vervals) == 'string' then
		return vervals
	else
		return vervals[1][3]
	end
end

function p.createTable(frame)
    -- Start of the wiki table
    local wikitable = '{| class="wikitable sortable"\n'

    wikitable = wikitable .. '! Id || Type || Title \n'

    local counter = 0 -- Initialize counter
    for id, diag in pairs(diagsData) do
        if counter >= 100 then -- Check if counter has reached 1000
            break -- Stop the loop
        end
        wikitable = wikitable .. '|-\n'
        wikitable = wikitable .. string.format('| [[Clang:Diag/%s|%s]] || %s || %s\n', id, id, diag.type, diag.title)
		counter = counter + 1 -- Increment counter
	end

    -- End of the table
    wikitable = wikitable .. '\n|}'

    return wikitable
end

function p.createInfo(frame)
	local id = frame.args.id
	local diag = getLongDiagData(id)
	
    local wiki = ''
    local title = diag.prefix .. diag.title
    local hierarchical = diag.hierarchical or diag.title 
    
	frame:preprocess(string.format('{{DISPLAYTITLE:Clang %s (%s)}}', title, id))
	frame:preprocess(string.format('{{DEFAULTSORT:%s}}', diag.title))

    wiki = wiki .. string.format('[[Category:Clang %ss]]\n', diag.type)
    wiki = wiki .. string.format('[[Category:Clang %s Category]]\n', getLatestMultiVersionValue(diag.category_hist))
    wiki = wiki .. '{| class="wikitable sortable"\n'
    wiki = wiki .. '! Text\n'
    wiki = wiki .. '| ' .. formatMultiVersionValues(diag.hierarchical_hist, formatHierarchicalPrefix2) .. '\n'
    wiki = wiki .. '|-\n'
    wiki = wiki .. '! Type\n'
    wiki = wiki .. '| ' .. diag.type .. '\n'
    wiki = wiki .. '|-\n'
    wiki = wiki .. '! Category\n'
    wiki = wiki .. '| ' .. formatMultiVersionValues(diag.category_hist, escapeSpecialChars) .. '\n'
    wiki = wiki .. '|-\n'
    wiki = wiki .. '! Internal Id\n'
    wiki = wiki .. '| ' .. formatMultiVersionValues(diag.id_hist, escapeSpecialChars) .. '\n'
	
	local defaultactive = diag.defaultactive == nil or diag.defaultactive
	if diag.type == 'Warning' or diag.type == "Downgradable Error" then
	    wiki = wiki .. '|-\n'
	    wiki = wiki .. '! Active by Default\n'
	    if defaultactive then
		    wiki = wiki .. '| Yes\n'
		else
		    wiki = wiki .. '| No\n'
	    end
	end

	if diag.groups ~= nil then
	    wiki = wiki .. '|-\n'
	    wiki = wiki .. '! Flags\n'
	    wiki = wiki .. '| '

	    for no, group in ipairs(diag.groups) do
	    	local g = groupsData[group]
			wiki = wiki .. formatGroupLink(g, defaultactive) .. '<br>'
		end

	    wiki = wiki .. '\n'
	end

    wiki = wiki .. '|-\n'
    wiki = wiki .. '! Internal Message\n'
--    wiki = wiki .. '| ' .. string.format('<code>%s</code>\n', escapeSpecialChars(diag.message))
    wiki = wiki .. '| ' .. formatMultiVersionValues(diag.message_hist, formatInlineCode) .. '\n'

    wiki = wiki .. '|-\n'
    wiki = wiki .. '! Regular Expression\n'
    wiki = wiki .. '| ' .. string.format('<code>%s</code>\n', escapeSpecialChars(diag.regex1 .. diag.regex2 .. diag.regex3))

	if diag.commit ~= nil then
	    wiki = wiki .. '|-\n'
	    wiki = wiki .. '! First Commit\n'
	    wiki = wiki .. '| ' .. string.format('%s [https://github.com/llvm/llvm-project/commit/%s %s] %s\n', os.date("%Y-%m-%d", diag.commit[2]), diag.commit[1], diag.commit[1], escapeSpecialChars(diag.commit[3]))
	end

    wiki = wiki .. '|}\n'
    wiki = wiki .. '\n'
    wiki = wiki .. '== Description ==\n'
    
	
	return wiki
end

function p.createInternalInfo(frame)
	local id = frame.args.id
	local diag = getLongDiagData(id)

    local wiki = ''

	wiki = wiki .. string.format('== Clang Internals (%s)==\n', clangVersion)

	if diag.commit ~= nil then
	    wiki = wiki .. '=== Git Commit Message ===\n'
	    wiki = wiki .. string.format('<pre>%s</pre>\n', escapeSpecialChars(diag.commit[4]));
	end
	
	if diag.source ~= nil then
	    wiki = wiki .. '=== Used in Clang Sources ===\n'
	    wiki = wiki .. 'This section lists all occurrences of the diagnostic within the Clang\'s codebase. For each occurrence, an auto-extracted snipped from the source code is listed including key elements like control structures, functions, or classes. It should illustrate the conditions under which the diagnostic is activated.\n'
	    for _, source in ipairs(diag.source) do
	    	local lineno = source[2] - 1
		    wiki = wiki .. string.format('==== [https://github.com/llvm/llvm-project/blob/llvmorg-%s/%s#L%d %s (line %d)] ====\n', clangVersion, source[1], lineno, source[1], lineno)
		    local highlight = string.format('<syntaxhighlight lang="cpp">\n%s\n</syntaxhighlight>\n', source[3])
	    	wiki = wiki .. frame:preprocess(highlight);
	    end
	end
	
	if diag.tests2 ~= nil then
	    wiki = wiki .. '=== Triggered in Clang Tests ===\n'
	    wiki = wiki .. 'This section lists all internal Clang test cases that trigger the diagnostic.\n'
	    for file, tests in pairs(diag.tests2) do
		    wiki = wiki .. string.format('==== [https://github.com/llvm/llvm-project/blob/llvmorg-%s/%s %s] ====\n', clangVersion, file, file)
		    for _, test in ipairs(tests) do
		    	wiki = wiki .. '* ' .. formatMessage(test) .. '\n\n'
		    end
		end		
	end
	
	return wiki
end

local function createGroupDiagList(diags, defaultactive2)
    local wiki = '{| class=wikitable\n'
    local count = 0

	for _, diagid in ipairs(diags) do
		local diag = diagsData[diagid]
		local defaultactive = diag.defaultactive == nil or diag.defaultactive
		local hierarchical = diag.hierarchical or diag.title
		if defaultactive2 == defaultactive then
			wiki = wiki .. '| ' .. string.format('[[Clang:Diag/%s|%s]]\n', diagid, diagid)
			wiki = wiki .. '| ' .. formatHierarchicalPrefix(diag.prefix, hierarchical) .. '\n'
			wiki = wiki .. '|-\n'
			count = count + 1
		end
	end
	wiki = wiki .. '|}\n'
	
	if count == 0 then
		wiki = 'None\n'
	end
	
	return wiki
end

function p.createGroupInfo(frame)
	local id = frame.args.id
	local group = groupsData[id]
	
	frame:preprocess(string.format('{{DISPLAYTITLE:Clang Flag: %s / %s}}', group.flag, group.flagoff))
	frame:preprocess(string.format('{{DEFAULTSORT:%s}}', id))

    local wiki = ''

    wiki = wiki .. string.format('[[Category:Clang Flags]]\n')

	wiki = wiki .. '\n'
	wiki = wiki .. '== Supergroups ==\n'
	for _, g in ipairs(group.supergroups) do
		wiki = wiki .. string.format('* %s\n', formatGroupLink(groupsData[g], false))
	end

	wiki = wiki .. '\n'
	wiki = wiki .. '== Subroups ==\n'
	for _, g in ipairs(group.subgroups) do
		wiki = wiki .. string.format('* %s\n', formatGroupLink(groupsData[g], false))
	end

	wiki = wiki .. '\n'
	wiki = wiki .. '== Warnings/Remarks ==\n'
	wiki = wiki .. string.format('=== Default Active (Deactivate with %s) ===\n', group.flagoff)
	wiki = wiki .. createGroupDiagList(group.diags, true)
	wiki = wiki .. string.format('=== Default Inactive (Activate with %s) ===\n', group.flag)
	wiki = wiki .. createGroupDiagList(group.diags, false)
	
	return wiki
end

-- Function to output the number of diagnostics
function p.countDiags()
    local count = 0
    for _ in pairs(diagsData) do
        count = count + 1
    end
    return count
end

return p