88 lines
3.3 KiB
Bash
Executable File
88 lines
3.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Validation coverage gate (validation-rules.md, "Every public type is validated or excluded").
|
|
#
|
|
# Enumerates every public type in the PureYAML target from the compiler's symbol graph (the
|
|
# authoritative public surface, not a grep heuristic), then checks each against the coverage
|
|
# manifest. Every public type MUST be classified exactly once as `validated` or `excluded`; an
|
|
# unclassified type, or a manifest entry that is no longer public, fails the build by name.
|
|
#
|
|
# Phase 1 enforces classification completeness. The framework phase additionally asserts that each
|
|
# `validated` entry is backed by a `Validation<Subject>` whose Subject is that type.
|
|
set -euo pipefail
|
|
|
|
cd "$(dirname "$0")/.."
|
|
|
|
MANIFEST="scripts/validation-coverage.tsv"
|
|
GRAPH_DIR=".build/out/symbolgraph"
|
|
|
|
if [[ ! -f "$MANIFEST" ]]; then
|
|
echo "check-validation-coverage: missing manifest $MANIFEST" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# SwiftPM's own command writes the graph for every toolchain. Passing the compiler flags through
|
|
# -Xswiftc wrote nothing on Swift 6.4 and the gate then failed with no message. Its output is kept
|
|
# so a failed build says why, and a missing graph is named.
|
|
log=$(mktemp)
|
|
trap 'rm -f "$log"' EXIT
|
|
if ! swift package dump-symbol-graph >"$log" 2>&1; then
|
|
cat "$log" >&2
|
|
echo "check-validation-coverage: swift package dump-symbol-graph failed" >&2
|
|
exit 1
|
|
fi
|
|
if [[ ! -f "$GRAPH_DIR/PureYAML.symbols.json" ]]; then
|
|
cat "$log" >&2
|
|
echo "check-validation-coverage: no symbol graph at $GRAPH_DIR/PureYAML.symbols.json" >&2
|
|
exit 1
|
|
fi
|
|
|
|
python3 - "$GRAPH_DIR/PureYAML.symbols.json" "$MANIFEST" <<'PY'
|
|
import json
|
|
import sys
|
|
|
|
graph_path, manifest_path = sys.argv[1], sys.argv[2]
|
|
|
|
type_kinds = {"swift.struct", "swift.enum", "swift.class", "swift.actor", "swift.protocol"}
|
|
with open(graph_path) as handle:
|
|
graph = json.load(handle)
|
|
|
|
public_types = {
|
|
".".join(symbol["pathComponents"])
|
|
for symbol in graph["symbols"]
|
|
if symbol["kind"]["identifier"] in type_kinds and symbol.get("accessLevel") == "public"
|
|
}
|
|
|
|
classified = {}
|
|
duplicates = []
|
|
with open(manifest_path) as handle:
|
|
for raw in handle:
|
|
line = raw.split("#", 1)[0].strip()
|
|
if not line:
|
|
continue
|
|
parts = line.split("\t")
|
|
if len(parts) < 3 or parts[1] not in ("validated", "excluded"):
|
|
print(f"check-validation-coverage: malformed manifest line: {raw.rstrip()}", file=sys.stderr)
|
|
sys.exit(1)
|
|
name, kind = parts[0], parts[1]
|
|
if name in classified:
|
|
duplicates.append(name)
|
|
classified[name] = kind
|
|
|
|
errors = []
|
|
for name in sorted(public_types - set(classified)):
|
|
errors.append(f" unclassified public type (add to {manifest_path} as validated or excluded): {name}")
|
|
for name in sorted(set(classified) - public_types):
|
|
errors.append(f" stale manifest entry (no longer a public type): {name}")
|
|
for name in sorted(set(duplicates)):
|
|
errors.append(f" duplicate manifest entry: {name}")
|
|
|
|
if errors:
|
|
print("check-validation-coverage: FAILED", file=sys.stderr)
|
|
print("\n".join(errors), file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
validated = sum(1 for k in classified.values() if k == "validated")
|
|
excluded = sum(1 for k in classified.values() if k == "excluded")
|
|
print(f"check-validation-coverage: OK, {len(public_types)} public types ({validated} validated, {excluded} excluded)")
|
|
PY
|