From 423c4f8207098a1065a23199e20b2598dce958fd Mon Sep 17 00:00:00 2001
From: afestini <86552107+afestini@users.noreply.github.com>
Date: Wed, 12 Jan 2022 22:07:12 +0100
Subject: [PATCH] Htmlreport changes (#3690)
---
htmlreport/cppcheck-htmlreport | 190 +++++++++++++++++++++++----------
1 file changed, 134 insertions(+), 56 deletions(-)
diff --git a/htmlreport/cppcheck-htmlreport b/htmlreport/cppcheck-htmlreport
index 51b0cd5ad..32b2fc829 100755
--- a/htmlreport/cppcheck-htmlreport
+++ b/htmlreport/cppcheck-htmlreport
@@ -2,7 +2,7 @@
from __future__ import unicode_literals
-import datetime
+from datetime import date
import io
import locale
import operator
@@ -74,7 +74,7 @@ h1 {
grid-area: menu;
text-align: left;
overflow: auto;
- padding: 0 15px 15px 15px;
+ padding: 0 23px 15px 15px;
border-right: thin solid #aaa;
min-width: 200px;
}
@@ -92,6 +92,45 @@ h1 {
overflow: auto;
}
+label {
+ white-space: nowrap;
+}
+
+label.checkBtn.disabled {
+ color: #606060;
+ background: #e0e0e0;
+ font-style: italic;
+}
+
+label.checkBtn, input[type="text"] {
+ border: 1px solid grey;
+ border-radius: 4px;
+ box-shadow: 1px 1px inset;
+ padding: 1px 5px;
+}
+
+label.checkBtn {
+ white-space: nowrap;
+ background: #ccddff;
+}
+
+label.unchecked {
+ background: #eff8ff;
+ box-shadow: 1px 1px 1px;
+}
+
+label.checkBtn:hover, label.unchecked:hover{
+ box-shadow: 0 0 2px;
+}
+
+label.disabled:hover {
+ box-shadow: 1px 1px inset;
+}
+
+label.checkBtn > input {
+ display:none;
+}
+
.summaryTable {
width: 100%;
}
@@ -160,7 +199,7 @@ table.summaryTable td { padding: 0 5px 0 5px; }
padding-right: 6px;
}
-.id-filtered, .severity-filtered, .file-filtered, .tool-filtered {
+.id-filtered, .severity-filtered, .file-filtered, .tool-filtered, .text-filtered {
visibility: collapse;
}
"""
@@ -228,6 +267,7 @@ HTML_HEAD = """
}
function toggleSeverity(cb) {
+ cb.parentElement.classList.toggle("unchecked", !cb.checked);
var elements = document.querySelectorAll(".sev_" + cb.id);
for (var i = 0, len = elements.length; i < len; i++) {
@@ -238,6 +278,8 @@ HTML_HEAD = """
}
function toggleTool(cb) {
+ cb.parentElement.classList.toggle("unchecked", !cb.checked);
+
var elements;
if (cb.id == "clang-tidy")
elements = document.querySelectorAll("[class^=clang-tidy-]");
@@ -264,13 +306,40 @@ HTML_HEAD = """
}
}
+ function filterFile(filter) {
+ var elements = document.querySelectorAll(".fileEntry");
+
+ for (var i = 0, len = elements.length; i < len; i++) {
+ var visible = elements[i].querySelector("tr").querySelector("td").textContent.toLowerCase().includes(filter.toLowerCase());
+ elements[i].classList.toggle("text-filtered", !visible);
+ }
+ }
+
+ function filterText(text) {
+ filter = text.toLowerCase();
+ var elements = document.querySelectorAll(".issue");
+
+ for (var i = 0, len = elements.length; i < len; i++) {
+ var visible = false;
+ var fields = elements[i].querySelectorAll("td");
+ for (var n = 0, num = fields.length; n < num; n++) {
+ if (fields[n].textContent.toLowerCase().includes(filter)) {
+ visible = true;
+ break;
+ }
+ }
+ elements[i].classList.toggle("text-filtered", !visible);
+ }
+
+ updateFileRows();
+ }
+
function updateFileRows(element) {
var elements = document.querySelectorAll(".fileEntry");
for (var i = 0, len = elements.length; i < len; i++) {
- var issue_count = elements[i].querySelectorAll(".issue").length;
- var invisible_count = elements[i].querySelectorAll(".id-filtered, .severity-filtered, .tool-filtered").length;
- elements[i].classList.toggle("file-filtered", (invisible_count == issue_count));
+ var visible = elements[i].querySelector(".issue:not(.id-filtered):not(.severity-filtered):not(.tool-filtered):not(.text-filtered)");
+ elements[i].classList.toggle("file-filtered", !visible);
}
}
@@ -283,21 +352,6 @@ HTML_HEAD = """
Cppcheck report - %s%s
"""
-HTML_SEVERITY_FILTER = """
-
-
-
-
-
-
-
-
- |
-
-
-
-"""
-
HTML_HEAD_END = """
"""
@@ -341,54 +395,77 @@ html_unescape_table = {v: k for k, v in html_escape_table.items()}
def html_escape(text):
return escape(text, html_escape_table)
+def filter_button(enabled_filters, id, function):
+ enabled = enabled_filters.get(id, False)
+ return '\n '\
+ % (' disabled' if not enabled else '', function, id, 'checked' if enabled else 'disabled', id)
-def git_blame(line, path, file, blame_options):
- git_blame_dict = {}
+def filter_bar(enabled):
+ return ''.join([
+ ' \n'
+ ,''.join([filter_button(enabled, severity, 'toggleSeverity') for severity in ['error', 'warning', 'portability', 'performance', 'style', 'information']])
+ ,'\n | '
+ ,''.join([filter_button(enabled, tool, 'toggleTool') for tool in ['cppcheck', 'clang-tidy']])
+ ,'\n | '
+ ,'\n '
+ ,'\n '
+ ,'\n
\n'])
+
+def git_blame(errors, path, file, blame_options):
+ last_line= errors[-1]['line']
+ if last_line == 0:
+ return {}
+
+ first_line = next((error for error in errors if error['line'] > 0))['line']
full_path = os.path.join(path, file)
- path, file = os.path.split(full_path)
+ path, filename = os.path.split(full_path)
if path:
os.chdir(path)
- cmd_args = ['git', 'blame', '-L %d' % line]
+ cmd_args = ['git', 'blame', '-L %d,%d' % (first_line, last_line)]
if '-w' in blame_options:
cmd_args.append('-w')
if '-M' in blame_options:
cmd_args.append('-M')
- cmd_args = cmd_args + ['--porcelain', '--', file]
+ cmd_args = cmd_args + ['--porcelain', '--incremental', '--', filename]
try:
result = subprocess.check_output(cmd_args)
result = result.decode(locale.getpreferredencoding())
except:
- return {}
+ return []
finally:
os.chdir(cwd)
if result.startswith('fatal'):
- return {}
+ return []
- disallowed_characters = '<>'
- for line in result.split('\n')[1:]:
- space_pos = line.find(' ')
- if space_pos > 30:
- break
- key = line[:space_pos]
- val = line[space_pos + 1:]
+ blame_data = []
+ line_from = 0
+ line_to = 0
+ blame_info = dict()
- for character in disallowed_characters:
- val = val.replace(character, "")
- git_blame_dict[key] = val
+ for line_str in result.splitlines():
+ field, _, value = line_str.partition(' ')
+ if len(field) == 40:
+ line_nr, count = value.split(' ')[1:]
+ line_from = int(line_nr)
+ line_to = line_from + int(count)
+ elif field.startswith('author'):
+ blame_info[field] = value
+ if field == 'author-time':
+ author_date = date.fromtimestamp(int(blame_info['author-time']))
+ blame_info['author-time'] = author_date.strftime("%d/%m/%Y")
+ elif field == 'filename':
+ blame_data.append([line_from, line_to, blame_info.copy()])
- datetime_object = datetime.date.fromtimestamp(float(git_blame_dict['author-time']))
- year = datetime_object.strftime("%Y")
- month = datetime_object.strftime("%m")
- day = datetime_object.strftime("%d")
+ return blame_data
- git_blame_dict['author-time'] = '%s/%s/%s' % (day, month, year)
- return git_blame_dict
+def blame_lookup(blame_data, line):
+ return next((data for start, end, data in blame_data if line >= start and line < end), {})
def tr_str(td_th, line, id, cwe, severity, message, author, author_mail, date, add_author, tr_class=None, htmlfile=None, message_class=None):
@@ -408,9 +485,9 @@ def tr_str(td_th, line, id, cwe, severity, message, author, author_mail, date, a
for field in add_author:
if field == 'name':
- ret += '<%s>%s%s>' % (td_th, author, td_th)
+ ret += '<%s>%s%s>' % (td_th, html_escape(author), td_th)
elif field == 'email':
- ret += '<%s>%s%s>' % (td_th, author_mail, td_th)
+ ret += '<%s>%s%s>' % (td_th, html_escape(author_mail), td_th)
elif field == 'date':
ret += '<%s>%s%s>' % (td_th, date, td_th)
@@ -609,7 +686,7 @@ if __name__ == '__main__':
file_no = 0
for error in contentHandler.errors:
filename = error['file']
- if filename not in files.keys():
+ if filename not in files:
files[filename] = {
'errors': [], 'htmlfile': str(file_no) + '.html'}
file_no = file_no + 1
@@ -718,9 +795,12 @@ if __name__ == '__main__':
stats_count = 0
stats = []
+ filter_enabled = {}
for filename, data in sorted(files.items()):
for error in data['errors']:
stats.append(error['id']) # get the stats
+ filter_enabled[error['severity']] = True
+ filter_enabled['clang-tidy' if error['id'].startswith('clang-tidy-') else 'cppcheck'] = True
stats_count += 1
counter = Counter(stats)
@@ -744,7 +824,7 @@ if __name__ == '__main__':
stat_html.append(stat_fmt.format(_id, _id, dict(counter.most_common())[_id], _id))
output_file.write(HTML_HEAD % (options.title, '', options.title, ''))
- output_file.write(HTML_SEVERITY_FILTER)
+ output_file.write(filter_bar(filter_enabled))
output_file.write(HTML_HEAD_END)
output_file.write(HTML_MENU.replace('id="menu"', 'id="menu_index"', 1).replace("Defects:", "Defect summary", 1) % (''))
output_file.write('\n ')
@@ -763,8 +843,9 @@ if __name__ == '__main__':
for filename, data in sorted(files.items()):
file_error = filename in decode_errors or filename.endswith('*')
-
+ is_file = filename != '' and not file_error
row_content = filename if file_error else "%s" % (data['htmlfile'], filename)
+ htmlfile = data.get('htmlfile') if is_file else None
output_file.write("\n ")
output_file.write("\n %s |
" % row_content)
@@ -772,11 +853,10 @@ if __name__ == '__main__':
if filename in decode_errors:
output_file.write("\n Could not generated due to UnicodeDecodeError |
")
- for error in sorted(data['errors'], key=lambda k: k['line']):
- if add_author_information:
- git_blame_dict = git_blame(error['line'], source_dir, error['file'], blame_options)
- else:
- git_blame_dict = {}
+ sorted_errors = sorted(data['errors'], key=lambda k: k['line'])
+ blame_data = git_blame(sorted_errors, source_dir, filename, blame_options) if add_author_information else []
+ for error in sorted_errors:
+ git_blame_dict = blame_lookup(blame_data, error['line'])
message_class = None
try:
if error['inconclusive'] == 'true':
@@ -794,9 +874,7 @@ if __name__ == '__main__':
if error['severity'] in ['error', 'warning']:
message_class = error['severity']
- is_file = filename != '' and not file_error
line = error["line"] if is_file else ""
- htmlfile = data.get('htmlfile') if is_file else None
output_file.write(
'\n %s' %