#!/usr/bin/env python3 """Quick utility to find TODO/FIXME comments in code files.""" import os import re import sys from pathlib import Path from collections import defaultdict from typing import Dict, List, Tuple, Set # Match TODO/FIXME/XXX/HACK as comment markers TODO_PATTERNS = { 'TODO': re.compile(r'#\s*TODO[:\s]*(.*?)(?:\n|$)', re.IGNORECASE), 'TODO.md': re.compile(r'#\s*TODO\.md[:\s]*(.*?)(?:\n|$)', re.IGNORECASE), 'FIXME': re.compile(r'#\s*FIXME[:\s]*(.*?)(?:\n|$)', re.IGNORECASE), 'XXX': re.compile(r'#\s*XXX[:\s]*(.*?)(?:\n|$)', re.IGNORECASE), 'HACK': re.compile(r'#\s*HACK[:\s]*(.*?)(?:\n|$)', re.IGNORECASE), } def find_todos(directory: str, file_patterns: List[str] = None) -> Dict[str, List[Tuple[str, int, str]]]: """Find TODO/FIXME comments in files.""" if file_patterns is None: file_patterns = ['*.py', '*.js', '*.ts', '*.md', '*.txt', '*.sh', '*.go', '*.rs', '*.c', '*.cpp', '*.h', '*.java', '*.rb', '*.rs'] results = defaultdict(list) root_path = Path(directory) # If path is a file, search just that file if root_path.is_file(): files_to_search = [root_path] else: # If path is a directory, search for matching files files_to_search = list(root_path.rglob('*')) files_to_search = [f for f in files_to_search if f.is_file() and any(f.match(p) for p in file_patterns)] for filepath in files_to_search: try: with open(filepath, 'r', encoding='utf-8', errors='ignore') as f: for line_num, line in enumerate(f, 1): for name, regex in TODO_PATTERNS.items(): match = regex.search(line) if match: comment = match.group(1).strip() if match.lastindex else '' base_dir = root_path if root_path.is_dir() else root_path.parent results[name].append((str(filepath), line_num, comment)) except Exception: continue return dict(results) def main(): directory = sys.argv[1] if len(sys.argv) > 1 else '.' print(f"Searching for TODOs in: {directory}") print("=" * 60) todos = find_todos(directory) total = sum(len(v) for v in todos.values()) print(f"\nFound {total} item(s) across {len(todos)} pattern type(s)\n") for pattern, items in sorted(todos.items()): if items: print(f"\n{pattern.upper()}:") print("-" * 40) for filepath, line_num, comment in items: rel_path = os.path.relpath(filepath, directory if Path(directory).is_dir() else Path(directory).parent) display = f"{rel_path}:{line_num}" print(f" {display}") if comment: print(f" {comment}") if __name__ == '__main__': main()