@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.
- package/LICENSE +34 -0
- package/README.md +118 -0
- package/data/godot-4.4.symbols.json.gz +0 -0
- package/data/godot-4.5.symbols.json.gz +0 -0
- package/data/tree-sitter-gdscript.wasm +0 -0
- package/dist/capabilities.d.ts +21 -0
- package/dist/capabilities.js +24 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.js +105 -0
- package/dist/cli.js.map +1 -0
- package/dist/gdscript/ast.d.ts +25 -0
- package/dist/gdscript/ast.js +82 -0
- package/dist/gdscript/ast.js.map +1 -0
- package/dist/gdscript/parser.d.ts +7 -0
- package/dist/gdscript/parser.js +44 -0
- package/dist/gdscript/parser.js.map +1 -0
- package/dist/godot/text-format.d.ts +35 -0
- package/dist/godot/text-format.js +105 -0
- package/dist/godot/text-format.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/lint/config.d.ts +83 -0
- package/dist/lint/config.js +55 -0
- package/dist/lint/config.js.map +1 -0
- package/dist/lint/engine.d.ts +7 -0
- package/dist/lint/engine.js +33 -0
- package/dist/lint/engine.js.map +1 -0
- package/dist/lint/project.d.ts +12 -0
- package/dist/lint/project.js +49 -0
- package/dist/lint/project.js.map +1 -0
- package/dist/lint/rules/await-misuse.d.ts +3 -0
- package/dist/lint/rules/await-misuse.js +34 -0
- package/dist/lint/rules/await-misuse.js.map +1 -0
- package/dist/lint/rules/connect-without-disconnect.d.ts +7 -0
- package/dist/lint/rules/connect-without-disconnect.js +38 -0
- package/dist/lint/rules/connect-without-disconnect.js.map +1 -0
- package/dist/lint/rules/delta-misuse.d.ts +7 -0
- package/dist/lint/rules/delta-misuse.js +78 -0
- package/dist/lint/rules/delta-misuse.js.map +1 -0
- package/dist/lint/rules/float-grid-equality.d.ts +6 -0
- package/dist/lint/rules/float-grid-equality.js +39 -0
- package/dist/lint/rules/float-grid-equality.js.map +1 -0
- package/dist/lint/rules/get-node-in-process.d.ts +3 -0
- package/dist/lint/rules/get-node-in-process.js +31 -0
- package/dist/lint/rules/get-node-in-process.js.map +1 -0
- package/dist/lint/rules/index.d.ts +4 -0
- package/dist/lint/rules/index.js +21 -0
- package/dist/lint/rules/index.js.map +1 -0
- package/dist/lint/rules/missing-onready.d.ts +3 -0
- package/dist/lint/rules/missing-onready.js +52 -0
- package/dist/lint/rules/missing-onready.js.map +1 -0
- package/dist/lint/rules/stringly-nodepath.d.ts +3 -0
- package/dist/lint/rules/stringly-nodepath.js +35 -0
- package/dist/lint/rules/stringly-nodepath.js.map +1 -0
- package/dist/lint/rules/untyped-declaration.d.ts +7 -0
- package/dist/lint/rules/untyped-declaration.js +25 -0
- package/dist/lint/rules/untyped-declaration.js.map +1 -0
- package/dist/lint/types.d.ts +36 -0
- package/dist/lint/types.js +2 -0
- package/dist/lint/types.js.map +1 -0
- package/dist/resources/skills.d.ts +12 -0
- package/dist/resources/skills.js +45 -0
- package/dist/resources/skills.js.map +1 -0
- package/dist/scenes/paths.d.ts +14 -0
- package/dist/scenes/paths.js +38 -0
- package/dist/scenes/paths.js.map +1 -0
- package/dist/scenes/report.d.ts +17 -0
- package/dist/scenes/report.js +25 -0
- package/dist/scenes/report.js.map +1 -0
- package/dist/scenes/validate.d.ts +16 -0
- package/dist/scenes/validate.js +185 -0
- package/dist/scenes/validate.js.map +1 -0
- package/dist/server-info.d.ts +13 -0
- package/dist/server-info.js +21 -0
- package/dist/server-info.js.map +1 -0
- package/dist/symbols/db.d.ts +11 -0
- package/dist/symbols/db.js +42 -0
- package/dist/symbols/db.js.map +1 -0
- package/dist/symbols/lookup.d.ts +89 -0
- package/dist/symbols/lookup.js +271 -0
- package/dist/symbols/lookup.js.map +1 -0
- package/dist/symbols/schema.d.ts +1419 -0
- package/dist/symbols/schema.js +109 -0
- package/dist/symbols/schema.js.map +1 -0
- package/dist/tools/api.d.ts +3 -0
- package/dist/tools/api.js +91 -0
- package/dist/tools/api.js.map +1 -0
- package/dist/tools/lint.d.ts +3 -0
- package/dist/tools/lint.js +45 -0
- package/dist/tools/lint.js.map +1 -0
- package/dist/tools/scenes.d.ts +3 -0
- package/dist/tools/scenes.js +43 -0
- package/dist/tools/scenes.js.map +1 -0
- package/package.json +57 -0
- package/skills/data-driven-resources.md +78 -0
- package/skills/resource-preload.md +43 -0
- package/skills/signal-hygiene.md +46 -0
- 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
|
+

|
|
6
|
+

|
|
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
|
package/dist/cli.js.map
ADDED
|
@@ -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
|