Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
2646e4b
Create clang-tidy report in CI
filipe-cuim Dec 19, 2025
6d28326
Fix llvm-namespace-comment
filipe-cuim Dec 19, 2025
244baea
Fix bugprone-too-small-loop-variable
filipe-cuim Dec 19, 2025
88abe96
Fix clang-analyzer-deadcode.DeadStores
filipe-cuim Dec 19, 2025
ec30f62
Fix clang-analyzer-core.NonNullParamChecker
filipe-cuim Dec 19, 2025
1488f1d
Fix bugprone-implicit-widening-of-multiplication-result
filipe-cuim Dec 19, 2025
902f706
Fix bugprone-reserved-identifier
filipe-cuim Dec 19, 2025
67238c5
Fix bugprone-branch-clone
filipe-cuim Dec 29, 2025
2b3363f
Fix bugprone-narrowing-conversions
filipe-cuim Dec 29, 2025
aa69f42
Fix llvm-qualified-auto
filipe-cuim Dec 29, 2025
6707211
Fix google-readability-casting
filipe-cuim Dec 29, 2025
260cd52
Fix llvm-else-after-return
filipe-cuim Dec 29, 2025
6ce894b
Update clang-tidy script with arguments
filipe-cuim Dec 30, 2025
904ba21
Update requirements.txt
filipe-cuim Dec 30, 2025
80f463e
Address comments
filipe-cuim Jan 20, 2026
0d0171f
Fix clang-analyzer-core.NullDereference
filipe-cuim Jan 23, 2026
d436606
Fix clang-analyzer-security.insecureAPI.strcpy
filipe-cuim Jan 23, 2026
c993114
Fix clang-analyzer-core.CallAndMessage
filipe-cuim Jan 23, 2026
eb5f9d0
Fix clang-diagnostic-error related to bspIo
filipe-cuim Jan 26, 2026
5098ac8
Suppress bugprone-unhandled-self-assignment
filipe-cuim Jan 27, 2026
7bd88c2
Suppress bugprone-exception-escape
filipe-cuim Jan 27, 2026
561923b
Suppress bugprone-unhandled-self-assignment
filipe-cuim Jan 27, 2026
58a2ae3
Fix cert-err58-cpp
filipe-cuim Jan 27, 2026
425bb38
Suppress cert-dcl50-cpp
filipe-cuim Jan 27, 2026
43f5f1a
Suppress cert-dcl37-c
filipe-cuim Jan 27, 2026
05b7394
Suppress cert-dcl51-cpp
filipe-cuim Jan 27, 2026
d43d313
Fix cert-err33-c
filipe-cuim Jan 27, 2026
ce1bb67
Fix cert-dcl51-cpp
filipe-cuim Jan 27, 2026
e0f4a00
Suppress cert-msc30-c and others related
filipe-cuim Jan 27, 2026
5ed683f
Fix .clang-tidy config error
filipe-cuim Jan 27, 2026
b52ac0d
Suppress clang-analyzer-core.DivideZero
filipe-cuim Jan 28, 2026
19193f1
Ignore clang-diagnostic-error for now at script level
filipe-cuim Jan 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
228 changes: 228 additions & 0 deletions .ci/clang-tidy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
"""
Clang-tidy Checker
==================
This script runs the llvm helper tool run-clang-tidy on all compilation units specified by compile_commands.json.
It filters out files from excluded directories (by default: 3rdparty), exports the findings to a specified YAML file,
and generates a detailed report with the number of findings per diagnostic which is printed to stdout.

The script exits with:
- Exit code 0: No findings detected
- Exit code 1: Findings were detected or an error occurred
"""

import argparse
import subprocess
import sys
import yaml
from pathlib import Path
from collections import Counter


def parse_arguments():
"""
Parse command line arguments.

Returns:
argparse.Namespace: Parsed command line arguments
"""
parser = argparse.ArgumentParser(
description="Run clang-tidy on a CMake build directory and analyze findings.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s --build_directory build/tests/posix/Debug --output_file ct-findings.yaml
%(prog)s --build_directory build/tests/posix/Debug --output_file ct-findings.yaml --exclude "test|mock"
%(prog)s --build_directory build/tests/posix/Debug --output_file ct-findings.yaml --quiet
%(prog)s --build_directory build/tests/posix/Debug --output_file ct-findings.yaml --verbose
""",
)

parser.add_argument(
"--build_directory",
type=Path,
help="Path to the build directory containing compile_commands.json generated by CMake",
)
Comment on lines +40 to +44
Copy link

@simon-d-bmw simon-d-bmw Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
parser.add_argument(
"--build_directory",
type=Path,
help="Path to the build directory containing compile_commands.json generated by CMake",
)
parser.add_argument(
"--build_directory",
type=Path,
help="Path to the build directory containing compile_commands.json generated by CMake",
required=True
)


parser.add_argument(
"--output_file",
type=str,
help="Path to the output YAML file where clang-tidy findings will be stored",
Copy link

@simon-d-bmw simon-d-bmw Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
help="Path to the output YAML file where clang-tidy findings will be stored",
help="Path to the output YAML file where clang-tidy findings will be stored",
required=True

)

parser.add_argument(
"--exclude",
type=str,
default="3rdparty",
help="Regular expression pattern to match files to exclude (default: '3rdparty')",
)

parser.add_argument(
"--ignore-checks",
type=str,
default="",
help="Comma-separated list of diagnostic names to ignore (e.g., 'clang-diagnostic-error,clang-diagnostic-warning')",
)

output_group = parser.add_mutually_exclusive_group()
output_group.add_argument(
"--quiet",
action="store_true",
help="Suppress run-clang-tidy progress output (default)",
)

output_group.add_argument(
"--verbose",
action="store_true",
help="Show run-clang-tidy progress output",
)

args = parser.parse_args()

# Validate that build directory exists
if not args.build_directory.exists():
parser.error(f"Build directory does not exist: {args.build_directory}")

# Validate that compile_commands.json exists in build directory
compile_commands = args.build_directory / "compile_commands.json"
if not compile_commands.exists():
parser.error(
f"compile_commands.json not found in build directory: {args.build_directory}"
)

return args


def count_findings(file_name: Path, ignored_checks: list = []) -> int:
"""
Reads the YAML file generated by clang-tidy and counts the number of findings per diagnostic name.

Args:
file_name: Path to the YAML file containing clang-tidy findings
ignored_checks: List of diagnostic names to ignore

Returns:
Total number of findings (excluding ignored checks)
"""

with open(file_name, "r") as f:
data = yaml.safe_load(f)

# Count findings per diagnostic name
counter = Counter()
ignored_counter = Counter()

for diagnostic in data.get("Diagnostics", []):
diagnostic_name = diagnostic.get("DiagnosticName")
if diagnostic_name:
if diagnostic_name in ignored_checks:
ignored_counter[diagnostic_name] += 1
else:
counter[diagnostic_name] += 1

# Sort by count (descending) and then by name
sorted_findings = sorted(counter.items(), key=lambda x: (-x[1], x[0]))

# Print the report
total_findings = sum(counter.values())
total_ignored = sum(ignored_counter.values())
print("Clang-Tidy Findings Report")
print("=" * 60)
print(f"\nTotal unique checks: {len(sorted_findings)}")
print(f"Total findings: {total_findings}")
if total_ignored > 0:
print(f"Total ignored findings: {total_ignored}")

if sorted_findings:
print("\nFindings per check:")
print("-" * 60)

for check_name, count in sorted_findings:
print(f"{check_name}: {count}")
else:
print("\n✓ No findings detected!")

if total_ignored > 0:
print("\nIgnored findings:")
print("-" * 60)
for check_name, count in sorted(
ignored_counter.items(), key=lambda x: (-x[1], x[0])
):
print(f"{check_name}: {count}")

return total_findings


def run_clang_tidy(
build_dir: Path, output_file: Path, exclude_pattern: str, quiet: bool
) -> int:
"""
Run clang-tidy on the build directory.

Args:
build_dir: Path to the build directory
output_file: Path to the output YAML file
exclude_pattern: Regular expression pattern to exclude files
quiet: Whether to suppress run-clang-tidy output

Returns:
Return code from run-clang-tidy
"""
# Convert the exclusion pattern to a negative lookahead pattern
# This makes run-clang-tidy check files that DO NOT match the exclude pattern
negated_pattern = f"^(?!.*({exclude_pattern})).*"

cmd = [
"run-clang-tidy-17.py",
"-p",
str(build_dir),
"-export-fixes",
str(output_file),
negated_pattern,
]

if quiet:
cmd.insert(3, "-quiet")

try:
result = subprocess.run(cmd, check=False)
return result.returncode
except FileNotFoundError:
print(
"Error: run-clang-tidy not found. Please ensure LLVM tools are installed.",
file=sys.stderr,
)
sys.exit(1)
except Exception as e:
print(f"Error running clang-tidy: {e}", file=sys.stderr)
sys.exit(1)


if __name__ == "__main__":
args = parse_arguments()

print(f"Running clang-tidy on build directory: {args.build_directory}")
print(f"Output file: {args.output_file}")
print(f"Exclude pattern: {args.exclude}")
print("-" * 60)

# Run clang-tidy
run_clang_tidy(
args.build_directory,
args.output_file,
args.exclude,
not args.verbose, # quiet mode is default unless --verbose is specified
)

# Parse ignored checks
ignored_checks = [
check.strip() for check in args.ignore_checks.split(",") if check.strip()
]

# Count and report findings
number_of_findings = count_findings(args.output_file, ignored_checks)

# Exit with appropriate code
if number_of_findings != 0:
sys.exit(1)
else:
sys.exit(0)
Loading