@danielblomma/cortex-mcp 1.3.1 → 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/doctor.sh +64 -10
- 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
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Classic Visual Basic 6 parser for Cortex.
|
|
4
|
+
*
|
|
5
|
+
* VB6 has no tree-sitter grammar (the tree-sitter-wasms bundle ships
|
|
6
|
+
* nothing for VB, and tree-sitter-vb-dotnet targets VB.NET which has
|
|
7
|
+
* materially different syntax). Roslyn can only parse VB.NET, not
|
|
8
|
+
* VB6. So this is a regex-based "lightweight first-pass" — same
|
|
9
|
+
* approach the legacy cpp.mjs and pre-tree-sitter rust.mjs used.
|
|
10
|
+
*
|
|
11
|
+
* Covered extensions:
|
|
12
|
+
* .bas — standard module
|
|
13
|
+
* .cls — class module
|
|
14
|
+
* .frm — form
|
|
15
|
+
* .ctl — user control
|
|
16
|
+
*
|
|
17
|
+
* Extracts Sub / Function / Property (Get|Let|Set) / Type / Enum
|
|
18
|
+
* declarations. Strips the VB6 binary-ish header block (VERSION ...,
|
|
19
|
+
* BEGIN ... END, Attribute ...) that .cls/.frm/.ctl files carry
|
|
20
|
+
* before real code. `.frm` designer BEGIN ... END property blocks
|
|
21
|
+
* are also stripped so the parser only sees code.
|
|
22
|
+
*
|
|
23
|
+
* Naming:
|
|
24
|
+
* .bas -> ModuleName.Proc (ModuleName from `Attribute VB_Name` or filename)
|
|
25
|
+
* .cls -> ClassName.Method
|
|
26
|
+
* .frm -> FormName.EventHandler / FormName.Helper
|
|
27
|
+
* .ctl -> ControlName.Method
|
|
28
|
+
*
|
|
29
|
+
* VB6 has no imports in source code — references live in the .vbp
|
|
30
|
+
* project file. So chunk.imports is always [].
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import path from "node:path";
|
|
34
|
+
import fs from "node:fs";
|
|
35
|
+
|
|
36
|
+
const KIND_BY_EXT = {
|
|
37
|
+
".bas": "module",
|
|
38
|
+
".cls": "class",
|
|
39
|
+
".frm": "form",
|
|
40
|
+
".ctl": "usercontrol"
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const VBP_HEADER_PREFIXES = ["VERSION ", "Attribute ", "Object="];
|
|
44
|
+
|
|
45
|
+
const ATTR_VB_NAME = /Attribute\s+VB_Name\s*=\s*"([^"]+)"/i;
|
|
46
|
+
|
|
47
|
+
// VB6 builtins / intrinsics / common API surfaces — not user calls.
|
|
48
|
+
const CALL_FILTER = new Set([
|
|
49
|
+
"MsgBox", "InputBox", "Debug", "Err", "Me", "Nothing", "New",
|
|
50
|
+
"Len", "LenB", "Str", "Val", "CStr", "CInt", "CLng", "CDbl",
|
|
51
|
+
"CBool", "CByte", "CSng", "CDec", "CVar", "CDate",
|
|
52
|
+
"Left", "Right", "Mid", "UCase", "LCase", "Trim", "LTrim", "RTrim",
|
|
53
|
+
"Chr", "Asc", "IsEmpty", "IsNull", "IsNumeric", "IsDate", "IsArray",
|
|
54
|
+
"IsObject", "VarType", "TypeName", "UBound", "LBound",
|
|
55
|
+
"Array", "Split", "Join", "Replace", "InStr", "InStrRev",
|
|
56
|
+
"Abs", "Int", "Fix", "Sgn", "Sqr", "Exp", "Log", "Sin", "Cos", "Tan",
|
|
57
|
+
"Now", "Date", "Time", "DateAdd", "DateDiff", "DatePart", "Format",
|
|
58
|
+
"Dir", "FileExists", "GetAttr", "FileCopy", "Kill", "MkDir", "RmDir",
|
|
59
|
+
"Open", "Close", "Input", "Print", "Write", "LOF", "EOF", "Loc",
|
|
60
|
+
"If", "Else", "ElseIf", "End", "Do", "Loop", "While", "Wend",
|
|
61
|
+
"For", "Next", "Each", "To", "Step", "Exit", "Select", "Case",
|
|
62
|
+
"With", "GoTo", "GoSub", "Return", "Resume", "On", "Error",
|
|
63
|
+
"DoEvents", "RaiseEvent", "Event", "Call", "Stop", "Beep",
|
|
64
|
+
"Set", "Get", "Let", "Dim", "ReDim", "Preserve", "Static",
|
|
65
|
+
"Const", "Public", "Private", "Friend", "Sub", "Function", "Property",
|
|
66
|
+
"True", "False", "And", "Or", "Not", "Xor", "Eqv", "Imp", "Mod",
|
|
67
|
+
"App", "Screen", "Forms", "Printer", "Clipboard"
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
const SUPPORTED_EXTS = new Set([".bas", ".cls", ".frm", ".ctl"]);
|
|
71
|
+
|
|
72
|
+
function normalizeWhitespace(value) {
|
|
73
|
+
return String(value).replace(/\s+/g, " ").trim();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function extractModuleName(rawSource, filePath) {
|
|
77
|
+
const attrMatch = rawSource.match(ATTR_VB_NAME);
|
|
78
|
+
if (attrMatch) return attrMatch[1];
|
|
79
|
+
// Fall back to filename without extension.
|
|
80
|
+
const base = path.basename(filePath);
|
|
81
|
+
const dot = base.lastIndexOf(".");
|
|
82
|
+
return dot === -1 ? base : base.slice(0, dot);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Strip the VB6 binary-ish header that .cls/.frm/.ctl files carry
|
|
87
|
+
* before real source code. Only applied to those extensions — .bas
|
|
88
|
+
* files begin directly with code (or `Attribute VB_Name` lines).
|
|
89
|
+
* For .frm / .ctl we also strip the designer BEGIN ... END block
|
|
90
|
+
* that describes controls and property values.
|
|
91
|
+
*/
|
|
92
|
+
function stripHeader(source, ext) {
|
|
93
|
+
let out = source;
|
|
94
|
+
|
|
95
|
+
if (ext === ".bas") {
|
|
96
|
+
// Strip `Attribute VB_Name = "..."` and similar leading Attribute
|
|
97
|
+
// lines. Keep the rest intact.
|
|
98
|
+
const lines = out.split("\n");
|
|
99
|
+
let firstCodeLine = 0;
|
|
100
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
101
|
+
const trimmed = lines[i].trim();
|
|
102
|
+
if (trimmed === "" || trimmed.startsWith("Attribute ")) {
|
|
103
|
+
firstCodeLine = i + 1;
|
|
104
|
+
} else {
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Preserve original line numbers by blanking (not deleting) headers.
|
|
109
|
+
for (let i = 0; i < firstCodeLine; i += 1) lines[i] = "";
|
|
110
|
+
return lines.join("\n");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// .cls / .frm / .ctl — strip VERSION + BEGIN/END designer + Attribute lines.
|
|
114
|
+
const lines = out.split("\n");
|
|
115
|
+
let beginDepth = 0;
|
|
116
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
117
|
+
const line = lines[i];
|
|
118
|
+
const trimmed = line.trim();
|
|
119
|
+
const trimmedLower = trimmed.toLowerCase();
|
|
120
|
+
|
|
121
|
+
if (beginDepth > 0) {
|
|
122
|
+
if (/^begin\b/i.test(trimmed)) beginDepth += 1;
|
|
123
|
+
else if (/^end\s*$/i.test(trimmed)) beginDepth -= 1;
|
|
124
|
+
lines[i] = "";
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (VBP_HEADER_PREFIXES.some((p) => trimmed.startsWith(p))) {
|
|
129
|
+
lines[i] = "";
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (/^begin\b/i.test(trimmed)) {
|
|
134
|
+
beginDepth = 1;
|
|
135
|
+
lines[i] = "";
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return lines.join("\n");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function countLinesBefore(text, index) {
|
|
143
|
+
let count = 1;
|
|
144
|
+
for (let i = 0; i < index; i += 1) {
|
|
145
|
+
if (text[i] === "\n") count += 1;
|
|
146
|
+
}
|
|
147
|
+
return count;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function findBlockEnd(source, startIndex, endKeyword) {
|
|
151
|
+
const endPattern = new RegExp(
|
|
152
|
+
`^[ \\t]*End\\s+${endKeyword}\\b`,
|
|
153
|
+
"im"
|
|
154
|
+
);
|
|
155
|
+
endPattern.lastIndex = startIndex;
|
|
156
|
+
const slice = source.slice(startIndex);
|
|
157
|
+
const match = slice.match(endPattern);
|
|
158
|
+
if (!match) return -1;
|
|
159
|
+
// match.index is offset within slice
|
|
160
|
+
const endLineStart = startIndex + match.index;
|
|
161
|
+
const newlineAfter = source.indexOf("\n", endLineStart + match[0].length);
|
|
162
|
+
return newlineAfter === -1 ? source.length : newlineAfter;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function extractCallsFromBody(body) {
|
|
166
|
+
const calls = new Set();
|
|
167
|
+
// Identifier followed by `(` — function/sub call
|
|
168
|
+
const callPattern = /\b([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
|
|
169
|
+
let m;
|
|
170
|
+
while ((m = callPattern.exec(body)) !== null) {
|
|
171
|
+
const name = m[1];
|
|
172
|
+
if (!CALL_FILTER.has(name)) calls.add(name);
|
|
173
|
+
}
|
|
174
|
+
// object.method — no parens needed in VB6
|
|
175
|
+
const methodPattern = /\.([A-Za-z_][A-Za-z0-9_]*)\b/g;
|
|
176
|
+
while ((m = methodPattern.exec(body)) !== null) {
|
|
177
|
+
const name = m[1];
|
|
178
|
+
if (!CALL_FILTER.has(name)) calls.add(name);
|
|
179
|
+
}
|
|
180
|
+
// Call <Ident>
|
|
181
|
+
const callKeywordPattern = /\bCall\s+([A-Za-z_][A-Za-z0-9_]*)/gi;
|
|
182
|
+
while ((m = callKeywordPattern.exec(body)) !== null) {
|
|
183
|
+
const name = m[1];
|
|
184
|
+
if (!CALL_FILTER.has(name)) calls.add(name);
|
|
185
|
+
}
|
|
186
|
+
// Bareword Sub call at start of line: VB6 lets you invoke a Sub
|
|
187
|
+
// without parens or Call keyword. The identifier must be alone on
|
|
188
|
+
// the line or followed by whitespace + argument list, and must not
|
|
189
|
+
// be an assignment (`x = ...`) or a declaration (`Dim x`).
|
|
190
|
+
const barewordPattern = /^[ \t]*([A-Za-z_][A-Za-z0-9_]*)(?:[ \t]+[^=\n:]|[ \t]*$)/gm;
|
|
191
|
+
while ((m = barewordPattern.exec(body)) !== null) {
|
|
192
|
+
const name = m[1];
|
|
193
|
+
if (!CALL_FILTER.has(name)) calls.add(name);
|
|
194
|
+
}
|
|
195
|
+
return [...calls];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function buildBlockChunk({ source, strippedSource, ownerName, kind, keyword, pattern, language }) {
|
|
199
|
+
const chunks = [];
|
|
200
|
+
pattern.lastIndex = 0;
|
|
201
|
+
let match;
|
|
202
|
+
while ((match = pattern.exec(strippedSource)) !== null) {
|
|
203
|
+
const matchStart = match.index;
|
|
204
|
+
const endOfBlock = findBlockEnd(strippedSource, matchStart + match[0].length, keyword);
|
|
205
|
+
if (endOfBlock === -1) continue;
|
|
206
|
+
const body = strippedSource.slice(matchStart, endOfBlock);
|
|
207
|
+
const startLine = countLinesBefore(strippedSource, matchStart);
|
|
208
|
+
const endLine = countLinesBefore(strippedSource, endOfBlock);
|
|
209
|
+
const visibility = match[1] ? match[1].toLowerCase() : "";
|
|
210
|
+
const exported = visibility !== "private";
|
|
211
|
+
const memberName = match[match.length - 1];
|
|
212
|
+
const qualifiedName = ownerName ? `${ownerName}.${memberName}` : memberName;
|
|
213
|
+
|
|
214
|
+
chunks.push({
|
|
215
|
+
name: qualifiedName,
|
|
216
|
+
kind,
|
|
217
|
+
signature: normalizeWhitespace(body.split("\n")[0]),
|
|
218
|
+
body,
|
|
219
|
+
startLine,
|
|
220
|
+
endLine,
|
|
221
|
+
language,
|
|
222
|
+
exported,
|
|
223
|
+
calls: extractCallsFromBody(body),
|
|
224
|
+
imports: []
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
return chunks;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function buildTypeOrEnumChunks({ strippedSource, ownerName, kind, keyword, pattern, language }) {
|
|
231
|
+
const chunks = [];
|
|
232
|
+
pattern.lastIndex = 0;
|
|
233
|
+
let match;
|
|
234
|
+
while ((match = pattern.exec(strippedSource)) !== null) {
|
|
235
|
+
const matchStart = match.index;
|
|
236
|
+
const endOfBlock = findBlockEnd(strippedSource, matchStart + match[0].length, keyword);
|
|
237
|
+
if (endOfBlock === -1) continue;
|
|
238
|
+
const body = strippedSource.slice(matchStart, endOfBlock);
|
|
239
|
+
const startLine = countLinesBefore(strippedSource, matchStart);
|
|
240
|
+
const endLine = countLinesBefore(strippedSource, endOfBlock);
|
|
241
|
+
const typeName = match[match.length - 1];
|
|
242
|
+
const qualifiedName = ownerName ? `${ownerName}.${typeName}` : typeName;
|
|
243
|
+
const visibility = match[1] ? match[1].toLowerCase() : "";
|
|
244
|
+
chunks.push({
|
|
245
|
+
name: qualifiedName,
|
|
246
|
+
kind,
|
|
247
|
+
signature: normalizeWhitespace(body.split("\n")[0]),
|
|
248
|
+
body,
|
|
249
|
+
startLine,
|
|
250
|
+
endLine,
|
|
251
|
+
language,
|
|
252
|
+
exported: visibility !== "private",
|
|
253
|
+
calls: [],
|
|
254
|
+
imports: []
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
return chunks;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function buildOwnerChunk({ source, strippedSource, ownerName, kind, language }) {
|
|
261
|
+
// One chunk for the whole file representing the module/class/form/control.
|
|
262
|
+
const lines = strippedSource.split("\n");
|
|
263
|
+
// Find first non-blank line as start; last non-blank as end.
|
|
264
|
+
let startLine = 1;
|
|
265
|
+
let endLine = lines.length;
|
|
266
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
267
|
+
if (lines[i].trim() !== "") { startLine = i + 1; break; }
|
|
268
|
+
}
|
|
269
|
+
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
270
|
+
if (lines[i].trim() !== "") { endLine = i + 1; break; }
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
name: ownerName,
|
|
274
|
+
kind,
|
|
275
|
+
signature: `${kind} ${ownerName}`,
|
|
276
|
+
body: source,
|
|
277
|
+
startLine,
|
|
278
|
+
endLine,
|
|
279
|
+
language,
|
|
280
|
+
exported: true,
|
|
281
|
+
calls: [],
|
|
282
|
+
imports: []
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function parseCode(code, filePath, language = "vb6") {
|
|
287
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
288
|
+
if (!SUPPORTED_EXTS.has(ext)) {
|
|
289
|
+
return { chunks: [], errors: [] };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const ownerName = extractModuleName(code, filePath);
|
|
293
|
+
const ownerKind = KIND_BY_EXT[ext] ?? "module";
|
|
294
|
+
const memberKind = ext === ".bas" ? "function" : "method";
|
|
295
|
+
const strippedSource = stripHeader(code, ext);
|
|
296
|
+
|
|
297
|
+
const subPattern = /^[ \t]*(?:(Public|Private|Friend)\s+)?(?:Static\s+)?Sub\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/gim;
|
|
298
|
+
const functionPattern = /^[ \t]*(?:(Public|Private|Friend)\s+)?(?:Static\s+)?Function\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/gim;
|
|
299
|
+
const propertyPattern = /^[ \t]*(?:(Public|Private|Friend)\s+)?Property\s+(?:Get|Let|Set)\s+([A-Za-z_][A-Za-z0-9_]*)/gim;
|
|
300
|
+
const typePattern = /^[ \t]*(?:(Public|Private)\s+)?Type\s+([A-Za-z_][A-Za-z0-9_]*)/gim;
|
|
301
|
+
const enumPattern = /^[ \t]*(?:(Public|Private)\s+)?Enum\s+([A-Za-z_][A-Za-z0-9_]*)/gim;
|
|
302
|
+
|
|
303
|
+
const chunks = [];
|
|
304
|
+
|
|
305
|
+
chunks.push(buildOwnerChunk({
|
|
306
|
+
source: code,
|
|
307
|
+
strippedSource,
|
|
308
|
+
ownerName,
|
|
309
|
+
kind: ownerKind,
|
|
310
|
+
language
|
|
311
|
+
}));
|
|
312
|
+
|
|
313
|
+
chunks.push(...buildBlockChunk({
|
|
314
|
+
source: code,
|
|
315
|
+
strippedSource,
|
|
316
|
+
ownerName,
|
|
317
|
+
kind: memberKind,
|
|
318
|
+
keyword: "Sub",
|
|
319
|
+
pattern: subPattern,
|
|
320
|
+
language
|
|
321
|
+
}));
|
|
322
|
+
|
|
323
|
+
chunks.push(...buildBlockChunk({
|
|
324
|
+
source: code,
|
|
325
|
+
strippedSource,
|
|
326
|
+
ownerName,
|
|
327
|
+
kind: memberKind,
|
|
328
|
+
keyword: "Function",
|
|
329
|
+
pattern: functionPattern,
|
|
330
|
+
language
|
|
331
|
+
}));
|
|
332
|
+
|
|
333
|
+
const propertyChunks = buildBlockChunk({
|
|
334
|
+
source: code,
|
|
335
|
+
strippedSource,
|
|
336
|
+
ownerName,
|
|
337
|
+
kind: "property",
|
|
338
|
+
keyword: "Property",
|
|
339
|
+
pattern: propertyPattern,
|
|
340
|
+
language
|
|
341
|
+
});
|
|
342
|
+
// Property Get/Let/Set with same name collapse to one property chunk —
|
|
343
|
+
// keep only the first occurrence per qualified name so the graph
|
|
344
|
+
// doesn't show three property chunks for one logical property.
|
|
345
|
+
const seenProps = new Set();
|
|
346
|
+
for (const chunk of propertyChunks) {
|
|
347
|
+
if (seenProps.has(chunk.name)) continue;
|
|
348
|
+
seenProps.add(chunk.name);
|
|
349
|
+
chunks.push(chunk);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
chunks.push(...buildTypeOrEnumChunks({
|
|
353
|
+
strippedSource,
|
|
354
|
+
ownerName,
|
|
355
|
+
kind: "type",
|
|
356
|
+
keyword: "Type",
|
|
357
|
+
pattern: typePattern,
|
|
358
|
+
language
|
|
359
|
+
}));
|
|
360
|
+
|
|
361
|
+
chunks.push(...buildTypeOrEnumChunks({
|
|
362
|
+
strippedSource,
|
|
363
|
+
ownerName,
|
|
364
|
+
kind: "enum",
|
|
365
|
+
keyword: "Enum",
|
|
366
|
+
pattern: enumPattern,
|
|
367
|
+
language
|
|
368
|
+
}));
|
|
369
|
+
|
|
370
|
+
// Dedupe by (kind, name, startLine, endLine) — mirrors other parsers.
|
|
371
|
+
const seen = new Set();
|
|
372
|
+
const deduped = chunks.filter((chunk) => {
|
|
373
|
+
const key = `${chunk.kind}|${chunk.name}|${chunk.startLine}|${chunk.endLine}`;
|
|
374
|
+
if (seen.has(key)) return false;
|
|
375
|
+
seen.add(key);
|
|
376
|
+
return true;
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
return { chunks: deduped, errors: [] };
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export function isAvailable() {
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
387
|
+
const target = process.argv[2];
|
|
388
|
+
if (!target) {
|
|
389
|
+
console.error("Usage: vb6.mjs <file.{bas,cls,frm,ctl}>");
|
|
390
|
+
process.exit(1);
|
|
391
|
+
}
|
|
392
|
+
const code = fs.readFileSync(target, "utf8");
|
|
393
|
+
const result = parseCode(code, target, "vb6");
|
|
394
|
+
console.log(JSON.stringify(result, null, 2));
|
|
395
|
+
}
|