0.0.1-alpha.3
This commit is contained in:
88
scripts/audit_rust_reexport_blocks.py
Normal file
88
scripts/audit_rust_reexport_blocks.py
Normal file
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env python3
|
||||
# file: scripts/audit_rust_reexport_blocks.py
|
||||
# version: 1
|
||||
|
||||
"""Audit crate-root Rust re-export block visibility and spacing."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import pathlib
|
||||
import re
|
||||
import sys
|
||||
|
||||
from _rust_audit_common import line_depths, mask_source, selected_files
|
||||
|
||||
_EXPORT = re.compile(r"^(pub(?:\(crate\))?)\s+use\s+")
|
||||
|
||||
|
||||
def audit_file(path: pathlib.Path) -> list[str]:
|
||||
"""Require public re-exports before crate-public re-exports and compact blocks."""
|
||||
|
||||
text = path.read_text(encoding="utf-8")
|
||||
lines = text.splitlines()
|
||||
masked, _ = mask_source(text)
|
||||
depths = line_depths(masked)
|
||||
exports: list[tuple[int, str]] = []
|
||||
for number, line in enumerate(lines, 1):
|
||||
if depths[number - 1] != 0:
|
||||
continue
|
||||
match = _EXPORT.match(line.strip())
|
||||
if match is None:
|
||||
continue
|
||||
visibility = "pub(crate)" if match.group(1) == "pub(crate)" else "pub"
|
||||
exports.append((number, visibility))
|
||||
|
||||
errors: list[str] = []
|
||||
crate_seen = False
|
||||
previous_by_visibility: dict[str, int] = {}
|
||||
for number, visibility in exports:
|
||||
if visibility == "pub(crate)":
|
||||
crate_seen = True
|
||||
elif crate_seen:
|
||||
errors.append(f"{path}:{number}: pub use block must precede pub(crate) use block")
|
||||
previous = previous_by_visibility.get(visibility)
|
||||
if previous is not None:
|
||||
between = lines[previous:number - 1]
|
||||
if any(not candidate.strip() for candidate in between):
|
||||
errors.append(f"{path}:{number}: {visibility} use block contains an empty line")
|
||||
previous_by_visibility[visibility] = number
|
||||
|
||||
public = [number for number, visibility in exports if visibility == "pub"]
|
||||
crate_public = [number for number, visibility in exports if visibility == "pub(crate)"]
|
||||
if public and crate_public:
|
||||
transition = lines[public[-1]:crate_public[0] - 1]
|
||||
blank_count = sum(1 for candidate in transition if not candidate.strip())
|
||||
if blank_count != 1:
|
||||
errors.append(f"{path}:{crate_public[0]}: pub use and pub(crate) use blocks require exactly one blank line; found {blank_count}")
|
||||
return errors
|
||||
|
||||
|
||||
def main() -> int:
|
||||
"""Run the read-only re-export-block audit on explicitly selected Rust files."""
|
||||
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("paths", nargs="+", help="Rust crate-root files or directories to inspect")
|
||||
args = parser.parse_args()
|
||||
files, errors = selected_files(args.paths)
|
||||
if errors or not files:
|
||||
for error in errors or ["no Rust files selected"]:
|
||||
print(f"Rust re-export-block audit: {error}", file=sys.stderr)
|
||||
return 2
|
||||
for path in files:
|
||||
try:
|
||||
errors.extend(audit_file(path))
|
||||
except (OSError, UnicodeError) as error:
|
||||
print(f"Rust re-export-block audit: cannot read {path}: {error}", file=sys.stderr)
|
||||
return 2
|
||||
for error in errors:
|
||||
print(error, file=sys.stderr)
|
||||
if errors:
|
||||
print(f"Rust re-export-block audit: {len(errors)} error(s) across {len(files)} file(s)", file=sys.stderr)
|
||||
return 1
|
||||
print(f"Rust re-export-block audit: clean ({len(files)} file(s))")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user