197 lines
6.6 KiB
Python
197 lines
6.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Program to check if #define names are used anywhere in C++ code files.
|
|
Use this to verify that defines are safe to remove.
|
|
"""
|
|
|
|
import re
|
|
import argparse
|
|
import sys
|
|
from pathlib import Path
|
|
from collections import defaultdict
|
|
|
|
def load_names_to_check(names_file):
|
|
"""Load the list of names to check from the specified file."""
|
|
try:
|
|
with open(names_file, 'r', encoding='utf-8') as f:
|
|
names = [line.strip() for line in f if line.strip()]
|
|
return names
|
|
except FileNotFoundError:
|
|
print(f"Error: Names file '{names_file}' not found.")
|
|
sys.exit(1)
|
|
except Exception as e:
|
|
print(f"Error reading names file: {e}")
|
|
sys.exit(1)
|
|
|
|
def check_usage_in_file(file_path, names_to_check, exclude_defines=True):
|
|
"""Check if any of the names are used in the given file."""
|
|
try:
|
|
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
content = f.read()
|
|
except Exception as e:
|
|
print(f"Error reading file '{file_path}': {e}")
|
|
return {}
|
|
|
|
lines = content.splitlines()
|
|
usage_found = defaultdict(list)
|
|
|
|
# Pattern to match #define statements (to potentially exclude them)
|
|
define_pattern = re.compile(r'^\s*#define\s+(\w+)')
|
|
|
|
# Pre-compile regex patterns for better performance
|
|
name_patterns = {name: re.compile(r'\b' + re.escape(name) + r'\b') for name in names_to_check}
|
|
|
|
for line_num, line in enumerate(lines, 1):
|
|
# Skip #define lines if exclude_defines is True
|
|
if exclude_defines and define_pattern.match(line):
|
|
continue
|
|
|
|
# Skip comments (basic comment detection)
|
|
# Remove single-line comments
|
|
if '//' in line:
|
|
line = line.split('//')[0]
|
|
|
|
# Skip lines that are mostly comments (basic /* */ detection)
|
|
if line.strip().startswith('/*') or line.strip().startswith('*'):
|
|
continue
|
|
|
|
# Check each name using pre-compiled patterns
|
|
for name, pattern in name_patterns.items():
|
|
if pattern.search(line):
|
|
usage_found[name].append({
|
|
'line_num': line_num,
|
|
'line': line.strip(),
|
|
'file': str(file_path)
|
|
})
|
|
|
|
return usage_found
|
|
|
|
def find_cpp_files(directories, extensions=None):
|
|
"""Find all C++ files in the given directories."""
|
|
if extensions is None:
|
|
extensions = {'.cpp', '.cxx', '.cc', '.c', '.hpp', '.hxx', '.h', '.hh'}
|
|
|
|
cpp_files = []
|
|
for directory in directories:
|
|
dir_path = Path(directory)
|
|
if dir_path.is_file():
|
|
if dir_path.suffix.lower() in extensions:
|
|
cpp_files.append(dir_path)
|
|
elif dir_path.is_dir():
|
|
for ext in extensions:
|
|
cpp_files.extend(dir_path.rglob(f'*{ext}'))
|
|
else:
|
|
print(f"Warning: '{directory}' is not a valid file or directory")
|
|
|
|
return cpp_files
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Check if #define names are used anywhere in C++ code"
|
|
)
|
|
parser.add_argument(
|
|
"names_file",
|
|
help="File containing names to check (one name per line)"
|
|
)
|
|
parser.add_argument(
|
|
"paths",
|
|
nargs="+",
|
|
help="Files or directories to search (directories are searched recursively)"
|
|
)
|
|
parser.add_argument(
|
|
"--include-defines",
|
|
action="store_true",
|
|
help="Include #define statements in the search (default: exclude them)"
|
|
)
|
|
parser.add_argument(
|
|
"--extensions",
|
|
default=".cpp,.cxx,.cc,.c,.hpp,.hxx,.h,.hh",
|
|
help="Comma-separated list of file extensions to search (default: .cpp,.cxx,.cc,.c,.hpp,.hxx,.h,.hh)"
|
|
)
|
|
parser.add_argument(
|
|
"--summary-only",
|
|
action="store_true",
|
|
help="Show only summary of used/unused names"
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Parse extensions
|
|
extensions = {ext.strip() for ext in args.extensions.split(',') if ext.strip()}
|
|
|
|
# Load names to check
|
|
names_to_check = load_names_to_check(args.names_file)
|
|
print(f"Checking usage of {len(names_to_check)} names")
|
|
|
|
# Find C++ files
|
|
cpp_files = find_cpp_files(args.paths, extensions)
|
|
print(f"Found {len(cpp_files)} C++ files to search")
|
|
|
|
if not cpp_files:
|
|
print("No C++ files found to search!")
|
|
sys.exit(1)
|
|
|
|
# Check usage in all files
|
|
all_usage = defaultdict(list)
|
|
files_processed = 0
|
|
|
|
for cpp_file in cpp_files:
|
|
usage = check_usage_in_file(cpp_file, names_to_check, exclude_defines=not args.include_defines)
|
|
files_processed += 1
|
|
|
|
for name, occurrences in usage.items():
|
|
all_usage[name].extend(occurrences)
|
|
|
|
# Progress indicator for large codebases
|
|
if files_processed % 100 == 0:
|
|
print(f"Processed {files_processed} files...")
|
|
|
|
# Analyze results
|
|
used_names = set()
|
|
unused_names = set()
|
|
|
|
for name in names_to_check:
|
|
if name in all_usage:
|
|
used_names.add(name)
|
|
else:
|
|
unused_names.add(name)
|
|
|
|
# Display results
|
|
print(f"\n{'='*60}")
|
|
print("USAGE ANALYSIS RESULTS")
|
|
print(f"{'='*60}")
|
|
|
|
if used_names:
|
|
print(f"\n❌ USED NAMES ({len(used_names)}) - DO NOT REMOVE:")
|
|
for name in sorted(used_names):
|
|
print(f" • {name}")
|
|
if not args.summary_only:
|
|
print(f" Used in {len(set(occ['file'] for occ in all_usage[name]))} file(s):")
|
|
for occurrence in all_usage[name][:5]: # Show first 5 occurrences
|
|
print(f" {occurrence['file']}:{occurrence['line_num']}: {occurrence['line']}")
|
|
if len(all_usage[name]) > 5:
|
|
print(f" ... and {len(all_usage[name]) - 5} more occurrences")
|
|
print()
|
|
|
|
if unused_names:
|
|
print(f"\n✅ UNUSED NAMES ({len(unused_names)}) - SAFE TO REMOVE:")
|
|
for name in sorted(unused_names):
|
|
print(f" • {name}")
|
|
|
|
# Summary
|
|
print(f"\n{'='*60}")
|
|
print("SUMMARY:")
|
|
print(f" Total names checked: {len(names_to_check)}")
|
|
print(f" Names in use: {len(used_names)}")
|
|
print(f" Names unused: {len(unused_names)}")
|
|
print(f" Files searched: {files_processed}")
|
|
|
|
if used_names:
|
|
print(f"\n⚠️ WARNING: {len(used_names)} names are still in use!")
|
|
print(" Review the usage before removing these #define statements.")
|
|
else:
|
|
print(f"\n✅ All {len(names_to_check)} names appear to be unused and safe to remove.")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|