1#!/usr/bin/python 2 3# Copyright (c) 2019 by Austin Hale, <ah at unc dot edu> 4# Copyright (c) 2020-2021 by Gilles Caulier, <caulier dot gilles at gmail dot com> 5# 6# Export Clazy static analyzer output to HTML reports. 7# 8# Redistribution and use is allowed according to the terms of the BSD license. 9# For details see the accompanying COPYING-CMAKE-SCRIPTS file. 10# 11 12import sys 13import argparse 14import urllib.request 15import re 16from bs4 import BeautifulSoup 17from datetime import datetime 18 19checks_list = ["[-Wclazy-assert-with-side-effects]", 20 "[-Wclazy-container-inside-loop]", 21 "[-Wclazy-detaching-member]", 22 "[-Wclazy-heap-allocated-small-trivial-type]", 23 "[-Wclazy-ifndef-define-typo]", 24 "[-Wclazy-inefficient-qlist]", 25 "[-Wclazy-isempty-vs-count]", 26 "[-Wclazy-jni-signatures]", 27 "[-Wclazy-qhash-with-char-pointer-key]", 28 "[-Wclazy-qproperty-type-mismatch]", 29 "[-Wclazy-qrequiredresult-candidates]", 30 "[-Wclazy-qstring-varargs]", 31 "[-Wclazy-qt-keywords]", 32 "[-Wclazy-qt4-qstring-from-array]", 33 "[-Wclazy-qt6-qhash-signature]", 34 "[-Wclazy-qt6-qlatin1char-to-u]", 35 "[-Wclazy-qt6-qlatin1string-to-u]", 36 "[-Wclazy-qt6-qlatin1stringchar-to-u]", 37 "[-Wclazy-qt6-qdir-fixes]", 38 "[-Wclazy-qvariant-template-instantiation]", 39 "[-Wclazy-raw-environment-function]", 40 "[-Wclazy-reserve-candidates]", 41 "[-Wclazy-signal-with-return-value]", 42 "[-Wclazy-thread-with-slots]", 43 "[-Wclazy-tr-non-literal]", 44 "[-Wclazy-unneeded-cast]", 45 "[-Wclazy-use-chrono-in-qtimer]", 46 "[-Wclazy-connect-by-name]", 47 "[-Wclazy-connect-non-signal]", 48 "[-Wclazy-connect-not-normalized]", 49 "[-Wclazy-container-anti-pattern]", 50 "[-Wclazy-empty-qstringliteral]", 51 "[-Wclazy-fully-qualified-moc-types]", 52 "[-Wclazy-lambda-in-connect]", 53 "[-Wclazy-lambda-unique-connection]", 54 "[-Wclazy-lowercase-qml-type-name]", 55 "[-Wclazy-mutable-container-key]", 56 "[-Wclazy-overloaded-signal]", 57 "[-Wclazy-qcolor-from-literal]", 58 "[-Wclazy-qdatetime-utc]", 59 "[-Wclazy-qenums]", 60 "[-Wclazy-qfileinfo-exists]", 61 "[-Wclazy-qgetenv]", 62 "[-Wclazy-qmap-with-pointer-key]", 63 "[-Wclazy-qstring-arg]", 64 "[-Wclazy-qstring-comparison-to-implicit-char]", 65 "[-Wclazy-qstring-insensitive-allocation]", 66 "[-Wclazy-qstring-ref]", 67 "[-Wclazy-qt-macros]", 68 "[-Wclazy-strict-iterators]", 69 "[-Wclazy-temporary-iterator]", 70 "[-Wclazy-unused-non-trivial-variable]", 71 "[-Wclazy-writing-to-temporary]", 72 "[-Wclazy-wrong-qevent-cast]", 73 "[-Wclazy-wrong-qglobalstatic]", 74 "[-Wclazy-auto-unexpected-qstringbuilder]", 75 "[-Wclazy-child-event-qobject-cast]", 76 "[-Wclazy-connect-3arg-lambda]", 77 "[-Wclazy-const-signal-or-slot]", 78 "[-Wclazy-detaching-temporary]", 79 "[-Wclazy-foreach]", 80 "[-Wclazy-incorrect-emit]", 81 "[-Wclazy-inefficient-qlist-soft]", 82 "[-Wclazy-install-event-filter]", 83 "[-Wclazy-non-pod-global-static]", 84 "[-Wclazy-overridden-signal]", 85 "[-Wclazy-post-event]", 86 "[-Wclazy-qdeleteall]", 87 "[-Wclazy-qhash-namespace]", 88 "[-Wclazy-qlatin1string-non-ascii]", 89 "[-Wclazy-qproperty-without-notify]", 90 "[-Wclazy-qstring-left]", 91 "[-Wclazy-range-loop]", 92 "[-Wclazy-returning-data-from-temporary]", 93 "[-Wclazy-rule-of-two-soft]", 94 "[-Wclazy-skipped-base-method]", 95 "[-Wclazy-virtual-signal]", 96 "[-Wclazy-base-class-event]", 97 "[-Wclazy-copyable-polymorphic]", 98 "[-Wclazy-ctor-missing-parent-argument]", 99 "[-Wclazy-function-args-by-ref]", 100 "[-Wclazy-function-args-by-value]", 101 "[-Wclazy-global-const-char-pointer]", 102 "[-Wclazy-implicit-casts]", 103 "[-Wclazy-missing-qobject-macro]", 104 "[-Wclazy-missing-typeinfo]", 105 "[-Wclazy-old-style-connec]", 106 "[-Wclazy-qstring-allocations]", 107 "[-Wclazy-returning-void-expression]", 108 "[-Wclazy-rule-of-three]", 109 "[-Wclazy-static-pmf]", 110 "[-Wclazy-virtual-call-ctor]", 111] 112 113# Each check will have its own node of information. 114class checks: 115 def __init__(self, dataval=None): 116 self.name = '' 117 self.count = 0 118 self.data = '' 119 120# Begin here. 121def main(): 122 checks_list.sort() 123 124 # Process command line arguments. 125 args = parse_command_line_options() 126 external_link = '' 127 external_name = '' 128 129 contents = args.file.readlines() 130 131 checks_used = [0] * len(checks_list) 132 133 # Increments each occurrence of a check. 134 for line, content in enumerate(contents): 135 content = content.replace('<', '<') 136 content = content.replace('>', '>') 137 for check_name in checks_list: 138 if content.find(check_name) != -1: 139 checks_used[checks_list.index(check_name)] += 1 140 141 # Counts the max number of used checks in the log file. 142 num_used_checks = 0 143 for line, check in enumerate(checks_list): 144 if checks_used[line] != 0: 145 num_used_checks += 1 146 147 names_of_used = [None] * num_used_checks 148 names_of_usedL = [None] * num_used_checks 149 150 # Creates new check classes for each used check. 151 used_line = 0 152 total_num_checks = 0 153 for line, check in enumerate(checks_list): 154 if checks_used[line] != 0: 155 new_node = checks(check) 156 new_node.name = check 157 new_node.count = checks_used[line] 158 total_num_checks += checks_used[line] 159 names_of_used[used_line] = new_node 160 161 names_of_usedL[used_line] = checks_list[line] 162 used_line += 1 163 164 # Adds details for each organized check. 165 for line, content in enumerate(contents): 166 # Goes through each used check. 167 for initial_check in names_of_usedL: 168 # Adds the lines that detail the warning message. 169 if content.find(initial_check) != -1: 170 content = content.replace('<', '<') 171 content = content.replace('>', '>') 172 names_of_used[names_of_usedL.index(initial_check)].data += content 173 details = line + 1 174 finished = False 175 while not finished: 176 # Ensure there is no overflow. 177 if details >= len(contents): 178 break 179 # If the line includes a used Clang-Tidy check name, 180 # continue to find the next. 181 for end_check in names_of_usedL: 182 if contents[details].find(end_check) != -1: 183 finished = True 184 break 185 # Otherwise, add the data to the specific used check 186 # name for the organization of checks in the HTML file. 187 if not finished: 188 names_of_used[names_of_usedL.index(initial_check)].data += contents[details] 189 details += 1 190 191 args.file.close() 192 f = open("clazy.html", "w") 193 194 # Functions for writing to the clazy.html file. 195 writeHeader(f) 196 writeList(f, num_used_checks, names_of_used, args, 197 external_link, external_name, total_num_checks) 198 sortLogs(f, contents, num_used_checks, names_of_used, 199 args, external_link, external_name) 200 writeScript(f, num_used_checks) 201 202 # Close the file. 203 f.close() 204 sys.exit() 205 206# Parses through the given command line options (-b, --button) 207# and returns the given file's contents if read successfully. 208def parse_command_line_options(): 209 parser = argparse.ArgumentParser() 210 parser.add_argument('file', type=argparse.FileType('r')) 211 212 try: 213 args = parser.parse_args() 214 except: 215 parser.print_help() 216 usage() 217 sys.exit() 218 219 return args 220 221# Prints usage information for the script. 222def usage(): 223 print("**--------------------------- Clazy Visualizer --------------------------**\n\n \ 224 Generates an html file as a visual for clazy checks.\n\n \ 225 Arguments: python clazy_visualizer.py [logfile.log]\n\n \ 226 \t\t-ex: python clazy_visualizer [logfile.log] \ 227 \n\n**------------------------------------------------------------------------**") 228 229# Header of the clazy.html file. 230def writeHeader(f): 231 f.write("<!DOCTYPE html>\n") 232 f.write("<html>\n") 233 f.write("<head>\n") 234 f.write("\t<title>Clazy Visualizer</title>\n\t<meta charset=\"UTF-8\">\n") 235 f.write("\t<meta name=\"description\" content=\"Documentation tool for visualizing Clazy checks.\">\n") 236 f.write("\t<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n") 237 f.write("\t<link rel=\"stylesheet\" href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css\">\n") 238 f.write("\t<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js\"></script>\n") 239 f.write("\t<script src=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js\"></script>\n") 240 f.write("</head>\n") 241 242# List the used checks found in the source code. 243def writeList(f, num_used_checks, names_of_used, args, external_link, external_name, total_num_checks): 244 now = datetime.now() 245 f.write("<body style=\"background: rgb(220, 227, 230); width: 100%; height: 100%;\">\n") 246 f.write("<div id=\"container\" style=\"margin-left: 2%; margin-right: 2%;\">\n") 247 f.write("\t<div id=\"header\" style=\"height: 55px; display: flex; justify-content: left; position: relative;\">\n") 248 f.write("\t\t<h3 style=\"text-align: center; color: #111; font-family: 'Helvetica Neue', sans-serif; font-weight: bold; \ 249 letter-spacing: 0.5px; line-height: 1;\">digiKam Clazy Checks - %s</h3>\n" % (now.strftime("%m/%d/%Y %H:%M:%S"))) 250 f.write("\t\t<div class=\"btn-group\" role=\"group\" style=\"position: absolute; right: 0;\">\n") 251 f.write("\t\t</div>\n\t</div><br>\n") 252 f.write("\t<ul id=\"list\" class=\"list-group\" align=\"left\" style=\"display: block; width: 25%; height: 0; margin-bottom: 0;\">\n") 253 254 # Iterates through each used check's details and organizes them into the given <pre> sections. 255 f.write("\t\t<a id=\"log\" href=\"#\" class=\"list-group-item list-group-item-success\" style=\"color: black; font-weight: bold; letter-spacing:0.4px;\" \ 256 onclick=\"toggleLog()\">%d Original Log</a>\n" % (total_num_checks)) 257 258 for line in range(0, num_used_checks): 259 f.write("\t\t<a id=\"check%d\" style=\"color: black\" href=\"#\" class=\"list-group-item list-group-item-action\" \ 260 onclick=\"toggleInfo(%d)\">%d %s</a>\n" % (line, line, names_of_used[line].count, names_of_used[line].name)) 261 262 f.write("\t</ul>\n\n") 263 f.write("\t<div id=\"showLog\" style=\"display: none; width: 75%; float: right;\">\n") 264 f.write("\t\t<div style=\"display: flex; justify-content: left; position: relative;\">\n") 265 f.write("\t\t\t<button id=\"collapse-btn0\" type=\"button\" class=\"btn nohover\" onclick=\"collapseSidebar()\" style=\"outline: none; \ 266 background-color: lightgray\" title=\"Collapse sidebar\">\n") 267 f.write("\t\t\t<span id=\"collapse-img0\" class=\"glyphicon glyphicon-menu-left\"></button></span>\n") 268 f.write("\t\t\t<h4 style=\"margin-top: 0; color: #111; position: absolute; left: 50%; transform: translateX(-50%); margin-bottom: 10;\">Original Log</h4>\n") 269 270 f.write("\t\t</div>\n\t\t<pre>\n") 271 272# Sort through the used check logs for outputting the html. 273def sortLogs(f, contents, num_used_checks, names_of_used, args, external_link, external_name): 274 for line in contents: 275 line = line.replace('<', '<') 276 line = line.replace('>', '>') 277 f.write("%s" % (line)) 278 279 f.write("\n\t\t</pre>\n\t</div>\n") 280 281 for check_idx in range(0, num_used_checks): 282 collapse_idx = check_idx+1 283 f.write("\t<div id=\"show%d\"" % (check_idx)) 284 f.write("style=\"display: none; width: 75%; float: right\">\n") 285 f.write("\t\t<div style=\"display: flex; justify-content: left; position: relative;\">\n") 286 f.write("\t\t\t<button id=\"collapse-btn%d\" type=\"button\" class=\"btn nohover\" onclick=\"collapseSidebar()\" \ 287 style=\"outline: none; background-color: lightgray\" title=\"Collapse sidebar\">\n" % (collapse_idx)) 288 f.write("\t\t\t<span id=\"collapse-img%d\" class=\"glyphicon glyphicon-menu-left\"></button></span>\n" % (collapse_idx)) 289 f.write("\t\t\t<h4 style=\"margin-top: 0; color: #111; position: absolute; left: 50%; transform: translateX(-50%); margin-bottom: 10\">") 290 f.write("<a href=\"https://github.com/KDE/clazy/blob/master/docs/checks/README-%s.md\">%s</a></h4>\n" % (names_of_used[check_idx].name[9:-1], names_of_used[check_idx].name[9:-1])) 291 f.write("\t\t</div>\n\t\t<pre>\n") 292 names_of_used[check_idx].data = names_of_used[check_idx].data.replace('<', '<') 293 names_of_used[check_idx].data = names_of_used[check_idx].data.replace('>', '>') 294 f.write("%s\t\t</pre>\n\t</div>\n" % (names_of_used[check_idx].data)) 295 296 f.write("</div>\n</body>\n") 297 298# Writes Javascript and JQuery code to the html file for button and grouping functionalities. 299def writeScript(f, num_used_checks): 300 f.write("<script>\nvar selected_idx;\nvar checks_arr = [];\nvar highlights = 'highlights';\n") 301 f.write("// Retrieves local storage data on document load for highlighted checks.\n") 302 f.write("$(document).ready(function() {\n\tfor (var all_checks=0; all_checks<%d; all_checks++) {\n" % (num_used_checks)) 303 f.write("\t\tvar check_hl = document.getElementById(\"check\"+all_checks);\n") 304 f.write("\t\tswitch (JSON.parse(localStorage.getItem(highlights))[all_checks]) {\n") 305 f.write("\t\t\tcase \"warning\":\n\t\t\tcheck_hl.classList.add('list-group-item-warning');\n") 306 f.write("\t\t\tchecks_arr[all_checks] = \"warning\"; break;\n\t\t\tcase \"danger\":\n") 307 f.write("\t\t\tcheck_hl.classList.add('list-group-item-danger');\n\t\t\tchecks_arr[all_checks] = \"danger\"; break;\n") 308 f.write("\t\t\tdefault:\n\t\t\tchecks_arr[all_checks] = \"action\";\n\t\t\tif (check_hl !== null) {\n") 309 f.write("\t\t\t\tcheck_hl.classList.add('list-group-item-action');\n\t\t\t} break;\n\t\t}\n\t}\n") 310 f.write("localStorage.setItem(highlights, JSON.stringify(checks_arr));\n});\n\n") 311 312 f.write("function toggleLog() {\n\tvar log = document.getElementById(\"showLog\");\n\tclearContent();\n") 313 f.write("\tif (log.style.display === \"none\") {\n\t\tlog.style.display = \"block\";\n\t} else {\n") 314 f.write("\t\tlog.style.display = \"none\";\n\t}\n}\n\n") 315 316 f.write("function toggleInfo(check_position) {\n\tselected_idx = check_position;\n\tclearContent();\n") 317 f.write("\t// Displays the chosen clang-tidy category.\n\tvar category = document.getElementById(\"show\"+check_position);\n") 318 f.write("\tif (category.style.display === \"none\") {\n\t\tcategory.style.display = \"block\";\n\t} else {\n") 319 f.write("\t\tcategory.style.display = \"none\";\n\t}\n}\n\n") 320 321 f.write("// Clears document when choosing another selection.\nfunction clearContent() {\n") 322 f.write("\tfor (var all_checks=0; all_checks<%d; all_checks++) {\n\t\tvar clear = document.getElementById(\"show\"+all_checks);\n" % (num_used_checks)) 323 f.write("\t\tif (clear.style.display === \"block\") {\n\t\tclear.style.display = \"none\";\n\t\t}\n\t}\n") 324 f.write("\tvar clearLog = document.getElementById(\"showLog\");\n\tif (clearLog.style.display === \"block\") {\n") 325 f.write("\t\tclearLog.style.display = \"none\";\n\t}\n}\n\n") 326 327 f.write("// Type 1 used for highlighting danger checks and 0 for warnings.\nfunction highlightChecks(type) {\n") 328 f.write("\tvar check_hl = document.getElementById(\"check\"+selected_idx);\n\tif (check_hl !== null) {\n") 329 f.write("\t\tif (check_hl.classList.contains('list-group-item-action')) {\n\t\t\tcheck_hl.classList.remove('list-group-item-action');\n") 330 f.write("\t\t\ttype == 1 ? check_hl.classList.add('list-group-item-danger') : check_hl.classList.add('list-group-item-warning');\n") 331 f.write("\t\t\ttype == 1 ? checks_arr[selected_idx] = \"danger\" : checks_arr[selected_idx] = \"warning\";\n") 332 f.write("\t\t} else if (check_hl.classList.contains('list-group-item-warning')) {\n\t\t\tcheck_hl.classList.remove('list-group-item-warning');\n") 333 f.write("\t\t\ttype == 1 ? check_hl.classList.add('list-group-item-danger') : check_hl.classList.add('list-group-item-action');\n") 334 f.write("\t\t\ttype == 1 ? checks_arr[selected_idx] = \"danger\" : checks_arr[selected_idx] = \"action\";\n\t\t} else {\n") 335 f.write("\t\t\tcheck_hl.classList.remove('list-group-item-danger');\n") 336 f.write("\t\t\ttype == 1 ? check_hl.classList.add('list-group-item-action') : check_hl.classList.add('list-group-item-warning');\n") 337 f.write("\t\t\ttype == 1 ? checks_arr[selected_idx] = \"action\" : checks_arr[selected_idx] = \"warning\";\n\t\t}\n\t}\n") 338 f.write("\t// Sets local storage for each occurrence of a highlighted check.\n\tlocalStorage.setItem(highlights, JSON.stringify(checks_arr));\n}\n\n") 339 340 f.write("function clearChecks(type) {\n\tfor (var all_checks=0; all_checks<%d; all_checks++) {\n" % (num_used_checks)) 341 f.write("\t\tvar clear = (document.getElementById(\"check\"+all_checks));\n\t\tchecks_arr[all_checks] = \"action\";\n") 342 f.write("\t\tif (clear !== null) {\n") 343 f.write("\t\t\tif (clear.classList.contains('list-group-item-warning')) {\n\t\t\t\tclear.classList.remove('list-group-item-warning');\n") 344 f.write("\t\t\t} else if (clear.classList.contains('list-group-item-danger')) {\n\t\t\t\tclear.classList.remove('list-group-item-danger');\n\t\t\t}\n") 345 f.write("\t\t\tclear.classList.add('list-group-item-action');\n\t\t}\n\t}\n\t// Restores all checks to unhighlighted state on local storage.\n") 346 f.write("\tlocalStorage.removeItem(highlights);\n}\n\n") 347 348 f.write("function collapseSidebar() {\n\tvar list = document.getElementById(\"list\"); var hasExpanded;\n") 349 f.write("\tvar log_details = document.getElementById(\"showLog\");\n\tlist.style.display === \"block\" ? hasSidebar = true : hasSidebar = false;\n") 350 f.write("\thasSidebar ? list.style.display = \"none\" : list.style.display = \"block\";\n") 351 f.write("\tfor (var all_checks=0; all_checks<=%d; all_checks++) {\n\t\tvar collapse_img = document.getElementById(\"collapse-img\"+all_checks);\n" % (num_used_checks)) 352 f.write("\t\tvar collapse_btn = document.getElementById(\"collapse-btn\"+all_checks);\n\t\tvar check_details = document.getElementById(\"show\"+all_checks);\n") 353 f.write("\t\tif (collapse_img !== null) {\n\t\t\thasSidebar ? collapse_img.classList.remove('glyphicon-menu-left') : collapse_img.classList.remove('glyphicon-menu-right');\n") 354 f.write("\t\t\thasSidebar ? collapse_img.classList.add('glyphicon-menu-right') : collapse_img.classList.add('glyphicon-menu-left');\n") 355 f.write("\t\t\thasSidebar ? collapse_btn.title = \"Expand sidebar\" : collapse_btn.title = \"Collapse sidebar\";\n\t\t}\n") 356 f.write("\t\tif (check_details !== null) {hasSidebar ? check_details.style.width = \"100%\" : check_details.style.width = \"75%\";}\n\t}\n") 357 f.write("\thasSidebar ? log_details.style.width = \"100%\" : log_details.style.width = \"75%\";\n}\n") 358 359 # Begins writing style elements. 360 f.write("</script>\n<style>\n\tpre {\n\t\twhite-space: pre-wrap;\n") 361 f.write("\t\tword-break: keep-all;\n\t}\n\t#header {\n") 362 f.write("\t\tborder-bottom: 2px solid darkgray\n\t}\n") 363 f.write("</style>\n</html>") 364 365 366# Calls main function. 367if __name__ == "__main__": 368 main() 369