0.0.5-alpha.8
This commit is contained in:
87
scripts/audit_rust_module_order.py
Executable file
87
scripts/audit_rust_module_order.py
Executable file
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env python3
|
||||
# file: scripts/audit_rust_module_order.py
|
||||
# version: 1
|
||||
|
||||
"""Audit top-level Rust module ordering with test modules last."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import pathlib
|
||||
import re
|
||||
import sys
|
||||
|
||||
from _rust_audit_common import line_depths, mask_source, selected_files
|
||||
|
||||
|
||||
def _natural_key(value: str) -> tuple[tuple[int, object], ...]:
|
||||
parts = re.findall(r"[A-Za-z_]+|[0-9]+", value)
|
||||
return tuple((1, int(part)) if part.isdigit() else (0, part) for part in parts)
|
||||
|
||||
|
||||
def audit_file(path: pathlib.Path) -> list[str]:
|
||||
"""Require production modules before test modules and alphabetic order within each group."""
|
||||
|
||||
text = path.read_text(encoding="utf-8")
|
||||
lines = text.splitlines()
|
||||
masked, _ = mask_source(text)
|
||||
depths = line_depths(masked)
|
||||
modules: list[tuple[int, bool, str]] = []
|
||||
for number, line in enumerate(lines, 1):
|
||||
if depths[number - 1] != 0:
|
||||
continue
|
||||
match = re.match(r"^mod\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?:;|\{)", line.strip())
|
||||
if match is None:
|
||||
continue
|
||||
cursor = number - 2
|
||||
is_test = False
|
||||
while cursor >= 0 and lines[cursor].strip().startswith("#["):
|
||||
if "cfg(test)" in lines[cursor].replace(" ", ""):
|
||||
is_test = True
|
||||
cursor -= 1
|
||||
modules.append((number, is_test, match.group(1)))
|
||||
|
||||
production = [(number, name) for number, is_test, name in modules if not is_test]
|
||||
tests = [(number, name) for number, is_test, name in modules if is_test]
|
||||
errors: list[str] = []
|
||||
if production and tests and min(number for number, _ in tests) < max(number for number, _ in production):
|
||||
errors.append(f"{path}:{min(number for number, _ in tests)}: test modules must follow production modules")
|
||||
for group, label in ((production, "production"), (tests, "test")):
|
||||
names = [name for _, name in group]
|
||||
expected = sorted(names, key=_natural_key)
|
||||
if names != expected:
|
||||
for (number, name), wanted in zip(group, expected, strict=True):
|
||||
if name != wanted:
|
||||
errors.append(f"{path}:{number}: {label} module declarations are not alphabetically ordered")
|
||||
break
|
||||
return errors
|
||||
|
||||
|
||||
def main() -> int:
|
||||
"""Run the read-only module-order audit on explicitly selected Rust files."""
|
||||
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("paths", nargs="+", help="Rust 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 module-order 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 module-order audit: cannot read {path}: {error}", file=sys.stderr)
|
||||
return 2
|
||||
for error in errors:
|
||||
print(error, file=sys.stderr)
|
||||
if errors:
|
||||
print(f"Rust module-order audit: {len(errors)} error(s) across {len(files)} file(s)", file=sys.stderr)
|
||||
return 1
|
||||
print(f"Rust module-order audit: clean ({len(files)} file(s))")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user