Adopt the validation-rules discipline that every public type is either validated or excluded, enforced mechanically rather than by review. A new gate enumerates the public surface from the compiler symbol graph and fails the build, naming the type, if any public type is missing from the coverage manifest or any manifest entry is no longer public. scripts/validation-coverage.tsv classifies all 58 public types: the value models with the mapping-key-uniqueness runtime invariant are validated, and the namespaces, closed sum types, data-transfer values, error types, and engine machinery are excluded with a one-line reason each. The gate runs in check-all.sh and CI; a planted unclassified type was confirmed to fail it. This is the coverage-first phase of moving the validation layer to the type-directed Validation idiom; the framework backing the validated entries, the de-duplication of the Tagged validator machinery, and the exhaustive tests follow.
77 lines
2.8 KiB
Bash
Executable File
77 lines
2.8 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/symbolgraph"
|
|
|
|
if [[ ! -f "$MANIFEST" ]]; then
|
|
echo "check-validation-coverage: missing manifest $MANIFEST" >&2
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$GRAPH_DIR"
|
|
swift build --target PureYAML \
|
|
-Xswiftc -emit-symbol-graph \
|
|
-Xswiftc -emit-symbol-graph-dir -Xswiftc "$GRAPH_DIR" >/dev/null
|
|
|
|
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
|