v0.2.8-pre.005-fix.001
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# file: scripts/audit_rust_export_completeness.py
|
||||
# version: 3
|
||||
# version: 4
|
||||
|
||||
"""Audit crate-root export completeness and canonical same-crate paths."""
|
||||
|
||||
@@ -32,6 +32,7 @@ class Declaration:
|
||||
module: str
|
||||
name: str
|
||||
visibility: str
|
||||
kind: str
|
||||
path: pathlib.Path
|
||||
line: int
|
||||
|
||||
@@ -62,17 +63,47 @@ def declaration_candidates(crate: pathlib.Path, path: pathlib.Path) -> list[Decl
|
||||
depths = line_depths(mask_rust_source(text))
|
||||
found: list[Declaration] = []
|
||||
pattern = re.compile(
|
||||
r"^\s*(pub(?:\(crate\))?)\s+(?:(?:async|unsafe|const)\s+)*(?:const|static|type|struct|enum|trait|union|fn)\s+([A-Za-z_][A-Za-z0-9_]*)"
|
||||
r"^\s*(pub(?:\(crate\))?)\s+(?:(?:async|unsafe|const)\s+)*(const|static|type|struct|enum|trait|union|fn)\s+([A-Za-z_][A-Za-z0-9_]*)"
|
||||
)
|
||||
for idx, line in enumerate(lines, 1):
|
||||
if depths[idx - 1] != 0:
|
||||
continue
|
||||
match = pattern.match(line)
|
||||
if match is not None:
|
||||
found.append(Declaration(module_path(crate, path), match.group(2), match.group(1), path, idx))
|
||||
found.append(Declaration(module_path(crate, path), match.group(3), match.group(1), match.group(2), path, idx))
|
||||
return found
|
||||
|
||||
|
||||
def private_declaration_candidates(crate: pathlib.Path, path: pathlib.Path) -> list[Declaration]:
|
||||
"""Return module-level strictly private declarations from one source module."""
|
||||
|
||||
text = path.read_text(encoding="utf-8")
|
||||
lines = text.splitlines()
|
||||
depths = line_depths(mask_rust_source(text))
|
||||
found: list[Declaration] = []
|
||||
pattern = re.compile(r"^\s*(?:(?:async|unsafe|const)\s+)*(const|static|type|struct|enum|trait|union|fn)\s+([A-Za-z_][A-Za-z0-9_]*)")
|
||||
for idx, line in enumerate(lines, 1):
|
||||
if depths[idx - 1] != 0:
|
||||
continue
|
||||
if line.lstrip().startswith(("pub ", "pub(crate) ")):
|
||||
continue
|
||||
match = pattern.match(line)
|
||||
if match is not None:
|
||||
found.append(Declaration(module_path(crate, path), match.group(2), "private", match.group(1), path, idx))
|
||||
return found
|
||||
|
||||
|
||||
def unqualified_test_reference_pattern(declaration: Declaration) -> re.Pattern[str]:
|
||||
"""Return a conservative pattern for one unqualified parent-item reference in a separated unit test."""
|
||||
|
||||
name = re.escape(declaration.name)
|
||||
if declaration.kind == "fn":
|
||||
return re.compile(rf"(?<![\w:.]){name}\s*\(")
|
||||
if declaration.kind in {"const", "static", "type", "struct", "enum", "trait", "union"}:
|
||||
return re.compile(rf"(?<![\w:]){name}\b")
|
||||
return re.compile(rf"(?<![\w:]){name}\b")
|
||||
|
||||
|
||||
def root_exports(crate: pathlib.Path) -> dict[tuple[str, str], str]:
|
||||
"""Return explicit crate-root re-exports keyed by source module and symbol."""
|
||||
|
||||
@@ -160,10 +191,12 @@ def audit_crate(workspace: pathlib.Path, crate: pathlib.Path) -> list[Candidate]
|
||||
exports = root_exports(crate)
|
||||
tests = unit_test_parents(crate)
|
||||
declarations: dict[tuple[str, str], Declaration] = {}
|
||||
private_declarations_by_source: dict[pathlib.Path, dict[str, Declaration]] = {}
|
||||
candidates: list[Candidate] = []
|
||||
for path in sorted((crate / "src").rglob("*.rs")):
|
||||
if path == crate_root:
|
||||
continue
|
||||
private_declarations_by_source[path.resolve()] = {item.name: item for item in private_declaration_candidates(crate, path)}
|
||||
for declaration in declaration_candidates(crate, path):
|
||||
key = (declaration.module, declaration.name)
|
||||
declarations[key] = declaration
|
||||
@@ -198,19 +231,28 @@ def audit_crate(workspace: pathlib.Path, crate: pathlib.Path) -> list[Candidate]
|
||||
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.
|
||||
# Separated unit tests use `super::Item` only for strictly private parent items; visible items use the crate-root façade.
|
||||
declarations_by_source: dict[pathlib.Path, dict[str, Declaration]] = {}
|
||||
for declaration in declarations.values():
|
||||
declarations_by_source.setdefault(declaration.path.resolve(), {})[declaration.name] = declaration
|
||||
super_pattern = re.compile(r"\bsuper::([A-Za-z_][A-Za-z0-9_]*)")
|
||||
for test, parent in tests.items():
|
||||
parent_declarations = declarations_by_source.get(parent, {})
|
||||
parent_private_declarations = private_declarations_by_source.get(parent, {})
|
||||
relative = test.relative_to(workspace).as_posix()
|
||||
for idx, line in enumerate(test.read_text(encoding="utf-8").splitlines(), 1):
|
||||
text = test.read_text(encoding="utf-8")
|
||||
masked_lines = mask_rust_source(text).splitlines()
|
||||
for idx, line in enumerate(masked_lines, 1):
|
||||
for match in super_pattern.finditer(line):
|
||||
declaration = parent_declarations.get(match.group(1))
|
||||
if declaration is not None and declaration.visibility in {"pub", "pub(crate)"}:
|
||||
candidates.append(Candidate("RUST-IMPORT-202", relative, idx, f"`super::{declaration.name}` targets {declaration.visibility}; use crate-root `crate::{declaration.name}`"))
|
||||
for declaration in parent_private_declarations.values():
|
||||
if unqualified_test_reference_pattern(declaration).search(line) is not None:
|
||||
candidates.append(Candidate("RUST-IMPORT-204", relative, idx, f"strictly private parent item `{declaration.name}` must be accessed as `super::{declaration.name}` in separated unit tests"))
|
||||
for declaration in parent_declarations.values():
|
||||
if unqualified_test_reference_pattern(declaration).search(line) is not None:
|
||||
candidates.append(Candidate("RUST-IMPORT-205", relative, idx, f"{declaration.visibility} parent item `{declaration.name}` must be accessed through crate-root `crate::{declaration.name}` in separated unit tests"))
|
||||
return candidates
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user