Added reportFormat sonarQubeGenericIssueData
This commit is contained in:
parent
19e9b9093a
commit
3b6bbad9fe
|
@ -173,6 +173,15 @@ The "--report" option writes a JSON report on the static analysis checks
|
|||
document above to standard output. This file is usually used by the D plugin for
|
||||
SonarQube located [here](https://github.com/economicmodeling/sonar-d-plugin).
|
||||
|
||||
Using option "--reportFormat sonarQubeGenericIssueData" a report in a sonar-scanner
|
||||
supported [Generic Issue Data format](https://docs.sonarqube.org/latest/analysis/generic-issue/) can be created.
|
||||
|
||||
$ dscanner --reportFormat sonarQubeGenericIssueData . > sonar-generic-issue-data.json
|
||||
|
||||
Reference the report filename in sonar-project.properties using key "sonar.externalIssuesReportPaths"
|
||||
|
||||
sonar.externalIssuesReportPaths=sonar-generic-issue-data.json
|
||||
|
||||
### Find Declaration
|
||||
Ack, grep, and The Silver Searcher are useful for finding usages of symbols, but
|
||||
their signal to noise ratio is not very good when searching for a symbol's
|
||||
|
|
|
@ -87,6 +87,7 @@ import dsymbol.conversion.second;
|
|||
import dsymbol.modulecache : ModuleCache;
|
||||
|
||||
import dscanner.utils;
|
||||
import dscanner.reports : SonarQubeGenericIssueDataReporter;
|
||||
|
||||
bool first = true;
|
||||
|
||||
|
@ -180,6 +181,31 @@ void generateReport(string[] fileNames, const StaticAnalysisConfig config,
|
|||
writeln("}");
|
||||
}
|
||||
|
||||
void generateSonarQubeGenericIssueDataReport(string[] fileNames, const StaticAnalysisConfig config,
|
||||
ref StringCache cache, ref ModuleCache moduleCache)
|
||||
{
|
||||
auto reporter = new SonarQubeGenericIssueDataReporter();
|
||||
|
||||
auto writeMessages = delegate void(string fileName, size_t line, size_t column, string message, bool isError){
|
||||
reporter.addMessage(Message(fileName, line, column, "dscanner.syntax", message), isError);
|
||||
};
|
||||
|
||||
foreach (fileName; fileNames)
|
||||
{
|
||||
auto code = readFile(fileName);
|
||||
// Skip files that could not be read and continue with the rest
|
||||
if (code.length == 0)
|
||||
continue;
|
||||
RollbackAllocator r;
|
||||
const(Token)[] tokens;
|
||||
const Module m = parseModule(fileName, code, &r, cache, tokens, writeMessages, null, null, null);
|
||||
MessageSet messageSet = analyze(fileName, m, config, moduleCache, tokens, true);
|
||||
reporter.addMessageSet(messageSet);
|
||||
}
|
||||
|
||||
writeln(reporter.getContent());
|
||||
}
|
||||
|
||||
/**
|
||||
* For multiple files
|
||||
*
|
||||
|
@ -217,24 +243,33 @@ bool analyze(string[] fileNames, const StaticAnalysisConfig config, string error
|
|||
}
|
||||
|
||||
const(Module) parseModule(string fileName, ubyte[] code, RollbackAllocator* p,
|
||||
string errorFormat, ref StringCache cache, bool report, ref const(Token)[] tokens,
|
||||
ulong* linesOfCode = null, uint* errorCount = null, uint* warningCount = null)
|
||||
ref StringCache cache, ref const(Token)[] tokens,
|
||||
MessageDelegate dlgMessage, ulong* linesOfCode = null,
|
||||
uint* errorCount = null, uint* warningCount = null)
|
||||
{
|
||||
import dscanner.stats : isLineOfCode;
|
||||
|
||||
auto writeMessages = delegate(string fileName, size_t line, size_t column, string message, bool isError){
|
||||
return messageFunctionFormat(errorFormat, Message(fileName, line, column, message), isError);
|
||||
};
|
||||
|
||||
LexerConfig config;
|
||||
config.fileName = fileName;
|
||||
config.stringBehavior = StringBehavior.source;
|
||||
tokens = getTokensForParser(code, config, &cache);
|
||||
if (linesOfCode !is null)
|
||||
(*linesOfCode) += count!(a => isLineOfCode(a.type))(tokens);
|
||||
return dparse.parser.parseModule(tokens, fileName, p,
|
||||
|
||||
return dparse.parser.parseModule(tokens, fileName, p, dlgMessage, errorCount, warningCount);
|
||||
}
|
||||
|
||||
const(Module) parseModule(string fileName, ubyte[] code, RollbackAllocator* p,
|
||||
string errorFormat, ref StringCache cache, bool report, ref const(Token)[] tokens,
|
||||
ulong* linesOfCode = null, uint* errorCount = null, uint* warningCount = null)
|
||||
{
|
||||
auto writeMessages = delegate(string fileName, size_t line, size_t column, string message, bool isError){
|
||||
return messageFunctionFormat(errorFormat, Message(fileName, line, column, message), isError);
|
||||
};
|
||||
|
||||
return parseModule(fileName, code, p, cache, tokens,
|
||||
report ? toDelegate(&messageFunctionJSON) : writeMessages,
|
||||
errorCount, warningCount);
|
||||
linesOfCode, errorCount, warningCount);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -62,6 +62,7 @@ else
|
|||
bool defaultConfig;
|
||||
bool report;
|
||||
bool skipTests;
|
||||
string reportFormat;
|
||||
string symbolName;
|
||||
string configLocation;
|
||||
string[] importPaths;
|
||||
|
@ -91,6 +92,7 @@ else
|
|||
"declaration|d", &symbolName,
|
||||
"config", &configLocation,
|
||||
"report", &report,
|
||||
"reportFormat", &reportFormat,
|
||||
"I", &importPaths,
|
||||
"version", &printVersion,
|
||||
"muffinButton", &muffin,
|
||||
|
@ -155,6 +157,9 @@ else
|
|||
if (absImportPaths.length)
|
||||
moduleCache.addImportPaths(absImportPaths);
|
||||
|
||||
if (reportFormat.length)
|
||||
report = true;
|
||||
|
||||
immutable optionCount = count!"a"([sloc, highlight, ctags, tokenCount, syntaxCheck, ast, imports,
|
||||
outline, tokenDump, styleCheck, defaultConfig, report,
|
||||
symbolName !is null, etags, etagsAll, recursiveImports]);
|
||||
|
@ -237,7 +242,21 @@ else
|
|||
if (skipTests)
|
||||
config.enabled2SkipTests;
|
||||
if (report)
|
||||
{
|
||||
switch (reportFormat)
|
||||
{
|
||||
default:
|
||||
stderr.writeln("Unknown report format specified, using dscanner format");
|
||||
goto case;
|
||||
case "":
|
||||
case "dscanner":
|
||||
generateReport(expandArgs(args), config, cache, moduleCache);
|
||||
break;
|
||||
case "sonarQubeGenericIssueData":
|
||||
generateSonarQubeGenericIssueDataReport(expandArgs(args), config, cache, moduleCache);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
return analyze(expandArgs(args), config, errorFormat, cache, moduleCache, true) ? 1 : 0;
|
||||
}
|
||||
|
@ -386,6 +405,9 @@ Options:
|
|||
however the exit code will still be zero if errors or warnings are
|
||||
found.
|
||||
|
||||
--reportFormat <dscanner | sonarQubeGenericIssueData>...
|
||||
Specifies the format of the generated report.
|
||||
|
||||
--config <file>
|
||||
Use the given configuration file instead of the default located in
|
||||
$HOME/.config/dscanner/dscanner.ini
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
// 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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue