npm.io
0.3.0 • Published yesterday

@gdscript-analyzer/core

Licence
(MIT OR Apache-2.0)
Version
0.3.0
Deps
0
Size
36 kB
Vulns
0
Weekly
0

@gdscript-analyzer/core

Native Node.js bindings for gdscript-analyzer — a fast, embeddable GDScript (Godot 4.x) static-analysis library. "Roslyn for Godot."

npm License: MIT OR Apache-2.0

Analyze GDScript headlessly — no running Godot editor — from Node: diagnostics, type-aware hover, completion, go-to-definition, find-references, rename, document & workspace symbols, signature help, folding ranges, inlay hints, and code actions. This is the native binding (a napi-rs addon, no WASM overhead); for the browser use @gdscript-analyzer/wasm.

npm i @gdscript-analyzer/core

Prebuilt binaries ship for macOS (x64 / arm64), Windows (x64), and Linux (x64 / arm64-gnu); npm installs only the one for your platform.


Quick start

import { AnalysisHandle } from "@gdscript-analyzer/core";

const az = new AnalysisHandle();
const uri = "inmemory://player.gd";

az.openDocument(uri, `extends Node

func _ready() -> void:
	var half = 5 / 2   # integer division
	print(half)
`, null);

// Diagnostics come back as a JSON string.
console.log(JSON.parse(az.diagnostics(uri)));
// → [{ code: "INTEGER_DIVISION", severity: "warning", range: { start, end }, message: ... }]

Every query that returns data returns a JSON string (parse it). Positions are UTF-8 byte offsets (see Positions below).


The session model

AnalysisHandle is a live, URI-keyed session. Construct it once, push documents, then query — the underlying Rust AnalysisHost (and its incremental salsa cache) stays warm across edits, so re-queries after a small edit are cheap.

const az = new AnalysisHandle();

// Open / replace / close documents by URI.
az.openDocument(uri, text, resPath);   // resPath ("res://…") is optional — see below
az.changeDocument(uri, newText);       // replace text (unknown URI ⇒ upsert)
az.closeDocument(uri);
az.isOpen(uri);                        // → boolean

// Optional project context (enables [autoload] singleton resolution).
az.setProjectConfig(projectGodotText);
Cross-file resolution

Pass each document's res:// path on first open to enable cross-file preload(...), extends "res://…", and autoload resolution:

az.openDocument("inmemory://player.gd", playerSrc, "res://entities/player.gd");
az.openDocument("inmemory://enemy.gd",  enemySrc,  "res://entities/enemy.gd");
// Now hover/goto across the two files resolves correctly.

resPath is recorded once and ignored on later openDocument calls for the same URI (re-sending would needlessly invalidate the resource-path registry). Use changeDocument for edits.


API

All queries take a uri; offset-based queries take a UTF-8 byte offset. Array/object queries return a JSON string; the … | null ones return JS null when there's nothing at the offset.

Method Returns What
diagnostics(uri) JSON array parse + type diagnostics
documentSymbols(uri) JSON array the document outline
foldingRanges(uri) JSON array foldable ranges
inlayHints(uri) JSON array inferred-type / param inlay hints
completions(uri, offset) JSON array completions at offset
hover(uri, offset) JSON string | null type + docs at offset
signatureHelp(uri, offset) JSON string | null active call signature
codeActions(uri, offset) JSON array quick fixes at offset
gotoDefinition(uri, offset) JSON array definition target(s)
findReferences(uri, offset) JSON array all references
rename(uri, offset, newName) JSON string {"ok": SourceChange} or {"error": RenameError}
workspaceSymbols(query) JSON array project-wide symbol search
syntaxTree(uri) JSON string | null pretty-printed CST (debugging)
const offset = 38; // a UTF-8 byte offset into the document
JSON.parse(az.completions(uri, offset));
const hover = az.hover(uri, offset);          // string | null
if (hover) console.log(JSON.parse(hover));
JSON.parse(az.gotoDefinition(uri, offset));
Positions (byte offsets)

The analyzer speaks UTF-8 byte offsets, not line/column or UTF-16 code units. JavaScript strings are UTF-16, so convert at the boundary:

const enc = new TextEncoder();
// UTF-16 index (e.g. from an editor) → UTF-8 byte offset
const byteOffset = enc.encode(text.slice(0, utf16Index)).length;

If you're building an LSP/editor integration, the diagnostic/hover/definition ranges come back as byte offsets too — convert them back to your editor's position type on the way out.


Notes

  • No Godot required. Analysis is fully static; it never launches or talks to a Godot editor.
  • Single-threaded ownership. An AnalysisHandle is owned by the JS thread that created it; don't share one instance across worker threads (create one per thread instead).
  • Engine model. The Godot 4.x class/method/signal model is embedded in the native binary — no extra asset to load (unlike the wasm package).

Keywords