import re
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent))
import ratchet
PUB_USE = re.compile(r"^[ \t]*pub(?:\([^)]*\))?\s+use\s+(.+?);", re.M | re.S)
LEAF = re.compile(r"([A-Za-z_][A-Za-z0-9_]*)(?:\s+as\s+([A-Za-z_][A-Za-z0-9_]*))?")
def exported_names(stmt: str) -> list[str]:
stmt = " ".join(stmt.split())
if stmt.endswith("*") or "::*" in stmt:
return [] if "{" not in stmt:
m = re.search(r"([A-Za-z_][A-Za-z0-9_]*)(?:\s+as\s+([A-Za-z_][A-Za-z0-9_]*))?$", stmt)
if not m:
return []
return [m.group(2) or m.group(1)]
body = stmt[stmt.index("{") + 1 : stmt.rindex("}")]
body = re.sub(r"[A-Za-z_][A-Za-z0-9_]*\s*::\s*\{", "{", body)
names: list[str] = []
for chunk in re.split(r"[,{}]", body):
chunk = chunk.strip()
if not chunk or chunk == "self":
continue
m = LEAF.fullmatch(chunk)
if not m:
continue
names.append(m.group(2) or m.group(1))
return names
def main(argv: list[str]) -> int:
roots = [Path("src")] + sorted(p / "src" for p in Path("crates").glob("*"))
lib_files = [r / "lib.rs" for r in roots if (r / "lib.rs").is_file()]
if not lib_files:
print("check_exports: no crate roots found — the guard would pass vacuously")
return 1
scan_dirs = [d for d in roots + [Path("tests"), Path("benches")] if d.is_dir()]
corpus = {
f: f.read_text(encoding="utf-8")
for d in scan_dirs
for f in sorted(d.rglob("*.rs"))
}
findings: dict[str, int] = {}
occurrences: dict[str, list[str]] = {}
for lib in lib_files:
text = corpus.get(lib) or lib.read_text(encoding="utf-8")
without_reexports = PUB_USE.sub("", text)
for m in PUB_USE.finditer(text):
for name in exported_names(m.group(1)):
word = re.compile(rf"\b{re.escape(name)}\b")
used = any(
word.search(without_reexports if f == lib else body)
for f, body in corpus.items()
)
if used:
continue
key = f"unused-export|{lib.as_posix()}|{name}"
findings[key] = findings.get(key, 0) + 1
line = text.count("\n", 0, m.start()) + 1
occurrences.setdefault(key, []).append(
f"{lib.as_posix()}:{line}: `{name}` is re-exported and never named"
)
return ratchet.ratchet(
"exports", "crate-root re-exports", findings, occurrences, argv
)
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))