v0.2.5-pre.010.fix.002
This commit is contained in:
44
scripts/audit_rust_export_completeness.py
Executable file → Normal file
44
scripts/audit_rust_export_completeness.py
Executable file → Normal file
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# file: scripts/audit_rust_export_completeness.py
|
||||
# version: 1
|
||||
# version: 3
|
||||
|
||||
"""Audit crate-root export completeness and canonical same-crate paths."""
|
||||
|
||||
@@ -124,6 +124,33 @@ def external_usage(crate: pathlib.Path, declaration: Declaration, test_parents:
|
||||
return False
|
||||
|
||||
|
||||
|
||||
|
||||
def crate_root_symbols(crate: pathlib.Path) -> set[str]:
|
||||
"""Return names that can resolve directly after `crate::`."""
|
||||
|
||||
crate_root = crate / "src/lib.rs"
|
||||
if not crate_root.is_file():
|
||||
crate_root = crate / "src/main.rs"
|
||||
symbols: set[str] = set()
|
||||
if crate_root.is_file():
|
||||
text = crate_root.read_text(encoding="utf-8")
|
||||
for match in re.finditer(r"^\s*mod\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?:;|\{)", text, re.MULTILINE):
|
||||
symbols.add(match.group(1))
|
||||
for match in re.finditer(r"^\s*(?:(?:pub|pub\(crate\))\s+)?(?:(?:async|const|unsafe)\s+)*(?:const|static|type|struct|enum|trait|union|fn)\s+([A-Za-z_][A-Za-z0-9_]*)", text, re.MULTILINE):
|
||||
symbols.add(match.group(1))
|
||||
for match in re.finditer(r"^\s*pub(?:\(crate\))?\s+use\s+[^;]*::([A-Za-z_][A-Za-z0-9_]*)\s*;", text, re.MULTILINE):
|
||||
symbols.add(match.group(1))
|
||||
for match in re.finditer(r"^\s*pub\s+extern\s+crate\s+([A-Za-z_][A-Za-z0-9_]*)\s*;", text, re.MULTILINE):
|
||||
symbols.add(match.group(1))
|
||||
for path in sorted((crate / "src").rglob("*.rs")):
|
||||
text = path.read_text(encoding="utf-8")
|
||||
pattern = re.compile(r"#\[macro_export\]\s*\n\s*macro_rules!\s+([A-Za-z_][A-Za-z0-9_]*)")
|
||||
for match in pattern.finditer(text):
|
||||
symbols.add(match.group(1))
|
||||
return symbols
|
||||
|
||||
|
||||
def audit_crate(workspace: pathlib.Path, crate: pathlib.Path) -> list[Candidate]:
|
||||
"""Audit one crate for export completeness and canonical paths."""
|
||||
|
||||
@@ -156,6 +183,21 @@ def audit_crate(workspace: pathlib.Path, crate: pathlib.Path) -> list[Candidate]
|
||||
alias = exports.get((match.group(1), match.group(2)))
|
||||
if alias is not None:
|
||||
candidates.append(Candidate("RUST-IMPORT-201", relative, idx, f"long internal path `crate::{match.group(1)}::{match.group(2)}` must use `crate::{alias}`"))
|
||||
# Simple `crate::Item` references must resolve at the crate root.
|
||||
root_symbols = crate_root_symbols(crate)
|
||||
simple_root = re.compile(r"\bcrate::([A-Za-z_][A-Za-z0-9_]*)\b(?!::)")
|
||||
for path in sorted(crate.rglob("*.rs")):
|
||||
if "target" in path.parts:
|
||||
continue
|
||||
relative = path.relative_to(workspace).as_posix()
|
||||
text = path.read_text(encoding="utf-8")
|
||||
masked = mask_rust_source(text)
|
||||
for idx, line in enumerate(masked.splitlines(), 1):
|
||||
for match in simple_root.finditer(line):
|
||||
symbol = match.group(1)
|
||||
if symbol not in root_symbols:
|
||||
candidates.append(Candidate("RUST-IMPORT-203", relative, idx, f"`crate::{symbol}` does not resolve to a declared/re-exported crate-root symbol"))
|
||||
|
||||
# `super::Item` is reserved to strictly private parent items in separated unit tests.
|
||||
declarations_by_source: dict[pathlib.Path, dict[str, Declaration]] = {}
|
||||
for declaration in declarations.values():
|
||||
|
||||
Reference in New Issue
Block a user