Skip to content

Commit 0093408

Browse files
committed
htmlreport: support remote GitHub/GitLab links for source files
Motivation: Cppcheck-htmlreport previously generated local annotated HTML for all source files. For private or large repositories, generating local HTML is unnecessary and cumbersome. It consumes additional disk space, increases report generation time, and duplicates functionality already provided by GitHub/GitLab browseable HTML pages. This patch allows the cppcheck report itself to be public, while the actual source code remains protected on GitHub/GitLab using their standard access controls. Changes: - Detect --source-dir URLs pointing to GitHub/GitLab. - Use remote URLs in index.html instead of generating local HTML for those files. - Line numbers link directly to GitHub/GitLab with proper anchors (#L123). - Remote links open in a new tab (target="_blank"), preserving local HTML behavior for normal files.
1 parent d1e4660 commit 0093408

File tree

1 file changed

+43
-18
lines changed

1 file changed

+43
-18
lines changed

htmlreport/cppcheck-htmlreport

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -494,9 +494,20 @@ def tr_str(td_th, line, id, cwe, severity, classification, guideline, message, t
494494
if classification:
495495
items.extend([classification, guideline])
496496
if htmlfile:
497-
ret += '<%s><a href="%s#line-%d">%d</a></%s>' % (td_th, htmlfile, line, line, td_th)
497+
if htmlfile.startswith("http://") or htmlfile.startswith("https://"):
498+
# GitHub/GitLab style line anchor
499+
href = f"{htmlfile.rstrip('#L1')}#L{line}"
500+
# Emit **line number with link**
501+
ret += f'<{td_th}><a href="{href}" target="_blank" rel="noopener noreferrer">{line}</a></{td_th}>'
502+
else:
503+
# local HTML annotated
504+
href = f"{htmlfile}#line-{line}"
505+
# Emit **line number with link**
506+
ret += f'<{td_th}><a href="{href}">{line}</a></{td_th}>'
507+
508+
# Emit id, cwe, severity, classification, ...
498509
for item in items:
499-
ret += '<%s>%s</%s>' % (td_th, item, td_th)
510+
ret += f'<{td_th}>{item}</{td_th}>'
500511
else:
501512
items.insert(0,line)
502513
for item in items:
@@ -705,6 +716,10 @@ def main() -> None:
705716
if options.source_dir:
706717
source_dir = options.source_dir
707718

719+
is_remote = False
720+
if source_dir.startswith("http://") or source_dir.startswith("https://"):
721+
is_remote = True
722+
708723
add_author_information = []
709724
if options.add_author_information:
710725
fields = [x.strip() for x in options.add_author_information.split(',')]
@@ -753,9 +768,14 @@ def main() -> None:
753768
for error in contentHandler.errors:
754769
filename = error['file']
755770
if filename not in files:
756-
files[filename] = {
757-
'errors': [], 'htmlfile': str(file_no) + '.html'}
758-
file_no = file_no + 1
771+
if is_remote:
772+
# Construct remote URL for GitHub/GitLab
773+
# tr_str() will use the actual line number, so we can just start with line 1
774+
remote_url = source_dir.rstrip('/') + '/' + filename + '#L1'
775+
files[filename] = {'errors': [], 'htmlfile': remote_url}
776+
else:
777+
files[filename] = {'errors': [], 'htmlfile': str(file_no) + '.html'}
778+
file_no += 1
759779
files[filename]['errors'].append(error)
760780

761781
# Make sure that the report directory is created if it doesn't exist.
@@ -795,20 +815,25 @@ def main() -> None:
795815
if filename == '':
796816
continue
797817

798-
source_filename = os.path.join(source_dir, filename)
799-
try:
800-
with io.open(source_filename, 'r', encoding=options.source_encoding) as input_file:
801-
content = input_file.read()
802-
except IOError:
803-
if error['id'] != 'unmatchedSuppression':
804-
sys.stderr.write("ERROR: Source file '%s' not found.\n" %
818+
if is_remote:
819+
# Remote source: do NOT generate local annotated HTML files.
820+
# The index will still point directly to GitHub/GitLab URLs.
821+
continue;
822+
else:
823+
source_filename = os.path.join(source_dir, filename)
824+
try:
825+
with io.open(source_filename, 'r', encoding=options.source_encoding) as input_file:
826+
content = input_file.read()
827+
except IOError:
828+
if error['id'] != 'unmatchedSuppression':
829+
sys.stderr.write("ERROR: Source file '%s' not found.\n" %
830+
source_filename)
831+
continue
832+
except UnicodeDecodeError:
833+
sys.stderr.write("WARNING: Unicode decode error in '%s'.\n" %
805834
source_filename)
806-
continue
807-
except UnicodeDecodeError:
808-
sys.stderr.write("WARNING: Unicode decode error in '%s'.\n" %
809-
source_filename)
810-
decode_errors.append(source_filename[2:]) # "[2:]" gets rid of "./" at beginning
811-
continue
835+
decode_errors.append(source_filename[2:]) # "[2:]" gets rid of "./" at beginning
836+
continue
812837

813838
htmlFormatter = AnnotateCodeFormatter(linenos=True,
814839
style='colorful',

0 commit comments

Comments
 (0)