FileContentChecker implementation for detecting includes after namespace declarations.
| 280 | |
| 281 | |
| 282 | class NamespaceIncludesChecker(FileContentChecker): |
| 283 | """FileContentChecker implementation for detecting includes after namespace declarations.""" |
| 284 | |
| 285 | def __init__(self): |
| 286 | self.violations: dict[str, list[IncludeViolation]] = {} |
| 287 | |
| 288 | def should_process_file(self, file_path: str) -> bool: |
| 289 | """Check if file should be processed (only .h and .hpp files in src/).""" |
| 290 | # Skip build directories, third-party code, and tests |
| 291 | if any( |
| 292 | part in file_path.lower() |
| 293 | for part in [ |
| 294 | ".build", |
| 295 | ".pio", |
| 296 | ".venv", |
| 297 | "libdeps", |
| 298 | "third_party", |
| 299 | "vendor", |
| 300 | "tests", |
| 301 | ] |
| 302 | ): |
| 303 | return False |
| 304 | |
| 305 | # Only check .h and .hpp files |
| 306 | return file_path.endswith((".h", ".hpp")) |
| 307 | |
| 308 | def check_file_content(self, file_content: FileContent) -> list[str]: |
| 309 | """Check file content for includes after namespace declarations.""" |
| 310 | # Check for allow directive first |
| 311 | allow_directive_pattern = re.compile(r"//\s*allow-include-after-namespace") |
| 312 | for line in file_content.lines: |
| 313 | if allow_directive_pattern.search(line): |
| 314 | return [] # Return empty if directive is found |
| 315 | |
| 316 | violations: list[IncludeViolation] = [] |
| 317 | current_namespace: NamespaceInfo | None = None |
| 318 | seen_namespace = False # Track if we've ever seen a namespace |
| 319 | seen_include_after_namespace = ( |
| 320 | False # Track if include appears after any namespace |
| 321 | ) |
| 322 | |
| 323 | # Compiled regex patterns for validation (only used after fast string search) |
| 324 | using_namespace_pattern = re.compile(r"^\s*using\s+namespace\s+\w+\s*;") |
| 325 | namespace_open_pattern = re.compile(r"^\s*namespace\s+\w+\s*\{") |
| 326 | include_pattern = re.compile(r"^\s*#\s*include") |
| 327 | |
| 328 | def truncate_snippet(text: str, max_len: int = 50) -> str: |
| 329 | """Truncate text to max_len characters.""" |
| 330 | text = text.strip() |
| 331 | if len(text) <= max_len: |
| 332 | return text |
| 333 | return text[: max_len - 3] + "..." |
| 334 | |
| 335 | # Get lines with newlines for original_line tracking |
| 336 | lines_with_newlines = file_content.content.splitlines(keepends=True) |
| 337 | |
| 338 | for i, line in enumerate(file_content.lines, 1): |
| 339 | original_line = ( |
no outgoing calls
no test coverage detected