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('<', '&lt;')
136        content = content.replace('>', '&gt;')
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('<', '&lt;')
171                content = content.replace('>', '&gt;')
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('<', '&lt;')
276        line = line.replace('>', '&gt;')
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('<', '&lt;')
293        names_of_used[check_idx].data = names_of_used[check_idx].data.replace('>', '&gt;')
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