extends SceneTree ## Parse-sweep: open EVERY project .gd so the engine reports the broken ones. ## ## Why this exists. `tooling/godot-cold-parse` only ever sees scripts on the ## STARTUP path — autoloads and the main scene chain. That is the correct ## scope for the job it was built for (Sprint 36's `Could not find base class ## "MetaScreen"`, an autoload-vs-class_name registration ORDER bug), but it is ## far narrower than its name suggests, and most of the codebase is invisible ## to it. Verified 2026-07-27 by deliberately breaking a non-startup UI script ## and a test file in turn: cold-parse reported `clean`, exit 0, for both. ## ## That gap cost real time. A parse error in `test_step_canvas_annotation_layer.gd` ## went unnoticed for hours because it passed cold-parse AND because gdUnit4 ## reports the suites that DID load as a clean pass (fixed separately by ## `tests/run-godot`'s harness guards). Two gates, one blind spot: neither ## verified that a file it never opened was openable. ## ## **This script deliberately makes no verdict of its own.** It opens every ## script and lets Godot's own front-end print the diagnosis; the wrapper ## (`tooling/godot-parse-sweep`) greps for that. Two rejected alternatives, ## both found the hard way while building this: ## ## - `ResourceLoader.load(path, "GDScript", CACHE_MODE_IGNORE)` **segfaults ## the engine** (signal 11, Godot 4.6) on a script that fails to parse — ## i.e. it dies on exactly the input this tool exists to find. ## - `GDScript.new()` + `source_code` + `reload()` returns a clean error code, ## but detaches the script from its `resource_path`, so `class_name`, ## `preload()` and relative `extends` stop resolving: it reported 150 of ## 226 healthy scripts as broken. ## ## Plain `ResourceLoader.load()` neither crashes nor lies — but it returns a ## NON-null object for a broken script, so the return value cannot be trusted ## either. The engine's stderr is the only honest signal, which is why the ## verdict lives in the wrapper. ## ## Run via `tooling/godot-parse-sweep` (never directly — this half cannot fail). ## Vendor and generated trees are not ours to verify. `addons/` is gdUnit4, ## whose own test corpus deliberately contains broken fixtures — sweeping it ## would report failures that are the vendor's intended state. const SKIP_PREFIXES: PackedStringArray = ["res://addons/", "res://.godot/"] var _checked: int = 0 var _unreadable_dirs: int = 0 func _initialize() -> void: var scripts: PackedStringArray = [] _collect("res://", scripts) scripts.sort() # deterministic ordering, so output diffs cleanly run to run for path in scripts: _checked += 1 # Return value deliberately discarded — see the header. Touching the # file is the whole job; the engine prints the diagnosis. ResourceLoader.load(path) # stdout only. A non-zero verdict is the wrapper's to make, from stderr. print("parse-sweep: opened %d scripts (%d unreadable dirs)" % [_checked, _unreadable_dirs]) quit(0) ## Recursive walk of the VIRTUAL filesystem. DirAccess rather than a shell ## `find`, because `res://` is what the engine can actually open — a source-tree ## walk would include files the engine ignores and miss the distinction. func _collect(dir_path: String, out: PackedStringArray) -> void: for prefix in SKIP_PREFIXES: if dir_path.begins_with(prefix): return var dir := DirAccess.open(dir_path) if dir == null: _unreadable_dirs += 1 return dir.list_dir_begin() var name := dir.get_next() while name != "": if name.begins_with("."): name = dir.get_next() continue var full := dir_path.path_join(name) if dir_path != "res://" else "res://" + name if dir.current_is_dir(): _collect(full, out) elif name.ends_with(".gd"): out.append(full) name = dir.get_next() dir.list_dir_end()