#!/usr/bin/env bash # tooling/godot-parse-sweep — open every project .gd and fail on any that won't parse. # # Complements `godot-cold-parse`, which does NOT cover this. That script only # ever sees scripts on the STARTUP path (autoloads + the main scene chain) — # correct for the registration-ORDER bug it was built for, far narrower than # its name implies. Verified 2026-07-27 by breaking a non-startup UI script # and a test file in turn: cold-parse reported "clean", exit 0, for both. # # Division of labour, deliberate: # godot-cold-parse — cold-cache STARTUP ordering (class_name/autoload race) # godot-parse-sweep — does every file in the project parse at all # # The GDScript half (client/tools/parse_sweep.gd) only opens files; it makes no # verdict, because no Godot API reports GDScript parse failure reliably (see # that file's header — one segfaults, one false-positives 150/226, and plain # load() returns non-null for a broken script). The engine's own stderr is the # only honest signal, so the verdict is made here by scraping it. # # Exit 0 + "clean" if every script parsed. Exit 1 + the offending lines if not. set -euo pipefail REPO_ROOT="$(git rev-parse --show-toplevel)" set +e RAW=$(godot --headless --path "$REPO_ROOT/client" -s res://tools/parse_sweep.gd 2>&1) GODOT_EXIT=$? set -e if [ "$GODOT_EXIT" -ne 0 ]; then echo "godot-parse-sweep: godot itself exited $GODOT_EXIT — not a parse verdict" >&2 printf '%s\n' "$RAW" | tail -20 >&2 exit "$GODOT_EXIT" fi # The sweep must actually have run. Without this, a future change that breaks # the walk (or renames the script) would produce zero error lines and read as # a clean sweep — the same false-green shape this tool exists to close. if ! printf '%s\n' "$RAW" | grep -q '^parse-sweep: opened'; then echo "godot-parse-sweep: the sweep did not report completion — no verdict possible" >&2 printf '%s\n' "$RAW" | tail -20 >&2 exit 1 fi SUMMARY=$(printf '%s\n' "$RAW" | grep '^parse-sweep: opened') # NOTE: unlike godot-cold-parse, "Cannot infer the type" is NOT filtered here. # That filter is why cold-parse stayed silent about tests/util/scene_helper.gd, # which genuinely does not parse — the suppressed class was hiding a real # failure, not noise. MATCHES=$(printf '%s\n' "$RAW" \ | grep -E 'Parse Error|Failed to load script' \ | grep -v "Failed loading resource: res://assets" || true) if [ -n "$MATCHES" ]; then echo "PARSE SWEEP FAILED — at least one script does not parse." >&2 printf '%s\n' "$MATCHES" | head -40 >&2 echo "" >&2 echo " ${SUMMARY}" >&2 echo " An unparseable file cannot run. If it is a test suite, it did not" >&2 echo " execute, and any pass count reported elsewhere excludes it." >&2 exit 1 fi echo "godot-parse-sweep: clean — ${SUMMARY#parse-sweep: }"