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 import dscanner.analysis.stats_collector; 14 15 class DScannerJsonReporter 16 { 17 struct Issue 18 { 19 Message message; 20 string type; 21 } 22 23 private Appender!(Issue[]) _issues; 24 25 this() 26 { 27 _issues = appender!(Issue[]); 28 } 29 30 void addMessageSet(MessageSet messageSet) 31 { 32 _issues ~= toIssues(messageSet); 33 } 34 35 void addMessage(Message message, bool isError = false) 36 { 37 _issues ~= toIssue(message, isError); 38 } 39 40 string getContent(StatsCollector stats, ulong lineOfCodeCount) 41 { 42 JSONValue result = [ 43 "issues" : JSONValue(_issues.data.map!(e => toJson(e)).array), 44 "interfaceCount": JSONValue(stats.interfaceCount), 45 "classCount": JSONValue(stats.classCount), 46 "functionCount": JSONValue(stats.functionCount), 47 "templateCount": JSONValue(stats.templateCount), 48 "structCount": JSONValue(stats.structCount), 49 "statementCount": JSONValue(stats.statementCount), 50 "lineOfCodeCount": JSONValue(lineOfCodeCount), 51 "undocumentedPublicSymbols": JSONValue(stats.undocumentedPublicSymbols) 52 ]; 53 return result.toPrettyString(); 54 } 55 56 private static JSONValue toJson(Issue issue) 57 { 58 // dfmt off 59 JSONValue js = JSONValue([ 60 "key": JSONValue(issue.message.key), 61 "fileName": JSONValue(issue.message.fileName), 62 "line": JSONValue(issue.message.line), 63 "column": JSONValue(issue.message.column), 64 "message": JSONValue(issue.message.message), 65 "type": JSONValue(issue.type) 66 ]); 67 // dfmt on 68 69 if (issue.message.checkName !is null) 70 { 71 js["name"] = JSONValue(issue.message.checkName); 72 } 73 74 return js; 75 } 76 77 private static Issue[] toIssues(MessageSet messageSet) 78 { 79 return messageSet[].map!(e => toIssue(e)).array; 80 } 81 82 private static Issue toIssue(Message message, bool isError = false) 83 { 84 // dfmt off 85 Issue issue = { 86 message: message, 87 type : isError ? "error" : "warn" 88 }; 89 // dfmt on 90 return issue; 91 } 92 } 93 94 class SonarQubeGenericIssueDataReporter 95 { 96 enum Type 97 { 98 bug = "BUG", 99 vulnerability = "VULNERABILITY", 100 codeSmell = "CODE_SMELL" 101 } 102 103 enum Severity 104 { 105 blocker = "BLOCKER", 106 critical = "CRITICAL", 107 major = "MAJOR", 108 minor = "MINOR", 109 info = "INFO" 110 } 111 112 struct Issue 113 { 114 string engineId; 115 string ruleId; 116 Location primaryLocation; 117 string type; 118 string severity; 119 int effortMinutes; 120 Location[] secondaryLocations; 121 } 122 123 struct Location 124 { 125 string message; 126 string filePath; 127 TextRange textRange; 128 } 129 130 struct TextRange 131 { 132 // SonarQube Generic Issue only allows specifying start line only 133 // or the complete range, for which start and end has to differ 134 // D-Scanner does not provide the complete range info 135 long startLine; 136 } 137 138 private Appender!(Issue[]) _issues; 139 140 this() 141 { 142 _issues = appender!(Issue[]); 143 } 144 145 void addMessageSet(MessageSet messageSet) 146 { 147 _issues ~= toIssues(messageSet); 148 } 149 150 void addMessage(Message message, bool isError = false) 151 { 152 _issues ~= toIssue(message, isError); 153 } 154 155 string getContent() 156 { 157 JSONValue result = [ 158 "issues" : JSONValue(_issues.data.map!(e => toJson(e)).array) 159 ]; 160 return result.toPrettyString(); 161 } 162 163 private static JSONValue toJson(Issue issue) 164 { 165 // dfmt off 166 return JSONValue([ 167 "engineId": JSONValue(issue.engineId), 168 "ruleId": JSONValue(issue.ruleId), 169 "severity": JSONValue(issue.severity), 170 "type": JSONValue(issue.type), 171 "primaryLocation": JSONValue([ 172 "message": JSONValue(issue.primaryLocation.message), 173 "filePath": JSONValue(issue.primaryLocation.filePath), 174 "textRange": JSONValue([ 175 "startLine": JSONValue(issue.primaryLocation.textRange.startLine) 176 ]), 177 ]), 178 ]); 179 // dfmt on 180 } 181 182 private static Issue[] toIssues(MessageSet messageSet) 183 { 184 return messageSet[].map!(e => toIssue(e)).array; 185 } 186 187 private static Issue toIssue(Message message, bool isError = false) 188 { 189 // dfmt off 190 Issue issue = { 191 engineId: "dscanner", 192 ruleId : message.key, 193 severity : (isError) ? Severity.blocker : getSeverity(message.key), 194 type : getType(message.key), 195 primaryLocation : getPrimaryLocation(message) 196 }; 197 // dfmt on 198 return issue; 199 } 200 201 private static Location getPrimaryLocation(Message message) 202 { 203 return Location(message.message, message.fileName, TextRange(message.line)); 204 } 205 206 private static string getSeverity(string key) 207 { 208 auto a = key.split("."); 209 210 if (a.length <= 1) 211 { 212 return Severity.major; 213 } 214 else 215 { 216 switch (a[1]) 217 { 218 case "style": 219 return Severity.minor; 220 default: 221 return Severity.major; 222 } 223 } 224 } 225 226 private static string getType(string key) 227 { 228 auto a = key.split("."); 229 230 if (a.length <= 1) 231 { 232 return Type.bug; 233 } 234 else 235 { 236 switch (a[1]) 237 { 238 case "style": 239 return Type.codeSmell; 240 default: 241 return Type.bug; 242 } 243 } 244 } 245 }