74 lines
2.4 KiB
Bash
74 lines
2.4 KiB
Bash
|
|
#!/usr/bin/env bash
|
||
|
|
# Guard the `mc-ai::abstract_state` docs contract.
|
||
|
|
#
|
||
|
|
# The AbstractRolloutState POD is the Rust↔WGSL interface. Every public field's
|
||
|
|
# docstring must include its byte offset (e.g. `@24`) so readers of either side
|
||
|
|
# can cross-reference without opening the other. `deny(missing_docs)` catches
|
||
|
|
# *missing* docs; this script catches docs that drift off the offset contract.
|
||
|
|
#
|
||
|
|
# Run from EDIT host (pure text scan, no toolchain needed):
|
||
|
|
# bash tools/check-abstract-state-docs.sh
|
||
|
|
#
|
||
|
|
# On RUN host, `cargo doc -p mc-ai --no-deps` also enforces the `deny` at build
|
||
|
|
# time — that's the authoritative gate. This script is the fast local check.
|
||
|
|
|
||
|
|
set -euo pipefail
|
||
|
|
|
||
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||
|
|
FILE="$REPO_ROOT/src/simulator/crates/mc-ai/src/abstract_state.rs"
|
||
|
|
|
||
|
|
if [[ ! -f "$FILE" ]]; then
|
||
|
|
echo "ERROR: $FILE not found" >&2
|
||
|
|
exit 2
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Every public field (including `_padN`) must have `@<offset>` somewhere in its
|
||
|
|
# doc comments. We walk the AbstractPlayerState struct body and flag any
|
||
|
|
# `pub <name>: <type>,` line whose preceding doc block is missing `@\d+`.
|
||
|
|
python3 - "$FILE" <<'PY'
|
||
|
|
import re
|
||
|
|
import sys
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
src = Path(sys.argv[1]).read_text()
|
||
|
|
|
||
|
|
# Isolate the AbstractPlayerState struct body.
|
||
|
|
m = re.search(
|
||
|
|
r"pub struct AbstractPlayerState \{(.*?)\n\}",
|
||
|
|
src,
|
||
|
|
re.DOTALL,
|
||
|
|
)
|
||
|
|
if not m:
|
||
|
|
print("ERROR: could not locate AbstractPlayerState struct body", file=sys.stderr)
|
||
|
|
sys.exit(2)
|
||
|
|
body = m.group(1)
|
||
|
|
|
||
|
|
# Walk lines, track the current doc block, check each `pub <field>: <type>,`.
|
||
|
|
lines = body.splitlines()
|
||
|
|
doc_block: list[str] = []
|
||
|
|
failures: list[tuple[str, str]] = []
|
||
|
|
for raw in lines:
|
||
|
|
line = raw.strip()
|
||
|
|
if line.startswith("///"):
|
||
|
|
doc_block.append(line)
|
||
|
|
continue
|
||
|
|
field_m = re.match(r"pub (\w+):", line)
|
||
|
|
if field_m:
|
||
|
|
field = field_m.group(1)
|
||
|
|
doc_text = " ".join(doc_block)
|
||
|
|
if not re.search(r"@\d+", doc_text):
|
||
|
|
failures.append((field, doc_text or "<no doc>"))
|
||
|
|
doc_block = []
|
||
|
|
elif line and not line.startswith("//"):
|
||
|
|
# Non-doc, non-field line — reset the doc block.
|
||
|
|
doc_block = []
|
||
|
|
|
||
|
|
if failures:
|
||
|
|
print("FAIL: AbstractPlayerState fields missing `@<offset>` in docstring:")
|
||
|
|
for field, doc in failures:
|
||
|
|
print(f" - {field}: {doc[:80]}")
|
||
|
|
sys.exit(1)
|
||
|
|
|
||
|
|
print("OK: every AbstractPlayerState field docstring includes a byte offset.")
|
||
|
|
PY
|