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 }