Checker that enforces correct Singleton vs SingletonShared usage.
| 25 | |
| 26 | |
| 27 | class SingletonInHeadersChecker(FileContentChecker): |
| 28 | """Checker that enforces correct Singleton vs SingletonShared usage.""" |
| 29 | |
| 30 | # Match Singleton< but NOT SingletonShared< |
| 31 | _SINGLETON_PATTERN = re.compile(r"(?<!Shared)\bSingleton\s*<") |
| 32 | # Match SingletonShared< (or SingletonShared without template for friend decls) |
| 33 | _SINGLETON_SHARED_PATTERN = re.compile(r"\bSingletonShared\b") |
| 34 | # Match friend class declarations |
| 35 | _FRIEND_PATTERN = re.compile(r"\bfriend\s+class\b") |
| 36 | |
| 37 | def __init__(self) -> None: |
| 38 | self.violations: dict[str, list[tuple[int, str]]] = {} |
| 39 | |
| 40 | def should_process_file(self, file_path: str) -> bool: |
| 41 | """Process header files (.h, .hpp) and implementation files (.cpp.hpp).""" |
| 42 | if not file_path.endswith((".h", ".hpp")): |
| 43 | return False |
| 44 | # Exclude the singleton.h definition file itself |
| 45 | normalized = file_path.replace("\\", "/") |
| 46 | if normalized.endswith("fl/stl/singleton.h"): |
| 47 | return False |
| 48 | # Exclude private implementation headers (.hpp only, not .h) |
| 49 | # Private headers are included in .cpp.hpp files, so Singleton<T> is sufficient |
| 50 | if file_path.endswith(".hpp"): |
| 51 | return False |
| 52 | if any(file_path.endswith(excluded) for excluded in EXCLUDED_FILES): |
| 53 | return False |
| 54 | return True |
| 55 | |
| 56 | def check_file_content(self, file_content: FileContent) -> list[str]: |
| 57 | """Check singleton usage rules.""" |
| 58 | is_cpp_hpp = file_content.path.endswith(".cpp.hpp") |
| 59 | # Check first 50 lines for IWYU pragma (to catch pragmas after #pragma once) |
| 60 | is_private_header = any( |
| 61 | "IWYU pragma: private" in line for line in file_content.lines[:50] |
| 62 | ) |
| 63 | violations: list[tuple[int, str]] = [] |
| 64 | in_multiline_comment = False |
| 65 | |
| 66 | # Skip checks if this is a private header or .cpp.hpp file |
| 67 | if is_cpp_hpp or is_private_header: |
| 68 | return [] |
| 69 | |
| 70 | for line_number, line in enumerate(file_content.lines, 1): |
| 71 | stripped = line.strip() |
| 72 | |
| 73 | # Track multi-line comment state |
| 74 | if "/*" in line: |
| 75 | in_multiline_comment = True |
| 76 | if "*/" in line: |
| 77 | in_multiline_comment = False |
| 78 | continue |
| 79 | if in_multiline_comment: |
| 80 | continue |
| 81 | |
| 82 | # Skip single-line comments |
| 83 | if stripped.startswith("//"): |
| 84 | continue |