1 //			Copyright Brian Schott (Hackerpilot) 2012.
2 // Distributed under the Boost Software License, Version 1.0.
3 //	  (See accompanying file LICENSE_1_0.txt or copy at
4 //			http://www.boost.org/LICENSE_1_0.txt)
5 
6 module dscanner.reports;
7 
8 import std.json;
9 import std.algorithm : map;
10 import std.array : split, array, Appender, appender;
11 
12 import dscanner.analysis.base : Message, MessageSet;
13 
14 class SonarQubeGenericIssueDataReporter
15 {
16 	enum Type
17 	{
18 		bug = "BUG",
19 		vulnerability = "VULNERABILITY",
20 		codeSmell = "CODE_SMELL"
21 	}
22 
23 	enum Severity
24 	{
25 		blocker = "BLOCKER",
26 		critical = "CRITICAL",
27 		major = "MAJOR",
28 		minor = "MINOR",
29 		info = "INFO"
30 	}
31 
32 	struct Issue
33 	{
34 		string engineId;
35 		string ruleId;
36 		Location primaryLocation;
37 		string type;
38 		string severity;
39 		int effortMinutes;
40 		Location[] secondaryLocations;
41 	}
42 
43 	struct Location
44 	{
45 		string message;
46 		string filePath;
47 		TextRange textRange;
48 	}
49 
50 	struct TextRange
51 	{
52 		// SonarQube Generic Issue only allows specifying start line only
53 		// or the complete range, for which start and end has to differ
54 		// D-Scanner does not provide the complete range info
55 		long startLine;
56 	}
57 
58 	private Appender!(Issue[]) _issues;
59 
60 	this()
61 	{
62 		_issues = appender!(Issue[]);
63 	}
64 
65 	void addMessageSet(MessageSet messageSet)
66 	{
67 		_issues ~= toIssues(messageSet);
68 	}
69 	
70 	void addMessage(Message message, bool isError = false)
71 	{
72 		_issues ~= toIssue(message, isError);
73 	}
74 
75 	string getContent()
76 	{
77 		JSONValue result = [
78 			"issues" : JSONValue(_issues.data.map!(e => toJson(e)).array)
79 		];
80 		return result.toPrettyString();
81 	}
82 
83 	private static JSONValue toJson(Issue issue)
84 	{
85 		// dfmt off
86 		return JSONValue([
87 			"engineId": JSONValue(issue.engineId),
88 			"ruleId": JSONValue(issue.ruleId),
89 			"severity": JSONValue(issue.severity),
90 			"type": JSONValue(issue.type),
91 			"primaryLocation": JSONValue([
92 				"message": JSONValue(issue.primaryLocation.message),
93 				"filePath": JSONValue(issue.primaryLocation.filePath),
94 				"textRange": JSONValue([
95 					"startLine": JSONValue(issue.primaryLocation.textRange.startLine)
96 				]),
97 			]),
98 		]);
99 		// dfmt on
100 	}
101 
102 	private static Issue[] toIssues(MessageSet messageSet)
103 	{
104 		return messageSet[].map!(e => toIssue(e)).array;
105 	}
106 
107 	private static Issue toIssue(Message message, bool isError = false)
108 	{		
109 		// dfmt off
110 		Issue issue = {
111 			engineId: "dscanner",
112 			ruleId : message.key,
113 			severity : (isError) ? Severity.blocker : getSeverity(message.key),
114 			type : getType(message.key),
115 			primaryLocation : getPrimaryLocation(message)
116 		};
117 		// dfmt on
118 		return issue;
119 	}
120 
121 	private static Location getPrimaryLocation(Message message)
122 	{
123 		return Location(message.message, message.fileName, TextRange(message.line));
124 	}
125 
126 	private static string getSeverity(string key)
127 	{
128 		auto a = key.split(".");
129 
130 		if (a.length <= 1)
131 		{
132 			return Severity.major;
133 		}
134 		else
135 		{
136 			switch (a[1])
137 			{
138 				case "style":
139 					return Severity.minor;
140 				default:
141 					return Severity.major;
142 			}
143 		}
144 	}
145 
146 	private static string getType(string key)
147 	{
148 		auto a = key.split(".");
149 
150 		if (a.length <= 1)
151 		{
152 			return Type.bug;
153 		}
154 		else
155 		{
156 			switch (a[1])
157 			{
158 				case "style":
159 					return Type.codeSmell;
160 				default:
161 					return Type.bug;
162 			}
163 		}
164 	}
165 }