Files

82 lines
2.5 KiB
Python

"""Check infrastructure: shared filter/match utilities."""
from __future__ import annotations
import fnmatch
import os
from pathlib import Path
LINTIGNORE_PREFIX = ".lintignore"
class CheckError(Exception):
"""Raised when a check could not complete (missing tool, timeout, crash).
Distinct from "ran and found zero problems": the orchestrator surfaces this
loudly so a partial run is never silently reported as a clean one (returning
[] on failure made a timed-out/crashed tool look identical to a passing one).
"""
def __init__(self, reason: str) -> None:
super().__init__(reason)
self.reason = reason
def _matches_any(text: str, patterns: list[str]) -> bool:
return any(fnmatch.fnmatch(text, p) for p in patterns)
def is_excluded(path: Path, root: Path, excludes: list[str]) -> bool:
rel = path.relative_to(root)
for part in rel.parts:
if _matches_any(part, excludes):
return True
return _matches_any(str(rel), excludes)
def is_excepted(rel_path: str, rule: str, exceptions: dict[str, list[str]]) -> bool:
return _matches_any(rel_path, exceptions.get(rule, []))
def collect_lintignores(root: Path, excludes: list[str]) -> dict[Path, set[str]]:
"""Scan *root* for ``.lintignore*`` sentinel files.
Returns ``{directory: set_of_ignored_rules}``.
The special token ``"__all__"`` means every rule is ignored.
"""
ignores: dict[Path, set[str]] = {}
for dirpath_str, dirnames, filenames in os.walk(root):
dp = Path(dirpath_str)
if is_excluded(dp, root, excludes):
dirnames.clear()
continue
for fname in filenames:
if fname == LINTIGNORE_PREFIX:
ignores.setdefault(dp, set()).add("__all__")
elif fname.startswith(f"{LINTIGNORE_PREFIX}-"):
rule = fname[len(LINTIGNORE_PREFIX) + 1 :]
ignores.setdefault(dp, set()).add(rule)
return ignores
def is_lintignored(
path: Path,
root: Path,
rule: str,
ignores: dict[Path, set[str]],
) -> bool:
"""Return True if *path* is covered by a ``.lintignore`` file for *rule*.
Walks from *path* up to *root* checking each ancestor directory.
"""
current = path if path.is_dir() else path.parent
root = root.resolve()
while True:
rules = ignores.get(current)
if rules and ("__all__" in rules or rule in rules):
return True
if current == root:
break
current = current.parent
return False