Checks that headers in src/fl/ / use the proper fl:: :: namespace.
| 23 | |
| 24 | |
| 25 | class SubdirNamespaceChecker(FileContentChecker): |
| 26 | """Checks that headers in src/fl/<subdir>/ use the proper fl::<subdir>:: namespace.""" |
| 27 | |
| 28 | def __init__(self, subdir: str) -> None: |
| 29 | self.subdir = subdir |
| 30 | self.subdir_root = str(PROJECT_ROOT / "src" / "fl" / subdir).replace("\\", "/") |
| 31 | self.violations: dict[str, list[tuple[int, str]]] = {} |
| 32 | |
| 33 | def should_process_file(self, file_path: str) -> bool: |
| 34 | normalized = file_path.replace("\\", "/") |
| 35 | if ( |
| 36 | not normalized.startswith(self.subdir_root + "/") |
| 37 | and normalized != self.subdir_root |
| 38 | ): |
| 39 | return False |
| 40 | return file_path.endswith(".h") |
| 41 | |
| 42 | def _expected_namespace_parts(self, file_path: str) -> list[str]: |
| 43 | """Compute expected namespace parts from file path. |
| 44 | |
| 45 | Only requires the first-level subdirectory namespace (fl::<subdir>), |
| 46 | not deeper levels, since deeper directory names may collide with |
| 47 | C++ type names (e.g., 'fixed_point' is both a directory and a class). |
| 48 | |
| 49 | For subdir="net": |
| 50 | src/fl/net/fetch.h -> ["fl", "net"] |
| 51 | src/fl/net/http/stream_client.h -> ["fl", "net"] |
| 52 | |
| 53 | For subdir="math": |
| 54 | src/fl/math/random.h -> ["fl", "math"] |
| 55 | src/fl/math/filter/kalman.h -> ["fl", "math"] |
| 56 | """ |
| 57 | return ["fl", self.subdir] |
| 58 | |
| 59 | def check_file_content(self, file_content: FileContent) -> list[str]: |
| 60 | expected = self._expected_namespace_parts(file_content.path) |
| 61 | # e.g. ["fl", "net"] or ["fl", "net", "http"] |
| 62 | |
| 63 | # Collect all namespace identifiers from declarations in the file |
| 64 | found_parts: set[str] = set() |
| 65 | has_any_namespace = False |
| 66 | |
| 67 | for line in file_content.lines: |
| 68 | stripped = line.strip() |
| 69 | # Skip obvious comment-only lines |
| 70 | if ( |
| 71 | stripped.startswith("//") |
| 72 | or stripped.startswith("/*") |
| 73 | or stripped.startswith("*") |
| 74 | ): |
| 75 | continue |
| 76 | # Remove inline comments |
| 77 | code = stripped.split("//")[0] |
| 78 | |
| 79 | for match in _NAMESPACE_RE.finditer(code): |
| 80 | has_any_namespace = True |
| 81 | ns_name = match.group(1) |
| 82 | for part in ns_name.split("::"): |
no outgoing calls
no test coverage detected