@danielblomma/cortex-mcp 1.3.2 → 1.4.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/README.md +62 -14
- package/package.json +2 -2
- package/scaffold/mcp/package-lock.json +3 -7
- package/scaffold/mcp/package.json +1 -1
- package/scaffold/scripts/dashboard.mjs +15 -1
- package/scaffold/scripts/ingest.mjs +323 -50
- package/scaffold/scripts/parsers/bash-treesitter.mjs +229 -0
- package/scaffold/scripts/parsers/cpp-dispatch.mjs +56 -0
- package/scaffold/scripts/parsers/cpp-treesitter.mjs +333 -0
- package/scaffold/scripts/parsers/csharp.mjs +197 -10
- package/scaffold/scripts/parsers/dotnet/CSharpParser/CSharpParser.csproj +1 -0
- package/scaffold/scripts/parsers/dotnet/CSharpParser/Program.cs +126 -21
- package/scaffold/scripts/parsers/go-treesitter.mjs +283 -0
- package/scaffold/scripts/parsers/java-treesitter.mjs +250 -0
- package/scaffold/scripts/parsers/javascript/ast.mjs +118 -12
- package/scaffold/scripts/parsers/javascript/chunks.mjs +4 -0
- package/scaffold/scripts/parsers/javascript/patterns.mjs +6 -0
- package/scaffold/scripts/parsers/javascript.mjs +4 -19
- package/scaffold/scripts/parsers/node_modules/.package-lock.json +57 -0
- package/scaffold/scripts/parsers/node_modules/acorn/CHANGELOG.md +972 -0
- package/scaffold/scripts/parsers/node_modules/acorn/LICENSE +21 -0
- package/scaffold/scripts/parsers/node_modules/acorn/README.md +301 -0
- package/scaffold/scripts/parsers/node_modules/acorn/bin/acorn +4 -0
- package/scaffold/scripts/parsers/node_modules/acorn/dist/acorn.d.mts +883 -0
- package/scaffold/scripts/parsers/node_modules/acorn/dist/acorn.d.ts +883 -0
- package/scaffold/scripts/parsers/node_modules/acorn/dist/acorn.js +6295 -0
- package/scaffold/scripts/parsers/node_modules/acorn/dist/acorn.mjs +6266 -0
- package/scaffold/scripts/parsers/node_modules/acorn/dist/bin.js +90 -0
- package/scaffold/scripts/parsers/node_modules/acorn/package.json +50 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/CHANGELOG.md +421 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/LICENSE +21 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/README.md +81 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/error.d.ts +103 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/error.js +78 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/error.js.map +1 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/decorators.d.ts +167 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/decorators.js +75 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/decorators.js.map +1 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/import-assertions.d.ts +177 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/import-assertions.js +56 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/import-assertions.js.map +1 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/index.d.ts +198 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/index.js +327 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/index.js.map +1 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/xhtml.d.ts +256 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/xhtml.js +256 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/extentions/jsx/xhtml.js.map +1 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/index.d.ts +472 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/index.js +1 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/index.js.map +1 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/index.mjs +1 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/middleware.d.ts +159 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/middleware.js +2 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/middleware.js.map +1 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/parseutil.d.ts +10 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/parseutil.js +38 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/parseutil.js.map +1 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/scopeflags.d.ts +12 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/scopeflags.js +29 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/scopeflags.js.map +1 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/tokenType.d.ts +2 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/tokenType.js +118 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/tokenType.js.map +1 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/types.d.ts +60 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/types.js +2 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/types.js.map +1 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/whitespace.d.ts +2 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/whitespace.js +19 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/lib/whitespace.js.map +1 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/package.json +53 -0
- package/scaffold/scripts/parsers/node_modules/acorn-typescript/tsconfig.json +19 -0
- package/scaffold/scripts/parsers/node_modules/acorn-walk/CHANGELOG.md +209 -0
- package/scaffold/scripts/parsers/node_modules/acorn-walk/LICENSE +21 -0
- package/scaffold/scripts/parsers/node_modules/acorn-walk/README.md +124 -0
- package/scaffold/scripts/parsers/node_modules/acorn-walk/dist/walk.d.mts +152 -0
- package/scaffold/scripts/parsers/node_modules/acorn-walk/dist/walk.d.ts +152 -0
- package/scaffold/scripts/parsers/node_modules/acorn-walk/dist/walk.js +485 -0
- package/scaffold/scripts/parsers/node_modules/acorn-walk/dist/walk.mjs +467 -0
- package/scaffold/scripts/parsers/node_modules/acorn-walk/package.json +50 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/LICENSE +24 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/README.md +23 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-bash.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-c.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-c_sharp.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-cpp.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-css.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-dart.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-elisp.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-elixir.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-elm.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-embedded_template.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-go.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-html.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-java.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-javascript.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-json.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-kotlin.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-lua.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-objc.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-ocaml.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-php.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-python.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-ql.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-rescript.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-ruby.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-rust.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-scala.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-solidity.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-swift.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-systemrdl.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-tlaplus.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-toml.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-tsx.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-typescript.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-vue.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-yaml.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/out/tree-sitter-zig.wasm +0 -0
- package/scaffold/scripts/parsers/node_modules/tree-sitter-wasms/package.json +64 -0
- package/scaffold/scripts/parsers/node_modules/web-tree-sitter/LICENSE +21 -0
- package/scaffold/scripts/parsers/node_modules/web-tree-sitter/README.md +198 -0
- package/scaffold/scripts/parsers/node_modules/web-tree-sitter/package.json +37 -0
- package/scaffold/scripts/parsers/node_modules/web-tree-sitter/tree-sitter-web.d.ts +242 -0
- package/scaffold/scripts/parsers/node_modules/web-tree-sitter/tree-sitter.js +1 -0
- package/scaffold/scripts/parsers/node_modules/web-tree-sitter/tree-sitter.wasm +0 -0
- package/scaffold/scripts/parsers/package-lock.json +19 -1
- package/scaffold/scripts/parsers/package.json +3 -1
- package/scaffold/scripts/parsers/python-treesitter.mjs +271 -0
- package/scaffold/scripts/parsers/ruby-treesitter.mjs +271 -0
- package/scaffold/scripts/parsers/rust-dispatch.mjs +43 -0
- package/scaffold/scripts/parsers/rust-treesitter.mjs +291 -0
- package/scaffold/scripts/parsers/tree-sitter/base.mjs +163 -0
- package/scaffold/scripts/parsers/tree-sitter/queries/bash.calls.scm +7 -0
- package/scaffold/scripts/parsers/tree-sitter/queries/bash.chunks.scm +6 -0
- package/scaffold/scripts/parsers/tree-sitter/queries/bash.imports.scm +5 -0
- package/scaffold/scripts/parsers/tree-sitter/queries/cpp.calls.scm +17 -0
- package/scaffold/scripts/parsers/tree-sitter/queries/cpp.chunks.scm +30 -0
- package/scaffold/scripts/parsers/tree-sitter/queries/cpp.imports.scm +6 -0
- package/scaffold/scripts/parsers/tree-sitter/queries/go.calls.scm +11 -0
- package/scaffold/scripts/parsers/tree-sitter/queries/go.chunks.scm +19 -0
- package/scaffold/scripts/parsers/tree-sitter/queries/go.imports.scm +6 -0
- package/scaffold/scripts/parsers/tree-sitter/queries/java.calls.scm +6 -0
- package/scaffold/scripts/parsers/tree-sitter/queries/java.chunks.scm +23 -0
- package/scaffold/scripts/parsers/tree-sitter/queries/java.imports.scm +6 -0
- package/scaffold/scripts/parsers/tree-sitter/queries/python.calls.scm +11 -0
- package/scaffold/scripts/parsers/tree-sitter/queries/python.chunks.scm +11 -0
- package/scaffold/scripts/parsers/tree-sitter/queries/python.imports.scm +13 -0
- package/scaffold/scripts/parsers/tree-sitter/queries/ruby.calls.scm +6 -0
- package/scaffold/scripts/parsers/tree-sitter/queries/ruby.chunks.scm +16 -0
- package/scaffold/scripts/parsers/tree-sitter/queries/ruby.imports.scm +8 -0
- package/scaffold/scripts/parsers/tree-sitter/queries/rust.calls.scm +31 -0
- package/scaffold/scripts/parsers/tree-sitter/queries/rust.chunks.scm +29 -0
- package/scaffold/scripts/parsers/tree-sitter/queries/rust.imports.scm +5 -0
- package/scaffold/scripts/parsers/vb6.mjs +395 -0
|
@@ -2,11 +2,16 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Conditional C# parser bridge for Cortex.
|
|
4
4
|
*
|
|
5
|
-
* Uses a Roslyn sidecar via
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Uses a Roslyn sidecar via a pre-published DLL when a .NET SDK is available.
|
|
6
|
+
* On first use the sidecar is published to bin/Release/<tfm>/publish/ and the
|
|
7
|
+
* DLL path is cached; subsequent invocations skip the msbuild cycle and run
|
|
8
|
+
* `dotnet <dll>` directly — roughly 10× faster per call than `dotnet run`.
|
|
9
|
+
*
|
|
10
|
+
* If no runtime/SDK exists, callers should skip structured chunk extraction
|
|
11
|
+
* and fall back to plain file-level indexing.
|
|
8
12
|
*/
|
|
9
13
|
|
|
14
|
+
import fs from "node:fs";
|
|
10
15
|
import path from "node:path";
|
|
11
16
|
import { fileURLToPath } from "node:url";
|
|
12
17
|
import { spawnSync } from "node:child_process";
|
|
@@ -15,8 +20,10 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
15
20
|
const __dirname = path.dirname(__filename);
|
|
16
21
|
const DEFAULT_DOTNET_COMMAND = "dotnet";
|
|
17
22
|
const DEFAULT_PROJECT_PATH = path.join(__dirname, "dotnet", "CSharpParser", "CSharpParser.csproj");
|
|
23
|
+
const DEFAULT_TARGET_FRAMEWORK = "net10.0";
|
|
18
24
|
|
|
19
25
|
let runtimeCache = null;
|
|
26
|
+
let publishCache = null;
|
|
20
27
|
|
|
21
28
|
function getDotnetCommand() {
|
|
22
29
|
const override = process.env.CORTEX_DOTNET_CMD;
|
|
@@ -28,8 +35,51 @@ function getProjectPath() {
|
|
|
28
35
|
return override && override.trim().length > 0 ? override.trim() : DEFAULT_PROJECT_PATH;
|
|
29
36
|
}
|
|
30
37
|
|
|
38
|
+
function getTargetFramework() {
|
|
39
|
+
const override = process.env.CORTEX_CSHARP_PARSER_TFM;
|
|
40
|
+
return override && override.trim().length > 0 ? override.trim() : DEFAULT_TARGET_FRAMEWORK;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getPublishDir() {
|
|
44
|
+
const override = process.env.CORTEX_CSHARP_PUBLISH_DIR;
|
|
45
|
+
if (override && override.trim().length > 0) return override.trim();
|
|
46
|
+
const projectDir = path.dirname(getProjectPath());
|
|
47
|
+
return path.join(projectDir, "bin", "Release", getTargetFramework(), "publish");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getDllPath() {
|
|
51
|
+
return path.join(getPublishDir(), "CSharpParser.dll");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getMaxSourceMtime() {
|
|
55
|
+
const projectDir = path.dirname(getProjectPath());
|
|
56
|
+
const sources = [getProjectPath(), path.join(projectDir, "Program.cs")];
|
|
57
|
+
let max = 0;
|
|
58
|
+
for (const src of sources) {
|
|
59
|
+
try {
|
|
60
|
+
const mtime = fs.statSync(src).mtimeMs;
|
|
61
|
+
if (mtime > max) max = mtime;
|
|
62
|
+
} catch {
|
|
63
|
+
// missing source — treated as stale below
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return max;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function needsPublish() {
|
|
70
|
+
const dll = getDllPath();
|
|
71
|
+
let dllMtime;
|
|
72
|
+
try {
|
|
73
|
+
dllMtime = fs.statSync(dll).mtimeMs;
|
|
74
|
+
} catch {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
return getMaxSourceMtime() > dllMtime;
|
|
78
|
+
}
|
|
79
|
+
|
|
31
80
|
export function resetCSharpParserRuntimeCache() {
|
|
32
81
|
runtimeCache = null;
|
|
82
|
+
publishCache = null;
|
|
33
83
|
}
|
|
34
84
|
|
|
35
85
|
export function getCSharpParserRuntime() {
|
|
@@ -69,19 +119,69 @@ export function isCSharpParserAvailable() {
|
|
|
69
119
|
return getCSharpParserRuntime().available;
|
|
70
120
|
}
|
|
71
121
|
|
|
122
|
+
export function ensureCSharpParserPublished() {
|
|
123
|
+
if (publishCache) return publishCache;
|
|
124
|
+
|
|
125
|
+
const runtime = getCSharpParserRuntime();
|
|
126
|
+
if (!runtime.available) {
|
|
127
|
+
publishCache = { ok: false, reason: runtime.reason };
|
|
128
|
+
return publishCache;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const dllPath = getDllPath();
|
|
132
|
+
if (!needsPublish()) {
|
|
133
|
+
publishCache = { ok: true, dllPath };
|
|
134
|
+
return publishCache;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!process.env.CORTEX_QUIET) {
|
|
138
|
+
process.stderr.write("[cortex] Publishing Roslyn C# parser (one-time, ~15s)...\n");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const result = spawnSync(
|
|
142
|
+
runtime.command,
|
|
143
|
+
[
|
|
144
|
+
"publish",
|
|
145
|
+
runtime.projectPath,
|
|
146
|
+
"-c", "Release",
|
|
147
|
+
"-o", getPublishDir(),
|
|
148
|
+
"--nologo",
|
|
149
|
+
"-v", "quiet"
|
|
150
|
+
],
|
|
151
|
+
{ encoding: "utf8", timeout: 180000 }
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
if (result.error || result.status !== 0) {
|
|
155
|
+
publishCache = {
|
|
156
|
+
ok: false,
|
|
157
|
+
reason:
|
|
158
|
+
result.error?.message ||
|
|
159
|
+
result.stderr?.trim() ||
|
|
160
|
+
`dotnet publish failed with exit code ${result.status ?? "unknown"}`
|
|
161
|
+
};
|
|
162
|
+
return publishCache;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
publishCache = { ok: true, dllPath };
|
|
166
|
+
return publishCache;
|
|
167
|
+
}
|
|
168
|
+
|
|
72
169
|
export function parseCode(code, filePath, language = "csharp") {
|
|
73
170
|
const runtime = getCSharpParserRuntime();
|
|
74
171
|
if (!runtime.available) {
|
|
75
172
|
return { chunks: [], errors: [] };
|
|
76
173
|
}
|
|
77
174
|
|
|
175
|
+
const published = ensureCSharpParserPublished();
|
|
176
|
+
if (!published.ok) {
|
|
177
|
+
return {
|
|
178
|
+
chunks: [],
|
|
179
|
+
errors: [{ message: `C# parser publish failed: ${published.reason}` }]
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
78
183
|
const args = [
|
|
79
|
-
|
|
80
|
-
"--project",
|
|
81
|
-
runtime.projectPath,
|
|
82
|
-
"--configuration",
|
|
83
|
-
"Release",
|
|
84
|
-
"--",
|
|
184
|
+
published.dllPath,
|
|
85
185
|
"--stdin",
|
|
86
186
|
"--file",
|
|
87
187
|
filePath,
|
|
@@ -128,8 +228,95 @@ export function parseCode(code, filePath, language = "csharp") {
|
|
|
128
228
|
}
|
|
129
229
|
}
|
|
130
230
|
|
|
231
|
+
/**
|
|
232
|
+
* Batch-parse an entire C# project as one CSharpCompilation, enabling
|
|
233
|
+
* SemanticModel-based call resolution. Calls are emitted as fully-
|
|
234
|
+
* qualified names (e.g. "System.IO.File.ReadAllText") instead of
|
|
235
|
+
* short names. Unresolved calls fall back to the syntax name.
|
|
236
|
+
*
|
|
237
|
+
* @param {Array<{path: string, content: string}>} files
|
|
238
|
+
* @returns {Map<string, {chunks: Array, errors: Array}>}
|
|
239
|
+
*/
|
|
240
|
+
export function parseProject(files) {
|
|
241
|
+
const runtime = getCSharpParserRuntime();
|
|
242
|
+
if (!runtime.available) {
|
|
243
|
+
const empty = new Map();
|
|
244
|
+
for (const file of files) {
|
|
245
|
+
empty.set(file.path, { chunks: [], errors: [] });
|
|
246
|
+
}
|
|
247
|
+
return empty;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const published = ensureCSharpParserPublished();
|
|
251
|
+
if (!published.ok) {
|
|
252
|
+
const errors = [{ message: `C# parser publish failed: ${published.reason}` }];
|
|
253
|
+
const fallback = new Map();
|
|
254
|
+
for (const file of files) {
|
|
255
|
+
fallback.set(file.path, { chunks: [], errors });
|
|
256
|
+
}
|
|
257
|
+
return fallback;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const args = [published.dllPath, "--batch"];
|
|
261
|
+
|
|
262
|
+
const payload = JSON.stringify({
|
|
263
|
+
files: files.map((f) => ({ path: f.path, source: f.content }))
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
const result = spawnSync(runtime.command, args, {
|
|
267
|
+
input: payload,
|
|
268
|
+
encoding: "utf8",
|
|
269
|
+
timeout: 120000,
|
|
270
|
+
maxBuffer: 256 * 1024 * 1024
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
if (result.error || result.status !== 0) {
|
|
274
|
+
const errors = [
|
|
275
|
+
{
|
|
276
|
+
message:
|
|
277
|
+
result.error?.message ||
|
|
278
|
+
result.stderr?.trim() ||
|
|
279
|
+
`C# batch parser failed with exit code ${result.status ?? "unknown"}`
|
|
280
|
+
}
|
|
281
|
+
];
|
|
282
|
+
const fallback = new Map();
|
|
283
|
+
for (const file of files) {
|
|
284
|
+
fallback.set(file.path, { chunks: [], errors });
|
|
285
|
+
}
|
|
286
|
+
return fallback;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
const parsed = JSON.parse(result.stdout);
|
|
291
|
+
const out = new Map();
|
|
292
|
+
const perFile = parsed.files ?? {};
|
|
293
|
+
for (const file of files) {
|
|
294
|
+
const entry = perFile[file.path];
|
|
295
|
+
if (entry) {
|
|
296
|
+
out.set(file.path, {
|
|
297
|
+
chunks: Array.isArray(entry.chunks) ? entry.chunks : [],
|
|
298
|
+
errors: Array.isArray(entry.errors) ? entry.errors : []
|
|
299
|
+
});
|
|
300
|
+
} else {
|
|
301
|
+
out.set(file.path, { chunks: [], errors: [] });
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return out;
|
|
305
|
+
} catch (error) {
|
|
306
|
+
const errors = [
|
|
307
|
+
{
|
|
308
|
+
message: `C# batch parser returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
309
|
+
}
|
|
310
|
+
];
|
|
311
|
+
const fallback = new Map();
|
|
312
|
+
for (const file of files) {
|
|
313
|
+
fallback.set(file.path, { chunks: [], errors });
|
|
314
|
+
}
|
|
315
|
+
return fallback;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
131
319
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
132
|
-
const fs = await import("node:fs");
|
|
133
320
|
const filePath = process.argv[2];
|
|
134
321
|
|
|
135
322
|
if (!filePath) {
|
|
@@ -4,9 +4,16 @@ using Microsoft.CodeAnalysis.CSharp;
|
|
|
4
4
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
5
5
|
|
|
6
6
|
var options = ParseArgs(args);
|
|
7
|
+
|
|
8
|
+
if (options.Batch)
|
|
9
|
+
{
|
|
10
|
+
RunBatchMode();
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
7
14
|
if (string.IsNullOrWhiteSpace(options.FilePath))
|
|
8
15
|
{
|
|
9
|
-
Console.Error.WriteLine("Missing required --file argument.");
|
|
16
|
+
Console.Error.WriteLine("Missing required --file argument (or use --batch for project-wide mode).");
|
|
10
17
|
Environment.Exit(1);
|
|
11
18
|
}
|
|
12
19
|
|
|
@@ -14,12 +21,8 @@ var source = options.UseStdin
|
|
|
14
21
|
? Console.In.ReadToEnd()
|
|
15
22
|
: File.ReadAllText(options.FilePath);
|
|
16
23
|
|
|
17
|
-
var parseResult = ParseCSharp(source, options.FilePath, options.Language);
|
|
18
|
-
var json = JsonSerializer.Serialize(parseResult,
|
|
19
|
-
{
|
|
20
|
-
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
21
|
-
WriteIndented = false
|
|
22
|
-
});
|
|
24
|
+
var parseResult = ParseCSharp(source, options.FilePath, options.Language, semanticModel: null);
|
|
25
|
+
var json = JsonSerializer.Serialize(parseResult, JsonOut());
|
|
23
26
|
|
|
24
27
|
Console.WriteLine(json);
|
|
25
28
|
|
|
@@ -48,16 +51,31 @@ static ParseOptions ParseArgs(string[] args)
|
|
|
48
51
|
options.Language = args[++index];
|
|
49
52
|
}
|
|
50
53
|
break;
|
|
54
|
+
case "--batch":
|
|
55
|
+
options.Batch = true;
|
|
56
|
+
break;
|
|
51
57
|
}
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
return options;
|
|
55
61
|
}
|
|
56
62
|
|
|
57
|
-
static
|
|
63
|
+
static JsonSerializerOptions JsonOut() => new()
|
|
64
|
+
{
|
|
65
|
+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
66
|
+
WriteIndented = false
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
static JsonSerializerOptions JsonIn() => new()
|
|
70
|
+
{
|
|
71
|
+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
72
|
+
PropertyNameCaseInsensitive = true
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
static ParserOutput ParseCSharp(string source, string filePath, string language, SemanticModel? semanticModel)
|
|
58
76
|
{
|
|
59
|
-
var tree = CSharpSyntaxTree.ParseText(source, path: filePath);
|
|
60
|
-
var root = tree.
|
|
77
|
+
var tree = semanticModel?.SyntaxTree ?? CSharpSyntaxTree.ParseText(source, path: filePath);
|
|
78
|
+
var root = (CompilationUnitSyntax)tree.GetRoot();
|
|
61
79
|
var diagnostics = tree.GetDiagnostics()
|
|
62
80
|
.Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)
|
|
63
81
|
.Select(diagnostic => new ParserError(
|
|
@@ -72,24 +90,73 @@ static ParserOutput ParseCSharp(string source, string filePath, string language)
|
|
|
72
90
|
return new ParserOutput(new List<ChunkOutput>(), diagnostics);
|
|
73
91
|
}
|
|
74
92
|
|
|
75
|
-
var collector = new CSharpChunkCollector(tree, root, source, language);
|
|
93
|
+
var collector = new CSharpChunkCollector(tree, root, source, language, semanticModel);
|
|
76
94
|
return new ParserOutput(collector.Collect(), diagnostics);
|
|
77
95
|
}
|
|
78
96
|
|
|
97
|
+
static void RunBatchMode()
|
|
98
|
+
{
|
|
99
|
+
var input = Console.In.ReadToEnd();
|
|
100
|
+
BatchInput? batch;
|
|
101
|
+
try
|
|
102
|
+
{
|
|
103
|
+
batch = JsonSerializer.Deserialize<BatchInput>(input, JsonIn());
|
|
104
|
+
}
|
|
105
|
+
catch (JsonException ex)
|
|
106
|
+
{
|
|
107
|
+
Console.Error.WriteLine($"Invalid batch JSON: {ex.Message}");
|
|
108
|
+
Environment.Exit(1);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (batch?.Files == null || batch.Files.Count == 0)
|
|
113
|
+
{
|
|
114
|
+
var empty = new BatchOutput { Files = new Dictionary<string, ParserOutput>() };
|
|
115
|
+
Console.WriteLine(JsonSerializer.Serialize(empty, JsonOut()));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
var trees = batch.Files
|
|
120
|
+
.Where(f => !string.IsNullOrEmpty(f.Path))
|
|
121
|
+
.Select(f => CSharpSyntaxTree.ParseText(f.Source ?? string.Empty, path: f.Path!))
|
|
122
|
+
.ToList();
|
|
123
|
+
|
|
124
|
+
var references = Basic.Reference.Assemblies.Net100.References.All.ToList();
|
|
125
|
+
var compilation = CSharpCompilation.Create(
|
|
126
|
+
"Cortex",
|
|
127
|
+
trees,
|
|
128
|
+
references,
|
|
129
|
+
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
var result = new BatchOutput { Files = new Dictionary<string, ParserOutput>() };
|
|
133
|
+
foreach (var tree in trees)
|
|
134
|
+
{
|
|
135
|
+
var model = compilation.GetSemanticModel(tree);
|
|
136
|
+
var file = batch.Files.First(f => f.Path == tree.FilePath);
|
|
137
|
+
var parseResult = ParseCSharp(file.Source ?? string.Empty, tree.FilePath, "csharp", model);
|
|
138
|
+
result.Files[tree.FilePath] = parseResult;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
Console.WriteLine(JsonSerializer.Serialize(result, JsonOut()));
|
|
142
|
+
}
|
|
143
|
+
|
|
79
144
|
sealed class CSharpChunkCollector
|
|
80
145
|
{
|
|
81
146
|
private readonly SyntaxTree _tree;
|
|
82
147
|
private readonly CompilationUnitSyntax _root;
|
|
83
148
|
private readonly string _source;
|
|
84
149
|
private readonly string _language;
|
|
150
|
+
private readonly SemanticModel? _model;
|
|
85
151
|
private readonly string[] _imports;
|
|
86
152
|
|
|
87
|
-
public CSharpChunkCollector(SyntaxTree tree, CompilationUnitSyntax root, string source, string language)
|
|
153
|
+
public CSharpChunkCollector(SyntaxTree tree, CompilationUnitSyntax root, string source, string language, SemanticModel? model)
|
|
88
154
|
{
|
|
89
155
|
_tree = tree;
|
|
90
156
|
_root = root;
|
|
91
157
|
_source = source;
|
|
92
158
|
_language = language;
|
|
159
|
+
_model = model;
|
|
93
160
|
_imports = CollectUsings(root);
|
|
94
161
|
}
|
|
95
162
|
|
|
@@ -97,7 +164,6 @@ sealed class CSharpChunkCollector
|
|
|
97
164
|
{
|
|
98
165
|
var usings = new List<string>();
|
|
99
166
|
|
|
100
|
-
// Top-level using directives (including global using)
|
|
101
167
|
foreach (var directive in root.Usings)
|
|
102
168
|
{
|
|
103
169
|
var name = directive.Name?.ToString();
|
|
@@ -107,7 +173,6 @@ sealed class CSharpChunkCollector
|
|
|
107
173
|
}
|
|
108
174
|
}
|
|
109
175
|
|
|
110
|
-
// Namespace-scoped using directives
|
|
111
176
|
foreach (var member in root.Members)
|
|
112
177
|
{
|
|
113
178
|
if (member is BaseNamespaceDeclarationSyntax ns)
|
|
@@ -129,12 +194,10 @@ sealed class CSharpChunkCollector
|
|
|
129
194
|
public List<ChunkOutput> Collect()
|
|
130
195
|
{
|
|
131
196
|
var chunks = new List<ChunkOutput>();
|
|
132
|
-
|
|
133
197
|
foreach (var member in _root.Members)
|
|
134
198
|
{
|
|
135
199
|
CollectMember(chunks, member, null);
|
|
136
200
|
}
|
|
137
|
-
|
|
138
201
|
return chunks;
|
|
139
202
|
}
|
|
140
203
|
|
|
@@ -357,25 +420,50 @@ sealed class CSharpChunkCollector
|
|
|
357
420
|
return modifiers.Any(modifier => modifier.IsKind(SyntaxKind.PublicKeyword));
|
|
358
421
|
}
|
|
359
422
|
|
|
360
|
-
private
|
|
423
|
+
private IReadOnlyCollection<string> GetCalls(SyntaxNode node)
|
|
361
424
|
{
|
|
362
425
|
return node.DescendantNodes()
|
|
363
426
|
.OfType<InvocationExpressionSyntax>()
|
|
364
|
-
.Select(
|
|
365
|
-
.Select(GetInvocationName)
|
|
427
|
+
.Select(ResolveCallName)
|
|
366
428
|
.Where(name => !string.IsNullOrWhiteSpace(name))
|
|
429
|
+
.Select(name => name!)
|
|
367
430
|
.Distinct(StringComparer.Ordinal)
|
|
368
431
|
.ToArray();
|
|
369
432
|
}
|
|
370
433
|
|
|
371
|
-
private
|
|
434
|
+
private string? ResolveCallName(InvocationExpressionSyntax invocation)
|
|
435
|
+
{
|
|
436
|
+
if (_model != null)
|
|
437
|
+
{
|
|
438
|
+
var info = _model.GetSymbolInfo(invocation);
|
|
439
|
+
var method = info.Symbol as IMethodSymbol
|
|
440
|
+
?? info.CandidateSymbols.OfType<IMethodSymbol>().FirstOrDefault();
|
|
441
|
+
if (method != null)
|
|
442
|
+
{
|
|
443
|
+
return FullyQualifiedMethodName(method);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return GetInvocationSyntaxName(invocation.Expression);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
private static string FullyQualifiedMethodName(IMethodSymbol method)
|
|
450
|
+
{
|
|
451
|
+
var container = method.ContainingType?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) ?? "";
|
|
452
|
+
if (container.StartsWith("global::", StringComparison.Ordinal))
|
|
453
|
+
{
|
|
454
|
+
container = container.Substring("global::".Length);
|
|
455
|
+
}
|
|
456
|
+
return string.IsNullOrEmpty(container) ? method.Name : $"{container}.{method.Name}";
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
private static string? GetInvocationSyntaxName(ExpressionSyntax expression)
|
|
372
460
|
{
|
|
373
461
|
return expression switch
|
|
374
462
|
{
|
|
375
463
|
IdentifierNameSyntax identifier => identifier.Identifier.Text,
|
|
376
464
|
GenericNameSyntax genericName => genericName.Identifier.Text,
|
|
377
465
|
MemberAccessExpressionSyntax memberAccess => memberAccess.Name.Identifier.Text,
|
|
378
|
-
InvocationExpressionSyntax nestedInvocation =>
|
|
466
|
+
InvocationExpressionSyntax nestedInvocation => GetInvocationSyntaxName(nestedInvocation.Expression),
|
|
379
467
|
_ => null
|
|
380
468
|
};
|
|
381
469
|
}
|
|
@@ -384,6 +472,7 @@ sealed class CSharpChunkCollector
|
|
|
384
472
|
sealed record ParseOptions
|
|
385
473
|
{
|
|
386
474
|
public bool UseStdin { get; set; }
|
|
475
|
+
public bool Batch { get; set; }
|
|
387
476
|
public string FilePath { get; set; } = "";
|
|
388
477
|
public string Language { get; set; } = "csharp";
|
|
389
478
|
}
|
|
@@ -404,3 +493,19 @@ sealed record ChunkOutput(
|
|
|
404
493
|
sealed record ParserError(string Message, int Line, int Column);
|
|
405
494
|
|
|
406
495
|
sealed record ParserOutput(List<ChunkOutput> Chunks, List<ParserError> Errors);
|
|
496
|
+
|
|
497
|
+
sealed class BatchInput
|
|
498
|
+
{
|
|
499
|
+
public List<BatchFile> Files { get; set; } = new();
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
sealed class BatchFile
|
|
503
|
+
{
|
|
504
|
+
public string? Path { get; set; }
|
|
505
|
+
public string? Source { get; set; }
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
sealed class BatchOutput
|
|
509
|
+
{
|
|
510
|
+
public Dictionary<string, ParserOutput> Files { get; set; } = new();
|
|
511
|
+
}
|