@tmhs/godot-correctness-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/LICENSE +34 -0
  2. package/README.md +118 -0
  3. package/data/godot-4.4.symbols.json.gz +0 -0
  4. package/data/godot-4.5.symbols.json.gz +0 -0
  5. package/data/tree-sitter-gdscript.wasm +0 -0
  6. package/dist/capabilities.d.ts +21 -0
  7. package/dist/capabilities.js +24 -0
  8. package/dist/capabilities.js.map +1 -0
  9. package/dist/cli.d.ts +12 -0
  10. package/dist/cli.js +105 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/gdscript/ast.d.ts +25 -0
  13. package/dist/gdscript/ast.js +82 -0
  14. package/dist/gdscript/ast.js.map +1 -0
  15. package/dist/gdscript/parser.d.ts +7 -0
  16. package/dist/gdscript/parser.js +44 -0
  17. package/dist/gdscript/parser.js.map +1 -0
  18. package/dist/godot/text-format.d.ts +35 -0
  19. package/dist/godot/text-format.js +105 -0
  20. package/dist/godot/text-format.js.map +1 -0
  21. package/dist/index.d.ts +4 -0
  22. package/dist/index.js +38 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/lint/config.d.ts +83 -0
  25. package/dist/lint/config.js +55 -0
  26. package/dist/lint/config.js.map +1 -0
  27. package/dist/lint/engine.d.ts +7 -0
  28. package/dist/lint/engine.js +33 -0
  29. package/dist/lint/engine.js.map +1 -0
  30. package/dist/lint/project.d.ts +12 -0
  31. package/dist/lint/project.js +49 -0
  32. package/dist/lint/project.js.map +1 -0
  33. package/dist/lint/rules/await-misuse.d.ts +3 -0
  34. package/dist/lint/rules/await-misuse.js +34 -0
  35. package/dist/lint/rules/await-misuse.js.map +1 -0
  36. package/dist/lint/rules/connect-without-disconnect.d.ts +7 -0
  37. package/dist/lint/rules/connect-without-disconnect.js +38 -0
  38. package/dist/lint/rules/connect-without-disconnect.js.map +1 -0
  39. package/dist/lint/rules/delta-misuse.d.ts +7 -0
  40. package/dist/lint/rules/delta-misuse.js +78 -0
  41. package/dist/lint/rules/delta-misuse.js.map +1 -0
  42. package/dist/lint/rules/float-grid-equality.d.ts +6 -0
  43. package/dist/lint/rules/float-grid-equality.js +39 -0
  44. package/dist/lint/rules/float-grid-equality.js.map +1 -0
  45. package/dist/lint/rules/get-node-in-process.d.ts +3 -0
  46. package/dist/lint/rules/get-node-in-process.js +31 -0
  47. package/dist/lint/rules/get-node-in-process.js.map +1 -0
  48. package/dist/lint/rules/index.d.ts +4 -0
  49. package/dist/lint/rules/index.js +21 -0
  50. package/dist/lint/rules/index.js.map +1 -0
  51. package/dist/lint/rules/missing-onready.d.ts +3 -0
  52. package/dist/lint/rules/missing-onready.js +52 -0
  53. package/dist/lint/rules/missing-onready.js.map +1 -0
  54. package/dist/lint/rules/stringly-nodepath.d.ts +3 -0
  55. package/dist/lint/rules/stringly-nodepath.js +35 -0
  56. package/dist/lint/rules/stringly-nodepath.js.map +1 -0
  57. package/dist/lint/rules/untyped-declaration.d.ts +7 -0
  58. package/dist/lint/rules/untyped-declaration.js +25 -0
  59. package/dist/lint/rules/untyped-declaration.js.map +1 -0
  60. package/dist/lint/types.d.ts +36 -0
  61. package/dist/lint/types.js +2 -0
  62. package/dist/lint/types.js.map +1 -0
  63. package/dist/resources/skills.d.ts +12 -0
  64. package/dist/resources/skills.js +45 -0
  65. package/dist/resources/skills.js.map +1 -0
  66. package/dist/scenes/paths.d.ts +14 -0
  67. package/dist/scenes/paths.js +38 -0
  68. package/dist/scenes/paths.js.map +1 -0
  69. package/dist/scenes/report.d.ts +17 -0
  70. package/dist/scenes/report.js +25 -0
  71. package/dist/scenes/report.js.map +1 -0
  72. package/dist/scenes/validate.d.ts +16 -0
  73. package/dist/scenes/validate.js +185 -0
  74. package/dist/scenes/validate.js.map +1 -0
  75. package/dist/server-info.d.ts +13 -0
  76. package/dist/server-info.js +21 -0
  77. package/dist/server-info.js.map +1 -0
  78. package/dist/symbols/db.d.ts +11 -0
  79. package/dist/symbols/db.js +42 -0
  80. package/dist/symbols/db.js.map +1 -0
  81. package/dist/symbols/lookup.d.ts +89 -0
  82. package/dist/symbols/lookup.js +271 -0
  83. package/dist/symbols/lookup.js.map +1 -0
  84. package/dist/symbols/schema.d.ts +1419 -0
  85. package/dist/symbols/schema.js +109 -0
  86. package/dist/symbols/schema.js.map +1 -0
  87. package/dist/tools/api.d.ts +3 -0
  88. package/dist/tools/api.js +91 -0
  89. package/dist/tools/api.js.map +1 -0
  90. package/dist/tools/lint.d.ts +3 -0
  91. package/dist/tools/lint.js +45 -0
  92. package/dist/tools/lint.js.map +1 -0
  93. package/dist/tools/scenes.d.ts +3 -0
  94. package/dist/tools/scenes.js +43 -0
  95. package/dist/tools/scenes.js.map +1 -0
  96. package/package.json +57 -0
  97. package/skills/data-driven-resources.md +78 -0
  98. package/skills/resource-preload.md +43 -0
  99. package/skills/signal-hygiene.md +46 -0
  100. package/skills/typed-gdscript.md +47 -0
package/LICENSE ADDED
@@ -0,0 +1,34 @@
1
+ Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International
2
+
3
+ Copyright (c) 2026 TM Hospitality Strategies
4
+
5
+ This work is licensed under the Creative Commons
6
+ Attribution-NonCommercial-NoDerivatives 4.0 International License.
7
+
8
+ You are free to:
9
+
10
+ Share - copy and redistribute the material in any medium or format.
11
+
12
+ The licensor cannot revoke these freedoms as long as you follow the
13
+ license terms.
14
+
15
+ Under the following terms:
16
+
17
+ Attribution - You must give appropriate credit, provide a link to the
18
+ license, and indicate if changes were made. You may do so in any
19
+ reasonable manner, but not in any way that suggests the licensor
20
+ endorses you or your use.
21
+
22
+ NonCommercial - You may not use the material for commercial purposes.
23
+
24
+ NoDerivatives - If you remix, transform, or build upon the material,
25
+ you may not distribute the modified material.
26
+
27
+ No additional restrictions - You may not apply legal terms or
28
+ technological measures that legally restrict others from doing
29
+ anything the license permits.
30
+
31
+ Full license text:
32
+ https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode
33
+
34
+ SPDX-License-Identifier: CC-BY-NC-ND-4.0
package/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # godot-correctness-mcp
2
+
3
+ **Offline static correctness MCP server for Godot 4.x GDScript projects**
4
+
5
+ ![License: CC-BY-NC-ND-4.0](https://img.shields.io/badge/license-CC--BY--NC--ND--4.0-green)
6
+ ![Version](https://img.shields.io/badge/version-0.1.0-blue)
7
+
8
+ ---
9
+
10
+ A Model Context Protocol server (and `gdcorrect` CLI) that statically checks the
11
+ correctness of Godot 4.x GDScript projects. It runs **headless, offline, and in
12
+ CI** — it never launches the editor, never needs a running Godot instance, and
13
+ never touches the network at runtime.
14
+
15
+ ## Three pillars
16
+
17
+ 1. **GDScript linting** — anti-pattern detection via a committed tree-sitter
18
+ grammar (WASM). Rules like `delta-misuse`, `get-node-in-process`,
19
+ `missing-onready`, `untyped-declaration`, `await-misuse`, `stringly-nodepath`,
20
+ `connect-without-disconnect`, `float-grid-equality`. See [docs/lint.md](docs/lint.md).
21
+ 2. **Cross-version API lookup & diff** — query classes/methods/properties/signals
22
+ and diff the API between Godot **4.4** and **4.5**, from committed offline
23
+ symbol databases. See [docs/symbol-db.md](docs/symbol-db.md).
24
+ 3. **Scene / resource / registry analysis** — validate `.tscn`, `.tres`, and
25
+ `project.godot`, including deep validation of data-driven `.tres` registries
26
+ (the dangling-path check). See [docs/scenes.md](docs/scenes.md).
27
+
28
+ ## Non-goals
29
+
30
+ - **No live bridge.** It does not connect to a running editor or game.
31
+ - **No editor plugin.** Nothing to install inside Godot.
32
+ - **No C#.** GDScript only.
33
+ - **Nothing older than Godot 4.4.** Symbol data is pinned to 4.4 and 4.5.
34
+
35
+ This is deliberately the complement to the live-session Godot MCP tools: it is the
36
+ one you run in CI and headless pipelines.
37
+
38
+ ## Installation
39
+
40
+ As an MCP server (Claude Desktop / Cursor `mcpServers` config):
41
+
42
+ ```json
43
+ {
44
+ "mcpServers": {
45
+ "godot-correctness": {
46
+ "command": "npx",
47
+ "args": ["-y", "@tmhs/godot-correctness-mcp"]
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ As a CLI:
54
+
55
+ ```bash
56
+ npx -y @tmhs/godot-correctness-mcp gdcorrect lint . --format json
57
+ ```
58
+
59
+ ## MCP surface
60
+
61
+ **Tools:** `server_info`; `api_symbol_lookup`, `api_class_summary`, `api_diff`;
62
+ `lint_file`, `lint_project`; `validate_scenes`, `validate_project_config`,
63
+ `validate_registries`, `project_report`.
64
+
65
+ **Resources:** curated GDScript skill snippets under `skill://` (data-driven
66
+ resources, signal hygiene, typed GDScript, preload vs load).
67
+
68
+ ## CLI and CI
69
+
70
+ `gdcorrect <command> <projectPath> [--format json|pretty]`, where `<command>` is
71
+ `lint`, `validate-scenes`, `validate-project`, `validate-registries`, or `report`.
72
+ JSON is the primary, machine-readable format. Exit codes: **0** clean, **1** on
73
+ any error-severity finding, **2** on tool failure.
74
+
75
+ ```yaml
76
+ # .github/workflows/godot-correctness.yml
77
+ jobs:
78
+ correctness:
79
+ runs-on: ubuntu-latest
80
+ steps:
81
+ - uses: actions/checkout@v4
82
+ - uses: actions/setup-node@v4
83
+ with: { node-version: 22 }
84
+ - name: Lint GDScript (fails on error-severity findings)
85
+ run: npx -y @tmhs/godot-correctness-mcp gdcorrect lint . --format json
86
+ - name: Validate registries
87
+ run: npx -y @tmhs/godot-correctness-mcp gdcorrect validate-registries . --format json
88
+ ```
89
+
90
+ ## Configuration
91
+
92
+ Place `godot-correctness.config.json` at your project root (see
93
+ [`godot-correctness.config.example.json`](godot-correctness.config.example.json)):
94
+ enable/disable rules, override severities, set include/exclude globs, and declare
95
+ `registryDirs` / `pathPropertyPatterns` for registry validation. Full reference in
96
+ [docs/lint.md](docs/lint.md#configuration) and [docs/scenes.md](docs/scenes.md#configuration).
97
+
98
+ ## Symbol database regeneration
99
+
100
+ The API pillar reads committed gzipped symbol DBs generated locally from pinned
101
+ Godot binaries (CI validates them but never runs Godot). Regeneration procedure:
102
+ [docs/symbol-db.md](docs/symbol-db.md).
103
+
104
+ ## Roadmap
105
+
106
+ See [ROADMAP.md](ROADMAP.md) and [BACKLOG.md](BACKLOG.md).
107
+
108
+ ## Contributing
109
+
110
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
111
+
112
+ ## License
113
+
114
+ CC-BY-NC-ND-4.0 -- see [LICENSE](LICENSE) for details.
115
+
116
+ ---
117
+
118
+ **Built by TMHSDigital**
Binary file
Binary file
Binary file
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Godot minor versions this server carries symbol data for. Deliberately
3
+ * narrow: the server targets 4.4 and 4.5 and nothing older. See README non-goals.
4
+ */
5
+ export declare const PINNED_GODOT_VERSIONS: readonly ["4.4", "4.5"];
6
+ export type GodotVersion = (typeof PINNED_GODOT_VERSIONS)[number];
7
+ /**
8
+ * The three correctness pillars. A pillar is "available" only when the offline
9
+ * artifacts it depends on are actually present in the package. Later phases add
10
+ * those artifacts, which flips the flags without any code change here.
11
+ */
12
+ export interface PillarAvailability {
13
+ /** GDScript anti-pattern linting (tree-sitter grammar WASM). Phase 3. */
14
+ gdscriptLint: boolean;
15
+ /** Cross-version API symbol lookup/diff (committed symbol databases). Phase 2. */
16
+ apiSymbols: boolean;
17
+ /** Static .tscn/.tres/project.godot analysis incl. registry validation. Phase 4. */
18
+ sceneResourceAnalysis: boolean;
19
+ }
20
+ /** Report which pillars are currently usable based on committed artifacts. */
21
+ export declare function pillarAvailability(): PillarAvailability;
@@ -0,0 +1,24 @@
1
+ import { existsSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ /**
5
+ * Godot minor versions this server carries symbol data for. Deliberately
6
+ * narrow: the server targets 4.4 and 4.5 and nothing older. See README non-goals.
7
+ */
8
+ export const PINNED_GODOT_VERSIONS = ["4.4", "4.5"];
9
+ // dist/capabilities.js and src/capabilities.ts both sit one level below the
10
+ // package root, so data/ resolves the same whether running compiled or via tsx.
11
+ const dataDir = join(dirname(fileURLToPath(import.meta.url)), "..", "data");
12
+ /** Report which pillars are currently usable based on committed artifacts. */
13
+ export function pillarAvailability() {
14
+ const apiSymbols = PINNED_GODOT_VERSIONS.every((v) => existsSync(join(dataDir, `godot-${v}.symbols.json.gz`)));
15
+ const gdscriptLint = existsSync(join(dataDir, "tree-sitter-gdscript.wasm"));
16
+ return {
17
+ gdscriptLint,
18
+ apiSymbols,
19
+ // Phase 4 static scene/resource/registry analysis is pure TypeScript with
20
+ // no external artifact; it is always available once shipped.
21
+ sceneResourceAnalysis: true,
22
+ };
23
+ }
24
+ //# sourceMappingURL=capabilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,KAAK,EAAE,KAAK,CAAU,CAAC;AAkB7D,4EAA4E;AAC5E,gFAAgF;AAChF,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;AAE5E,8EAA8E;AAC9E,MAAM,UAAU,kBAAkB;IAChC,MAAM,UAAU,GAAG,qBAAqB,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CACnD,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,kBAAkB,CAAC,CAAC,CACxD,CAAC;IACF,MAAM,YAAY,GAAG,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,2BAA2B,CAAC,CAAC,CAAC;IAC5E,OAAO;QACL,YAAY;QACZ,UAAU;QACV,0EAA0E;QAC1E,6DAA6D;QAC7D,qBAAqB,EAAE,IAAI;KAC5B,CAAC;AACJ,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import type { Finding } from "./lint/types.js";
3
+ interface CommandResult {
4
+ root: string;
5
+ findings: Finding[];
6
+ /** Full JSON payload for --format json (already includes findings). */
7
+ payload: unknown;
8
+ }
9
+ export declare function renderPretty(res: CommandResult): string;
10
+ /** Exit code: 0 clean, 1 on any error-severity finding, 2 on tool failure. */
11
+ export declare function runCli(argv: string[], log?: (s: string) => void, errOut?: (s: string) => void): Promise<number>;
12
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env node
2
+ import { pathToFileURL } from "node:url";
3
+ import { lintProject } from "./lint/project.js";
4
+ import { projectReport } from "./scenes/report.js";
5
+ import { validateProjectConfig, validateRegistries, validateScenes } from "./scenes/validate.js";
6
+ const COMMANDS = new Set([
7
+ "lint",
8
+ "validate-scenes",
9
+ "validate-project",
10
+ "validate-registries",
11
+ "report",
12
+ ]);
13
+ async function runCommand(command, root) {
14
+ switch (command) {
15
+ case "lint": {
16
+ const r = await lintProject(root);
17
+ return { root, findings: r.findings, payload: r };
18
+ }
19
+ case "validate-scenes": {
20
+ const findings = validateScenes(root);
21
+ return { root, findings, payload: { root, findings, count: findings.length } };
22
+ }
23
+ case "validate-project": {
24
+ const findings = validateProjectConfig(root);
25
+ return { root, findings, payload: { root, findings, count: findings.length } };
26
+ }
27
+ case "validate-registries": {
28
+ const r = validateRegistries(root);
29
+ return { root, findings: r.findings, payload: { root, ...r } };
30
+ }
31
+ case "report": {
32
+ const r = await projectReport(root);
33
+ return {
34
+ root,
35
+ findings: [...r.scenes, ...r.project, ...r.registries, ...r.lint],
36
+ payload: r,
37
+ };
38
+ }
39
+ default:
40
+ throw new Error(`Unknown command: ${command}`);
41
+ }
42
+ }
43
+ function countErrors(findings) {
44
+ return findings.filter((f) => f.severity === "error").length;
45
+ }
46
+ export function renderPretty(res) {
47
+ const lines = [];
48
+ let lastFile = "";
49
+ const sorted = [...res.findings].sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line || a.col - b.col);
50
+ for (const f of sorted) {
51
+ if (f.file !== lastFile) {
52
+ lines.push(`\n${f.file}`);
53
+ lastFile = f.file;
54
+ }
55
+ lines.push(` ${f.line}:${f.col} ${f.severity.toUpperCase().padEnd(7)} ${f.ruleId}`);
56
+ lines.push(` ${f.message}`);
57
+ }
58
+ const e = countErrors(res.findings);
59
+ const w = res.findings.filter((f) => f.severity === "warning").length;
60
+ const i = res.findings.filter((f) => f.severity === "info").length;
61
+ lines.push(`\n${res.findings.length} finding(s): ${e} error(s), ${w} warning(s), ${i} info.`);
62
+ return lines.join("\n").trimStart();
63
+ }
64
+ function parseArgs(argv) {
65
+ let format = "json";
66
+ const positional = [];
67
+ for (let i = 0; i < argv.length; i++) {
68
+ const a = argv[i];
69
+ if (a === "--format" || a === "-f")
70
+ format = argv[++i] ?? "";
71
+ else if (a.startsWith("--format="))
72
+ format = a.slice("--format=".length);
73
+ else
74
+ positional.push(a);
75
+ }
76
+ return { command: positional[0], root: positional[1], format };
77
+ }
78
+ /** Exit code: 0 clean, 1 on any error-severity finding, 2 on tool failure. */
79
+ export async function runCli(argv, log = console.log, errOut = console.error) {
80
+ const { command, root, format } = parseArgs(argv);
81
+ if (!command || !COMMANDS.has(command) || !root) {
82
+ errOut(`Usage: gdcorrect <${[...COMMANDS].join("|")}> <projectPath> [--format json|pretty]`);
83
+ return 2;
84
+ }
85
+ if (format !== "json" && format !== "pretty") {
86
+ errOut(`Unknown format: ${format}. Use 'json' or 'pretty'.`);
87
+ return 2;
88
+ }
89
+ try {
90
+ const res = await runCommand(command, root);
91
+ log(format === "pretty" ? renderPretty(res) : JSON.stringify(res.payload, null, 2));
92
+ return countErrors(res.findings) > 0 ? 1 : 0;
93
+ }
94
+ catch (err) {
95
+ errOut(`gdcorrect: ${err instanceof Error ? err.message : String(err)}`);
96
+ return 2;
97
+ }
98
+ }
99
+ const invokedDirectly = process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
100
+ if (invokedDirectly) {
101
+ runCli(process.argv.slice(2))
102
+ .then((code) => process.exit(code))
103
+ .catch(() => process.exit(2));
104
+ }
105
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import type { Node } from "web-tree-sitter";
2
+ /** Depth-first walk over all named nodes (inclusive of the start node). */
3
+ export declare function walk(node: Node): Generator<Node>;
4
+ /** Source text covered by a node. */
5
+ export declare function text(node: Node, source: string): string;
6
+ /** All named descendants of a given grammar type. */
7
+ export declare function findAll(root: Node, type: string): Node[];
8
+ /** Text of a named field child, or null. */
9
+ export declare function fieldText(node: Node, field: string, source: string): string | null;
10
+ /** The operator token of a binary/assignment node (read from source between operands). */
11
+ export declare function operatorOf(node: Node, source: string): string | null;
12
+ /** Walk up to the nearest enclosing function_definition, or null. */
13
+ export declare function enclosingFunction(node: Node): Node | null;
14
+ /** Name of the enclosing function_definition, or null if not inside one. */
15
+ export declare function enclosingFunctionName(node: Node, source: string): string | null;
16
+ /**
17
+ * The identifier being invoked by a call. Handles both free calls
18
+ * `foo(...)` (a `call` node) and method calls `a.b.foo(...)` (an
19
+ * `attribute_call` node). Returns the method/function name or null.
20
+ */
21
+ export declare function calleeName(node: Node, source: string): string | null;
22
+ /** True if the subtree contains an identifier with the given name. */
23
+ export declare function containsIdentifier(node: Node, name: string, source: string): boolean;
24
+ /** True if the function body calls a function/method with the given name. */
25
+ export declare function functionCalls(fn: Node, name: string, source: string): boolean;
@@ -0,0 +1,82 @@
1
+ /** Depth-first walk over all named nodes (inclusive of the start node). */
2
+ export function* walk(node) {
3
+ yield node;
4
+ for (const child of node.namedChildren) {
5
+ if (child)
6
+ yield* walk(child);
7
+ }
8
+ }
9
+ /** Source text covered by a node. */
10
+ export function text(node, source) {
11
+ return source.slice(node.startIndex, node.endIndex);
12
+ }
13
+ /** All named descendants of a given grammar type. */
14
+ export function findAll(root, type) {
15
+ const out = [];
16
+ for (const n of walk(root))
17
+ if (n.type === type)
18
+ out.push(n);
19
+ return out;
20
+ }
21
+ /** Text of a named field child, or null. */
22
+ export function fieldText(node, field, source) {
23
+ const child = node.childForFieldName(field);
24
+ return child ? text(child, source) : null;
25
+ }
26
+ /** The operator token of a binary/assignment node (read from source between operands). */
27
+ export function operatorOf(node, source) {
28
+ const left = node.childForFieldName("left");
29
+ const right = node.childForFieldName("right");
30
+ if (!left || !right)
31
+ return null;
32
+ return source.slice(left.endIndex, right.startIndex).trim();
33
+ }
34
+ /** Walk up to the nearest enclosing function_definition, or null. */
35
+ export function enclosingFunction(node) {
36
+ let cur = node.parent;
37
+ while (cur) {
38
+ if (cur.type === "function_definition")
39
+ return cur;
40
+ cur = cur.parent;
41
+ }
42
+ return null;
43
+ }
44
+ /** Name of the enclosing function_definition, or null if not inside one. */
45
+ export function enclosingFunctionName(node, source) {
46
+ const fn = enclosingFunction(node);
47
+ return fn ? fieldText(fn, "name", source) : null;
48
+ }
49
+ /**
50
+ * The identifier being invoked by a call. Handles both free calls
51
+ * `foo(...)` (a `call` node) and method calls `a.b.foo(...)` (an
52
+ * `attribute_call` node). Returns the method/function name or null.
53
+ */
54
+ export function calleeName(node, source) {
55
+ if (node.type === "call") {
56
+ const fn = node.namedChild(0);
57
+ return fn && fn.type === "identifier" ? text(fn, source) : null;
58
+ }
59
+ if (node.type === "attribute_call") {
60
+ const id = node.namedChild(0);
61
+ return id && id.type === "identifier" ? text(id, source) : null;
62
+ }
63
+ return null;
64
+ }
65
+ /** True if the subtree contains an identifier with the given name. */
66
+ export function containsIdentifier(node, name, source) {
67
+ for (const n of walk(node)) {
68
+ if (n.type === "identifier" && text(n, source) === name)
69
+ return true;
70
+ }
71
+ return false;
72
+ }
73
+ /** True if the function body calls a function/method with the given name. */
74
+ export function functionCalls(fn, name, source) {
75
+ for (const n of walk(fn)) {
76
+ if ((n.type === "call" || n.type === "attribute_call") && calleeName(n, source) === name) {
77
+ return true;
78
+ }
79
+ }
80
+ return false;
81
+ }
82
+ //# sourceMappingURL=ast.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ast.js","sourceRoot":"","sources":["../../src/gdscript/ast.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { Parser, type Tree } from "web-tree-sitter";
2
+ /** Whether the committed GDScript grammar WASM is present. */
3
+ export declare function isGrammarAvailable(): boolean;
4
+ /** Get a shared, initialized GDScript parser (reused across files, sequential). */
5
+ export declare function getParser(): Promise<Parser>;
6
+ /** Parse GDScript source into a concrete syntax tree. */
7
+ export declare function parseGDScript(source: string): Promise<Tree>;
@@ -0,0 +1,44 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { Language, Parser } from "web-tree-sitter";
5
+ // dist/gdscript/parser.js and src/gdscript/parser.ts both sit two levels below
6
+ // the package root, so the committed grammar resolves the same either way.
7
+ const wasmPath = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "data", "tree-sitter-gdscript.wasm");
8
+ /** Whether the committed GDScript grammar WASM is present. */
9
+ export function isGrammarAvailable() {
10
+ return existsSync(wasmPath);
11
+ }
12
+ let initPromise = null;
13
+ let langPromise = null;
14
+ let parserPromise = null;
15
+ async function getLanguage() {
16
+ // Parser.init() loads web-tree-sitter's own runtime wasm (auto-located from
17
+ // node_modules on Node); the grammar is loaded from the committed bytes.
18
+ if (!initPromise)
19
+ initPromise = Parser.init();
20
+ await initPromise;
21
+ if (!langPromise)
22
+ langPromise = Language.load(new Uint8Array(readFileSync(wasmPath)));
23
+ return langPromise;
24
+ }
25
+ /** Get a shared, initialized GDScript parser (reused across files, sequential). */
26
+ export async function getParser() {
27
+ if (!parserPromise) {
28
+ parserPromise = getLanguage().then((lang) => {
29
+ const parser = new Parser();
30
+ parser.setLanguage(lang);
31
+ return parser;
32
+ });
33
+ }
34
+ return parserPromise;
35
+ }
36
+ /** Parse GDScript source into a concrete syntax tree. */
37
+ export async function parseGDScript(source) {
38
+ const parser = await getParser();
39
+ const tree = parser.parse(source);
40
+ if (!tree)
41
+ throw new Error("Failed to parse GDScript source.");
42
+ return tree;
43
+ }
44
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/gdscript/parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAa,MAAM,iBAAiB,CAAC;AAE9D,+EAA+E;AAC/E,2EAA2E;AAC3E,MAAM,QAAQ,GAAG,IAAI,CACnB,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EACvC,IAAI,EACJ,IAAI,EACJ,MAAM,EACN,2BAA2B,CAC5B,CAAC;AAEF,8DAA8D;AAC9D,MAAM,UAAU,kBAAkB;IAChC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC;AAC9B,CAAC;AAED,IAAI,WAAW,GAAyB,IAAI,CAAC;AAC7C,IAAI,WAAW,GAA6B,IAAI,CAAC;AACjD,IAAI,aAAa,GAA2B,IAAI,CAAC;AAEjD,KAAK,UAAU,WAAW;IACxB,4EAA4E;IAC5E,yEAAyE;IACzE,IAAI,CAAC,WAAW;QAAE,WAAW,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;IAC9C,MAAM,WAAW,CAAC;IAClB,IAAI,CAAC,WAAW;QAAE,WAAW,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IACtF,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,mFAAmF;AACnF,MAAM,CAAC,KAAK,UAAU,SAAS;IAC7B,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,aAAa,GAAG,WAAW,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;YAC1C,MAAM,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;YAC5B,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;YACzB,OAAO,MAAM,CAAC;QAChB,CAAC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,aAAa,CAAC;AACvB,CAAC;AAED,yDAAyD;AACzD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAc;IAChD,MAAM,MAAM,GAAG,MAAM,SAAS,EAAE,CAAC;IACjC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAClC,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IAC/D,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Tolerant parser for Godot 4.x text formats (.tscn, .tres, project.godot).
3
+ * These are INI-like: bracketed section headers with optional attributes,
4
+ * followed by `key = value` lines. Values may be strings, numbers, bools, or
5
+ * constructor literals (ExtResource("id"), Vector2(...), arrays/dicts that can
6
+ * span lines). Unknown constructs are preserved as opaque `other` values; the
7
+ * parser never throws.
8
+ */
9
+ export type GodotValueKind = "string" | "number" | "bool" | "extResource" | "subResource" | "other";
10
+ export interface GodotValue {
11
+ raw: string;
12
+ /** 1-based line where the value starts. */
13
+ line: number;
14
+ kind: GodotValueKind;
15
+ /** For kind "string": the unquoted content. */
16
+ string?: string;
17
+ /** For kind extResource/subResource: the referenced id. */
18
+ ref?: string;
19
+ }
20
+ export interface GodotSection {
21
+ /** Section kind: the first token in the header, e.g. "node", "ext_resource". */
22
+ kind: string;
23
+ attributes: Record<string, GodotValue>;
24
+ properties: Record<string, GodotValue>;
25
+ /** 1-based line of the section header. */
26
+ line: number;
27
+ }
28
+ export interface GodotFile {
29
+ /** The gd_scene / gd_resource header section, or null (e.g. project.godot). */
30
+ header: GodotSection | null;
31
+ sections: GodotSection[];
32
+ }
33
+ export declare function parseGodotFile(source: string): GodotFile;
34
+ /** Convenience: all sections of a given kind (e.g. "ext_resource", "node"). */
35
+ export declare function sectionsOfKind(file: GodotFile, kind: string): GodotSection[];
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Tolerant parser for Godot 4.x text formats (.tscn, .tres, project.godot).
3
+ * These are INI-like: bracketed section headers with optional attributes,
4
+ * followed by `key = value` lines. Values may be strings, numbers, bools, or
5
+ * constructor literals (ExtResource("id"), Vector2(...), arrays/dicts that can
6
+ * span lines). Unknown constructs are preserved as opaque `other` values; the
7
+ * parser never throws.
8
+ */
9
+ const HEADER_KINDS = new Set(["gd_scene", "gd_resource"]);
10
+ function classifyValue(raw, line) {
11
+ const t = raw.trim();
12
+ let m;
13
+ if (t.length >= 2 && t.startsWith('"') && t.endsWith('"')) {
14
+ return { raw: t, line, kind: "string", string: t.slice(1, -1) };
15
+ }
16
+ if ((m = /^ExtResource\(\s*"?([^")]*)"?\s*\)$/.exec(t))) {
17
+ return { raw: t, line, kind: "extResource", ref: m[1] };
18
+ }
19
+ if ((m = /^SubResource\(\s*"?([^")]*)"?\s*\)$/.exec(t))) {
20
+ return { raw: t, line, kind: "subResource", ref: m[1] };
21
+ }
22
+ if (/^-?\d+(\.\d+)?$/.test(t))
23
+ return { raw: t, line, kind: "number" };
24
+ if (t === "true" || t === "false")
25
+ return { raw: t, line, kind: "bool" };
26
+ return { raw: t, line, kind: "other" };
27
+ }
28
+ /** Split `key=value key2="v 2"` attribute text, respecting quotes. */
29
+ function parseAttributes(text, line) {
30
+ const attrs = {};
31
+ const re = /(\w[\w./-]*)\s*=\s*("(?:[^"\\]|\\.)*"|[^\s\]]+)/g;
32
+ let m;
33
+ while ((m = re.exec(text)))
34
+ attrs[m[1]] = classifyValue(m[2], line);
35
+ return attrs;
36
+ }
37
+ /** Whether brackets/braces/parens/quotes in `s` are balanced (string-aware). */
38
+ function isBalanced(s) {
39
+ let depth = 0;
40
+ let inStr = false;
41
+ for (let i = 0; i < s.length; i++) {
42
+ const c = s[i];
43
+ if (inStr) {
44
+ if (c === "\\")
45
+ i++;
46
+ else if (c === '"')
47
+ inStr = false;
48
+ }
49
+ else if (c === '"')
50
+ inStr = true;
51
+ else if (c === "(" || c === "[" || c === "{")
52
+ depth++;
53
+ else if (c === ")" || c === "]" || c === "}")
54
+ depth--;
55
+ }
56
+ return depth <= 0 && !inStr;
57
+ }
58
+ export function parseGodotFile(source) {
59
+ const lines = source.split(/\r?\n/);
60
+ const file = { header: null, sections: [] };
61
+ let current = null;
62
+ for (let i = 0; i < lines.length; i++) {
63
+ const rawLine = lines[i];
64
+ const line = rawLine.trim();
65
+ if (line === "" || line.startsWith(";"))
66
+ continue;
67
+ const headerMatch = /^\[(.+)\]\s*$/.exec(line);
68
+ if (headerMatch) {
69
+ const inner = headerMatch[1].trim();
70
+ const sp = inner.search(/\s/);
71
+ const kind = sp === -1 ? inner : inner.slice(0, sp);
72
+ const attrText = sp === -1 ? "" : inner.slice(sp + 1);
73
+ const section = {
74
+ kind,
75
+ attributes: parseAttributes(attrText, i + 1),
76
+ properties: {},
77
+ line: i + 1,
78
+ };
79
+ if (HEADER_KINDS.has(kind) && !file.header)
80
+ file.header = section;
81
+ else
82
+ file.sections.push(section);
83
+ current = section;
84
+ continue;
85
+ }
86
+ const propMatch = /^([\w./-]+)\s*=\s*(.*)$/.exec(line);
87
+ if (propMatch && current) {
88
+ const key = propMatch[1];
89
+ let value = propMatch[2];
90
+ // Continue multi-line values (arrays/dicts/constructors) until balanced.
91
+ while (!isBalanced(value) && i + 1 < lines.length) {
92
+ i++;
93
+ value += `\n${lines[i]}`;
94
+ }
95
+ current.properties[key] = classifyValue(value, i + 1);
96
+ }
97
+ // Anything else is opaque and intentionally ignored.
98
+ }
99
+ return file;
100
+ }
101
+ /** Convenience: all sections of a given kind (e.g. "ext_resource", "node"). */
102
+ export function sectionsOfKind(file, kind) {
103
+ return file.sections.filter((s) => s.kind === kind);
104
+ }
105
+ //# sourceMappingURL=text-format.js.map