245 lines
4.9 KiB
D
245 lines
4.9 KiB
D
// Copyright Brian Schott (Hackerpilot) 2012.
|
|
// Distributed under the Boost Software License, Version 1.0.
|
|
// (See accompanying file LICENSE_1_0.txt or copy at
|
|
// http://www.boost.org/LICENSE_1_0.txt)
|
|
|
|
module dscanner.reports;
|
|
|
|
import std.json;
|
|
import std.algorithm : map;
|
|
import std.array : split, array, Appender, appender;
|
|
|
|
import dscanner.analysis.base : Message, MessageSet;
|
|
import dscanner.analysis.stats_collector;
|
|
|
|
class DScannerJsonReporter
|
|
{
|
|
struct Issue
|
|
{
|
|
Message message;
|
|
string type;
|
|
}
|
|
|
|
private Appender!(Issue[]) _issues;
|
|
|
|
this()
|
|
{
|
|
_issues = appender!(Issue[]);
|
|
}
|
|
|
|
void addMessageSet(MessageSet messageSet)
|
|
{
|
|
_issues ~= toIssues(messageSet);
|
|
}
|
|
|
|
void addMessage(Message message, bool isError = false)
|
|
{
|
|
_issues ~= toIssue(message, isError);
|
|
}
|
|
|
|
string getContent(StatsCollector stats, ulong lineOfCodeCount)
|
|
{
|
|
JSONValue result = [
|
|
"issues" : JSONValue(_issues.data.map!(e => toJson(e)).array),
|
|
"interfaceCount": JSONValue(stats.interfaceCount),
|
|
"classCount": JSONValue(stats.classCount),
|
|
"functionCount": JSONValue(stats.functionCount),
|
|
"templateCount": JSONValue(stats.templateCount),
|
|
"structCount": JSONValue(stats.structCount),
|
|
"statementCount": JSONValue(stats.statementCount),
|
|
"lineOfCodeCount": JSONValue(lineOfCodeCount),
|
|
"undocumentedPublicSymbols": JSONValue(stats.undocumentedPublicSymbols)
|
|
];
|
|
return result.toPrettyString();
|
|
}
|
|
|
|
private static JSONValue toJson(Issue issue)
|
|
{
|
|
// dfmt off
|
|
JSONValue js = JSONValue([
|
|
"key": JSONValue(issue.message.key),
|
|
"fileName": JSONValue(issue.message.fileName),
|
|
"line": JSONValue(issue.message.line),
|
|
"column": JSONValue(issue.message.column),
|
|
"message": JSONValue(issue.message.message),
|
|
"type": JSONValue(issue.type)
|
|
]);
|
|
// dfmt on
|
|
|
|
if (issue.message.checkName !is null)
|
|
{
|
|
js["name"] = JSONValue(issue.message.checkName);
|
|
}
|
|
|
|
return js;
|
|
}
|
|
|
|
private static Issue[] toIssues(MessageSet messageSet)
|
|
{
|
|
return messageSet[].map!(e => toIssue(e)).array;
|
|
}
|
|
|
|
private static Issue toIssue(Message message, bool isError = false)
|
|
{
|
|
// dfmt off
|
|
Issue issue = {
|
|
message: message,
|
|
type : isError ? "error" : "warn"
|
|
};
|
|
// dfmt on
|
|
return issue;
|
|
}
|
|
}
|
|
|
|
class SonarQubeGenericIssueDataReporter
|
|
{
|
|
enum Type
|
|
{
|
|
bug = "BUG",
|
|
vulnerability = "VULNERABILITY",
|
|
codeSmell = "CODE_SMELL"
|
|
}
|
|
|
|
enum Severity
|
|
{
|
|
blocker = "BLOCKER",
|
|
critical = "CRITICAL",
|
|
major = "MAJOR",
|
|
minor = "MINOR",
|
|
info = "INFO"
|
|
}
|
|
|
|
struct Issue
|
|
{
|
|
string engineId;
|
|
string ruleId;
|
|
Location primaryLocation;
|
|
string type;
|
|
string severity;
|
|
int effortMinutes;
|
|
Location[] secondaryLocations;
|
|
}
|
|
|
|
struct Location
|
|
{
|
|
string message;
|
|
string filePath;
|
|
TextRange textRange;
|
|
}
|
|
|
|
struct TextRange
|
|
{
|
|
// SonarQube Generic Issue only allows specifying start line only
|
|
// or the complete range, for which start and end has to differ
|
|
// D-Scanner does not provide the complete range info
|
|
long startLine;
|
|
}
|
|
|
|
private Appender!(Issue[]) _issues;
|
|
|
|
this()
|
|
{
|
|
_issues = appender!(Issue[]);
|
|
}
|
|
|
|
void addMessageSet(MessageSet messageSet)
|
|
{
|
|
_issues ~= toIssues(messageSet);
|
|
}
|
|
|
|
void addMessage(Message message, bool isError = false)
|
|
{
|
|
_issues ~= toIssue(message, isError);
|
|
}
|
|
|
|
string getContent()
|
|
{
|
|
JSONValue result = [
|
|
"issues" : JSONValue(_issues.data.map!(e => toJson(e)).array)
|
|
];
|
|
return result.toPrettyString();
|
|
}
|
|
|
|
private static JSONValue toJson(Issue issue)
|
|
{
|
|
// dfmt off
|
|
return JSONValue([
|
|
"engineId": JSONValue(issue.engineId),
|
|
"ruleId": JSONValue(issue.ruleId),
|
|
"severity": JSONValue(issue.severity),
|
|
"type": JSONValue(issue.type),
|
|
"primaryLocation": JSONValue([
|
|
"message": JSONValue(issue.primaryLocation.message),
|
|
"filePath": JSONValue(issue.primaryLocation.filePath),
|
|
"textRange": JSONValue([
|
|
"startLine": JSONValue(issue.primaryLocation.textRange.startLine)
|
|
]),
|
|
]),
|
|
]);
|
|
// dfmt on
|
|
}
|
|
|
|
private static Issue[] toIssues(MessageSet messageSet)
|
|
{
|
|
return messageSet[].map!(e => toIssue(e)).array;
|
|
}
|
|
|
|
private static Issue toIssue(Message message, bool isError = false)
|
|
{
|
|
// dfmt off
|
|
Issue issue = {
|
|
engineId: "dscanner",
|
|
ruleId : message.key,
|
|
severity : (isError) ? Severity.blocker : getSeverity(message.key),
|
|
type : getType(message.key),
|
|
primaryLocation : getPrimaryLocation(message)
|
|
};
|
|
// dfmt on
|
|
return issue;
|
|
}
|
|
|
|
private static Location getPrimaryLocation(Message message)
|
|
{
|
|
return Location(message.message, message.fileName, TextRange(message.line));
|
|
}
|
|
|
|
private static string getSeverity(string key)
|
|
{
|
|
auto a = key.split(".");
|
|
|
|
if (a.length <= 1)
|
|
{
|
|
return Severity.major;
|
|
}
|
|
else
|
|
{
|
|
switch (a[1])
|
|
{
|
|
case "style":
|
|
return Severity.minor;
|
|
default:
|
|
return Severity.major;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static string getType(string key)
|
|
{
|
|
auto a = key.split(".");
|
|
|
|
if (a.length <= 1)
|
|
{
|
|
return Type.bug;
|
|
}
|
|
else
|
|
{
|
|
switch (a[1])
|
|
{
|
|
case "style":
|
|
return Type.codeSmell;
|
|
default:
|
|
return Type.bug;
|
|
}
|
|
}
|
|
}
|
|
} |