Skip to content

feat(extraction): add GDScript language support - #1579

Open
Plumvery wants to merge 1 commit into
colbymchenry:mainfrom
Plumvery:feat/gdscript-language-support
Open

feat(extraction): add GDScript language support#1579
Plumvery wants to merge 1 commit into
colbymchenry:mainfrom
Plumvery:feat/gdscript-language-support

Conversation

@Plumvery

@Plumvery Plumvery commented Aug 21, 2026

Copy link
Copy Markdown

Summary

Adds GDScript (.gd, Godot Engine) as a supported language on the wasm extraction path, following the shape of previous language additions (Nix #1190, ArkTS #648).

  • Extractor (src/extraction/languages/gdscript.ts, ~100 lines): functions with typed signatures, _init constructors, inner classes with methods, the full var/const/@export/@onready variable family, signals, enums with members, static func detection, and call edges through call / attribute_call / base_call.
  • Grammar: PrestonKnopp/tree-sitter-gdscript v6.1.0 (MIT), vendored as an ABI-15 wasm rebuilt from upstream source (tree-sitter-cli 0.25.10 generate + build --wasm, emscripten/emsdk:4.0.4), external scanner (indentation) included — the same approach as the Nix wasm.

Grammar shapes that needed care

  • The var/const family names its target via a name-typed child, not identifier, so the core's generic variable fallback can't read them. A visitNode hook creates variable/constant nodes itself, then walks initializers so calls inside them (preload(...), Foo.new()) are captured.
  • func _init(...) (constructor_definition) has no name fieldresolveName supplies the conventional _init. enumerator names its identifier via left.
  • static on functions is a named static_keyword child with no field (the var statements carry it via a field) — a child scan covers both.
  • signal foo(a, b) extracts as a property carrying the parameter list, so connect()-heavy scripts expose their signal surface in the graph.
  • obj.method(args) parses as attribute(identifier, attribute_call(...))attribute_call joins callTypes and the core's namedChild(0) callee fallback yields the bare method name (name-match resolution, same as self/this receivers elsewhere).

Validation

scripts/add-lang/check-grammar.mjs: ABI 15, 20/20 clean parses, heap-safe under multi-grammar reuse (the Lua ABI-13 corruption scenario this script exists for does not occur).

scripts/add-lang/verify-extraction.mjs criteria on two corpora (all critical and soft checks pass):

Corpus .gd files nodes edges index time
godotengine/godot-demo-projects (public) 461 5,216 6,783 3.7s
Private Godot 4 game 138 2,996 11,484 1.8s

Kind spread on the public corpus: 2,027 functions / 184 methods / 45 classes / 141 signals→properties / 39 enums + 130 members / 1,570 variables / 422 constants — symbol density well above the 1-per-file soft bar.

Tests: 4 new extraction describe-blocks + detection/support assertions in __tests__/extraction.test.ts; full vitest suite passes locally (all pre-existing tests unaffected).

Not included (deliberately small)

  • No kernel routing (stays on the wasm path, like the other recent language additions).
  • No preload/load import-edge resolution to res:// paths (calls are captured; resolving them to file nodes would follow the Luau instance-path require pattern and can come as a follow-up).
  • No README language-grid SVG icon (asset pipeline is maintainer-side).

Rebased onto main (post-1.6.0). The only conflict was CHANGELOG.md: the
GDScript entry now sits under ## [Unreleased] rather than inside the released
1.6.0 section. src/ merged without conflicts — 1.6.0's grammars.ts changes
(the C++ base-clause detection) don't touch the language-registration sites.
tsc clean, __tests__/extraction.test.ts passes in full (627 tests) on the
rebased tree.

🤖 Generated with Claude Code

@tiantian520tt

Copy link
Copy Markdown

it's wonderful. we need it, pls. thanks a lot!

@wakaru44

Copy link
Copy Markdown

How does this relate to the already ongoing GDScript PR's? #1098

@Plumvery

Copy link
Copy Markdown
Author

Good question — short answer: #1098 is broader, this one is deliberately narrow.

So against #1098 it's overlap rather than duplication — scene-graph support is real
work this PR doesn't attempt, and it could sit on top of whichever extractor lands.

Two things here that are independent of which one wins: the wasm is rebuilt from
upstream grammar source as ABI 15 and clears scripts/add-lang/check-grammar.mjs
(20/20 parses, heap-safe under multi-grammar reuse), and the var/const/signal
family needs a visitNode hook because those name their target through a
name-typed child the generic variable fallback can't read.

No attachment to this one landing over the others — happy to close it if #1098 or
#1499 goes in first. @colbymchenry's call.

Adds GDScript (.gd) as a supported language on the wasm extraction path:

- Extractor (src/extraction/languages/gdscript.ts): functions and typed
  signatures, `_init` constructors (no name field in the grammar —
  resolveName supplies the conventional name), inner classes with methods,
  enums with members (`enumerator` names via `left`), `static func`
  detection (static_keyword child scan), and call edges through `call`,
  `attribute_call`, and `base_call`.
- The var/const family (`variable_statement`, `export_variable_statement`,
  `onready_variable_statement`, `const_statement`) names its target via a
  `name`-typed child, not `identifier`, so the core's generic variable
  fallback can't read them — a visitNode hook creates variable/constant
  nodes itself and walks initializers so calls inside them (`preload(...)`,
  `Foo.new()`) are captured.
- Signals extract as properties carrying their parameter list, so
  connect()-heavy scripts expose their signal surface in the graph.
- Grammar: PrestonKnopp/tree-sitter-gdscript v6.1.0 (MIT), vendored as an
  ABI-15 wasm rebuilt from upstream source (tree-sitter-cli 0.25.10
  generate + build --wasm, emscripten/emsdk:4.0.4); external scanner
  (indentation) included. check-grammar.mjs: ABI 15, 20/20 clean parses,
  heap-safe under multi-grammar reuse.

Validated with the add-lang verification gate on two corpora:
- godotengine/godot-demo-projects (public): 461 .gd files -> 5,216 nodes /
  6,783 edges in 3.7s (classes 45, methods 184, signals 141, enums 39+130).
- A private Godot 4 game (138 .gd files): 2,996 nodes / 11,484 edges;
  all critical and soft checks pass (2,858 structural symbols, density
  20.7/file).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Plumvery
Plumvery force-pushed the feat/gdscript-language-support branch from 8b2f95b to 7f75479 Compare August 27, 2026 06:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants