|
15 | 15 | from __future__ import annotations |
16 | 16 |
|
17 | 17 | import ast |
| 18 | +import re as _re |
18 | 19 | from pathlib import Path |
19 | 20 |
|
20 | 21 | import pytest |
@@ -156,7 +157,7 @@ def test_rule_findings_match_rule_def_severity() -> None: |
156 | 157 | Path(__file__).resolve().parents[1] / "recipe" / "test_bundled_recipes_dispatch_ready.py" |
157 | 158 | ) |
158 | 159 |
|
159 | | -_ALLOWLIST_CAP = 5 |
| 160 | +_ALLOWLIST_CAP = 4 |
160 | 161 |
|
161 | 162 |
|
162 | 163 | def _collect_allowlist_rule_names() -> set[str]: |
@@ -205,6 +206,39 @@ def test_dispatch_readiness_allowlist_size_cap() -> None: |
205 | 206 | ) |
206 | 207 |
|
207 | 208 |
|
| 209 | +def test_known_non_conforming_entries_have_tracking_comments() -> None: |
| 210 | + """Every _KNOWN_NON_CONFORMING_RULES entry must reference a tracking issue.""" |
| 211 | + source = _DISPATCH_READY_TEST.read_text() |
| 212 | + lines = source.splitlines() |
| 213 | + tree = ast.parse(source) |
| 214 | + |
| 215 | + missing: list[str] = [] |
| 216 | + for node in ast.walk(tree): |
| 217 | + if isinstance(node, ast.AnnAssign): |
| 218 | + target = node.target |
| 219 | + value = node.value |
| 220 | + elif isinstance(node, ast.Assign): |
| 221 | + target = node.targets[0] if node.targets else None |
| 222 | + value = node.value |
| 223 | + else: |
| 224 | + continue |
| 225 | + if not isinstance(target, ast.Name) or target.id != "_KNOWN_NON_CONFORMING_RULES": |
| 226 | + continue |
| 227 | + if not isinstance(value, ast.Dict): |
| 228 | + continue |
| 229 | + for key in value.keys: |
| 230 | + if isinstance(key, ast.Constant) and isinstance(key.value, str): |
| 231 | + line = lines[key.lineno - 1] |
| 232 | + if not _re.search(r"#\s*tracking:\s*#\d+", line): |
| 233 | + missing.append(f"{key.value!r} (line {key.lineno})") |
| 234 | + |
| 235 | + assert not missing, ( |
| 236 | + "Entries in _KNOWN_NON_CONFORMING_RULES missing tracking comments: " |
| 237 | + + ", ".join(missing) |
| 238 | + + ". Add '# tracking: #NNNN' with the relevant GitHub issue number." |
| 239 | + ) |
| 240 | + |
| 241 | + |
208 | 242 | def _decorator_rule_name(dec: ast.Call) -> str | None: |
209 | 243 | """Extract the ``name=`` keyword string value from a decorator call.""" |
210 | 244 | for kw in dec.keywords: |
|
0 commit comments