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 = """ -
- Severity: - - - - - - - | Tool: - - -
-""" - 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' % (td_th, author, td_th) + ret += '<%s>%s' % (td_th, html_escape(author), td_th) elif field == 'email': - ret += '<%s>%s' % (td_th, author_mail, td_th) + ret += '<%s>%s' % (td_th, html_escape(author_mail), td_th) elif field == 'date': ret += '<%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' %